⚙️ Теория
🧠 Ключевой принцип
Память живет столько же, сколько request.
Большая часть временных структур выделяется из пула запроса и освобождается одним действием в конце его жизненного цикла.
🧩 request memory pool
Для каждого HTTP request NGINX создаёт memory pool (r->pool):
- мелкие объекты (структуры, строки, контексты модулей) берутся из этого пула;
- при завершении request пул уничтожается целиком;
- нет необходимости делать
free()для каждого маленького объекта отдельно.
Если данные должны жить дольше request, их нельзя хранить в r->pool — для более долгого срока жизни используют другие контексты (например pool соединения).
🧱 slab allocator
slab используется для долгоживущей shared memory между worker-процессами:
limit_req_zone;limit_conn_zone;keys_zoneуproxy_cache;upstream zone.
Это другой класс памяти: не на один request, а на общие структуры и статистику.
📦 ngx_buf_t
ngx_buf_t описывает кусок данных:
- где лежат данные (RAM или файл);
- диапазон байт (
pos/last,file_pos/file_last); - можно ли буфер переиспользовать/отправлять/сохранять.
Важно: ngx_buf_t обычно метаданные, а не “владелец всей памяти”.
🔗 chain buffers
NGINX передаёт тело ответа как цепочки буферов (ngx_chain_t):
- модуль добавляет куски в chain;
- следующий фильтр читает chain и передаёт дальше;
- это удобно для стриминга и больших ответов без копирования всего тела в один большой блок.
🧹 cleanup handlers
cleanup handler регистрируется в request pool и вызывается при завершении request:
- закрыть временный файл;
- освободить внешний ресурс;
- отменить побочный state.
Это safety-механизм: cleanup вызывается при уничтожении pool, в том числе в аварийных путях завершения request.
❓ Что нужно уметь объяснить
Почему нет free() на каждый объект?
Потому что объекты запроса живут примерно одинаково долго.
Вместо тысяч malloc/free NGINX делает много быстрых выделений из pool и потом один общий destroy pool в конце request.
Что произойдет, если сохранить указатель вне request lifetime?
После завершения request память pool уже недействительна.
Такой указатель становится “висячим” (dangling pointer): чтение/запись через него может привести к крашу, повреждению данных или редким плавающим багам.
Почему это быстрее malloc/free?
- меньше вызовов аллокатора общего назначения;
- меньше фрагментации памяти;
- лучше locality (данные request лежат ближе друг к другу);
- дешевле очистка: один
pool destroyвместо множестваfree.
🧪 Практика
1. Включить большой client_body
http {
client_max_body_size 100m;
client_body_buffer_size 128k;
client_body_temp_path /var/lib/nginx/body 1 2;
}
И перезагрузить:
nginx -t && sudo nginx -s reload
2. Посмотреть временные файлы
Когда тело запроса не помещается в RAM-буфер, NGINX пишет его во временные файлы.
sudo ls -lah /var/lib/nginx/body
sudo find /var/lib/nginx/body -type f | head
3. Изменить proxy_buffering on/off
location /api/ {
proxy_pass http://127.0.0.1:18080;
proxy_buffering on; # потом сравнить с off
}
Сравнить два режима:
on: больше буферизации в NGINX, меньше pressure на медленных клиентов;off: ближе к стримингу, меньше внутренней буферизации, но выше зависимость от скорости клиента.
4. Наблюдать память через top
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Под нагрузкой сравнивать:
RES/MEM%у worker;- изменения при
proxy_buffering on/off; - рост временных файлов при больших request body.
🧾 Вывод
Модель памяти NGINX строится вокруг жизненного цикла request: быстрые выделения из pool, cleanup в конце и минимум лишней ручной деаллокации.
slab решает отдельную задачу общей памяти между worker, а ngx_buf_t + chain buffers обеспечивают эффективную передачу данных.
На практике поведение памяти лучше всего видно при больших body и переключении proxy_buffering, с параллельным наблюдением top и temp-файлов.
📚 Ссылки
- NGINX Development guide: Pools
- NGINX Development guide: Memory management
- NGINX docs: client_body_buffer_size
- NGINX docs: proxy_buffering