Vývoj

Jak snadno (a levně) začít měřit rychlost webu

Adam Havel

Vývoj

Měření rychlosti webů bylo vždy tak trochu okrajové téma, střežené kruhem zasvěcených a obnášející podivné rituály, podobně jako třeba SEO. Všichni tušili, že jde o téma důležité, snad i kritické. Četli ostatně studie, například tu od Amazonu, která tvrdí, že zpoždění o desetinu sekundy vede ke ztrátě celého jednoho procenta z případných prodejů, a nové vrásce na obličeji Jeffa Bezose. Málokdo však měření věnoval pozornost, kterou si zaslouží.

Tíha odpovědnosti dopadla plnou vahou až v momentě, kdy Google ze svého trůnu vydal prohlášení, že jsou to takzvané Core Web Vitals, které se připojí k privilegované řadě parametrů, jež ovlivňují pořadí výsledků vyhledávání. Podobný historický význam mělo zařazení šifrování (tedy protokolu HTTPS) a větší důraz na mobilní web. Lhal bych, kdybych tvrdil, že i nás v Heurece rychlý nástup takzvané mobilní indexace nepřekvapil a neurychlil konec naší staré mobilní domény.
 
Ale kdo se v tom má vyznat? Jakmile dvířka skřínky označenou slovy web performance pootevřeme, byť jen na škvíru, jako Jeskyňky na nás bez slitování vyskáčou podivná, zjevně starobylá hesla, skrývající strašlivá tajemství z dob, kdy slova ještě měla moc dávat, ale i brát život: LCP, FID, FCP, CLS, TTB, TTI, TBT. Člověk by se nedivil, kdyby zde narazil i na ten nejděsivější z tetragramů, posvátné JHVH. Boží pomoc ale naštěstí nebudeme potřebovat, postačí trochu odvahy a zdravého rozumu. 

I řekl Google: „Buď metriky!“

Core Web Vitals sestává z trojice dle Googlu nejdůležitějších metrik. LCP, FID a CLS. Strachu z neznámého se nejsnáze zbavíme, když jejich tajemství poodkryjeme a zjistíme, že o život zde nepůjde. V prvním případě se totiž za zkratkou skrývá pojem Largest Contentful Paint, hrubě přeloženo jako vykreslení největšího obsahu. Jde o moment v čase, kdy prohlížeč ve viditelné části načítaného webu zobrazí největší, snad tedy i nejdůležitější souvislý blok obsahu. Tím může být obrázek, video či odstavce textu.

Druhé zaklínadlo dlí ve slovech First Input Delay neboli zpoždění prvního vstupu. Oním vstupem je zde míněn moment, kdy uživatel během načítání stránky klikne na interaktivní prvek - typicky odkaz, tlačítko či formulář. Doba, za kterou web na kliknutí reaguje, se pohybuje v řádu stovek milisekund a určuje hodnotu FID. Zpoždění se v nejvyšší míře objevuje právě v prvních chvílích načítání webu, protože tehdy dochází k zahlcení procesoru, který musí rychle zpracovat všechna data potřebná pro zobrazení stránky a její chování. Největším viníkem zde bývají velké balíky JavaScriptu.

Poslední heslo v sobě schovává termín Cumulative Layout Shift, tedy souhrnná změna rozvržení. Řečeno normálním jazykem jde o to, nakolik obsah během načítání skáče. Například kvůli tomu, že se obrázky zobrazí až se zpožděním a část obsahu odsunou. V takovém případě je třeba obrázkům předem nastavit rozměry. 

Spojíme-li metriky dohromady, vznikne vcelku užitečný obrázek rychlosti daného webu. Potíž však nastává, když si umíníme případné nedostatky nějak řešit. LCP nám sice bez okolků naznačí, že se naše stránka načítá pomalu, ale samo o sobě již blíže neurčí, kde konkrétně se problém nachází. Může jít o problém infrastruktury nebo back-endu. V tom případě se díváme po TTB neboli Time to First Byte, metrice, která udává čas od prvního klientského požadavku po moment, kdy server odpoví a začne posílat první data. Ke zpomalení zde dochází vlivem latence v síti či pomalého zpracování požadavku na serveru.

Stejně tak ale mohou web brzdit soubory nutné pro vykreslení webu, typicky příliš velké soubory se styly či skripty bez atributů defer, async nebo ještě lépe, type="module". Pro ty účely se nám bude hodit metrika FCP, tedy First Contentful Paint, dle názvu zjevně příbuzná s LCP. Rozdíl tkví v tom, že FCP se neptá po čase vykreslení největšího bloku obsahu, nýbrž prvního obsahu vůbec. 

Jak se při měření neušpinit

Znalost metrik je sice krok správným směrem, ale abychom se měli nad čím rozčilovat, musíme nejprve začít měřit. Možnosti máme dvě. Buď si za nemalý peníz zaplatíme jedno z nesčetných SaaS řešení, nebo se vydáme po vlastních nohách. V druhém případě je záhodno začít průzkumem dostupných nástrojů. Záhy se dozvíme, že hlavní dělící linie leží mezi takzvanými laboratorními (lab) a terénními (field) daty.


V prvním případě metriky získáme analýzou webu v kontrolovaném prostředí, které lze opakovaně reprodukovat. Představme si pod tím třeba virtuální stroj s prohlížečem Chrome, procesorem odpovídajícím výkonem konkrétnímu zařízení (například Motorola G) a simulovaným připojením k internetu s pevně danými parametry latence a přenosových rychlostí. Výhodou je, že výsledky napříč jednotlivými měření jsou konzistentní (ne však bezvýhradně), a proto lze snadno a rychle zhodnotit aktuální stav, odhalit změny v rychlosti, či srovnat náš web s konkurencí v průběhu času.

Na tomto poli kralují dva nástroje: prvním je PageSpeed Insight přímo od Googlu, druhým pak WebPageTest. Oba lze spustit ručně nebo využít dostupného API. My se bez průběžného měření neobejdeme, nezbývá nám tedy než se zamyslet nad automatizací. Ta může nabýt buď podoby integrace s naším repozitářem a spuštěním měření po konkrétním úkonu (třeba merge do master větve), nebo klasického cron skriptu.

Ukážeme si nástin řešení druhého případu, kdy jsme se rozhodli pro WebPageTest API a jednoduchý Node.js skript. Cílem je průběžné měření našeho webu v několika klíčových sekcích včetně srovnání s konkurencí. Web je z oblasti e-commerce a za jeho hlavní sekce považujeme úvodní stránku, konkrétní kategorii produktů, konkrétní produkt a stránku s výsledky vyhledávání. V případě Heureky by tedy šlo například o tyto URL: www.heureka.cz, knihy.heureka.cz, knihy.heureka.cz/genealogie-moralky/ a https://www.heureka.cz/?h[fraze]=oikoymenh.

Zároveň chceme testovat nejen první zobrazení (first view), ale i to druhé (repeat view), kdy už nám pomáhá cache. A abychom se vyhnuli výkyvům (vlivem nepředvídatelných událostí), každý test provedeme třikrát a z výsledné trojice vybereme ten prostřední, tedy medián.

Když použijeme oficiální Node.js knihovnu, příliš se neudřeme. Výsledky testů chceme někde ukládat, v příkladu proto použijeme StatsD. Stejně tak ale můžeme volit Prometheus či jiné řešení:

const WebPageTest = require('webpagetest');
const StatsD = require('statsd-client');
const WPT_API_KEY = process.env.API_KEY;
const WPT_URL = 'www.webpagetest.org';
const WPT_OPTIONS = {
    pollResults: 5,
    location: 'ec2-eu-central-1:Chrome',
    connectivity: '4G',
    private: true,
    runs: 3,
    emulateMobile: true
};
const WPT_METRICS_MAPPING = {
    ttfb: 'TTFB',
    speedIndex: 'SpeedIndex',
    fcp: 'firstContentfulPaint',
    bytes: 'bytesIn',
    load: 'fullyLoaded',
    ...
};

V nastavení WebPageTest klienta nás zajímá hlavně location, která udává, z jakého místa budou testy provedeny. Vybrané ec2-eu-central-1:Chrome je nám nejbližší virtuální stroj ve Frankfurtu. connectivity určuje simulované připojení k internetu, runs počet testů, a emulateMobil omezuje rychlost procesoru na úroveň běžného (tedy nikterak výkonného) mobilu. Nedivme se proto, že výsledky testu nikdy nedopadnou obzvlášť oslnivě. Spíš než absolutní hodnoty jsou důležitější relativní srovnání s konkurencí a v průběhu času.

Slovník WPT_METRICS_MAPPING slouží k výběru metrik z obdržených výsledků a jejich přejmenování pro účely uložení a případné zobrazení.

Následně zvolíme testované adresy a spustíme našeho klienta:

const TEST_SET = [
    { label: 'homepage', urls: [ ... ] },
    { label: 'category', urls: [ ... ] },
    { label: 'product', urls: [ ... ] },
    { label: 'search', urls: [ ... ] }
];
const wptClient = new WebPageTest(WPT_URL, WPT_API_KEY);
const statsdClient = new StatsD(STATSD_CONFIG);
const testUrl = (url, options) =>
    new Promise((resolve, reject) => {
        wptClient.runTest(url, Object.assign({}, WPT_OPTIONS, options), (err, result) => {
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });

Pak už zbývá jen postupně otestovat všechny vybrané adresy:

(async () => {
    for (let page of TEST_SET) {
        for (let url of page.urls) {
            try {
                const [, targetLabel] = new URL(url).hostname.split('.');
                const { data: { median: result } } = await testUrl(url);
                Object.entries(WPT_METRICS_MAPPING).forEach(([ key, value ]) => {
                    statsdClient.gauge(`${page.label}.firstView.${key}.${targetLabel}`, result.firstView[value]);
                    statsdClient.gauge(`${page.label}.repeatView.${key}.${targetLabel}`, result.repeatView[value]);
                });
            } catch (err) {
                console.error(err);
            }
        }
    }
})();

Skriptu dodáme API klíč a necháme jej každý den v určitý čas běžet. Po pár dnech získaná data vizualizujeme třeba v Grafaně a rovnou přidáme i upozornění. Ta se odesílají automaticky v případě, že nějaká metrika poskočí nad rámec běžné fluktuace.

V tomto bodě se nicméně vyplatí oprášit kalkulačku. API od WebPageTest je placené a cena se odvíjí od množství testů, které v jednom měsíci spustíme. Měříme-li jednou denně čtyři sekce na našem webu a stejné množství třeba u čtyř našich konkurentů, dostaneme se k 620 testům měsíčně. Číslo nesmíme zapomenout vynásobit dvěma (první a druhé zobrazení) a ještě jednou třemi, protože každý test třikrát opakujeme. Skončíme tak s 3 720 testy měsíčně. Pokud se nám výsledná částka zdá příliš vysoká, můžeme zúžit výběr konkurence nebo si WebPageTest zprovoznit na vlastní infrastruktuře: oficiální Docker image jsou volně k dispozici. 

Práce v terénu

Druhou stranu mince představují terénní, field data, získaná pomocí takzvaného Real User Monitoring, neboli RUM. Zatímco v případě laboratorních dat rozumné kvality dosáhneme kontrolovaným prostředím, u dat terénních ji doháníme kvantitou. Měření tohoto typu probíhá přímo u návštěvníků našeho webu a vyžaduje klientský JavaScript.

Naštěstí nejde o nic složitého, máme-li k dispozici třeba knihovnu web-vitals. S její pomocí snadno získáme ony mýtické Core Web Vitals:

import { getCLS, getFID, getLCP } from 'web-vitals';
function uploadMetric(metric) {
   navigator.sendBeacon('/telemetry', JSON.stringify(metric));
}
getCLS(uploadMetric);
getFID(uploadMetric);
getLCP(uploadMetric);

Data pošleme do připraveného endpointu nebo třeba do Google Analytics. Samozřejmě se nemusíme spokojit jen s Web Vitals. Objekt window.performance obsahuje mnoho dalších zajímavých dat. V závislosti na návštěvnosti našeho webu je nicméně dobrý nápad sběr dat samplovat, neměřit tedy nutně každého návštěvníka. Dobře poslouží jednoduché if (!(Math.random() < 0.01)) return; na začátku skriptu, což sběr zhruba omezí na jedno procento návštěv.

Zdrojem terénních dat ale může být i již zmíněné PageSpeed Insight. Google totiž pro zadanou adresu, kromě (laboratorních) výsledků konkrétního testu, zobrazuje i souhrnná data z takzvaného Chrome User Experience Report (CrUX). Stručně řečeno jde o field data získaná od lidí používajících prohlížeč Chrome

Byl večer a bylo ráno, den první

Sběrem metrik naše cesta samozřejmě nekončí. Naopak, skutečná práce nás teprve čeká. Následuje totiž úkol o několik řádů méně přímočarý, tedy interpretace nasbíraných měření a náprava objevených prohřešků. Tím směrem se vydáme až příště.

Autor článku

Adam se v Heurece už přes čtyři roky stará o front-end. Krom JavaScriptu ho zajímá typografie, design obecně, decentralizované technologie a vše kolem ochrany soukromí. Většinu bdělého stavu tráví četbou, tancem swingu a pravidelným, byť neradostným, zvedáním těžkých předmětů.

Podobné články

Ikony bez kompromisů

Ikony bez kompromisů

I přes svou malou velikost představují ikony na webu zajímavý problém. Jeden přístup střídá další –…

Vánoční resuscitace serverů

Vánoční resuscitace serverů

O sysadminech v Heurece se dá říci leccos, nedostatek paranoie to ale není. Máme zdvojené téměř…

Jak Fialoví vymýšleli Dášenku, aneb nové párování nabídek

Jak Fialoví vymýšleli Dášenku, aneb nové párování nabídek

Již hrozně dlouhou dobu jsme ve fialovém týmu věděli, že nás jednoho dne čeká neuvěřitelně zábavný…

Zaber si svou židli!

<Nejsme asociálové/>

<Témata/>

Zajímá tě naše práce, technologie, tým nebo cokoliv jiného?
Napiš šéfovi vývoje Lukášovi Putnovi.

lukas.putna@heureka.cz