Сжатие текстовых ресурсов — это базовая мера оптимизации скорости загрузки веб‑приложений. Действительно, текста в вебе много: HTML, CSS, JS, JSON, SVG и не только. Казалось бы, тема простая: включили gzip, добавили типы контента для сжатия и всё готово. Однако, как всегда самое интересное в деталях. Разберёмся во всём подробно.

Навигация по циклу

  1. Почему стоит переходить на Angie.

  2. Установка Angie из пакетов и в докере.

  3. Переезд с Nginx на Angie. Пошаговая инструкция.

  4. Настройка location в Angie. Разделение динамических и статических запросов.

  5. Перенаправления в Angie: return, rewrite и примеры их применения.

  6. Сжатие текста в Angie: статика, динамика, производительность.

Видеоверсия

Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.

Механизм согласования компрессии

Процесс согласования вариантов сжатия ответа опирается на HTTP‑заголовки. Клиент в запросе может указать заголовок Accept‑Encoding, в котором содержится список вариантов, например:

Accept-Encoding: gzip, deflate, br, zstd

Сервер, в этом случае может использовать любой из указанных типов сжатия и добавляет соответствующий заголовок ответа:

Сontent-Encoding: gzip

На сегодня классическим компрессором для Angie является gzip, который доступен в стандартной поставке. Но также есть еще два более современных конкурента: brotli и zstd, они также заслуживают пристального внимания. Кстати, набор алгоритмов компрессии у браузеров зависит от протокола доступа (HTTP или HTTPS). Для обычного HTTP браузеры заявляют поддержку gzip и deflate, а более продвинутые zstd и brotli доступны только при использовании HTTPS.

По умолчанию, при отсутствии заголовка Accept‑Encoding, сервер должен отвечать несжатым документом. Этот аспект стоит учитывать при тестировании методов сжатия, не забывайте добавлять заголовок запроса, например для утилиты ab:

ab -n 1000 -c 4 -H "Accept-Encoding: gzip" -k http://127.1/

При использовании кэширующих прокси‑серверов сжатие может повлиять на совместимость с клиентами, поэтому в сжатых ответах можно встретить заголовок Vary, который указывает на различие ответа в зависимости от заголовков запроса. Например:

Vary: Accept-Encoding

В этом примере указана зависимость ответа от заголовка запроса Accept‑Encoding. Этот заголовок позволит корректно отдать из кэша сжатый ответ только тому клиенту, который сможет его прочитать.

Динамическое и статическое сжатие

Принципиально сжатие можно реализовать двумя методами: динамическим и статическим.

Динамическое сжатие — это компрессия «на лету», то есть непосредственно до отправки ответа клиенту. В терминах веб‑сервера сжатие происходит фильтром, поэтому соответствующие модули имеют в названии слово «filter». В этом варианте мы затрачиваем ресурсы процессора для сжатия на каждый ответ. Если сам ответ является динамическим — например это документ от бэкенда, то и сжимать его можно только динамически. Кстати, если бэкенд отвечает сжатым документом, то повторного сжатия не происходит — ответ передаётся в без изменений. За счет этого можно регулировать распределение нагрузки: если бекенд сжимает ответ, то нагрузка на нём, если отдаёт несжатый ответ, то сжатием будет заниматься фронтенд. Динамическое сжатие предъявляет повышенные требования к скорости работы алгоритма, поэтому используются быстрые компрессоры с низкими настройками степени сжатия.

Статическое сжатие — создание сжатой версии файла на диске для ответа пользователю. При использовании этого метода ресурсы процессора используются только один раз — при создании сжатой копии. При этом, веб-сервер к сжатию уже никакого отношения не имеет: сжатый файл создаётся заранее с помощью утилиты. Использование статического сжатия позволяет не только экономить ресурсы процессора, но и получить максимальную степень компрессии, так как нет необходимости экономить процессор при сжатии. То есть, можно использовать относительно медленные алгоритмы с максимальными настройками степени компрессии.

Далее в статье мы будем рассматривать оба метода сжатия и их применение в различных случаях. Теперь поговорим о доступных алгоритмах сжатия для веб-приложений. Конечно, нас интересуют только те варианты компрессии, которые поддерживаются браузерами и веб-сервером Angie.

Компрессор gzip

Алгоритм сжатия gzip наряду с deflate получил максимально широкую поддержку среди браузеров и веб‑серверов. Этот компрессор позволяет достаточно эффективно сжимать текстовые документы, пригоден для динамического и статического вариантов. Компрессор основан на алгоритме Хаффмана и кодировании LZ77. Таким образом, поддержку gzip‑формата сжатия можно считать базовым вариантом и хорошей практикой. Однако, необходимо корректно настроить Angie для получения нужного компромисса между затратами ресурсов процессора и степенью сжатия. Рассмотрим пример типичной конфигурации для gzip‑компрессии:

http {
  gzip  on;
  gzip_types text/plain text/css text/xml application/javascript application/json image/svg+xml application/font-ttf;
  gzip_comp_level 3;
  gzip_proxied    any;
  gzip_min_length 1000;
  gzip_vary	on;

  server {
	location ~* \.(?:js|css|svg|txt|ttf|xml)$ {
		gzip_static on;
    }
  }
}

Здесь активированы оба режима сжатия: динамический (gzip) и статический (gzip_static). Значение on в директиве gzip_static означает поиск сжатых версий файлов (с суффиксом .gz) на диске. Если сжатый файл найден, то он будет использоваться в качестве ответа. Также важно выставить дату модификации файла такой же, как у несжатой версии файла для лучшего кэширования. Обратите внимание, что хотя gzip_static можно указать на уровне http, мы указали её на уровне location, для исключения операций поиска сжатых файлов для каждого статического запроса (например, для картинок JPEG).

Далее перечисляются MIME‑типы для контента, который должен сжиматься. Обратите внимание, что название типов может отличаться на различных системах, поэтому стоит свериться с файлом mime.types из активной конфигурации Angie. Важнейший параметр для динамического сжатия: степень компрессии (gzip_comp_level). Его значение может изменяться от 1 до 9. Для обеспечения минимальной нагрузки на процессор можно оставить значение по умолчанию (1). Если ресурсов процессора достаточно и количество запросов с динамическим сжатием невелико, можно повышать степень сжатия до 3–5, дальше затраты на сжатие будут расти быстро, а степень компрессии изменится незначительно.

Дополнительно разрешено сжатие проксированных запросов (gzip_proxied), установлен минимальный размер ответа для сжатия (gzip_min_length) и добавлен заголовок ответа Vary.

Кроме указанных директив можно контролировать отключение gzip для определённых клиентов по регулярному выражению (gzip_disable), а также настраивать буфер для сжатия (gzip_buffers). В общем конфигурацию компрессии лучше расположить в контексте http, чтобы она работала на уровне всего сервера, но допускается более точная конфигурация на уровне server, location и if в location.

Подготовить сжатые файлы для статического режима можно с использованием стандартной утилиты gzip или с помощью оптимизированной под степень сжатие утилитой zopfli. Использование zopfli позволит сэкономить около 10-20% от размера сжатого файла. Примеры команд с сохранением исходного файла.

gzip -k9 'path/fname'
zopfli --gzip -i15 'path/fname'; touch -r 'path/fname' 'path/fname.gz'
zstd -f9 --format=gzip 'path/fname'

Использование gzip-сжатия обеспечивает максимальную совместимость с различными клиентами и поэтому практически обязательно для стандартной конфигурации Angie. Но существуют более совершенные форматы сжатия, специально созданные для задач веба, например brotli.

Компрессор brotli

Формат brotli является разработкой компании Google, его основная задача — максимальная степень компрессии текстового веб‑контента при сохранении высокой скорости распаковки. При этом скорость компрессии может быть гораздо ниже стандартного gzip, хотя на низких настройках они сопоставимы. Эффективность сжатия достигается за счет использования словаря и других улучшений алгоритма DEFLATE.

Для использования brotli в Angie необходимо установить сторонние модули, что не составляет проблем (они находятся в репозитории Angie):

sudo apt install angie-module-brotli
sudo dnf install angie-module-brotli

В пакете содержатся два модуля для статического и динамического сжатия. Для их использования необходимо добавить в главную секцию (контекст main) конфигурационного файла (в начало angie.conf) директивы загрузки динамических модулей:

load_module modules/ngx_http_brotli_static_module.so;
load_module modules/ngx_http_brotli_filter_module.so;

В целом конфигурация сжатия похожа на набор директив модулей gzip, но есть небольшие отличия. Пример рабочей конфигурации для динамического и статического сжатия:

http {
  brotli		on;
  brotli_comp_level 3;
  brotli_types	text/plain text/xml text/css application/javascript application/json image/x-icon image/svg+xml;

  server {
	location ~* \.(?:js|css|svg|txt|ttf|xml)$ {
		brotli_static    on;
    }
  }
}

Главный выбор, который необходимо сделать — это степень компрессии. В brotli доступен диапазон от 1 до 11. При этом одинаковые значения с gzip дают большую степень компрессии и как правило более низкую скорость сжатия. Подробнее сравнение скорости будет представлено в конце статьи. Также нужно учитывать, что особенно эффективно (по коэффициенту сжатия) brotli работает при большом размере файла (более 60 КБ).

Дополнительно для настройки доступны директивы минимального размера объекта для сжатия (brotli_min_length) и размера окна (brotli_window).

Подготовка статически сжатых файлов производится с помощью стандартной утилиты brotli, которую можно установить из пакета в Ubuntu.

sudo apt install brotli
brotli -fZ 'path/fname'

Здесь стоит напомнить, что поддержка brotli в браузерах работает только при использовании HTTPS.

Подводя промежуточный итог по brotli можно сказать, что это лучший вариант для статического режима и достаточно хороший для динамического при вдумчивом выборе степени компрессии. Но на этом варианты компрессии не исчерпаны, поэтому переходим к алгоритму Zstandard (zstd).

Компрессор zstd

Третий доступный на сегодня вариант сжатия в вебе это алгоритм Zstandard, реализованный в утилите zstd. Этот алгоритм основан на использовании LZ77 в сочетании с эффективным энтропийным кодированием. Для простоты будем далее называть алгоритм zstd. Задача при его разработке была получить высокую производительность при степени компрессии, сравнимой с алгоритмом deflate. Такие качества делают zstd отличным кандидатом на роль динамического компрессора.

Поддержку zstd в Angie можно получить за счет установки сторонних модулей из репозитория:

sudo apt install angie-module-zstd
sudo dnf install angie-module-zstd

По аналогии с модулями для brotli подключаем загрузку в конфиг:

load_module modules/ngx_http_zstd_static_module.so;
load_module modules/ngx_http_zstd_filter_module.so;

Конфигурация также аналогична другим модулям, кроме того, что статический режим скорее всего здесь не пригодится, поэтому настройка закомментирована (тогда можно убрать и загрузку модуля ngx_http_zstd_static_module).

http {
  zstd		on;
  #zstd_static	on;
  zstd_min_length 	1000;
  zstd_comp_level 	5;
  zstd_types		text/plain text/css text/xml application/javascript application/json image/x-icon image/svg+xml;
}

Как обычно, выбор степени компрессии для динамического режима является компромиссом между нагрузкой на процессор и степенью сжатия. На этом стоит посмотреть на таблицу сравнения различных алгоритмов в динамическом режиме.

Приоритет методов сжатия в Angie

С учетом дополнительных модулей мы получили три различных алгоритма сжатия, каждый из которых имеет статический и динамический режимы. Сразу же возникает вопрос: что будет, если клиент поддерживает все алгоритмы и мы подключили все возможные алгоритмы и их режимы? Здесь действует два правила.

  1. Среди статического и динамического режима выигрывает статический.

  2. Среди различных модулей в рамках одного режима выигрывает тот, который подключен позже в конфигурации.

Так как модуль gzip (и gzip_static) входят в стандартную сборку и их не требуется подключать, то они будут иметь самый низкий приоритет. Дополнительные модули (при установке из пакетов) мы подключаем как динамические и можем управлять порядком их следования в конфигурации.

Если следовать логике статьи и использовать brotli как приоритетный вариант для статического сжатия, а zstd для динамического, то порядок загрузки модулей будет следующим.

load_module modules/ngx_http_brotli_static_module.so;
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_zstd_filter_module.so;

При использовании статического сжатия будет проверяться brotli, а если его нет, то gzip. Для динамического сжатия будет использоваться следующий порядок: zstd, brotli, gzip.

Сравнение производительности компрессоров

Для выбора степени компрессии различных модулей необходимо протестировать пропускную способность. Чтобы упростить тестирование использовался один рабочий процесс Angie, доступ осуществлялся по HTTPS, нагрузка создавалась с помощью утилиты ab, примерно так:

ab -n 1000 -k -c 1 \
 -H "Accept-Encoding: gzip,br,zstd" https://site.ru/css/1.css

В качестве тестовых файлов использовались реальные файлы CSS, HTML, JS большого объёма (около 1МБ).

Результаты тестирования оказались очень неожиданными. Наиболее подробные результаты приводятся для CSS‑файла (770 КБ).

CSS

Gzip

Zstd

Brotli

Компрессия

RPS

Размер (%)

RPS

Размер (%)

RPS

Размер (%)

1

130

20,43%

235

18,24%

215

22,17%

2

125

19,16%

230

18,07%

170

17,61%

3

112

18,41%

212

17,40%

154

16,77%

4

104

17,06%

215

17,38%

125

16,02%

5

85

16,02%

175

16,05%

85

14,42%

6

68

15,63%

160

15,46%

80

14,09%

7

58

15,51%

130

14,83%

65

13,84%

8

38

15,44%

110

14,55%

51

13,69%

9

30

15,43%

90

14,28%

35

13,56%

10

83

14,28%

3

12,38%

11

70

14,28%

1

12,12%

Для HTML-файла (1,8 МБ) результаты аналогичные.

HTML

Gzip

Zstd

Brotli

Компрессия

RPS

Размер (%)

RPS

Размер (%)

RPS

Размер (%)

4

80

5,97%

250

4,38%

135

4,35%

5

75

5,58%

165

4,21%

82

3,93%

Для полноты картины результаты для JS-файла (1,5 МБ).

JS

Gzip

Zstd

Brotli

Компрессия

RPS

Размер (%)

RPS

Размер (%)

RPS

Размер (%)

4

40

26,59%

100

26,49%

48

25,80%

5

31

25,54%

75

25,34%

30

23,95%

Какие выводы можно сделать из результатов теста? Во‑первых, zstd однозначно является лидером по скорости и при средних коэффициентах сжатия быстрее gzip примерно в два раза. Этот результат ожидаем, так как для таких режимов zstd и был создан.

Но второй вывод в том, что brotli на низких значениях степени компрессии опережает gzip как по скорости, так и по коэффициенту сжатия. Это было довольно неожиданно: казалось бы, gzip должен справляться в динамическом режиме лучше, чем brotli. Возможно, сказался подбор тестовых файлов. В любом случае, стоит тестировать на реальных данных для выбора коэффициента сжатия.

С точки зрения рабочего процесса динамическое сжатие является блокирующей операцией, поэтому стоит как можно активнее использовать статические методы компрессии, а для динамического режима выбирать быстрые режимы и алгоритмы. При использовании протоколов HTTP/2 или HTTP/3 все запросы от одного клиента будут обрабатываться одним рабочим процессом, то есть практически на одном ядре процессора сервера, что обостряет проблему блокировки.

Также при выборе степени динамической компрессии нужно учитывать сумму времени сжатия и времени передачи сжатого файла. То есть, для каждого значения пропускной способности сети будет оптимальная настройка конкретного компрессора. Существует удобный ресурс для подбора этих сочетаний (там доступны gzip и brotli), но стоит учитывать, что он использует устаревшие версии компрессоров.

Итоги

Мы рассмотрели три основных решения для компрессии текстовых ресурсов в Angie.

Исходя из выводов по тестированию динамических режимов, самые слабые результаты показал стандартный gzip. Использование этого компрессора можно рекомендовать либо в статическом режиме (с использованием zopfli), либо с минимальными настройками компрессии для совместимости.

Алгоритм brotli является лидером по качеству сжатия и лучшим вариантом для статического режима. Но и в динамическом режиме он может конкурировать по скорости с gzip, при этом обеспечивая лучшую степень сжатия.

Наконец, zstd будет лучшим вариантом для динамического сжатия за счет максимальной скорости работы, при этом качество сжатия местами даже лучше, чем у gzip.

В результате довольно интересно выглядит связка с zstd для динамического режима, brotli для статического и gzip (динамический и статический) для совместимости со старыми клиентами или работы по HTTP.

Теперь вы сможете выбрать оптимальный вариант для своего сервера, основываясь на знаниях особенностей алгоритмов сжатия, доступных для веба на сегодня.

Комментарии (2)


  1. novoselov
    14.07.2025 15:05

    Интересно было бы увидеть оценку реального влияния на скорость.
    При размере меньше 1KB сжатие дает минимальный эффект, так что лучше сразу выставить min_length на уровне размера MTU. А дальше уже считать время сжатия на сервере и разжатия на клиенте при разных настройках и изменение в количестве передаваемых пакетов. При компрессии выше 6 уже практически не будет разницы по скорости передачи, а затраты на сжатие/расжатие будут расти сильнее.


    1. VBart
      14.07.2025 15:05

      Подгонять min_length строго под размер MTU - не имеет смысла, т.к. ответ состоит не только из самих сжимаемых данных, но и служебных данных от разного рода протокольных оберток, как тех же HTTP-заголовков, фрейминга, чанков и TLS-записей. В случае с HTTP/2 - все ещё гораздо сложнее, т.к. в один пакет могут паковаться данные сразу от нескольких ответов на разные запросы, а потому там в принципе чем меньше ответы - тем лучше, тем больше их поместится разом.

      Скорости компрессии и декомпрессии сильно зависят от вычислительных мощностей на сервере и клиенте, а скорость передачи от пропускной способности канала. Тут нет универсальных решений.