Történetek Korábbi munka, 2017–2019
Oszd meg és cache-elj
Egy kezdőoldal, amely 300 felhasználónál 10 másodperc alatt töltődött be, majd egy lekérdezés négyre bontása után 2000 felhasználónál 20 ms alatt
Írta: Fejér Attila Megjelent:
Egy nagyjából 200 000 felhasználós közszféra-platform három év munka után közvetlenül az első élesítés előtt állt. A kezdőoldal minden felhasználónak összefoglalta a teendőit, például az új feladatait, képzéseit és üzeneteit. A terheléses tesztben 300 konkurens felhasználónál 10 másodpercig tartott a betöltése. A profilozás egy lekérdezéshez vezetett, amely nagyjából 10 táblát kapcsolt össze. Négy kisebb lekérdezésre bontottam, és mindegyik eredményét felhasználónként cache-eltem. Az optimalizálás után ugyanabban a tesztben 2000 konkurens felhasználónak 20 ms alatt töltött be az oldal. Így a tervezett határidőre tudtunk is élesíteni.
- Előtte
- 300 konkurens felhasználó, 10 s (p80)
- Utána
- 2000 konkurens felhasználó, 20 ms (p99)
- Teljes felhasználói kör
- kb. 200 000
Előzmények
A platform egy régebbi rendszert váltott le, és a mögötte álló szervezet három évig fejlesztette a komponensek egész sorát, amelyeknek együtt kellett élesbe kerülniük. A felhasználóknak használniuk kellett, mert kötelező képzéseket kaptak rajta, így pontosan tudtuk, kik és hányan fognak belépni. Mindannyian ugyanarról az oldalról indultak: egy kezdőoldalról, amely sok rendszerből gyűjtötte össze, mit kell elintézniük, például az új feladatokat, a kötelező képzéseket és az üzeneteket.
Közvetlenül az élesítés előtt JMeterrel terheléses teszteket futtattunk, és elfogadhatatlan eredményt kaptunk. Nagyjából 300 konkurens felhasználónál a kezdőoldal kártyáit visszaadó végpont a 80. percentilisen 10 másodperc körül válaszolt. 100 felhasználóval még minden rendben volt.
A megoldás
Mielőtt bármihez hozzányúltam volna, tudni akartam, a rendszer melyik része lassú. Egy Java-profiler arra a metódusra mutatott, amely a kártyákat összeállította, és ez a metódus futtatta az adatbázis-lekérdezést. A connection pool hangolása nem segített. A kérdéses lekérdezés szerepelt az adatbázis slow query logjában. A visszaküldött adat mennyisége kicsi volt, így a hálózat nem lehetett a bűnös.
A lekérdezés egyszerre gyűjtötte össze mind a négyfajta megjelenő kártya adatait. Mindegyik kártyatípushoz néhány tábla kellett, így egyetlen lekérdezés nagyjából 10 táblát kapcsolt össze. Némelyikben több százezer sor volt, másokban több tízezer, így a join köztes eredménye óriási lett. Minden oszlopot, amely szerepelt szűrésben vagy joinban, már indexeltünk. Ez segített egy kicsit, de közel sem eleget.
A négy kártyatípus nem függött egymástól, így nem kellett az adatbázisban összekapcsolni őket. A lekérdezést négy kisebbre bontottam, kártyatípusonként egyre, mindegyik két-három táblával. Mivel összesen legfeljebb néhány tucat eredmény jött vissza, ezeket a backendben fésültem össze. Mindegyik gyorsan lefutott, és mindegyik csak a felhasználótól függött. Így könnyű volt felhasználónként cache-elni őket, egy több példányos Hazelcast clusterben. Az invalidálás egyszerű volt, mert csak néhány esemény változtathatott meg egy adott típusú kártyát: egy új üzenet törölte a cache-ből a felhasználó üzenetkártyáit, egy új feladat pedig a feladatkártyáit.
Eredmények
Ugyanazt a JMeter-tesztet futtattuk újra, üres cache-sel indulva, és szándékosan meredeken felfuttatva a terhelést, hogy lássuk a legrosszabb forgatókönyvet. 2000 konkurens felhasználónál a végpont a 99. percentilisen 20 ms alatt válaszolt. Sok kérés el sem jutott az adatbázisig, amelyik pedig igen, az is csak az egyik új kis lekérdezést futtatta.
Az élesítés a tervezett napon megtörtént. Panasz nem érkezett, és a felhasználóknak sokkal jobban tetszett az új rendszer, mint az, amelyet leváltott.
A tanulság
Bizonyíték alapján optimalizáljunk: mielőtt bármit megváltoztatunk, profilozzunk. Itt a profiler és a slow query log ugyanarra a lekérdezésre mutatott, így a javítás fókuszában is ez volt.
Emellett az adatok szerkezetét aszerint alakítsuk ki, hogyan változnak. Egyetlen nagy joinként a kezdőoldalt nehéz volt cache-elni, mert bármelyik kártya változott, az egész eredmény invalidálódott. Kártyatípusonként szétbontva minden kártya egy maroknyi ismert okból változott, és a cache-elés triviális lett.
További történetek
Minden korábbi munka Minden történet
Továbbiak fejlesztőcsapatoknak
Korábbi munka 2026
Feltétel nélkül
Feature flagek interfészként és osztályként, hogy a hívás helyén látszódjon, mit csinál a kód, a többit pedig a fordító ellenőrizze
Miért kezdtem el fejleszteni a FlagZent, egy nyílt forráskódú dispatch réteget Java feature flagekhez: mi a szétszórt feltételek ára, mit ellenőrizhet helyettük a fordító, és hol tart ma a projekt.
ElolvasomEsettanulmány 2024–2026
XP coaching 300+ Fortune 100-es csapatnál
Két év, nagyjából 1 000 coacholt ember, és körülbelül 10%-kal jobb átfutási idő (lead time) és hibás változtatások aránya (change failure rate)
Hogyan vitte el egy Fortune 100-as pénzügyi cég a csapatait az extreme programming felé: mit változtatott két hét tanteremben és hat hét a csapatok mellett, és mely szokások élték túl a coachok távozását.
ElolvasomKorábbi munka 2020–2022
Kerékcsere menet közben
Egy több tíz terabájtot archiváló monolit migrálása microservice architektúrára, miközben a rendszer zavartalanul üzemelt
Hogyan került át egy több tíz terabájtnyi érzékeny adatot archiváló és kereső rendszer egy monolitból és egy asztali kliensből microservice-ekre, az egész rendszer egy lépésben való újraírása nélkül: a sorrend, a számok, és hogy mikor csinálnám újra így.
ElolvasomMelyik oldaluk lassul be terhelés alatt?
Egy rövid beszélgetés arról, hol lassú a rendszerük, és mit mértek eddig. Javaslattal távoznak arról, hol érdemes először keresni.