У нагруженной системы всегда есть маршрут, который проходит каждая сделка: принять заявку, сверить с стаканом, исполнить, записать. Этот маршрут называют горячим путём (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 платит процессором за предсказуемость пробуждения.
  • Сначала измерь хвосты - потом оптимизируй.