Теория
Принцип
Память живёт столько же, сколько 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); - можно ли буфер переиспользовать/отправлять/сохранять.
Обычно метаданные, а не «владелец всей памяти».
chain buffers
Тело ответа передаётся как цепочки буферов (ngx_chain_t):
- модуль добавляет куски в chain;
- следующий фильтр читает chain и передаёт дальше;
- удобно для стриминга и больших ответов без копирования всего тела.
cleanup handlers
cleanup handler регистрируется в request pool и вызывается при завершении request:
- закрыть временный файл;
- освободить внешний ресурс;
- отменить побочный state.
Cleanup вызывается при уничтожении pool, в том числе на аварийных путях.
Что нужно уметь объяснить
Почему нет free() на каждый объект?
Объекты запроса живут примерно одинаково долго. Вместо тысяч malloc/free — много быстрых выделений из pool и один destroy pool в конце.
Что произойдёт, если сохранить указатель вне request lifetime?
После завершения request память pool уже недействительна. Указатель становится dangling: чтение/запись через него может привести к крашу или повреждению данных.
Почему это быстрее malloc/free?
- меньше вызовов аллокатора общего назначения;
- меньше фрагментации;
- лучше locality (данные request лежат рядом);
- дешевле очистка: один
pool destroyвместо множестваfree.
Практика
1. Увеличить client_body
По умолчанию client_max_body_size 1m. Поднимем:
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; # по умолчанию on; сравнить с off
}
on: больше буферизации в NGINX, меньше pressure от медленных клиентов;off: ближе к стримингу, меньше внутренней буферизации, выше зависимость от скорости клиента.
4. Память через top
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Под нагрузкой смотреть: RES/MEM% у worker, изменения при proxy_buffering on/off, рост temp-файлов при больших body.