Elasticsearch Index és a performancia

Az Elasticsearch alapértelmezetten nem spórol az indexekben tárolt dokumentumok kapcsán az erőforrásokkal. Ha az adott index nem rendelkezik egy jól felépített és átgondolt mappinggel, akkor az ES gyakorlatilag “szabadfolyást” tart, minden szöveges típust analizál, minden olyan adatot ami rendezhető vagy aggregálható azt inmemory bufferbe lapoz, ráadásul menedzsel egy csomó olyan virtuális fieldet is mint pl az: _all.

Ezzel az ES egy végtelen rugalmasságot és könnyed felhasználást teszt lehetővé, ami a legtöbb projekt esetén egyébként nagyon pozitívan értékelhető hozzáadott érték. Azonban ennek megvan az ára, ez pedig a performancia. Egy tetszőleges ES installment esetén elmondható, hogy néhány millió dokumentumig nem nagyon kell foglalkozni a mappingekkel, hiszen itt még bőven érvényesül az a fajta distributed processing hozzáállás, hogy ha kezd lassulni az indexelés vagy a keresés, akkor bővíteni kell a clustert egy-két extra node-dal (már persze ha az index shard beállításainál ügyeltünk arra, hogy ennek legyen értelme…) és máris normalizálódik a performancia.

Ha egy ES installment tervezési fázisában jogosan felmerülhet az igény a nagy mennyiségű, összetett dokumentumok tárolására (értsd milliárdos darabszám), akkor viszont nagyon fontos, hogy már az index megtervezési fázisában meghozzunk néhány nagyon fontos döntést, ami erősen ki fog hatni a későbbi performanciára, ezek:

  • Kezdjük az alapoknál: Alap esetben az elasticsearch az új indexeket 5:1 shard elosztással hozza létre, ami annyit tesz, hogy 5 primary shard jön létre és mindegyikről egy replika. Ez természetesen módosítható és érdemes is módosítani, azonban azt érdemes tudni, hogy egy index shard paramétereit annak CSAK a létrehozásánál lehet beállítani, utána módosítani azt már nem lehet. Ez a gyakorlatban azt jelenti, hogy MAXIMUM 5 node vehet részt az új adatok indexelésében és szintén maximum további 5 node vehet részt a queryk futtatásában, hiszen a queryk akár a replika shardokon is futhatnak a node balance miatt. Tehát ebben a konkrét (default) esetben a cluster 5 nodeig tud tökéletesen párhuzamosítani, és további 5 nodeig tud peak jelleggel további extra performanciát termelni, bár ez utóbbi már kevésbé releváns performancia. Viszont 10 node felett további nodeok bevonása már semmilyen módon nem hat pozitívan a performanciára. (ezen index szempontjából).
  • Az előző pontban bemutatott problémát könnyen kezelhetjük azzal, ha eleve több sharddal tervezzük az indexeket (már ha indokolt ez), vagy pedig ha az indexeket mondjuk napi jelleggel görgetjük. Így a napon túli queryk minden bizonnyal olyan indexeken fognak futni amelyek más nodeokon futnak, így lehet értelme a nodeok számának növelésének.

commercial break...

Ez eddig egy eléggé triviálisnak tűnő megoldás, azonban könnyen előfordulhat, hogy akkora adatmennyiséggel és annyira bonyolult dokumentum struktúrával kell dolgoznunk, ami már egy indexen belül is teljesítmény gondokat okozhat. Ilyenkor egyetlen út marad, ez pedig az index mappingjének (_mapping) alaposabb átgondolása. Erre néhány ötlet:

  • Minden dokumentum tárolja alapértelmezetten az eredeti (indexelés előtti) JSON-ját a _source értékben. Ez bonyolult dokumentumok esetén tetemes erőforrást igényelhet. A _source-t akár ki is lehet kapcsolni, bár ennek jócskán lehet negatív hatása (pl egy ilyen dokumentumot nem lehet updatelni és reindexelni) éppen ezért a _source teljes kikapcsolása helyett esetleg érdemes lehet excludeolni bizonyos fieldeket, amelyek tárolása felesleges és csak zabálja az erőforrásokat.

{
"mappings": {
"event": {
"_source": {
"includes": [
"*.count",
"meta.*"
],
"excludes": [
"meta.description",
"meta.other.*"
]}}}}

    • Az ES minden dokumentum betöltésekor automatikusa létrehoz egy _all fieldet, amiben az összes a dokumentumban fellelhető mező analizált adata szerepel felsorolásként. Ez sokat segít egy teljes indexre kiterjedő full text search queryhez, mindemellett viszont eléggé erőforrás igényes. Ha úgy egyébként ismerjük a saját adatmodellünket, akkor az _all kikapcsolható a mappingben ({“mappings”: {“type_X”: { “_all”: { “enabled”: false }}}}).
    • Megfelelően kialakított mapping nélkül az ES minden szöveges adatot text-ben tárol, minden számot pedig valamilyen numberic datatypeben. Előbbi azért lehet probléma, mert a “text” field type alapértelmezetten analizált. Az analizálás során az indexer elemi daraboka szedi a text tartalmát és szavanként tárolja azt a gyorsabb keresés érdekében. Ez egy erőforrás igényes művelet, amire a legtöbb esetben nem igazán van szükség, hiszen a legtöbb esetben nincs szükségünk a full text search funkcióra a text fieldeknél. Az analyze könnyedén kikapcsolható, amivel az indexelés gyorsítható

"XXX": {
"type": "text",
"index": "not_analyzed",
},

    • Ha egy mezőt nem analizálunk, akkor minden bizonnyal nem fogunk rá sortolni és aggregálni sem, ilyen esetben viszont érdemes felhívni arra az ES figyelmét, hogy ezeket a mezőket ne töltse be az in-memory bufferbe, hiszen az véges és nagy mennyiségű dokumentumoknál extra IO terhelést okozhat az aggregálandó adatok folyamatos ki/be töltögetése. Erre a célra találták ki a fielddata nevű mapping opciót, az így megjelölt típusú mezők adatai nem kerül betöltére az in-memory bufferbe a dokumentum betöltésekor. A fielddata opció egyébként alapértelmezetten ki van kapcsolva a text field typenál pont azért, hogy a nagy mennyiségű szövegek ne üssék ki folyamatosan a heapet.
    • Kerüljük a multi-fields definíciókat! Személyes tapasztalatom alapján a legtöbb multi-fields használat esetén valójában arról van csak szó, hogy az eredeti field type rosszul lett megválasztva. Tipikusan jó példa erre az date type alá létrehozott text vagy keyword fields. Ha pl használjuk az _all fieldet, akkor valójában az összes dátum típusú mező analizálva és tagolva elérhető abban is. Másik példa, amikor analizált text alatt kerül létrehozásra keyword típus csak azért, hogy lehessen aggregálni az adott mezőre. Mindezt úgy, hogy valójában az adott text mező egy darab relatív statikus szöveges adatot tartalmaz.
    • Ha már említésre került a “keyword” típus: Maga a típus nagyon hasznos, különösen, ha előre ismert és jellemzően valóban keyword felsorolásokat kap inputként. Ha viszont ez nem adott és változó hosszúságú maga az input, ami akár jelentős mennyiségű elemeket is tartalmazhat (pl. egy XML-t kell keywordökre bontani), akkor érdemes meghatározni a ignore_above paraméterrel azt, hogy maximum mennyi karakter hosszúságú szöveget bontson kulcsszavakra. Ezzel sokat lehet gyorsítani az indexelésen.

Mindezek a tippek nyilvánvalóan csak a jéghegy csúcsait jelentik, de az ennél komolyabb tippekhez persze már érdemes pontosan ismerni a konkrét index jellemzőit, adatait, szerkezetét, stb. Amikre érdemes még figyelni (ezekről lehet később írok külön postot):

  • Az ES performanciájának egyik legfontosabb kulcsa az IOPS tehát, hogy másodpercenként mennyi IO műveletet tud végrehajtani a diszk környezet. Ennek kapcsán számtalan apró ötlet van (pl a több path.data használata külön diszkeken, stb.) amivel sokat lehet nyerni.
  • Az indexing performanciára nagyon komoly hatást gyakorolhat a segment merge folyamat, tehát amikor az elemi index szegmenseket összefűzi az indexer. Ezt is lehet finomhangolni az index tartalma alapján. De teljesen máshogy kell paraméterezni a segment merget akkor ha SSD-n vagy ha hagyományos mozgó fejes diszken tároljuk az adatokat.
  • Ha az adott index feltöltése “bulk import” elven történik, tehát nem folyamatosan szúrogatjuk be az új dokumentumokat, hanem időzítetten történik nagy mennyiségű adat bulk importja, akkor érdemes a bulk import előtt kikapcsolni a replikákat, majd utána vissza, ezzel megspórolhatjuk azt, hogy az összes replika egyszerre hajtsa végre a költséghatékony indexelést. Majd a sikeres betöltés után csak vissza kell kapcsolni a replikákat és a recovery tartalom szinten állítja helyre azokat ahelyett, hogy tételesen indexelné be az összes dokumentumot.
  • Szintén a nagy mennyiségű betöltéseken tud segíteni az, ha a betöltések idejére felemelésre kerül az index.refresh_interval értéke. (ez alap esetben 1 másodperc ami azt jelenti, hogy másodpercenként keletkezik egy index szegmens, amit ezt követően mergel is). Az érték ideiglenes felemelésével ritkábban keletkeznek szegmensek így kevesebb merger is fut. Ez persze azt is jelenti, hogy ha menet közben elcrashel az elasticsearch, akkor minden dokumentum elveszik ami még nincs mergelve.
Bookmark the permalink.

Szólj hozzá: