Time to first byte (TTFB) optimizavimas

Kas iš tikrųjų yra tas TTFB ir kodėl jis svarbus

Kai kalbame apie svetainės greitį, dažniausiai girdime apie puslapio įkėlimo laiką ar Core Web Vitals. Bet yra viena metrika, kuri dažnai lieka užkulisiuose, nors iš tikrųjų ji yra tarsi domino kauliuko pradžia – Time to First Byte arba TTFB. Tai laikas, per kurį naršyklė gauna pirmąjį baitą duomenų iš serverio po to, kai išsiunčia užklausą.

Paprastai tariant, jei TTFB yra prastas, viskas kita irgi bus lėta. Galite turėti puikiai optimizuotą frontend’ą, suspaudintus paveikslėlius, minifikuotą CSS – bet jei serveris lėtai atsako, vartotojas vis tiek lauks. Google tai žino ir nuo 2021 metų TTFB tapo vienu iš Core Web Vitals signalų, tiesa, netiesiogiai – jis daro įtaką Largest Contentful Paint (LCP).

Įdomu tai, kad geras TTFB skiriasi priklausomai nuo konteksto. Statiniam HTML puslapiui 200-300ms yra priimtina, bet jei tai API endpoint’as, kuris turi atlikti sudėtingą duomenų bazės užklausą, 500-800ms gali būti visai normalu. Google rekomenduoja siekti mažiau nei 800ms, bet praktikoje geriau būtų laikytis 200-400ms ribos.

Serverio pusės butelių kakleliai

Pirmiausia reikia suprasti, iš ko susideda TTFB. Tai ne vienas vientisas procesas, o kelių etapų grandinė: DNS lookup, TCP handshake, SSL/TLS derybos, serverio apdorojimo laikas ir pirmojo baito siuntimas. Kiekvienas šių etapų gali tapti problemų šaltiniu.

Serverio apdorojimo laikas dažniausiai būna didžiausias kaltininkas. Jei jūsų PHP aplikacija kiekvieną kartą generuoja puslapį iš naujo, vykdo 50 duomenų bazės užklausų ir dar papildomai kreipiasi į išorinius API, nesistebėkite, kad TTFB siekia kelias sekundes. Matau tokių projektų nuolat – ypač su WordPress, kur įdiegta 30 įskiepių, iš kurių pusė vykdo savo užklausas kiekviename puslapio įkėlime.

Duomenų bazė – tai atskira istorija. Neoptimizuotos užklausos, trūkstami indeksai, N+1 problemos (ypač su ORM’ais kaip Eloquent ar Doctrine) – visa tai prideda šimtus milisekundžių. Esu matęs atvejų, kai viena užklausa be indekso ant 100k įrašų lentelės užtrukdavo 2-3 sekundes. Pridėjus vieną composite index’ą, laikas nukrito iki 20ms.

Dar vienas dažnas dalykas – išoriniai API kvietimai sinchroniškai. Jei jūsų aplikacija laukia atsakymo iš trečiosios šalies serviso prieš grąžindama atsakymą vartotojui, jūs priklausote nuo to serviso greičio. O jei tas servisas yra lėtas ar nepasiekiamas? Vartotojas žiūri į baltą ekraną.

Kešavimas kaip pagrindinė ginkluotė

Jei yra vienas dalykas, kuris iš tikrųjų veikia TTFB optimizavimui, tai kešavimas. Bet ne bet koks kešavimas – reikia strategijos.

Pirmasis lygmuo – HTTP kešavimas su CDN. Cloudflare, Fastly, AWS CloudFront – visi jie gali kešuoti jūsų turinį geografiškai arčiau vartotojo. Kai puslapis kešuojamas CDN edge serveryje, TTFB gali nukristi nuo 800ms iki 50ms. Tai ne teorija – tai realūs skaičiai iš projektų, su kuriais dirbau.

Bet CDN neišsprendžia visko. Dinaminis turinys paprastai negali būti kešuojamas edge’e, todėl reikia application-level kešavimo. Redis ar Memcached čia yra standartiniai sprendimai. Kešuokite duomenų bazės užklausų rezultatus, API atsakymus, fragmentus HTML – bet ką, kas yra brangu generuoti.

Vienas triukas, kurį dažnai naudoju – stale-while-revalidate strategija. Grąžinkite kešuotą versiją iškart (greitas TTFB), o fone atnaujinkite kešą. Vartotojas gauna greitą atsakymą, o turinys vis tiek lieka santykinai šviežias. Next.js tai daro puikiai su Incremental Static Regeneration.

Full-page kešavimas irgi veikia stebuklus, ypač WordPress ar Drupal svetainėms. Vietoj PHP vykdymo ir duomenų bazės užklausų, serveris tiesiog atiduoda jau sugeneruotą HTML failą. WP Rocket, W3 Total Cache ar tiesiog Nginx FastCGI cache gali sumažinti TTFB nuo 1000ms iki 20-30ms.

Infrastruktūros pasirinkimas ir konfigūracija

Ne visi hostingai sukurti vienodai. Tas pigus shared hosting už 3 eurus per mėnesį? Jame jūsų svetainė dalijasi resursais su šimtais kitų, ir kai kažkas iš jų gauna traffic spike’ą, jūsų TTFB pakyla į dangų.

VPS ar dedikuoti serveriai duoda daugiau kontrolės, bet reikia mokėti juos konfigūruoti. Nginx paprastai yra greitesnis už Apache statiniam turiniui, bet Apache turi geresnes .htaccess galimybes. Aš paprastai naudoju Nginx kaip reverse proxy prieš Apache arba PHP-FPM – geriausias iš abiejų pasaulių.

PHP versija irgi svarbi. PHP 8.2 yra žymiai greitesnis už 7.4, o 7.4 buvo revoliucija palyginti su 5.6. JIT kompiliatorius PHP 8.x versijose gali pagreitinti tam tikrus workload’us 20-30%. OPcache taip pat būtinas – jis kešuoja sukompiliuotą PHP bytecode’ą, kad nereikėtų kiekvieną kartą parsinti failų.

Serverio geografinė lokacija taip pat turi reikšmės. Jei jūsų serveris yra JAV, o vartotojai Lietuvoje, fizika prideda bent 100-150ms latency. Čia vėl padeda CDN arba multi-region deployment. AWS, Google Cloud, DigitalOcean – visi siūlo serverius skirtinguose regionuose.

Dar vienas aspektas – HTTP/2 ir HTTP/3. HTTP/2 leidžia multiplexing, o HTTP/3 naudoja QUIC protokolą, kuris sumažina connection setup laiką. Įjungti HTTP/2 Nginx paprastai tereikia pridėti „http2” prie listen direktyvos. HTTP/3 dar nėra visur palaikomas, bet jau verta stebėti.

Duomenų bazės optimizavimas giliau

Grįžkime prie duomenų bazės, nes tai tikrai vieta, kur galima laimėti daug milisekundžių. Pirmiausia – įjunkite slow query log. MySQL ar PostgreSQL gali loginti visas užklausas, kurios trunka ilgiau nei nustatytą laiką (pvz., 100ms). Tai jums parodys, kur problemos.

Indeksai – tai duomenų bazės optimizavimo pagrindas. Bet per daug indeksų taip pat gali būti problema, nes jie lėtina INSERT ir UPDATE operacijas. Reikia balanso. Composite indeksai dažnai efektyvesni nei keli atskiri, ypač kai WHERE sąlygoje naudojate kelis stulpelius.

N+1 problema – klasika su ORM’ais. Jūsų kodas atrodo nekaltas:

„`php
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name;
}
„`

Bet tai generuoja vieną užklausą postams gauti ir po vieną kiekvienam autoriui. Jei turite 100 postų, tai 101 užklausa. Sprendimas – eager loading:

„`php
$posts = Post::with(‘author’)->get();
„`

Dabar tik dvi užklausos. Skirtumas gali būti tarp 500ms ir 50ms.

Connection pooling taip pat svarbus. Kiekvienas naujas duomenų bazės connection’as užtrunka laiką. Persistent connections ar connection pooling (pvz., PgBouncer PostgreSQL) leidžia pakartotinai naudoti egzistuojančius connection’us.

Jei duomenų bazė tikrai yra butelių kaklelis ir kiti metodai nepadeda, galima pagalvoti apie read replicas. Master-slave setup’e write operacijos eina į master, o read – į slave’us. Tai paskirsto apkrovą, bet prideda kompleksiškumo.

Frontend’o įtaka TTFB

Gali atrodyti keista, bet frontend’as irgi gali daryti įtaką TTFB. Ne tiesiogiai, bet per tai, kaip jis inicijuoja užklausas ir kaip serveris turi reaguoti.

Server-Side Rendering (SSR) vs Client-Side Rendering (CSR) – amžinas ginčas. SSR duoda geresnį TTFB pradiniam HTML, bet serveris turi daugiau darbo. CSR grąžina tuščią HTML greitai (geras TTFB), bet vartotojas mato turinį tik po JavaScript įkėlimo ir vykdymo. Static Site Generation (SSG) su incremental regeneration dažnai yra geriausias kompromisas.

Prefetching ir preconnecting gali padėti. Jei žinote, kad vartotojas greičiausiai pereis į kitą puslapį, galite prefetch’inti jo duomenis. Preconnect leidžia naršyklei iš anksto užmegzti ryšį su serveriu:

„`html „`

Resource hints kaip rel=”preload” taip pat gali optimizuoti, kaip greitai naršyklė pradeda užklausas. Bet čia reikia būti atsargiems – per daug preload’ų gali sukelti priešingą efektą.

Monitoring ir debugging įrankiai

Negalite optimizuoti to, ko nematote. TTFB matavimas turi būti nuolatinis, ne vienkartinis.

WebPageTest – mano go-to įrankis. Jis ne tik parodo TTFB, bet ir skaido jį į komponentus: DNS, connect, SSL, wait. Tai leidžia tiksliai identifikuoti, kur problema. Galite testuoti iš skirtingų lokacijų ir skirtingais connection greičiais.

Chrome DevTools Network tab – paprastas, bet galingas. Waterfall diagrama aiškiai parodo, kiek laiko užtrunka kiekviena užklausa ir jos dalys. „Waiting (TTFB)” stulpelis – tai, ko ieškote.

Lighthouse auditas įtraukia TTFB į savo analizę, nors ne kaip atskirą metriką. Bet jis duoda bendrą vaizdą, kaip TTFB veikia kitas metrikos.

Real User Monitoring (RUM) įrankiai kaip New Relic, Datadog ar net Google Analytics 4 gali rodyti TTFB iš realių vartotojų. Tai svarbu, nes lab testai ne visada atspindi realybę. Vartotojai turi skirtingus interneto greičius, skirtingas lokacijas, skirtingas naršykles.

Server-side monitoring su APM (Application Performance Monitoring) įrankiais kaip Blackfire, Tideways ar Scout padeda identifikuoti lėtas funkcijas ir užklausas jūsų kode. Profiling production aplinkoje (su sampling, kad nepaveiktų performance) duoda neįkainojamų įžvalgų.

Vienas triukas – pridėkite custom header’ius į atsakymus, kurie rodo serverio apdorojimo laiką:

„`php
header(‘X-Processing-Time: ‘ . (microtime(true) – $_SERVER[‘REQUEST_TIME_FLOAT’]));
„`

Tada galite lengvai matyti, kiek laiko serveris užtruko apdoroti užklausą, atskiriant tai nuo network latency.

Kada optimizavimas tampa per daug

Yra tokia riba, kai tolimesnis optimizavimas nebeapsimoka. Jei jūsų TTFB yra 150ms ir norite nukritinti jį iki 100ms, galbūt reikės investuoti daug laiko ir resursų dėl 50ms, kurių dauguma vartotojų net nepastebės.

Premature optimization – tikra problema. Esu matęs projektų, kur developeriai praleido savaites kurdami sudėtingas kešavimo sistemas, nors svetainė turėjo 100 lankytojų per dieną. Pirmiausia padarykite, kad veiktų, tada padarykite, kad veiktų gerai, ir tik tada – kad veiktų tobulai.

Bet yra situacijų, kur kiekviena milisekundė svarbi. E-commerce svetainėse tyrimai rodo, kad 100ms pageload laiko padidėjimas gali sumažinti konversijas 1%. Jei kalbame apie milijonus apyvartos, tai rimti pinigai. Google, Amazon, Facebook – jie optimizuoja iki paskutinės milisekundės dėl to.

Taip pat reikia atsižvelgti į cost-benefit. Jei serverio upgrade’as iš 20€ į 50€ per mėnesį sumažina TTFB 300ms, tai greičiausiai verta. Bet jei reikia perrašyti pusę aplikacijos ir investuoti mėnesį darbo dėl 50ms, galbūt yra svarbesnių dalykų.

Dar vienas aspektas – kompleksiškumas. Kuo sudėtingesnė jūsų kešavimo strategija, tuo sunkiau ją palaikyti ir debug’inti. Cache invalidation yra viena iš sunkiausių problemų kompiuterių moksle ne be priežasties. Kartais paprastesnis sprendimas, kuris duoda 80% rezultato, yra geresnis nei tobulas sprendimas, kurį tik vienas žmogus komandoje supranta.

Kai viskas sueina į vieną vietą

TTFB optimizavimas nėra vieno sprendimo dalykas – tai visų komponentų simfonija. Geras hosting, optimizuotas kodas, efektyvi duomenų bazė, protingas kešavimas, CDN – visa tai turi veikti kartu.

Pradėkite nuo matavimo. Išsiaiškinkite, koks jūsų dabartinis TTFB ir kas jį sudaro. WebPageTest waterfall diagrama duos atsakymus. Tada identifikuokite didžiausią butelių kaklelį – paprastai tai bus serverio apdorojimo laikas arba duomenų bazė.

Greiti laimėjimai paprastai ateina iš kešavimo. Įjunkite OPcache, pridėkite Redis, naudokite CDN. Tai gali duoti 50-70% pagerinimą per kelias valandas darbo. Tada gilinkitės į duomenų bazės optimizavimą – pridėkite indeksus, išspręskite N+1 problemas, optimizuokite lėtas užklausas.

Infrastruktūros pagerinimas – serverio upgrade’as, HTTP/2, geresnė geografinė lokacija – tai vidutinio laikotarpio investicijos, bet jos duoda stabilų rezultatą. Ir galiausiai, architektūriniai sprendimai kaip microservices, async processing, static generation – tai ilgalaikės strategijos, kurios reikalauja daugiau pastangų, bet gali transformuoti jūsų aplikacijos performance.

Svarbiausia – nepamiršti, kad optimizavimas yra iteratyvus procesas. Išmatuokite, optimizuokite, išmatuokite vėl. Ir visada laikykite balansą tarp performance, kompleksiškumo ir development laiko. Geras TTFB yra svarbus, bet ne už kiekvieną kainą.

Parašykite komentarą

El. pašto adresas nebus skelbiamas. Būtini laukeliai pažymėti *