# теория · шаг 1 из 5
Что сжимать и чем
Коротко
Сжатие - самая дешёвая оптимизация на стороне nginx. Замер: 300 КБ текста уезжают как 85 КБ.
- По умолчанию сжимается только
text/html. Ни CSS, ни JS, ни JSON безgzip_typesне жмутся вовсе. - Уровень 9 против 5: минус 6% байт за втрое большее время. Рабочий диапазон - 4-6.
- Для неизменяемой статики жать на лету незачем:
gzip_static onотдаёт готовый.gz. gzip_vary onобязателен за любым кешем, а brotli и zstd в стандартную сборку не входят.
Если помнишь, что gzip_types по умолчанию покрывает один тип, - листай до «Теперь сам».
Сначала ответь сам
В конфиге написано ровно gzip on;. Что сожмётся из этого списка: HTML-страница,
app.js, data.json, photo.jpg?
Замер на стенде, размер ответа при Accept-Encoding: gzip:
for f in data.json app.js photo.jpg; do printf '%-12s ' $f; curl -s -H 'Accept-Encoding: gzip' -o /dev/null -w '%{size_download}\n' http://shop.local/$f; done
data.json 120391
app.js 78000
photo.jpg 2000000
Только HTML. gzip_types по умолчанию содержит один тип - text/html, - и это
единственное, что жмётся из коробки. Добавляем типы:
data.json 2204 (было 120391)
app.js 526 (было 78000)
На что это похоже
Вакуумный пакет для вещей. Свитер сжимается в четыре раза, потому что в нём воздух. Кирпич в таком пакете не сжимается вовсе - в нём нечего выдавливать, - а время на упаковку ты потратишь.
Текст - это свитер: в нём полно повторов. JPEG, видео и woff2 - кирпичи,
внутри уже сжатые своим алгоритмом.
Механизм: договор через два заголовка
gzip_vary по умолчанию off
Заголовок nginx сам не добавит. Ошибка редкая, но зато крайне неприятная: воспроизводится только через кеширующий прокси и только у части пользователей - они видят бинарный мусор вместо страницы.
Правило
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types text/css text/plain text/xml application/javascript application/json
application/xml image/svg+xml application/wasm;
| Сжимать | Не сжимать |
|---|---|
| HTML, CSS, JS, JSON, XML, SVG | JPEG, PNG, WebP, AVIF |
шрифты .ttf, .otf, .eot |
шрифты .woff, .woff2 (сжаты внутри) |
.txt, .csv, .map, .wasm |
видео, аудио, .zip, .gz, .pdf |
text/html в список писать не нужно - он сжимается всегда. И смотри на
content-type из ответа: application/javascript и text/javascript -
разные строки, и если бэкенд отдаёт вторую, а в конфиге первая, сжатия не будет.
Разбор: почему не девятка
gzip_comp_level принимает от 1 до 9, по умолчанию 1. Замер на 300 КБ реального
текста, лучшее время из семи прогонов:
| уровень | размер | время |
|---|---|---|
| без сжатия | 300 000 байт | - |
| 1 | 111 667 (37%) | 5,9 мс |
| 5 | 85 355 (28%) | 12,7 мс |
| 9 | 79 704 (27%) | 39,3 мс |
Переход с 1 на 5 стоит удвоения времени и даёт минус четверть байт - выгодно. Переход с 5 на 9 стоит втрое большего времени, а размер режет всего с 28% до 27% от оригинала - то есть примерно на 6% относительно уже сжатого (85 355 → 79 704). На лету это не окупается. Отсюда и рабочий диапазон 4-6.
Разбор: как не платить процессором вовсе
gzip_static on;
nginx поищет рядом с app.js готовый app.js.gz и отдаст его как есть. Замер:
файл, сжатый gzip -9 при сборке, весит 262 байта, и отдаётся он без единого
такта на сжатие:
Content-Length: 262
Content-Encoding: gzip
Готовый .gz кладут рядом с файлом при сборке: gzip -9 -k app.js создаёт
app.js.gz, не удаляя оригинал (-k). Файл готовится один раз при сборке фронтенда - там можно спокойно жать девятым
уровнем, это уже не стоит времени запроса. Так делают для всей неизменяемой
статики с хешем в имени.
Что ломается без этого
Сжатие меняет ответ сильнее, чем кажется, и два побочных эффекта всплывают в других главах.
Пропадает Content-Length. Сжатый ответ едет как Transfer-Encoding:
chunked - размер заранее неизвестен. Отсюда две вещи, которые ты уже видел:
докачка по Range на сжатых типах не работает вовсе (пятый раздел), а ETag
становится слабым (W/"...").
Поток задерживается. Сжатие обязано накопить данные, прежде чем их сжать, -
именно оно, а не буферизация, копит потоковый ответ (седьмой раздел). На
маршруте с событиями gzip off; ставят осознанно.
Зачем это в работе
Brotli на текстовых файлах даёт примерно на 15-20% меньше байт, чем gzip, и поддерживается всеми браузерами больше пяти лет. Zstd жмёт быстрее при похожем результате.
Важная деталь: в стандартной сборке nginx их нет. gzip - штатный модуль, а
brotli и zstd - внешние: ngx_brotli собирается как динамический модуль (в
популярных дистрибутивах есть готовые пакеты), zstd - только сторонние сборки.
Поэтому строка brotli on; в чужом конфиге - не признак свежей версии, а
признак того, что у человека собран модуль; на голом nginx:stable такой конфиг
просто не запустится.
Порядок внедрения: gzip первым, он бесплатен и работает везде; gzip_static
вторым, он снимает нагрузку; brotli третьим, когда готов собирать модуль и
поддерживать его при обновлениях.
Теперь сам
На сайте включили gzip on; gzip_comp_level 9; и добавили все типы. Через
неделю мониторинг показывает рост $request_time на страницах каталога при
неизменном $upstream_response_time. Что произошло и что делать?
Время выросло на сжатии: девятый уровень стоит втрое дороже пятого, а страницы
каталога большие. $upstream_response_time не изменился, потому что бэкенд тут
ни при чём - тормозит сам nginx. Делать: вернуть уровень 5, а для неизменяемой
статики включить gzip_static и жать девяткой при сборке - там время ничего не
стоит.
Главное
gzip_types по умолчанию покрывает только text/html: без явного списка не
жмётся ни CSS, ни JS, ни JSON. Уровень 4-6 - рабочий диапазон, девятка стоит
втрое больше времени ради шести процентов байт. Для неизменяемой статики
готовят .gz при сборке и включают gzip_static on. gzip_vary on обязателен
за любым кешем, картинки и woff2 жать бессмысленно, а brotli и zstd в
стандартную сборку nginx не входят. И помни, что сжатие убирает
Content-Length: отсюда сломанный Range и задержка потоковых ответов.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий