A Craftosphere előtti időkből. Az alapítónk 2017 és 2019 között egy közszféra-platform vezető fejlesztője és architektje volt. Ez a történet abból az időszakból való. Anonimizált: nincsenek benne nevek, és a szervezetet sem nevezzük meg, a részleteket pedig megváltoztattuk, ahol a tanulságot nem érintik.

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: 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.

Melyik 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.