- Nehezen kezelhető komplexitás (szerintem ez nem feltétlen csak Playben jött volna elő az ő esetükben)
- Borzalmas teljesítmény
Az első ponttal kapcsolatban megmutatta, hogy a LinkedIn oldalán rengeteg apró kis tartalmi rész van, amik ráadásul eltérnek a különböző felhasználó típusonként (újonc, prémium, etc), ezért nem lehetséges egyetlen kontrollerbe tenni a logikát (hecc kedvéért kipróbálták, és a fordító elszállt a scala fájl méretére hivatkozva). A megoldás, amit használnak, hogy minden egyes kis doboz önállóan is él, tehát van saját kontrollere, nézete, meg minden ami kell. Nagy előnye a módszernek, hogy nagyon könnyen tesztelhető, továbbá nagyon könnyen skálázható vízszintesen, ugyanis az egyes modulok szétoszthatóak több szerver között. A publikus oldalak kontrollereiben pedig egyszerűen legenerálják az egyes részeket, és összefűzik a kiszolgálandó HTMLbe. A technika hátulütője, hogy a statikus erőforrásokat a kontrollerben kell kezelni (szerk,. megj. illetve nem lehet ezen erőforrások tömörítését, egybefűzését valamilyen pluginnel leegyszerűsíteni).
Ezt a fragmentációs technikát választva az alábbiak igényelnek külön odafigyelést:
- Cookiek esetén szükségük van egy metódusra, ami a darabkák headerjeiből összefűzte a Cookie bejegyzéseket
- Statikus erőforrások beillesztésére szintén a headerbe kell tenni X.CSS ill. X-JS bejegyzéseket, amit aztán összefűznek, deduplikálják, és a HTML headbe tesznek (erről később, hogy miért pont oda)
- Hibakezeléshez a legfelső réteg kiolvassa a darabkák státsz-kódját
Miután végigmentünk a komplexitás témakörén, áttértünk a teljesítmény problémákra. Első lépésben átalakította az alkalmazást, hogy a hagyományos String alapú HTML generálás helyett streamelje a kimenetet. A Scala nyelvben vannak natív eszközök, mindössze egy custom render templatet kellett bekötni a Playbe (az alapértelmezett StringBuildert használ), és az Enumerator, Future párossal megoldotta, hogy a szerver az előállított HTMLt azonnal küldte is a kliensnek. Akit érdekel részletesebben a dolog itt tud utánaolvasni. Ez azért növeli a teljesítményt, mert a HTML headben elhelyezett erőforrások letöltésével nem kell a kliensnek megvárnia, amíg minden kis darabka összeáll. Következő trükk, amivel tovább fokozta a teljesítmény és a felhasználói élményt eléggé leleményes. A darabkák kimenetét a HTML bodyn kívülre, egy script type="text/html-stream" elembe tette, majd JavaScripttel a helyükre illesztette, amikor végzett velük a szerver. A módszer előnye, hogy nincs szükség darabonként egy AJAX kérésre, így tehermentesíthető a szerver, mégis a felhasználó azonnal kap választ a kérésére. Amire érdemes odafigyelni, hogy a hirtelen a helyükre kerülő elemek miatt ugrálhat az oldal, megzavarva ezzel az éppen kattintani vágyó felhasználót, valamint a headerek már a kérés elején kiküldésre kerülnek, ezért azokat a kérés további részében nem lehet változtatni. Nehezebb az oldalt tesztelni, és SEO problémák is felmerülhetnek. A demóalkalmazás egyébként elérhető githubon.
A következő prezentációt James Proper tartotta, aki szintén a teljesítmény-optimalizációt választotta témájául. Az első dolog, amit szép teljesítmény-tesztekkel bemutatott, hogy az aszinkron nem egyenlő a gyorsasággal, sőt! Egy egyszerű példán keresztül bebizonyította, hogy az aszinkron kérés mennyivel tud lassabb lenni a sok context switch miatt, amik elveszik a drága processzoridőt. Folytatásként több módszert is bemutatott, amivel növelhető a teljesítmény, és elkerülhető a rengeteg context switch.
- Az első javaslata az volt, hogy használjuk a Scala beépített ExecutionContextjét, ami a Java Fork/Join osztályaira épül. Bővebben
- Második lehetőségként azt ajánlotta, hogy ne használjuk az első pontban említett contextet, hanem váltsunk ImmediateExecutionContextre
Kiemelte azt is, hogy nagyon nagyon nagyon nagy weboldalaknál lehet teljesítmény-növekedést elérni, ha a routingot több különálló fájlba tesszük, és egy custom router megírásával csak a megfelelő fájlt dolgozzuk fel, amikor a kérés beérkezik. A gyorsulás oka, hogy a Play regexpeket illeszt az URI-re, és ha több száz illesztést kell csinálnia, mire a route fájl végén megtalálja a keresett bejegyzést, az felesleges processzor-terhelést jelent. A futtatott benchmarkoknál pár százalékos teljesítmény-növekedés volt mérhető.
A teljesítmény optimalizációs témaköröket elhagyva a következő előadást ismét Julien Tournay és Pascal Voitot tartotta, és témája a Scalaz-Stream volt. A prezentáció lényege pár mondatban összegfoglalva annyi, hogy miként lehet streamek fogadására lecserélni a jelenleg is működő Iteratee megoldást Playben erre, az egyébként még fejlesztés alatt álló, megoldásra. A prezentációban egyetemi előadásokra emlékeztető részletességgel mesélték el többek között, hogy miért is jó ez az újfajta megközelítés, hogy milyen problémákba ütközhetünk a HTTP, és a WebSocket eltérősége miatt, stb.
Ebédszünet előtt még Adam Evans és Asher Glynn tartott egy (számomra nem annyira érdekes) előadást arról, hogy milyen tapasztalataik voltak, amikor a BBC gyerekeknek szóló PHP-s megoldását lecserélték Play frameworkre. Architekturálisan volt egy PHP frontendjük, ami a BBC Java-s backendjéhez fordult adatokért. Ami miatt a Playre esett a választásuk:
A teljesítmény optimalizációs témaköröket elhagyva a következő előadást ismét Julien Tournay és Pascal Voitot tartotta, és témája a Scalaz-Stream volt. A prezentáció lényege pár mondatban összegfoglalva annyi, hogy miként lehet streamek fogadására lecserélni a jelenleg is működő Iteratee megoldást Playben erre, az egyébként még fejlesztés alatt álló, megoldásra. A prezentációban egyetemi előadásokra emlékeztető részletességgel mesélték el többek között, hogy miért is jó ez az újfajta megközelítés, hogy milyen problémákba ütközhetünk a HTTP, és a WebSocket eltérősége miatt, stb.
Ebédszünet előtt még Adam Evans és Asher Glynn tartott egy (számomra nem annyira érdekes) előadást arról, hogy milyen tapasztalataik voltak, amikor a BBC gyerekeknek szóló PHP-s megoldását lecserélték Play frameworkre. Architekturálisan volt egy PHP frontendjük, ami a BBC Java-s backendjéhez fordult adatokért. Ami miatt a Playre esett a választásuk:
- Mert Scala, ezt nem is fejtették ki bővebben
- Full stack web-framework
- Reaktív, könnyű benne non blocking szolgáltatásokat készíteni és hívni
- Typed safe template rendszer eléggé awesome
- Sok vállalat használja, és zizeg az egész
Miután kiválasztották a keretrendszert csináltak egy pilot projektet, ami nagyon jól sikerült, a PHP fejlesztőknek könnyű volt átszokni Scalára, és mindenki boldog.
A sponsor pitch után következő előadás igazán érdekes volt. Grant Klopper a The Guardian hírportál frontendjét kiszolgáló alkalmazásról beszélt, milyen problémákkal kell szembenézniük, milyen megoldásaik vannak, stb. Elmesélte, hogy naponta három Budapestnyi ember látogatja meg a weboldalukat, átlagosan mindegyik megnéz három oldalt. 900 kérést kell kiszolgálniuk minden másodpercben. A rendszer teljesen nyílt forrású, és elérhető a githubon, sőt nem csak elérhető, hanem konkrétan onnan buildelik a live rendszert. A teljes frontend letölthető egyetlen futtatható jar fájlként, és elindítható a java -jar frontend-artifact.jar paranccsal. A rengeteg kérés miatt mindent cachelnek, és a rendszer garantálja, hogy nem töltődik be sohasem ugyanaz a tartalom kétszer, a második kérés mindenféleképpen cache hit lesz. Beszélt továbbá még a deployment folyamatukról is, ami abból áll, hogy elindítanak három új verziót, regisztrálják őket a load balancerbe, majd lekapcsolják a régi hármat. Előadás után külön odamentem hozzá, és rákérdeztem miként kezelik azt a szituációt, amikor az összes node fut, de valami kódváltozás miatt a cachelt elem is megváltozik, és a régi verzióknak még a régit kell kiszolgálniuk, az újaknak pedig már az újakat. Dolgozom elosztott szinkron cachel, és ez nálunk bizony okoz problémát és fejfájást, éppen ezért mi kikapcsoljuk a cachet egészen addig, amíg minden nodeon az új verziójú szoftver nem fut. A válasz egész egyszerűen annyi volt, hogy sehogy. Azt mondta, hogy olyan rövid ideig fordulhat elő ez az eset, és a magas kérésszám miatt a statisztikában meg sem jeleni az a pár hibás kérés.
Utolsó előtti előadást Tobias Neef tartotta a kontrollerek absztraktálásának lehetőségeiről a Playben, hogy a lehető legjobban elkerülhessük a kódismétlést. Pár pontban összeszedve a lényeg:
- Készítsünk Actionönek az Actionjeinkhez
- Használjunk ActionBuildereket
- Compozáljuk actionjeinket (szép kifejezés)
- Használjuk a Filtereket
- Egyesítsük a hibakezelési és loggolási részeket
A konferencia Johan Andrén előadásával zárult, aki a különböző aszinkron lehetőségekről beszélt Scala és Java platformokon. Először a problémára világított rá Johan, miszerint az általunk írt kód a kiszolgálási idő nagyon kicsi részében van végrehajtási fázisban, és az idő nagy részét különböző erőforrásokra való várakozással tölti. Tegyük fel, hogy ha van 20 adatbázis, és 200 kiszolgáló szálunk, akkor a 200 + 1-edik adatbázist nem használó szál nem lesz kiszolgálva. A problémára háromféle megoldást mutatott be.
Az első, használjuk bátran a Future és Promise osztályokat (részletes információt itt találsz). Hátrányának azt nevezte meg, hogy amikor több rétegen keresztül dobáljuk a Future osztályokat, a rétegek között állandó jelleggel konvertálni kell azokat. Például egyik szerviz visszatér a userek listájával, de a hívónak JSON listát kell visszaadni, amit az őt hívónak HTML kóddá kell alakítani, stb. stb. Javaslata szerint az alábbi esetekben érdemes ezt a megoldás választanunk:
- Amikor más szolgáltatásokkal kommunikálunk
- Párhuzamos végrehajtásra, amikor a szálak teljesen elkülöníthetők egymástól
- Egyszerű háttérszolgáltatások implementálásakor
Utóbbi esetre nem tenném a nyakam, Tomcat esetén mivel a kiszolgáló szál referenciát őriz a Future objektumra szépen bevárja annak végeredményét, szóval én nem javaslom ezt a kombinációt.
Következő lehetőségként az Akka Actorsokat vetette fel, segítségével esemény vezérelt programozást tudunk megvalósítani. Az eljárás lényege, hogy van egy bemenő sor, amibe be tudják a kiszolgáló-szálak küldeni a kéréseket, és a rendszer egy szálon szépen sorban végrehajtja azokat. A technika nem sebességéről híres, blokkolja a többi futó szálat, de cserébe jól skálázható. Mikor használjuk:
- Amikor állapotokra van szükségünk
- Adat streamelés
- Esemény vezérelt programozásra
- Háttérfolyamatok végzésére
Utolsó lehetősségként a Iteratee-k használatát mutatta be. Sajnos ennek nincs Java-s megfelelője, a Chunks API tud valami hasonlót, de képességei messze elmaradnak a Scala natív megoldásától. A dolog lényege, hogy pici szeletekben adható át a feldolgozásnak az adat (Enumerators), és a rendszer lényegében reagál az adatra, és várja a következő darabkát.
- Enumerator[A] -> Iteratee[B, R]
- Enumerator[A] -> Enumeratee[A, B] -> Iteratee[C, R]
Lényeges külömbség a hagyományos feldolgozással szemben, hogy az első hibás darabka esetén már lehet kezelni a hibát, nem kell a teljes adatfolyamot megvárni.
Mikor használjuk:
- Streamel adatok esetén
A konferencia végére igencsak megfogyatkozott a létszám, láthatóan mindenki kellőképpen elfáradt. Meg kell hagyni, hogy elég tartalmasak voltak az előadások, szóval le a kalappal a szervezők előtt, nem bízták a véletlenre a mentális zombiságunkat. Gratula ezúton is.
Nincsenek megjegyzések:
Megjegyzés küldése