У нагруженной системы всегда есть маршрут, который проходит каждая сделка: принять заявку, сверить с стаканом, исполнить, записать. Этот маршрут называют горячим путём (hot-path). Всё остальное - холодный (cold-path), и сила low-latency архитектуры в том, чтобы не путать одно с другим.
Что убирают с горячего пути
На критическом пути избегают всего, что занимает время непредсказуемо:
- Аллокации. Каждая - работа аллокатора и давление на GC; на горячем пути переиспользуют объекты, читают из примитивных массивов, работают с off-heap-памятью, которую сборщик не трогает вовсе.
- Блокировки. Ожидание лока - неопределённой длины пауза; заменяют lock-free-структурами или локальным состоянием потока.
- Синхронный IO. Сеть и диск - миллисекунды; на горячем пути их нет, ответ выдаётся из памяти, а запись уходит асинхронно.
- Разные «по пути» вычисления. Валидация тяжёлых справочников, форматирование, логирование с обогащением - всё это в холодный контур.
Холодный путь - логирование, аналитика, отчётность, уведомления - не исчезает, а уезжает в асинхронную обработку: событие о сделке падает в журнал или очередь, и медленные потребители читают его потом, не тормозя ядро. Разделение позволяет держать горячий путь тонким, не жертвуя функциональностью: списки функций не сокращаются - сокращается путь.
Память: huge pages и NUMA
Дальше начинается тюнинг железа под микросекунды.
Huge pages. Виртуальная память транслируется страницами; обычная
страница - 4 КБ, и на куче в десятки гигабайт таблицы трансляции (TLB) не
влезают в кэш процессора - начинаются промахи. Большие страницы (2 МБ)
сокращают число записей в разы, и доступ к памяти становится ровнее.
Аналог в JVM - -XX:+UseLargePages.
NUMA. На многосокетных серверах у каждого сокета своя локальная память,
и доступ к чужой дороже. Поток, работающий с данными чужого узла, тихо
платит десятками наносекунд за каждое обращение - на горячем пути это
складывается в заметные суммы. Поэтому потоки и их данные стараются держать
на одном узле (numactl, привязка потоков).
Busy-spin вместо паркинга. Пробуждение заблокированного потока стоит микросекунды сам по себе. Там, где ожидание короткое и предсказуемое, поток просто крутится в цикле - не засыпая и не платя за пробуждение. Расточительно по CPU, зато предсказуемо: на бирже платят и за это.
Как не переусердствовать
Всё перечисленное - дорогая работа с малой отдачей вне действительно горячего пути. Правило простое: сначала измерь (хвосты, аллокации, промахи), потом оптимизируй то, что реально портит метрику. Система, которая и так отвечает в p99 = 50 мс, не выиграет от NUMA-привязки - ей нужны другие лекарства.
Что запомнить
- Горячий путь - маршрут каждой сделки: без аллокаций, блокировок и синхронного IO.
- Всё медленное - в холодный асинхронный контур через события.
- Huge pages и NUMA - тюнинг памяти под ровный доступ на микросекундах.
- Busy-spin платит процессором за предсказуемость пробуждения.
- Сначала измерь хвосты - потом оптимизируй.