Adatréteg
ClickHouse
A ClickHouse nyílt forráskódú oszlopos OLAP adatbázis: MergeTree táblák, ritka elsődleges index, materializált nézetek és a dokumentált korlátok.
A ClickHouse nyílt forráskódú, oszloporientált SQL adatbázis-kezelő rendszer, amelyet online analitikai feldolgozásra (OLAP) terveztek. A sororientált tárak egy sor összes értékét egymás mellé írják, az oszlopos tárak viszont egy oszlop értékeit tárolják együtt, ezért egy szűrés vagy aggregáció csak az érintett oszlopokat olvassa. Az analitikai lekérdezések rutinszerűen milliárdos nagyságrendű soron futnak.
Nálunk a ClickHouse a Docker alatt futó analitikai tároló: ide érkeznek az események, a kattintások és a teljesítményadatok, a tranzakciós oldalt pedig a PostgreSQL szolgálja ki. A szétválasztás szándékos.
Az oszlopos tárolás és a ritka index
A MergeTree táblák adatai rendezett, változtathatatlan részekből (part) állnak, és minden rész az elsődleges kulcs szerint rendezett. Az elsődleges index ritka: nem soronként, hanem granulumonként tartalmaz egy bejegyzést, ahol egy granulum alapértelmezés szerint 8192 sor. Egy több milliárd soros tábla indexe így is elfér a memóriában.
- Az ORDER BY adja a részeken belüli rendezést és egyben az elsődleges kulcsot; a partíciókivágás kihagyja a felesleges partíciókat, de a particionálás önmagában nem gyorsítja a lekérdezést, és a dokumentáció a havi vagy durvább felbontást javasolja.
- A ritka index nem egyedi kulcs: ugyanaz az elsődleges kulcs többször is szerepelhet, és a pontszerű, egy soros keresés nem ennek az adatbázisnak az erőssége.
- A másodlagos, adatkihagyó indexek (minmax, set, bloom_filter) a nem rendezési kulcson futó szűrőkön segítenek.
- A tárolás oszloponként tömörít (alapértelmezésben LZ4), a végrehajtás pedig vektorizált: az operátorok blokkokat adnak tovább.
A MergeTree család
Az alapmotor a MergeTree, a dokumentáció szerint a leggyakrabban használt és legstabilabb táblamotor. Minden beszúrás új, változtathatatlan részt hoz létre, amely az elsődleges kulcs szerint rendezett; a részeket a háttérfolyamat folyamatosan nagyobbakká olvasztja össze, hasonlóan az LSM-fákhoz. A különböző partíciók részeit soha nem olvassa össze, és nem garantált, hogy egy kulcs minden sora ugyanabba a részbe kerül. A sorok feloldása is olvasztáskor történik, ezért léteznek motorváltozatok.
- ReplacingMergeTree: az azonos kulcsú sorok közül csak a legutóbbi marad meg.
- SummingMergeTree: az azonos kulcsú sorok numerikus oszlopai összeadódnak.
- AggregatingMergeTree: aggregációs függvények részleges állapotait tárolja, és olvasztáskor kombinálja őket.
- CollapsingMergeTree: egy előjeloszlop (+1 és -1) alapján kioltják egymást a sorok.
Mivel a feloldás olvasztáskor történik, egy egyszerű SELECT még láthatja az össze nem olvasztott duplikátumokat; pontos eredményhez a FINAL módosító vagy egy kifejezett GROUP BY kell.
SQL és materializált nézetek
A ClickHouse deklaratív, SQL alapú nyelvet használ, amely sok esetben megegyezik az ANSI SQL szabvánnyal; támogatott a GROUP BY, az ORDER BY, a JOIN, az IN operátor és az ablakfüggvények.
A materializált nézet itt nem pillanatképet tároló objektum: a dokumentáció szerint egy trigger, amely a beérkező adatblokkokon futtat egy lekérdezést, és az eredményt egy cél táblába írja. A nézet a beszúrással egy időben frissül, tehát folyamatosan karbantartott indexként viselkedik, és a számítás a beszúrás idejére tolódik. A cél tábla ORDER BY kifejezésének és a nézet GROUP BY kifejezésének összhangban kell lennie, különben az olvasztás nem tudja helyesen összevonni a részleges állapotokat.
Bevitel: kötegek, nem soronkénti beszúrás
Minden beszúrás legalább egy új részt hoz létre, ezért a soronkénti írás sok apró részt eredményez, és a háttérolvasztás nem tud lépést tartani vele; a dokumentáció ezt a Too many parts hiba tipikus okaként írja le. A javasolt minta a kötegelt beszúrás, például húszezer soronként.
Az aszinkron beszúrás (async_insert) átveszi a kötegélést a kliens helyett: a szerver memóriapufferbe írja a sorokat, és csak akkor menti lemezre, ha a puffer eléri a beállított méretet (alapértelmezésben 100 MiB) vagy az időkorlát lejár. A dokumentáció a wait_for_async_insert = 1 beállítást ajánlja, mert ekkor a nyugta csak a lemezre írás után érkezik.
Amit a dokumentáció korlátként leír
- Nem OLTP adatbázis: a dokumentáció a tranzakciós munkaterhelést a sororientált tárak felé irányítja, mert gyakori, egyetlen rekordra vonatkozó olvasásra és írásra nem való.
- Nincs teljes, több utasításra kiterjedő tranzakció. Egy partícióba, egy MergeTree táblába, egyetlen blokként csomagolt beszúrás ACID: vagy minden sor bekerül, vagy egy sem. A több utasításos tranzakció kísérleti funkció, Keeper telepítéssel és nem replikált MergeTree motorral; a Buffer táblába írás pedig sem atomos, sem izolált, sem tartós.
- A memória szűk keresztmetszet lehet: az aggregációk hash táblákat építenek a memóriában, a join a jobb oldali táblát tölti be, ezért nagy joinok és nagy számosságú GROUP BY esetén memóriahiány léphet fel.
- Az olvasztás beállítás kérdése: az inaktív részeket a rendszer egy beállítható idő után törli (alapértelmezésben 8 perc), a nagy olvasztások tömörített mérete pedig mintegy 150 GB-ig nő.
- A replikáció konzisztenciája külön kérdés: az ACID fogalmai nem fedik le az elosztott rendszerek konzisztencia-szemantikáját.
Tovább olvasás
- MergeTree táblamotor
- A ritka elsődleges index gyakorlati bemutatása
- Inkrementális materializált nézetek
A CyberElectrónál a ClickHouse a konténeres analitikai réteg: ide futnak be az események és a teljesítményadatok, a tranzakciós állapot pedig a PostgreSQL-ben marad, a feldolgozást az n8n ütemezi.