По вопросам: @vice22821
Чат: @code_of_art
Post #338
234
Новый пост из серии про паттерны на реальном коде. Разбираем штуку, с которой рано или поздно встречается любой сервис, живущий в интернете: в него начинают лить больше, чем он способен переварить.
Ситуация простая. У тебя есть сервис, который что-то принимает извне — запросы, события, сообщения. И в какой-то момент поток на входе становится больше, чем ты успеваешь обрабатывать. Причины разные: у клиента выкатили баг, и он спамит одним и тем же по кругу; резкий наплыв пользователей; банальный DDoS. Если просто сидеть и принимать всё подряд, исход один из двух — либо сервис ложится под нагрузкой, либо копит необработанное в памяти, пока она не кончится, и падает уже намертво. Защита от этого называется backpressure: сервис не молча захлёбывается, а сам говорит источникам «э, притормозите».
Разберём на Sentry. Если не знаком: это сервис, куда приложения шлют информацию о своих ошибках и падениях — упало что-то в проде, полетел отчёт с деталями, разработчики видят его в удобном интерфейсе, а не роются в логах. Нагрузка у неё дикая: тысячи чужих приложений долбят её событиями безостановочно. На входе у Sentry стоит отдельный компонент — Relay, он первым встречает весь этот поток и решает, что с ним делать. На нём и посмотрим, как грамотно держать удар. Код открыт: https://github.com/getsentry/relay
Первое, что там сделано с умом, — ограничение стоит не одним общим рубильником на всё, а раздельно, по типам данных и по каждому проекту. Можно прижать один вид событий у одного клиента, не задев остальной трафик. Клиенту, который упёрся в лимит, прилетает ответ 429 и заголовок с числом: «подожди столько-то секунд». И вот ключевая тонкость: правильный клиент в ответ на это НЕ шлёт то же самое заново — он выбрасывает событие и ждёт. Потому что если в ответ на «перегруз» все дружно начнут повторять запросы, они этим перегруз только усилят. В этом и суть backpressure: смысл ответа — «перестань слать вообще», а не «попробуй ещё разок». Обычная ошибка говорит «повтори», backpressure говорит «уймись» — это разные вещи.
Второй умный момент — как Relay хранит сами лимиты. Он держит их в памяти и освежает раз в несколько минут, а не бегает во внешнее хранилище на каждое событие (иначе оно само стало бы узким горлышком). Отсюда забавная деталь: самый первый запрос, упёршийся в свежий лимит, может ещё проскочить с ответом 200 — просто потому что в памяти лимит ещё не обновился, а следующий уже получит честный отказ. Осознанный размен: чуть менее точно, зато во много раз дешевле под нагрузкой. Клиентов, которые давно в лимите, Relay отшивает вообще бесплатно — их «приговор» уже лежит в памяти.
Третий уровень — что делать с тем, что уже приняли, но обработать пока не успели. Relay складывает такие события в очередь в памяти, но следит за её размером: пока памяти занято меньше порога (по умолчанию 80%) — держит очередь в ней, ради скорости. Перевалило за порог — начинает скидывать на диск. Медленнее, зато не сожрёт всю память и не рухнет, пока разгребает завал. Такой буфер сглаживает всплеск: не теряем события и не падаем от переполнения памяти.
Теперь — как это утащить к себе, даже без всякого Sentry. Принцип работает везде, где что-то принимает поток извне. Пишешь API, которое дёргают чужие сервисы? Отдавай на перегрузе честный 429 с «подожди столько-то», а не пытайся героически переварить всё и не роняй сервер. Обрабатываешь очередь сообщений? Следи за её длиной и умей притормозить того, кто в неё пишет. Ходишь во внешний API сам? Уважай его 429 и не ретрай мгновенно — ты этим только добьёшь того, кто и так лежит.
И три мысли на вынос. Лимиты делай точечными, а не «рубильник на всё сразу». Сигнал перегруза — это «перестань», а не «повтори», и клиенты должны его уважать. И заранее думай, что делать с тем, что уже принял, но не осилил, — буфер с выходом на диск лучше, чем гордое падение. Наивное «очередь большая — верну 500» не делает ничего из этого, поэтому и падает первым.
Подписаться: @codeof_art
Вступить в чатик: @code_of_art
Ситуация простая. У тебя есть сервис, который что-то принимает извне — запросы, события, сообщения. И в какой-то момент поток на входе становится больше, чем ты успеваешь обрабатывать. Причины разные: у клиента выкатили баг, и он спамит одним и тем же по кругу; резкий наплыв пользователей; банальный DDoS. Если просто сидеть и принимать всё подряд, исход один из двух — либо сервис ложится под нагрузкой, либо копит необработанное в памяти, пока она не кончится, и падает уже намертво. Защита от этого называется backpressure: сервис не молча захлёбывается, а сам говорит источникам «э, притормозите».
Разберём на Sentry. Если не знаком: это сервис, куда приложения шлют информацию о своих ошибках и падениях — упало что-то в проде, полетел отчёт с деталями, разработчики видят его в удобном интерфейсе, а не роются в логах. Нагрузка у неё дикая: тысячи чужих приложений долбят её событиями безостановочно. На входе у Sentry стоит отдельный компонент — Relay, он первым встречает весь этот поток и решает, что с ним делать. На нём и посмотрим, как грамотно держать удар. Код открыт: https://github.com/getsentry/relay
Первое, что там сделано с умом, — ограничение стоит не одним общим рубильником на всё, а раздельно, по типам данных и по каждому проекту. Можно прижать один вид событий у одного клиента, не задев остальной трафик. Клиенту, который упёрся в лимит, прилетает ответ 429 и заголовок с числом: «подожди столько-то секунд». И вот ключевая тонкость: правильный клиент в ответ на это НЕ шлёт то же самое заново — он выбрасывает событие и ждёт. Потому что если в ответ на «перегруз» все дружно начнут повторять запросы, они этим перегруз только усилят. В этом и суть backpressure: смысл ответа — «перестань слать вообще», а не «попробуй ещё разок». Обычная ошибка говорит «повтори», backpressure говорит «уймись» — это разные вещи.
Второй умный момент — как Relay хранит сами лимиты. Он держит их в памяти и освежает раз в несколько минут, а не бегает во внешнее хранилище на каждое событие (иначе оно само стало бы узким горлышком). Отсюда забавная деталь: самый первый запрос, упёршийся в свежий лимит, может ещё проскочить с ответом 200 — просто потому что в памяти лимит ещё не обновился, а следующий уже получит честный отказ. Осознанный размен: чуть менее точно, зато во много раз дешевле под нагрузкой. Клиентов, которые давно в лимите, Relay отшивает вообще бесплатно — их «приговор» уже лежит в памяти.
Третий уровень — что делать с тем, что уже приняли, но обработать пока не успели. Relay складывает такие события в очередь в памяти, но следит за её размером: пока памяти занято меньше порога (по умолчанию 80%) — держит очередь в ней, ради скорости. Перевалило за порог — начинает скидывать на диск. Медленнее, зато не сожрёт всю память и не рухнет, пока разгребает завал. Такой буфер сглаживает всплеск: не теряем события и не падаем от переполнения памяти.
Теперь — как это утащить к себе, даже без всякого Sentry. Принцип работает везде, где что-то принимает поток извне. Пишешь API, которое дёргают чужие сервисы? Отдавай на перегрузе честный 429 с «подожди столько-то», а не пытайся героически переварить всё и не роняй сервер. Обрабатываешь очередь сообщений? Следи за её длиной и умей притормозить того, кто в неё пишет. Ходишь во внешний API сам? Уважай его 429 и не ретрай мгновенно — ты этим только добьёшь того, кто и так лежит.
И три мысли на вынос. Лимиты делай точечными, а не «рубильник на всё сразу». Сигнал перегруза — это «перестань», а не «повтори», и клиенты должны его уважать. И заранее думай, что делать с тем, что уже принял, но не осилил, — буфер с выходом на диск лучше, чем гордое падение. Наивное «очередь большая — верну 500» не делает ничего из этого, поэтому и падает первым.
Подписаться: @codeof_art
Вступить в чатик: @code_of_art
- ❤ 3
- ⚡ 2



