SuperOleg dev notes
1.87K subscribers
56 photos
149 links
Обзоры новостей и статей из мира frontend, интересные кейсы, исследования и мысли вслух

https://github.com/SuperOleg39

https://twitter.com/ODrapeza

@SuperOleg39
Download Telegram
Привет!

Достаточно давно делился статьей где описывал различные механизмы и подходы которые мы применяем для SSR приложений на Tramvai (сейчас доступна на хабре).

Один из механизмов - Request Limiter, модуль который ограничивает количество параллельно обрабатываемых запросов при перегруженном Event Loop приложения, для возможности стабильно отдавать 2xx ответы и рендерить странички даже под большими нагрузками.

Работает по похожим принципам с https://github.com/fastify/under-pressure, только не отбрасывает все запросы при нагрузке, а держит еще LIFO очередь что бы обеспечить большее количество успешных ответов без сильной деградации времени ответа.

Когда переводили наши интеграционные тесты на 20 версию Node.js, начали падать нагрузочные тесты Request Limiter. Основная проблема - перестали быстро отвечать health-чеки приложения (а отзывчивые health-чеки и метрики очень важны и что бы в реальном времени понимать что происходить и для graceful рестартов и так далее)

Долго исследовал проблему, опытным путем обнаружил что проблема где-то на уровне http модуля ноды, запросы как будто в какой-то момент скапливаются в очередь и не обрабатываются далее.

Завел issue, получил много интересного и ценного фидбека от Matteo Collina - https://github.com/nodejs/node/issues/57364

Оказалось, что изменения в libuv убрали запуск таймеров в начале цикла на старте event loop, теперь они запускаются только в конце - то есть после poll (входящие запросы и прочее) и check (setImmediate).

В веб-сервере под капотом Tramvai мы испольуем setImmediate для того что бы иметь возможность "разорвать" event loop между тяжелыми задачами по SSR и ответить на легкие запросы /metrics, /health и так далее.

У libuv есть логика по ограничению одновременно обрабатываемых immediate коллбэков - https://github.com/libuv/libuv/blob/49b6e4db0cfc2bdb4c4151030618981c2fc0795b/src/unix/core.c#L462-L465

http модуль внутри использует различные таймеры/интервалы, до обновления ноды все это вместе с request limiter работало так скажем в гармонии, логика по обработке запроса (в том числе таймеры) выполнялась на одной фазе event loop до immediate коллбэков.

После обновления libuv в Node.js, если я все правильно понял, логика обработки запроса теперь раскидана до и после check фазы с immediate коллбэками, и происходит что-то вроде взаимной блокировки - мы не можем полноценно обрабатывать новые запросы (в том числе быстро ответить 429 кодом) пока в очереди есть много immediate коллбэков.

Также Маттео накинул несколько кейсов почему в целом опасно использовать setImmediate для дробления обработки запросов и что это просаживает перф - https://github.com/fastify/fastify/pull/545

Проблем тут вижу несколько:
- в нашем кейсе, SSR это не тысячи а десятки RPS как в бенчмарках fastify/h3, и такие проблемы нам не важны
- но при этом у нас была реальная и очень полезная возможность оставаться отзывчивыми, и держать под нагрузкой адекватные 2xx RPS
- также я не вижу интеграционных тестов в репозитории under-pressure и сомневаюсь до конца что инструмент работает ожидаемо

Пару дней назад Маттео написал даже в блок Platformatic (это их коммерческий продукт для Node.js стека) про кейс, итого получился подробный обзор проблемы (правда черезчур нейросетевой):
- https://x.com/matteocollina/status/1951322487595090177?s=19
- https://blog.platformatic.dev/the-dangers-of-setimmediate

Основной поинт Маттео который я полностью поддерживаю - надо использовать worker_threads, и само приложение поднимать в воркере, таким образом изолировать его event loop (тут он рекламирует их сервер Watt который так делает из коробки)

Для Tramvai тут проблема что это сильно не вписывается в текущую архитектуру.

Придется делать еще один заход и смотреть как мы можем избавиться от setImmediate, и что в итоге выжать из Request Limiter под нагрузками на свежих версиях Node.js
👍14🔥103