Короткий ответ
Нужно отделить «память на один запрос» от «суммарной памяти при конкурентной нагрузке» — проблема почти всегда в том, что растёт число одновременно обрабатываемых запросов, буферов или незавершённых задач быстрее, чем они освобождаются, и решение зависит от того, какая из причин подтвердится профилированием под реальной многопоточной/многопроцессной нагрузкой.
Как это работает подробнее
Профиль на один запрос в изоляции не показывает эффект умножения на число конкурентных запросов, рост буферов очередей, накопление соединений в пуле или удержание больших объектов в кэше ответов дольше, чем нужно. При росте RPS каждый из этих факторов может расти нелинейно относительно «память на запрос».
- Компромисс. Жёсткое ограничение конкурентности защищает процесс от падения по памяти, но ухудшает пользовательский опыт под пиковой нагрузкой и требует продуманной стратегии деградации (очередь, retry на стороне клиента, приоритизация); увеличение лимитов памяти/масштабирование вширь откладывает проблему и стоит денег, но не устраняет архитектурный риск при ещё большей нагрузке — выбор зависит от допустимости деградации сервиса для бизнеса и бюджета на инфраструктуру.
Пример кода
example-282.txt
Наблюдение: при росте RPS с 100 до 500 RSS процесса растёт с 300МБ до 4ГБ и сервис падает по OOM,
хотя память одного запроса в профилировщике стабильно ~1-2МБ.
Диагностика: число одновременно обрабатываемых запросов не ограничено (асинхронный обработчик
без семафора), при пиковой нагрузке в памяти одновременно держится несколько тысяч запросов.
Решение: добавить asyncio.Semaphore(200) вокруг обработки запроса + очередь с ограниченным maxsize
на входе, явно отказывая в приёме новых запросов (503) при превышении лимита, вместо накопления.Что использовать на практике
- Практика. Воспроизводят нагрузку в контролируемом нагрузочном тесте, одновременно снимая метрики RSS процесса, числа активных соединений/задач, размеров очередей и пулов; смотрят, какая метрика растёт быстрее остальных перед падением. Дальше выбирают структурное решение: ограничение конкурентности (семафор на число одновременных обработок), backpressure на входе, ограничение размера кэшей и пулов соединений.
Подводные камни
- Риски. Чинят проблему увеличением лимита памяти контейнера без понимания первопричины, что откладывает инцидент, но не устраняет его при дальнейшем росте нагрузки; оптимизируют память одного запроса, хотя реальная причина — отсутствие ограничения на конкурентность или размер очередей.