Přeskočit na obsah
Zapolu

Výkonnostní audit: deep dive (vzorová zpráva)

Kompletní výstup výkonnostního auditu Magenta na smyšleném e-shopu: jedenáct nálezů, u každého doklad, cena opravy a plán seřazený podle návratnosti. Na konci jsou sondy, abyste si stejné kontroly pustili na svém e-shopu.

Stáhnout

Toto je vzor. Klient „Acme Outdoor s.r.o.“ neexistuje a identifikační údaje jsou změněné. Nálezy i čísla jsou anonymizovaná a poskládaná ze skutečných výkonnostních auditů, které Luboš Zápotočný odvedl během deseti let inženýrské práce v e-commerce, ještě před vznikem této praxe. Nejde o zakázky realizované pod značkou Zapolu, ta vlastní klientskou historii zatím nemá; doklady o té dřívější práci najdete na stránce O nás. Vzor ukazuje formát: čím je nález doložen, co stojí jeho oprava a jak je práce seřazená. Zpráva, kterou dostane klient, rozebírá každý nález stejně podrobně jako níže F1; tento vzor šest z jedenácti zkracuje, aby zůstal čitelný.

KlientAcme Outdoor s.r.o. (smyšlený)
E-shopacme-outdoor.example (Magento 2.4.6, frontend Luma)
StackVarnish 7, Redis 7, MySQL 8, 2 aplikační nody za load balancerem
Datum zprávy5. června 2026
Data z provozu7. května až 3. června 2026 (28 dní)
APM trace21. května až 3. června 2026 (14 dní) a jeden zátěžový test
NástrojeNew Relic, CrUX, Lighthouse, k6, varnishstat, MySQL slow log
AutorLuboš Zápotočný, Zapolu s.r.o.
ZakázkaDeep dive, 7 pracovních dní, fixní cena

Pro stručný přehled si přečtěte kapitolu 1 a kapitolu 5. Text mezi nimi tvoří podklady k jednotlivým nálezům.

1. Shrnutí

Pomalost e-shopu vzniká na straně serveru. LCP z field dat je na katalogových stránkách 4,6 s na p75 (cíl: ≤ 2,5 s); hlavní příčinou je TTFB 1,9 s. TTFB je vysoké proto, že full-page cache je fakticky vypnutá: Varnish obsluhuje jen 34 % katalogových požadavků a 38 % veškerého serverového času připadá na vykreslování produktových stránek, v průměru 2,2 s na požadavek. Během kampaňového večera 26. května dosáhl checkout p95 6,1 s a 3,4 % požadavků selhalo, zatímco CPU aplikačních serverů nepřekročilo 12 %.

Následuje jedenáct nálezů. Sedm z nich stojí den práce nebo méně a dva z těch sedmi (F2 a F8) jsou změny jen v konfiguračních souborech. Řádky 1 až 6 plánu oprav odstraní všechna selhání, která zákazník pocítí: checkout přestane ve špičce selhávat a checkout p95 během kampaní klesne z 6,1 s na 1,9 s. Largest Contentful Paint se jen s těmito řádky pod hranici 2,5 s stanovenou Googlem nedostane. Rozpočet:

StavCo se měníLCP kategorieLCP produktu
dnes4,6 s4,6 s
po řádku 2F1, TTFB katalogu 1,9 s → 1,2 s3,9 s3,9 s
po řádku 5F5, hero obrázky produktů3,9 s3,3 s
po řádku 7F4 dočasně, mrtvé tagy a odklad3,5 s2,9 s
po řádku 8F4, přestavba frontendu2,7 s2,1 s

Pod práh dostane produktové stránky až přestavba v řádku 8. Kategorie končí na 2,7 s, tedy stále nad prahem; co by zbytek dorovnalo, jsme nezjišťovali (kapitola 6). Řádky jsou seřazené podle návratnosti, takže plán lze kdykoli ukončit.

Přepočteno na peníze: během tří hodin kampaně večer 26. května skončilo chybou zhruba 230 checkoutů. Při průměrné hodnotě objednávky 1 800 Kč to dělá 414 000 Kč v objednávkách, které selhaly, tedy asi 17 000 € při kurzu 24,3 Kč za euro, a nezapočítává to zákazníky, kteří checkout po šesti sekundách čekání opustili. Ostatní nálezy uvádíme v milisekundách; částka v korunách by u nich byla spekulativní, proto ji neuvádíme.

2. Co a jak jsme měřili

  • Metriky reálných uživatelů: Core Web Vitals z 28denního CrUX dotazu, segmentované podle typu stránky (homepage / kategorie / produkt / checkout). Syntetické Lighthouse běhy sloužily jen k reprodukci nálezů, ne k jejich hodnocení. Jak si e-shop stojí proti prahovým hodnotám Googlu:

    Metrika (p75, field data)E-shopPráhVerdikt
    LCP4,6 s< 2,5 sneprošlo
    INP280 ms< 200 msneprošlo
    CLS0,04< 0,1prošlo
    FCP2,9 s< 1,8 sneprošlo
    TTFB1,9 s< 0,8 sneprošlo
  • Trasování na serveru: transakční profil z New Relic za 14 dní, tedy kam serverový čas doopravdy jde:

    TransakcePodíl serverového časuPrůměrná odpověď
    catalog/product/view38 %2,2 s
    catalog/category/view17 %1,8 s
    checkout REST (košík + doprava)11 %1,4 s (6,1 s p95 ve špičce)

    K tomu MySQL slow query log, profil volání externích služeb a statistiky hit/miss Varnishe podle URL vzorů z varnishstat.

  • Zátěžový test: replay průběhu provozu z kampaně večer 26. května (0 → 420 req/s během 20 minut) proti stagingu se stejným stavem cache jako v produkci.

  • Revize konfigurace: nastavení PHP a OPcache, Composer autoloader, env.php, Varnish VCL a nastavení MySQL, každé proti dokumentovaným doporučením pro Magento, navýšeným tam, kde kódová základna dokumentovanou hodnotu přerůstá.

  • Audit bundlů a assetů: co se posílá při prvním zobrazení produktové stránky, co blokuje vykreslení a co je zbytečná zátěž.

U většiny nálezů citujeme přesný příkaz nebo dotaz, který jsme použili, jednak aby si váš tým mohl naši práci překontrolovat, jednak proto, že stejné sondy poběží na kterémkoli Magento e-shopu, včetně toho vašeho. Kapitola 7 je sbírá tak, aby se daly spustit beze změny. U každého čísla níže je uvedeno, odkud pochází, a tabulka nálezů má sloupec, který říká, zda byl očekávaný dopad změřen, reprodukován v zátěži, nebo odhadnut.

3. Přehled nálezů

Vysoká: dopadá na zákazníky, nebo na ní závisí tržby. Střední: denně ji pociťuje tým, nebo tiše poškozuje data. Nízká: malé úsilí, které odstraní reálné provozní riziko.

#NálezZávažnostPracnostOčekávaný dopadPodklad
F1Full-page cache je fakticky vypnutá a nejde invalidovatVysoká1–2 dny−700 ms TTFB na p75 v kataloguzměřeno
F2PHP runtime a autoloader špatně nastavené pro MagentoVysoká0,5 dne−400 ms na každém necachovaném požadavkuodhad
F3Checkout blokuje synchronní dotaz na sklad v ERPVysoká3–5 dní−2,8 s checkout p95 ve špičce; konec timeoutových chybzátěžový test
F4Frontend posílá 2,3 MB JavaScriptu, blokuje vykresleníVysokáprojekt−1,2 s LCP (dočasná zmírnění: −0,4 s)odhad
F5Produktové obrázky: 1,6MB PNG, bez responzivních velikostíStřední1 den−0,6 s LCP na produktových stránkáchzměřeno
F6Indexery ponechané na „Update on Save“Střední0,5 dneUložení v adminu 30 s → ~2 s; konec nesouladu skladových stavůodhad
F7PHP-FPM pool vyčerpaný session locky ve špičceVysoká2 dnyCheckout přežije kampaňový provozzátěžový test
F8Varnish bez grace móduNízká0,5 dneZtlumí nápor po každém flushi cacheodhad
F9Dvě extenze poslouchají stejný event; jedna je opuštěnáStřední1 den−300 ms na přidání do košíkuzměřeno
F1041 GB mrtvých indexerových temp tabulek v MySQLNízká0,5 dneZálohy o 60 % menší; obnova zhruba za polovinu časuzměřeno
F11Order grid v adminu filtruje přes neindexovaný sloupecNízká0,5 dneGrid objednávek 12 s → pod 1 sodhad

Podklad říká, odkud očekávaný dopad pochází. Změřeno: viděli jsme to na tomto e-shopu. Zátěžový test: reprodukovali jsme to na stagingu při zátěži odpovídající kampani. Odhad: plyne z profilu a konfigurace a nikdo to zatím nepozoroval.

4. Nálezy podrobně

F1: Full-page cache je fakticky vypnutá a nejde invalidovat (Vysoká)

Co jsme viděli. Varnish hlásí na katalogových URL hit rate 34 %. Pro katalog této velikosti a s tímto podílem crawlerů je běžná hodnota nad 90 %. Každá kategorie i produktová stránka nese X-Magento-Cache-Debug: MISS. Příčiny jsou dvě nezávislé:

  1. Upravený default.xml označuje jeden blok v hlavičce (přepínač store view, který čte zákaznickou session) jako cacheable="false". V Magentu jediný necachovatelný blok v layoutu udělá necachovatelnou celou stránku. Blok přibyl s aktualizací extenze v listopadu 2025; hit rate se zhroutil ve stejném týdnu.
  2. Varnish je zapnutý v administraci (system/full_page_cache/caching_application = 2), ale v env.php chybí sekce http_cache_hosts, takže Magento vůbec neumí posílat purge požadavky do Varnishe. Tým to obchází restartem Varnishe po každém deployi, a právě proto jsou deploye problematické (viz F8).

Jak jsme to ověřili:

$ curl -sI https://acme-outdoor.example/stany | grep -i cache-debug
X-Magento-Cache-Debug: MISS
# stejně na každé katalogové URL

$ grep -R 'cacheable="false"' app/design app/code | wc -l
1
# přepínač store view v default.xml

$ php -r 'var_export(array_key_exists("http_cache_hosts", (include "app/etc/env.php")));'
false
# Magento neumí poslat purge do Varnishe

Oprava. Vykreslit přepínač přes private content (customer data JS), takže stránka zůstane cachovatelná (dokumentovaný vzor pro fragmenty závislé na session), a doplnit http_cache_hosts do env.php, aby fungovala invalidace přes tagy a restarty mohly skončit.

Pracnost / dopad / riziko. 1–2 dny včetně regresních testů. Očekávaných −700 ms TTFB na p75 katalogu (změřeno: cachovaná katalogová odpověď se vrací za 80 ms, necachovaná za 1,9 s). Riziko: nízké; obě změny jsou ohraničené a vratné po jednotlivých deployích.

F2: PHP runtime a autoloader špatně nastavené pro Magento (Vysoká)

Co jsme viděli. Základna kódu obsahuje ~125 000 PHP souborů; OPcache je nastavená na 10 000. Composer autoloader nebyl nikdy optimalizován: vendor/composer/autoload_static.php neobsahuje mapu tříd a v New Relic trace jsou vidět celé sekundy, které stráví ObjectManager sestavováním instancí při prvních požadavcích, než se naplní cache tříd. Aktuální stav a doporučení:

NastaveníAktuálníDoporučené
opcache.max_accelerated_files10 000100 000
opcache.memory_consumption128512
opcache.validate_timestampsOn, revalidace každé 2 sOff; reset při deployi
opcache.enable_cliOffOn (indexery a crony jsou také PHP)
realpath_cache_size4M32M
realpath_cache_ttl1207200
Composer autoloaderneoptimalizovanýdump-autoload --optimize v buildu

Jak jsme to ověřili:

$ find . -type f -name '*.php' | wc -l
124862
# PHP souborů v codebase

$ php -i | grep 'opcache.max_accelerated_files'
opcache.max_accelerated_files => 10000
# pojme 8 % z nich

$ grep -c 'classMap' vendor/composer/autoload_static.php
0
# autoloader nebyl nikdy optimalizován

Proč. S těmito hodnotami PHP průběžně znovu statuje a překládá velkou část codebase; profily připisují přes polovinu času necachovaného renderu načítání tříd a sestavování stránky místo obchodní logiky.

Oprava. Jedna změna konfigurace plus jeden krok v buildu. Jediná vazba, kterou je nutné respektovat: s vypnutým validate_timestamps musí deploy pipeline při release resetovat OPcache. Přesný krok pro vaši současnou pipeline jsme popsali v plánu oprav.

Pracnost / dopad / riziko. 0,5 dne. Odhadem −400 ms na každém necachovaném požadavku (a rychlejší crony a indexery jako vedlejší efekt). Riziko: nízké, pokud krok v deployi přistane spolu s konfigurací.

F3: Checkout blokuje synchronní dotaz na sklad v ERP (Vysoká)

Co jsme viděli. Ve fázi dopravy ukazuje trace synchronní HTTPS volání na skladový endpoint ERP s 3sekundovým timeoutem a jedním opakováním; profil externích služeb připisuje tomuto jedinému endpointu 92 % veškerého čekání na externí systémy. Při kampaňové zátěži se ERP zpomalí, volání si vyberou celý timeout a požadavky checkoutu se začnou hromadit. Přesně odsud pochází p95 6,1 s i 3,4 % selhání. Zátěžový test to na stagingu reprodukuje.

Proč. Potvrzování skladu v reálném čase přibylo po incidentu s přeprodejem v roce 2025. Záměr je správný, jen vkládá latenci cizího systému do každého checkoutu.

Oprava. Přesunout srovnávání skladu mimo request: rezervovat proti lokální inventuře, potvrzovat asynchronně a upozorňovat na nesoulad skladových stavů. Ochrana proti přeprodeji zůstane zachována a checkout přestane na ERP čekat. Stejný vzor rozebíráme v článku proč se ERP a e-shop neshodnou na skladu.

Pracnost / dopad / riziko. 3–5 dní včetně alertu na nesoulad stavů. Odstraní ~2,8 s z checkout p95 ve špičce a timeoutová selhání úplně (obojí změřeno v zátěžovém testu se zaslepeným voláním). Riziko: střední; vyžaduje písemné odsouhlasení nového okna přeprodeje (odhadem < 0,1 % kampaňových objednávek, proti dnešním 3,4 % selhávajících checkoutů).

F4: Frontend posílá 2,3 MB JavaScriptu (Vysoká)

Co jsme viděli. První zobrazení produktové stránky stáhne 2,3 MB JavaScriptu v 61 souborech; blokování hlavního vlákna dělá na běžném telefonu 2,9 s. Čtyři ze čtrnácti marketingových tagů už nemají za sebou funkční účet.

Proč. Zčásti architektura frontendu Luma, zčásti osm let přirůstání tagů; žádný jednotlivý tag v součtu nepřevažuje.

Oprava, krátkodobě (toto čtvrtletí). Odstranit čtyři mrtvé tagy, odložit analytický bundle a widget recenzí níže na stránce načítat až ve chvíli, kdy je potřeba. Odhadem −0,4 s LCP, 2 dny.

Oprava, skutečná (samostatné rozhodnutí). Přestavba frontendu na Hyvä. Nahrazuje javascriptovou vrstvu Lumy, místo aby její části jen odkládala, takže o objemu payloadu rozhoduje architektura, ne správa jednotlivých tagů. Je to projekt na 6–10 týdnů a zaslouží si vlastní nacenění, ne řádek v seznamu oprav. Zmiňujeme ho zde, aby krátkodobý odhad výše nebyl chápán jako dosažitelné maximum.

F7: PHP-FPM pool vyčerpaný session locky ve špičce (Vysoká)

Co jsme viděli. Během zátěžového testu začínají selhání kolem 280 req/s, zatímco CPU sedí na 12 %. Status FPM ukazuje všechny workery obsazené; trace ukazují čekání na session locky v Redisu: zákazníci, kteří se opakovaně dotazují stránky potvrzení objednávky, drží zámky, které řadí do fronty všechny další požadavky téže session.

Oprava. Vypnout zamykání session pro endpointy opakovaného dotazování, zvednout počet workerů na to, co paměťová rezerva reálně dovolí (dnešní číslo pochází z menšího typu instance), a přidat alert na saturaci FPM; tento výpadek zůstal nezpozorován, protože grafy CPU vypadaly zdravě.

Pracnost / dopad / riziko. 2 dny včetně opakování zátěžového testu. Spolu s F3 přežije staging 420 req/s s checkout p95 1,9 s. Riziko: nízké.

F5, F6, F8, F9, F10, F11 stručně

  • F5 Obrázky: hlavní (hero) obrázky produktů jsou PNG o 1,6 MB doručované v jedné velikosti všem zařízením. Konverze do WebP + responzivní velikosti přes CDN: 1 den, −0,6 s LCP na produktových stránkách (změřeno na převedeném vzorku).

  • F6 Indexery: přepnuté na „Update on Save“ při hromadném importu v únoru 2026 a už nikdy zpět. Uložení produktu v adminu trvá 30 s a nesoulad mezi skladem na webu a realitou ve skladu pochází z race conditions při reindexu. Vrátit plánované indexování: 0,5 dne. Těch 30 s je změřených; číslo po opravě je odhad.

  • F8 Varnish grace: každý flush cache dnes pošle celou vlnu provozu na PHP. Grace mód vrací staré objekty, dokud se cache znovu nenaplní: 0,5 dne. Dopad je odhad.

  • F9 Duplicitní observery: dvě extenze odebírají stejný checkout event; jednu z nich její dodavatel opustil v roce 2023 a při každém volání znovu načítá quote. Odstranit ji (její funkce se nepoužívá): 1 den včetně ověření, −300 ms na přidání do košíku (změřeno v trace).

  • F10 Úklid databáze: databáze nese 41 GB tabulek catalogrule_product__temp* (pozůstatky nedoběhnutých indexerů ze starší verze Magenta, která po sobě neuklízela) plus vlastní frontovou tabulku s 12 miliony řádků bez jakékoli retence. Najde je jedna query do information_schema:

    mysql> SELECT COUNT(*) tables, ROUND(SUM(data_length+index_length)
        -> /1024/1024/1024, 1) gb FROM information_schema.tables
        -> WHERE table_name LIKE 'catalogrule\_product\_\_temp%';
    +--------+------+
    | tables | gb   |
    +--------+------+
    |     96 | 41.2 |
    +--------+------+
    

    Smazání pozůstatků a nastavení pravidelného čištění: 0,5 dne. Zálohy se zmenší z 68 GB na 27 GB; obnova pak trvá zhruba polovinu času, což je odhad ze změny velikosti a počítá se hlavně v den, kdy obnovu opravdu potřebujete.

  • F11 Order grid v adminu: sloupec přidaný pro expedici filtruje přes neindexovaný atribut; grid se načítá 12 s a pokaždé blokuje jeden worker. Jedna indexová migrace: 0,5 dne. Těch 12 s je změřených; číslo po opravě je odhad.

5. Plán seřazený podle návratnosti

PořadíPoložkyPracnostCo získáte
1F2 + F81 denKaždý necachovaný požadavek −400 ms; flushe cache už nevyvolají špičku zátěže
2F11–2 dnyTTFB katalogu −700 ms; deploye přestanou vyžadovat restart Varnishe
3F6 + F10 + F111,5 dnePoužitelný admin; konec nesouladu skladových stavů; zálohy na polovinu
4F72 dnyCheckout přestane ve špičce selhávat
5F5 + F92 dnyLCP produktové stránky −0,6 s; přidání do košíku −300 ms
6F33–5 dníCheckout p95 pod 2 s během kampaní
7F4 krátkodobě2 dnyLCP −0,4 s, než padne rozhodnutí o frontendu
8F4 přestavbasamostatné naceněníLCP produktových stránek pod prahem 2,5 s

Řádky 1–6 dají dohromady 10,5 až 13,5 inženýrského dne a řeší všechna selhání, která zákazník pocítí. Po kterémkoli řádku můžete skončit a práce do té chvíle obstojí sama o sobě. Řádek 6 jako jediný zasahuje do architektury objednávkové cesty; ať už ho provede kdokoli, nechte návrh posoudit seniorním inženýrem.

Po řádku 6 přeměřte stejným dotazem na field data, kterým tato zpráva začala. Pokud se čísla nepohnou podle predikce, kontaktujte nás a zjistíme příčinu.

6. Co jsme nekontrolovali

Bezpečnost, SEO, přístupnost a kvalita kódu mimo profilovaná nejvytíženější místa kódu jsou mimo rozsah této zakázky. Audit pokrývá výchozí store view; B2B store view sdílí stack, ale neměřili jsme ho zvlášť. Rozpočet z kapitoly 1 nechává kategorie 0,2 s nad prahem 2,5 s a nezjišťovali jsme, čím je ten rozdíl daný: vyžaduje to průchod šablonami kategorií, na který se tato zakázka nezaměřila. Jedno omezení přístupů: náš auditní uživatel se během okna nedostal k admin socketu Varnishe, takže runtime čítače Varnishe pocházejí ze snapshotů varnishstat, které nám exportoval hosting, ne z přímé kontroly v reálném čase.

7. Příloha: sondy, které si můžete pustit sami

Toto jsou read-only kontroly, kterými tento audit začínal. Poběží na kterémkoli Magentu 2 a nepotřebují nic než shell a databázového klienta. Pusťte si je dříve, než si u kohokoli objednáte audit, u nás také: pokud jsou všechny výsledky v pořádku, problém je někde, kam tato zpráva nesahá.

1. Obsluhuje full-page cache doopravdy? Vyžádejte si katalogovou URL dvakrát a přečtěte si debug hlavičku.

$ curl -sI https://vas-eshop.example/nejaka-kategorie | grep -i cache-debug

V pořádku: HIT na druhý pokus. MISS pokaždé je F1.

2. Nedělá něco stránky necachovatelné? Jediný blok s cacheable="false" kdekoli v layoutu udělá necachovatelnou celou stránku.

$ grep -R 'cacheable="false"' app/design app/code | wc -l

V pořádku: 0.

3. Umí Magento vůbec purgovat Varnish?

$ php -r 'var_export(array_key_exists("http_cache_hosts", (include "app/etc/env.php")));'

V pořádku: true, pokud je nastavenou cache Varnish.

4. Pojme OPcache celou codebase? Porovnejte obě čísla.

$ find . -type f -name '*.php' | wc -l
$ php -i | grep 'opcache.max_accelerated_files'

V pořádku: druhé číslo je větší než první.

5. Optimalizuje build Composer autoloader?

$ grep -c 'classMap' vendor/composer/autoload_static.php

V pořádku: cokoli jiného než 0.

6. Běží indexery podle plánu?

$ php bin/magento indexer:show-mode

V pořádku: každý řádek hlásí „Update by Schedule“. Cokoli na „Update on Save“ je F6. (Název příkazu i názvy režimů podle referenční příručky Adobe k indexerům, ověřeno 1. srpna 2026.)

7. Kolik mrtvé váhy je v databázi?

mysql> SELECT COUNT(*) tables, ROUND(SUM(data_length+index_length)
    -> /1024/1024/1024, 1) gb FROM information_schema.tables
    -> WHERE table_name LIKE '%\_\_temp%';

V pořádku: pár tabulek. E-shop v této zprávě jich měl 96, o 41 GB.

8. Na co čeká checkout? Tato sonda potřebuje APM, ne shell: otevřete transakci checkoutu, seskupte podle externí služby a přečtěte si podíl na celkovém čase. V této zprávě připadalo na jediný endpoint 92 % toho času, což je F3.

8. Další kroky

Plán oprav zvládne provést kterýkoli kompetentní tým včetně vašeho: každý nález pojmenovává konkrétní změnu, kterou je třeba udělat. Pokud ho máme provést my, řádky 1–6 se vejdou do dvou- až třítýdenní zakázky za fixní cenu podle našich standardních podmínek spolupráce.

Dotazy k jednotlivým nálezům: info@zapolu.com, nebo si domluvte hovor a přineste vlastní čísla; na hovoru vám rovnou sdělíme, zda se audit vyplatí.