Skip to content

docs(kompresja): gzip_static bez plików .gz był atrapą — dowody + werdykt brotli - #31

Merged
mpasternak merged 1 commit into
mainfrom
docs/pulapki-kompresji-i-prekompresja
Aug 6, 2026
Merged

docs(kompresja): gzip_static bez plików .gz był atrapą — dowody + werdykt brotli#31
mpasternak merged 1 commit into
mainfrom
docs/pulapki-kompresji-i-prekompresja

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Co się stało

gzip_static on siedzi w _bpp-locations.conf od dawna. Ta dyrektywa nie znaczy „kompresuj statyki" — znaczy „wyślij x.js.gz, jeśli istnieje".

Zmierzone na iplweb/bpp_appserver:202608.1399rc5: 3429 plików w staticroot.baked, z tego 0 sztuk .gz. Nginx nie miał czego znaleźć, więc cicho spadał na gzip on i kompresował plotly.min.js (4,4 MB) od nowa przy każdym cache-missie.

Objawu nie ma żadnego — strona działa, Content-Encoding: gzip jest, rozmiar się zgadza. Różnica siedzi wyłącznie w CPU brzegu i poziomie kompresji (w locie 5, offline 9).

Co robi ten PR

Nie zmienia zachowania. Sama prekompresja trafia do obrazu BPP (osobny PR w iplweb/bpp), bo .gz musi powstawać w tym samym kroku co plik źródłowy: nginx serwując x.js.gz nie porównuje mtime z x.js, więc nieaktualny .gz = wieczne serwowanie starego JS-a wszystkim przeglądarkom, bez śladu w logach. Tutaj gzip_static on zacznie działać samo, gdy przyjedzie nowy obraz.

Ten PR zapisuje dowody, żeby nie zginęły:

  • nowa strona docs/rozwoj/pulapki-kompresji.md (+ wpis w mkdocs.yml),
  • tripwire w CLAUDE.md (reguła + objaw + anty-fix, zgodnie z podziałem z docs-sync),
  • akapit w docs/konfiguracja/architektura.md.

Brotli i zstd — zmierzone i odrzucone

Pytanie wraca w każdym review, więc niech ma pisemną odpowiedź.

Zysk jest realny: plotly.min.js 1315 KB → 948 KB (−28%), bundle.js 225 KB → 187 KB (−17%). Ale dotyczy tylko /static/, które ma Cache-Control: immutable — czyli poprawia głównie pierwszą wizytę.

Trzy blokady, wszystkie zweryfikowane empirycznie na owasp/modsecurity-crs:nginx:

  1. Repo nginx.org nie ma modułu brotli. Pełna lista dla trixie: geoip, image-filter, njs, otel, perl, xslt. Wariant „doinstaluj pakiet" nie istnieje.
  2. Moduł debianowy odbija się o ABI: [emerg] module version 1026003 instead of 1030004. --with-compat tego nie ratuje. Zostaje kompilacja ze źródeł (73 s).
  3. Łatka na nginx.conf nie przeżywa startu kontenera — entrypoint CRS regeneruje go z templates/nginx.conf.template. Build wychodzi zielony, a kontener nie wstaje: [emerg] unknown directive "brotli_static".

Prawdziwy koszt to nie Dockerfile (~25 linii), tylko szósty utrzymywany obraz sprzężony wersyjnie z pływającym tagiem upstreamu, gdzie tryb awarii brzmi „nginx nie wstaje" — plus zmiana modelu wdrożenia z pull na build, autoupdate.sh bez digestów i make test-waf testujący inny artefakt niż wdrożony.

zstd odpada bez dyskusji — tylko kompilacja ze źródeł trzeciej strony.

Niezmierzone i do sprawdzenia, gdyby temat wrócił: kolejność filtra brotli względem inspekcji odpowiedzi przez ModSecurity (RESPONSE-95x).

Przy okazji: dokumentacja kłamała

CLAUDE.md i docs/konfiguracja/architektura.md opisywały entrypoint jako cp -ru. Upstream zmienił to na cp -rf właśnie dlatego, że -u był pułapką — mtime w .baked pochodzi z builda obrazu, więc restart późniejszy niż build kazał -u pominąć kopiowanie i zostawić stare pliki. Opis mówił dosłownie odwrotnie niż kod.

Weryfikacja

mkdocs build --strict przechodzi. Brak zmian w kodzie i konfiguracji → brak wpływu na make up.

Zależność

Sparowany z PR-em w iplweb/bpp (prekompresja w obrazie). Ten może wejść niezależnie i wcześniej — nic nie psuje.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J35fM3toviqNDsGxRg6P1c

…dykt brotli

`gzip_static on` siedzi w _bpp-locations.conf od dawna, ale obraz appservera
nie mial ANI JEDNEGO pliku .gz (zmierzone na 202608.1399rc5: 3429 plikow,
0 sztuk .gz). Dyrektywa cicho spadala na `gzip on` i kompresowala kazdy plik
od nowa przy kazdym cache-missie. Zadnego objawu — strona dziala, naglowek
Content-Encoding jest, rozni sie tylko CPU brzegu i poziom kompresji (5 vs 9).

Prekompresja trafia do obrazu BPP (osobny PR w iplweb/bpp), bo .gz MUSI
powstawac w tym samym kroku co plik zrodlowy: nginx serwujac x.js.gz NIE
porownuje mtime z x.js, wiec nieaktualny .gz = wieczne serwowanie starego
JS-a wszystkim przegladarkom, bez sladu w logach. Tu nie zmienia sie nic —
gzip_static zaczyna dzialac samo, gdy przyjedzie nowy obraz.

Brotli/zstd zmierzone i odrzucone. Zysk realny (-28% plotly, -17% bundle),
ale trzy blokady wychodza dopiero przy probie, wszystkie zweryfikowane
empirycznie na owasp/modsecurity-crs:nginx:
  1. repo nginx.org NIE MA modulu brotli (jest tylko geoip/image-filter/
     njs/otel/perl/xslt) — wariant "doinstaluj pakiet" nie istnieje;
  2. modul debianowy odbija sie o ABI: [emerg] module version 1026003
     instead of 1030004, --with-compat tego nie ratuje;
  3. latka na nginx.conf NIE PRZEZYWA startu kontenera — entrypoint CRS
     regeneruje go z templates/nginx.conf.template, wiec build jest zielony,
     a kontener nie wstaje.
Prawdziwy koszt to nie Dockerfile, tylko szosty utrzymywany obraz sprzezony
wersyjnie z PLYWAJACYM tagiem upstreamu, gdzie tryb awarii = nginx nie wstaje.
zstd odpada bez dyskusji (tylko kompilacja ze zrodel).

Przy okazji: dokumentacja klamala o entrypoincie. Opisywala `cp -ru`, podczas
gdy upstream zmienil to na `cp -rf` wlasnie dlatego, ze -u byl pulapka (mtime
w .baked pochodzi z builda, wiec restart pozniejszy niz build kazal pominac
kopiowanie i zostawic stare pliki). Opis mowil doslownie odwrotnie niz kod.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J35fM3toviqNDsGxRg6P1c
@mpasternak
mpasternak merged commit 49a68e2 into main Aug 6, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant