A tuningolás egyébként izgalmas téma, ezer dologgal lehet húzni egy ilyen rendszert, adatbázis optimalizálás, cache réteg kidolgozása, a DAO réteg optimalizálása, profile-olás, garbage collector tuningolás, memóriaparaméterezés, jdbc connection pool buhera, threadpool buhera... és mindezen már túl voltam és az egész még mindig nem muzsikált úgy ahogy nekem tetszik. Azért éreztem nyekergésnek az egészet, mert ugyanezen a vason lemértem hogy a default apache installáció statikus html tartalommal mit tud kezdeni. Rendesen odacsűrt a procinak, de 5000 req/sec körül teljesített. Ez nyilván egy unfair összehasonlítás, mert az apacsnak semmi mást nem kellett csinálnia, csak kipumpálnia egy file-t a tcp socketen. Ez adta az ötletet, hogy ha az én rendszeremnek csak ennyit kellene csinálnia, akkor megcsíphetné az apacs tempóját.
És ezt a receptet főztem ki:
- Végy egy filtert. Nevezd el ResponseCacheFilternek
- Végy egy JCache (ez az oldal kiválló példája egy halott JCR-nek) implementációt, de ha nincs vagy bizonytalan vagy, jó lesz egy HashMap is, lehetőleg legalább az adatokat Ref-fel csomagold hogy OOM-et azért mégse okozzon
- Fogd meg a HttpServletResponse-t és gondosan tekerd be egy HttpServletResponseWrapper-be. Itt van néhány trükk amit csinálni kell, de a lényeg hogy írj egy olyan ServletOutputStream-et, ami hasonlóan a apache commons io TeeOutputStream-jéhez (esetleg építhetsz is erre az osztályra) másolatot készít a kiírt adatból. Szerintem célszerű egy ByteArrayOutputStream-et használni ha az eredmény nem véresen nagy. Esetleg csinálhatsz köré egy limitált verziót, ami egy limit után már nem ír (mint pl a CountingInputstream)
- A filtered egyszerűen csak dobja tovább a végrehajtást az adatbázisba matató kódnak, ami majd mégtovább dobja az adatbázisból kiberhelt adatokat kiszerializáló kódnak, satöbbi. A végén valahogy a vezérlés visszakerül a filterhez.
- No ekkor kapjuk el a HttpServletResponseWrapperünk grabancát és követeljük tőle servlet output stream-re kiírt adatok másolatát, ez egy plain byte tömb ha minden igaz. Egyszerűen csak csináljunk egy kulcsot a request paramétereiből, és az értékként használva a response másolatát dobjuk be a cache-be
- A következő requestek beérkezésekor a requestből összebütykölt kulccsal megnézheted, hogy van-e cachelt válasz, ha nincs lásd fenti pontok, ha pedig van, akkor boldogság, mert csak egy byte tömböt kell kidobnod.
- Ezt a filtert applikáld rá a tipikusan csakis olvasást végző műveleteidre. Pl getCustomer, getLatestPosts, stb.
- Lesz még itt bonyolítás: a módosítást végző kód kell hogy kérni tudja a fenti response cache egyes elemeinek kiürítését. Ide clusteres esetben szerintem egy jms topicon érdemes körbeküldeni egy ID-t vagy hasonlót, ha nincs igény culsterezésre, elég csak direktben törölgetni kulcsokat a cache-ből. (erre is lehet kultúráltnak látszó kódot írni, akármilyen bunkón is hangzik)
- Kész! Elő a benchmark eszközzel!
Csak ennyivel még nem sikerült megszorongatni az apacsot, bár a teljesítmény igen látványosan megnőtt, de még most jön egy desszert.
A browserek (söt talán már az internet explorer is) képesek tömörített tartalmat kezelni. Az "Accept-Encoding" headerben küldik meg, hogy ők mit fogadnak el. Na ezt ki lehet használni. Általában azt szokták mondani, hogy hülye dolog generált tartalmat is gzippelni, mert sok időt visz. Talán így van, de ha csak egyszer generáljuk a tartalmat, és az eredményt becacheljük, akkor onnantól már nem kell újra és újra gzipelnünk a generált tartalmat. Ez lényegesen csökkenti a hálózati forgalmat. Egy gzippelt response törtrésze az eredeti tartalomnak. Példaként a jól ismert JQuery minified 76 K gzippelve csak 26 K. Ez most egy rossz példa volt, mert az alig a harmada, de az én teszt kimeneteim harmincadukra estek össze és ez tényleg sokat dobott rajta.
Ez az egész csak akkor hatásos, ha az alkalmazás az olvasások számához képest ritkán módisítja a kimenetet ÉS a pillanatnyi pontos állapot nem fontosabb, mint a jó válaszidő. A legtöbb webapp tipikusan ilyen szerintem. Az internet ilyen. Egy bank esetében atomkatasztrófával érne fel, de speciel egy blogger cikk esetében nem nagyon érdekel senkit sem, hogy publish után még akár fél másodpercig is a régi tartalom jelenik meg másoknak.
Ez az egész csak akkor hatásos, ha az alkalmazás az olvasások számához képest ritkán módisítja a kimenetet ÉS a pillanatnyi pontos állapot nem fontosabb, mint a jó válaszidő. A legtöbb webapp tipikusan ilyen szerintem. Az internet ilyen. Egy bank esetében atomkatasztrófával érne fel, de speciel egy blogger cikk esetében nem nagyon érdekel senkit sem, hogy publish után még akár fél másodpercig is a régi tartalom jelenik meg másoknak.
Itt most egy kicsit kénytelen vagyok ködösíteni, mert mégiscsak a konkrét alkalmazáson múlik, de a cucc teljesítménye így vetekedhet, söt verheti is az apache statikus oldalak sebességét -ami mégegyszer nagyon unfair összehasonlítás, hiszen az apacsot nem tuningoltuk és esetleg csinál egy rakás egyebet amit a jetty vagy tomcat nem, és fordítva.
A válaszidőkről még annyit, hogy amikor egy-egy kulcs (vagy egyszerre több is) kiesik a cache-ből TTL, GC vagy a fenti kódolt mechanizmus által, újra a régi és lényegesen lassabbnak mért kódunk lép működésbe és ilyenkor amíg a cache-ben meg nem jelenik újra az eredmény esetleg több kérés is a háttérlogika végrehajtásához kezd, ami például tökönrúgja az adatbázist. Egy mérésben ez meg is fog látszani, tipikus bemélyedések szabályos időközönként.
Már erre is volt megoldásom egyébként, csak még nem építettem bele ebbe a response cache megoldásba, nagyjáből annyi, hogy háttérszálon is frissítheted az eredményt, ha nem borzasztó sürgős és kritikus fontosságú mindig pontos eredményt adni, és addig visszaadhatod a régi eredményt. Persze ha a cache-d nem dobja ki a régi eredményt. Szóval ez egy kicsit gubancosabb történet, de végig lehet járni ha valaki ki akarja kalapálni a csorbákat a performace görbéjén.
Na ennyi, remélem valamennyire tanulságos volt. A francba kedd lett közben, akkor jöjjön még egy ilyen a teljesítmény tuningoláshoz:
Egyik nap azt találom ki
Hogy másnap százon én nyerek
És nő hegyek vesznek körül
De én nem szeretek egyet se
Még párszor győzök
De már csak megszokásból