Druga część serii o czterech warstwach, w których rozmiar danych nie odpowiada temu, ile danych naprawdę istnieje. W pierwszej była baza. Teraz to, co wychodzi na zewnątrz - treść.
W pierwszej części tej serii opisywaliśmy bazę WordPressa, która zajmowała 5 GB, podczas gdy danych było w niej 144 MB. Przyczyną okazały się gigantyczne wiersze w tabeli wpisów - kreator wizualny zapisywał pełny układ strony wraz z osadzonymi stylami bezpośrednio w treści.
Ta przyczyna ma drugą stronę, o której tamten tekst tylko wspomniał. Bo to, co siedzi w treści wpisu, nie zostaje w bazie. Wychodzi na zewnątrz przy każdym żądaniu.
Ten tekst jest o tym, jak ta sama przyczyna wygląda z perspektywy crawlera - i dlaczego narzędzia, które zwykle sprawdzamy, pokazują ją równie słabo jak kokpit WordPressa pokazywał data_free.
Crawler nie ogląda strony
To jest założenie, od którego warto zacząć, bo bez niego reszta pomiarów nie ma sensu.
Przeglądarka pobiera HTML, wykonuje JavaScript, składa CSS i pokazuje wynik. Człowiek ocenia to, co widzi na końcu tego procesu. Crawler pobiera HTML i w większości przypadków na tym kończy - bo renderowanie kosztuje kilkadziesiąt razy więcej niż samo pobranie i nie każdy robot je wykonuje.
Z tego wynika rozróżnienie, które przewija się przez cały ten tekst:
To, co widzisz w przeglądarce, to wynik. To, co dostaje crawler, to materiał wyjściowy. Między jednym a drugim potrafi być kilkaset kilobajtów różnicy w obie strony - bo część treści dochodzi dopiero po renderze, a część kodu nie służy niczemu poza wyglądem.
Trzy liczby, które opisują tę warstwę
Tak samo jak przy bazie, sam rozmiar niczego nie rozstrzyga. Rozstrzygają proporcje.
Rozmiar transferu. Ile bajtów faktycznie przeszło przez sieć. To jest liczba, którą widzisz w narzędziach deweloperskich i w większości raportów wydajności - i ta, która najczęściej wygląda dobrze.
Rozmiar nieskompresowany. Ile bajtów HTML-a musi przetworzyć odbiorca po rozpakowaniu. Kompresja zmniejsza transfer, nie zmniejsza pracy przy parsowaniu i nie zmniejsza tego, co trafia do okna kontekstu modelu.
Objętość treści. Ile z tego to tekst, który ktokolwiek czyta - człowiek albo maszyna. Reszta to znaczniki, atrybuty, style osadzone w kodzie i dane konfiguracyjne komponentów.
Trzecia liczba jest tą, której nie pokazuje żadne standardowe narzędzie, i tą, która przy stronach z kreatorów wizualnych rozjeżdża się najbardziej.
Pomiar z jednej ze sprawdzanych stron. Najpierw dwie komendy różniące się wyłącznie tym, czy klient zadeklarował obsługę kompresji:
skompresowane: 34 735 B
nieskompresowane: 196 523 B
Prawie sześciokrotna różnica. Raport wydajności pokazuje pierwszą liczbę i wygląda to poprawnie. Odbiorca, który kompresji nie negocjuje, pobiera drugą - i tak samo drugą przetwarza każdy, kto tę stronę rozpakuje.
Potem trzecia liczba, po usunięciu z dokumentu znaczników oraz zawartości bloków style i script:
sama treść: 5 406 B
Z tego wychodzi rozkład całego dokumentu:
| Co | Bajty | Udział |
|---|---|---|
| Treść | 5 406 | 2,8% |
| Style i skrypty osadzone w kodzie | ~150 900 | 76,8% |
| Znaczniki | ~40 200 | 20,4% |
| Razem | 196 523 |
Jeden bajt treści na trzydzieści sześć bajtów dokumentu. Style osadzone w kodzie ważą dwadzieścia osiem razy więcej niż wszystko, co ktokolwiek na tej stronie czyta.
Warto od razu uprzedzić najprostszy zarzut: to nie jest strona pusta. Te pięć kilobajtów to około ośmiuset słów rozłożonych na kilkanaście sekcji z nagłówkami - opisy usług, opisy sytuacji klienta, czteroetapowy opis procesu współpracy. Normalna strona ofertowa firmy usługowej, z treścią napisaną i uporządkowaną.
I to nie jest strona zaniedbana. Ładuje się poprawnie, wygląda dobrze, kompresję ma włączoną.
Pomiar wykonano przy gzipie, bo taką kompresję serwer miał włączoną. Żądanie z deklaracją wyłącznie Brotli nie zwróciło niczego - serwer tego algorytmu nie obsługuje. Brotli przy HTML-u daje orientacyjnie o jedną piątą lepszy wynik, czyli w tym przypadku około dwudziestu ośmiu kilobajtów zamiast trzydziestu pięciu. To szacunek na podstawie ogólnych danych o obu algorytmach, nie drugi pomiar - ale pokazuje, że zapas w tej warstwie jeszcze jest, i że jest to najtańszy do wykorzystania zapas z całej listy.
Dla porządku: serwis opisywany w pierwszej części serii nie ma włączonej kompresji w ogóle. Te dwa przypadki dobrze pokazują rozpiętość - jeden ma kompresję działającą, ale nie najlepszą dostępną, drugi nie ma żadnej i wysyła pełny dokument każdemu odbiorcy.
Ta sama przyczyna, druga warstwa
W bazie problem polegał na tym, że pojedynczy wiersz ważył ponad sto kilobajtów, bo zawierał układ strony wraz ze stylami. Na froncie ten sam mechanizm daje HTML, w którym znaczna część kodu opisuje wygląd, a nie treść.
Trzy formy, w jakich to występuje:
Style osadzone w kodzie strony. Kreatory generują reguły CSS przypisane do konkretnych identyfikatorów sekcji i wstawiają je bezpośrednio w dokument - czasem w nagłówku, czasem przy każdym elemencie. Ten sam zestaw reguł powtarza się na każdej podstronie, bo nie ma wspólnego arkusza, do którego mógłby trafić.
Zagnieżdżenie strukturalne. Sekcja, wiersz, kolumna, moduł, wrapper modułu, wrapper treści - pięć poziomów zanim dojdzie się do akapitu. Każdy poziom to znacznik z klasami i atrybutami.
Dane konfiguracyjne komponentów. Ustawienia animacji, punkty przełamania układu, parametry interakcji - zapisane jako atrybuty przy elementach albo jako bloki danych w kodzie.
Żadna z tych rzeczy nie jest błędem. Wszystkie są ceną za edycję wizualną. Warto tylko wiedzieć, ile ta cena wynosi - bo płaci się ją przy każdym żądaniu, a nie raz.
Kompresja: gdzie pomaga, a gdzie tylko ukrywa
Tu dochodzimy do rzeczy, o którą warto zapytać, zanim ktoś powie „przecież mamy włączony gzip".
HTML kompresuje się bardzo dobrze, bo jest powtarzalny. Te same nazwy znaczników, te same klasy, te same reguły stylów - dokładnie to, na czym algorytmy słownikowe działają najlepiej. Strona z kreatora wizualnego kompresuje się często lepiej niż strona pisana ręcznie, właśnie dlatego, że powtórzeń jest w niej więcej.
Trzy algorytmy, z którymi się spotkasz:
gzip - obecny wszędzie, wspierany przez wszystko, domyślny na większości serwerów. Bezpieczny wybór.
Brotli - nowszy, zaprojektowany pod tekst, korzysta ze wbudowanego słownika typowych fragmentów kodu webowego. Dla HTML-a daje mniej więcej jedną piątą lepszy wynik niż gzip, dla CSS około szóstej części, dla JavaScriptu około siódmej. Oznaczany w nagłówkach jako br.
deflate - historycznie problematyczny przez niejednoznaczność implementacji i w praktyce niepotrzebny, skoro dostępne są dwa pozostałe.
Google deklaruje, że jego crawlery obsługują wszystkie trzy, a każdy z ich agentów podaje w nagłówku Accept-Encoding, co dokładnie przyjmuje.
I teraz rzecz najważniejsza w całej tej sekcji.
Kompresja zmniejsza transfer. Nie zmniejsza niczego innego.
Odbiorca po rozpakowaniu ma dokładnie tyle samo kodu do przetworzenia. Parser przechodzi przez pełny dokument. Model, który dostaje treść strony do przetworzenia, dostaje ją rozpakowaną - i zajmuje ona tyle miejsca w oknie kontekstu, ile ma znaków, a nie ile ważyła w transporcie.
Dokładnie jak w pierwszej części: kokpit WordPressa pokazywał kilkadziesiąt stron i wyglądało to poprawnie, bo patrzył na liczbę rekordów, a nie na to, co w nich siedzi. Tutaj raport wydajności pokazuje sześćdziesiąt kilobajtów i też wygląda poprawnie - bo patrzy na transfer, a nie na to, co się z tego rozpakowuje.
Kompresja jest negocjowana - a boty czasem nie negocjują
To jest szczegół, który zmienia obraz i który łatwo przeoczyć, bo w przeglądarce nigdy nie występuje.
Kompresja nie jest właściwością serwera. Jest wynikiem uzgodnienia: klient wysyła nagłówek Accept-Encoding z listą tego, co potrafi rozpakować, a serwer wybiera z tej listy. Jeśli nagłówka nie ma, serwer wysyła dokument nieskompresowany.
Przeglądarki wysyłają ten nagłówek zawsze. Klienty niebędące przeglądarką - biblioteki HTTP, skrypty, narzędzia wiersza poleceń - często nie, bo trzeba to ustawić świadomie. I dotyczy to również części botów oraz crawlerów.
Praktyczna konsekwencja jest dokładnie tą różnicą, którą pokazał pomiar wyżej. Ta sama strona, ten sam serwer, ta sama chwila: trzydzieści cztery kilobajty dla klienta, który zadeklarował obsługę kompresji, i sto dziewięćdziesiąt sześć dla klienta, który tego nie zrobił. Widać to wyłącznie w logach serwera, nigdy w narzędziach wydajnościowych - bo te ostatnie zawsze pytają tak jak przeglądarka.
Warto to sprawdzić u siebie, zamiast zakładać. Jak - w sekcji z komendami niżej.
Co jest w kodzie, a co dochodzi po renderze
Druga oś tego samego problemu i jedyna, która potrafi zadziałać przeciwko tobie w odwrotną stronę.
Jeśli treść pojawia się dopiero po wykonaniu JavaScriptu, to dla odbiorcy, który go nie wykonuje, po prostu jej nie ma. Najczęstsze przypadki:
Sekcje ładowane dopiero po interakcji. Zakładki, harmonijki i karuzele, w których treść dociąga się przy pierwszym kliknięciu. Zwinięcie wizualne jest bezpieczne - treść siedzi w kodzie i tylko jej nie widać. Dociąganie nie jest.
Elementy generowane po stronie klienta. Listy produktów, wyniki filtrowania, opinie z zewnętrznego serwisu.
Spisy treści i nawigacja budowane skryptem. Klasyczny przypadek: człowiek widzi działający spis, a w kodzie strony jest pusty pojemnik z komunikatem „brak nagłówków".
Tu warto rozróżnić dwie sytuacje, bo bywają mylone. Treść zwinięta stylem CSS jest w kodzie i zostanie odczytana. Treść wstrzykiwana skryptem po załadowaniu strony - nie, chyba że odbiorca renderuje. Wygląda to identycznie dla człowieka i zupełnie inaczej dla maszyny.
Naprawa ma swoją kolejność
Tak samo jak przy bazie: najpierw przyczyna, potem skutki. Odwrotnie problem wraca w ciągu kilku tygodni.
Włącz kompresję i sprawdź, którą. Sprawdzana strona miała gzip - czyli kompresja działała, ale nie najlepsza dostępna. Brotli tam, gdzie serwer pozwala, gzip jako rozwiązanie zapasowe. To jedyny krok, który daje efekt natychmiast i nie wymaga ruszania treści.
Przenieś style z treści do arkuszy. To jest ta sama zmiana, która była najważniejszym zaleceniem w części o bazie - i tutaj daje efekt drugi raz. Reguły w zewnętrznym arkuszu pobierają się raz i zostają w pamięci podręcznej. Reguły osadzone w kodzie strony pobierają się przy każdym żądaniu, na każdej podstronie.
Sprawdź, co dochodzi po renderze. Wszystko, co jest istotną treścią, powinno być w kodzie strony przy pierwszym pobraniu. Ozdoby i interakcje mogą dochodzić później.
Ogranicz zagnieżdżenie tam, gdzie się da. Efekt najmniejszy z całej czwórki i wymagający najwięcej pracy, więc na końcu.
Pierwszy krok zmniejsza transfer. Drugi zmniejsza to, co po rozpakowaniu trzeba przetworzyć. Trzeci decyduje o tym, czy treść w ogóle zostanie odczytana. Dopiero razem dają efekt.
Dlaczego to ma znaczenie
Nie chodzi o szybkość ładowania, choć i o nią też.
Budżet crawlowania. Robot ma ograniczony czas i ograniczoną liczbę żądań na serwis. Strony cięższe w pobraniu i przetworzeniu oznaczają mniej pobranych podstron w tej samej jednostce.
Okno kontekstu modelu. Odbiorca będący modelem językowym ma sztywny limit tego, ile tekstu przetworzy naraz. Jeśli połowę tego limitu zajmują znaczniki i style, to na treść zostaje połowa. Przy dłuższych stronach kończy się to obcięciem - i obcięte zostaje to, co jest na końcu dokumentu, czyli często najkonkretniejsze fragmenty.
Poprawność odczytu. Treść wstrzykiwana skryptem nie jest dla części odbiorców trudniejsza do przeczytania. Jej po prostu nie ma.
Jak sprawdzić własną stronę
Trzy komendy, wszystkie w wierszu poleceń, żadna nie wymaga instalowania niczego poza curl.
Czy kompresja w ogóle działa. Nagłówek Content-Encoding w odpowiedzi mówi, czy serwer coś skompresował i czym.
curl -sI -H "Accept-Encoding: gzip, deflate, br" https://twojadomena.pl | grep -i content-encoding
Jeśli nic się nie wyświetli, kompresji nie ma. Jeśli zobaczysz br, działa Brotli. Jeśli gzip, działa gzip.
Ile waży strona skompresowana, a ile nie. Dwie komendy, do porównania.
curl -s -H "Accept-Encoding: gzip, deflate, br" -o /dev/null -w "skompresowane: %{size_download} B\n" https://twojadomena.pl
curl -s -o /dev/null -w "nieskompresowane: %{size_download} B\n" https://twojadomena.pl
Druga komenda celowo nie wysyła nagłówka - pokazuje dokładnie to, co dostanie klient, który kompresji nie negocjuje.
Ile z tego to treść. Tu potrzeba jednej ostrożności: usunięcie samych znaczników nie wystarcza, bo reguły stylów i kod skryptów siedzą między znacznikami i zostaną policzone jako tekst. Przy stronach z kreatorów wizualnych to wywraca wynik - potrafi pokazać osiemdziesiąt procent treści tam, gdzie realnie jest jej kilkanaście.
Poniższa komenda usuwa najpierw całe bloki script i style, a dopiero potem znaczniki:
curl -s https://twojadomena.pl | python3 -c "
import sys, re
h = sys.stdin.read()
h = re.sub(r'<(script|style)[^>]*>.*?</\1>', '', h, flags=re.S|re.I)
t = re.sub(r'<[^>]+>', ' ', h)
t = re.sub(r'\s+', ' ', t).strip()
print(len(t))
"
Warto wykonać oba warianty i porównać. Wynik bez usuwania bloków minus wynik z usuwaniem daje osobną, użyteczną liczbę: ile kilobajtów dokumentu zajmują style i skrypty osadzone bezpośrednio w kodzie strony. Przy stronach z kreatorów bywa to więcej niż cała treść razem wzięta.
Orientacyjnie: udział treści poniżej dziesięciu procent to sygnał, żeby sprawdzić, co zajmuje resztę. Poniżej pięciu procent to zwykle znaczy, że style siedzą w kodzie strony - jak w pomiarze z początku tego tekstu, gdzie wyszło niecałe trzy.
Co widzi odbiorca, który nie renderuje. Najprostszy test na to, czy treść jest w kodzie: poszukaj w pobranym pliku fragmentu, który widzisz na stronie.
grep -c "fragment tekstu ze strony" strona.html
Zero oznacza, że tego fragmentu nie ma w kodzie i dochodzi dopiero po wykonaniu skryptów.
To druga część serii
Cztery warstwy, w tej samej kolejności, w jakiej je sprawdzamy:
- Baza danych - co siedzi w tabelach i ile z tego jest treścią. (część pierwsza)
- Struktura strony - co widzi crawler, a czego nie widzi. (ten tekst)
- Dane produktowe - spójność katalogu, feeda i tego, co jest na stronie.
- Schema - czy maszyny mają skąd wziąć fakty o firmie.
Każda z nich potrafi przez długi czas sprawiać wrażenie, że wszystko działa poprawnie.
W pierwszej części tym, co ukrywało problem, był kokpit WordPressa pokazujący liczbę wpisów zamiast tego, co w nich siedzi. Tutaj rolę tę pełni kompresja - pokazuje transfer zamiast tego, co się z niego rozpakowuje. Wzorzec jest ten sam: narzędzie mierzy coś sąsiedniego wobec problemu i przez to problem znika z pola widzenia.
Właśnie po to jest opieka nad stroną
Podobnie jak przy bazie, to nie są rzeczy, które zgłosi klient ani które pokaże komunikat w panelu. Strona ładuje się poprawnie, wygląda dobrze, raporty wydajności są w porządku.
Różnica ujawnia się dopiero wtedy, gdy zapyta się nie o to, jak strona wygląda, tylko co z niej wynika dla odbiorcy, który jej nie ogląda.
Jeżeli po uruchomieniu powyższych komend zobaczysz liczby, które budzą wątpliwości - takie przeglądy wykonujemy zarówno w ramach stałej opieki nad stronami WordPress, jak i jednorazowo.
FAQ
Czy Brotli jest zawsze lepszy od gzip? Dla treści tekstowych daje lepszą kompresję - przy HTML-u mniej więcej o jedną piątą. Wymaga jednak wsparcia po obu stronach i większego nakładu obliczeniowego przy kompresowaniu w locie. Rozsądna konfiguracja to Brotli tam, gdzie klient go akceptuje, i gzip jako rozwiązanie zapasowe.
Czy warto włączać deflate? Nie ma powodu. Historycznie sprawiał problemy przez niejednoznaczność implementacji, a nie daje nic ponad gzip. Wystarczy Brotli plus gzip.
Skoro kompresja zmniejsza rozmiar dziesięciokrotnie, czy rozmiar HTML ma jeszcze znaczenie? Ma, tylko w innym miejscu. Kompresja działa na transport. Nie zmniejsza czasu parsowania, nie zmniejsza zużycia pamięci u odbiorcy i nie zmniejsza tego, ile miejsca dokument zajmuje w oknie kontekstu modelu. Wszystko to liczy się po rozpakowaniu.
Czy każdy robot obsługuje kompresję? Wsparcie deklarują wszystkie duże crawlery. Ale kompresja jest negocjowana nagłówkiem Accept-Encoding, a klienty niebędące przeglądarką często go nie wysyłają. Sprawdzić to można wyłącznie w logach serwera - i warto, bo wynik bywa zaskakujący.
Czy treść ukryta pod zakładkami jest widoczna dla crawlera? Zależy od mechanizmu. Jeśli jest w kodzie strony i tylko schowana stylem - tak. Jeśli dociąga się skryptem po kliknięciu - nie, chyba że odbiorca renderuje JavaScript. Dla człowieka oba warianty wyglądają identycznie.
Ile powinien wynosić udział treści w kodzie strony? Nie ma normy i nie warto jej szukać. Praktycznie: poniżej dziesięciu procent warto sprawdzić przyczynę, poniżej pięciu zwykle chodzi o style osadzone w kodzie. Ważniejsze od samej wartości jest porównanie z innymi podstronami tego samego serwisu - jeśli jedna odstaje, to tam siedzi problem.
Czy to dotyczy tylko WordPressa z kreatorem? Mechanizm jest ogólny: dużo kodu opisującego wygląd i mało treści. Kreatory wizualne są najczęstszą przyczyną, ale to samo potrafią dać rozbudowane szablony, biblioteki komponentów generujące style w locie i wtyczki dokładające własne zasoby na każdej podstronie.

