⚙️ Теория
🧠 Где используется shared memory
Shared memory zone в NGINX нужна, когда данные должны быть общими для всех worker-процессов.
Типичные случаи:
limit_reqlimit_connssl_session_cache shared:...upstream zone
Без shared зоны каждый worker видел бы только свою локальную статистику, и глобальные лимиты/состояние работали бы некорректно.
🧱 slab allocator
Внутри shared zone память выдаётся slab-аллокатором:
- память заранее выделена крупным регионом;
- делится на блоки фиксированных классов размеров;
- уменьшает фрагментацию и ускоряет повторные выделения в shared сегменте.
🌳 rbtree
Для быстрого поиска ключей (IP, session id, upstream peer state) часто используется red-black tree:
- вставка/поиск/удаление примерно
O(log N); - предсказуемое поведение даже при большом числе записей;
- обычно сочетается с дополнительными структурами (очереди/ttl/счётчики).
🔒 shared mutex
Так как shared zone трогают разные worker-процессы, доступ синхронизируется lock-ами:
- в slab pool есть общий mutex (
ngx_shmtx_t); - критические секции в shared memory оборачиваются
ngx_shmtx_lock()/ngx_shmtx_unlock(); - при высокой конкуренции время ожидания lock растёт и становится заметным bottleneck.
⚛️ atomic operations
Atomic-инструкции в NGINX используются как строительные блоки синхронизации (в том числе в lock-механизмах) и для простых конкурентных обновлений:
- инкремент/декремент счётчиков;
- CAS-проверки;
- барьеры памяти при синхронизации.
Они дешевле тяжёлых lock-ов, но не заменяют их для сложных структур.
❓ Что нужно уметь объяснить
Где возможен lock contention?
- горячие ключи в
limit_req/limit_conn(много запросов в один и тот же key); - частые вставки/удаления в shared rbtree под burst-нагрузкой;
- интенсивные обновления
ssl session cacheпри коротких TLS-сессиях; upstream zoneпри большом числе worker и частых изменениях peer state.
Признаки: рост CPU без роста полезного RPS, ухудшение p95/p99, высокая системная активность.
Почему shared zone может стать bottleneck?
Потому что это общий ресурс для всех worker:
- каждое обращение конкурирует за общий lock-path;
- размер зоны ограничен, при нехватке памяти растут издержки на вытеснение/ошибки;
- при "горячих" ключах нагрузка концентрируется в одном участке структуры.
Итог: масштабирование по числу worker перестаёт давать линейный прирост.
Чем per-worker state отличается от shared state?
per-worker state:
- локально в процессе;
- без межпроцессных lock;
- быстрее, но не даёт глобально согласованной картины.
shared state:
- единое состояние для всех worker;
- требует синхронизации;
- немного дороже по CPU, но необходимо для глобальных лимитов и общей статистики.
🧪 Практика
1. Включить limit_req
http {
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=20r/s;
server {
listen 8080;
server_name _;
location / {
limit_req zone=req_zone burst=100 nodelay;
proxy_pass http://127.0.0.1:18080;
}
}
}
Проверка и reload:
nginx -t && sudo nginx -s reload
2. Подать burst-нагрузку
wrk -t4 -c400 -d30s http://127.0.0.1:8080/
Дополнительно можно увеличить пиковую конкуренцию:
wrk -t8 -c800 -d30s http://127.0.0.1:8080/
3. Посмотреть CPU
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Смотреть вместе:
- requests/sec и latency в
wrk; 503в ответах и логах (по умолчанию дляlimit_req;429только если заданlimit_req_status 429);- загрузку CPU по worker.
4. Изменить размер zone
Сравнить, например, так:
limit_req_zone $binary_remote_addr zone=req_zone:1m rate=20r/s; # маленькая зона
# vs
limit_req_zone $binary_remote_addr zone=req_zone:50m rate=20r/s; # большая зона
После каждого изменения:
nginx -t && sudo nginx -s reload
Сравнивать:
- стабильность RPS под burst;
- ошибки лимитера;
- CPU при одинаковом профиле нагрузки.
🧾 Вывод
Shared memory в NGINX даёт глобально согласованное состояние между worker, но за это платят синхронизацией.
При burst-нагрузке узким местом становятся горячие ключи, lock contention и недостаточный размер zone.
Практический баланс: держать только действительно общий state в shared зонах, а размер zone и лимиты подбирать по замерам CPU/latency под реальным трафиком.
📚 Ссылки
- NGINX docs: limit_req_zone
- NGINX docs: limit_conn_zone
- NGINX docs: ssl_session_cache
- NGINX docs: upstream zone
- NGINX Development guide: Shared memory
- NGINX docs: lock_file / atomic locks