Speciální enginy ClickHouse: když MergeTree nestačí
Představ si, že potřebuješ rychle zpracovat dávku sázek — seskupit, spočítat mezisoučty a pak odeslat do hlavní tabulky. Nechceš zapisovat na disk, protože data jsou dočasná a potřebná jen po dobu běhu dotazu.
Memory ENGINE ukládá data zcela v operační paměti (RAM). Je to nejrychlejší engine — žádné diskové operace, žádná komprese, žádné indexy (kromě primárního klíče). Ale je tu cena: při restartu ClickHouse je tabulka prázdná. Data se NEUKLÁDAJÍ.
Kdy použít:
- Staging (dočasné) tabulky pro ETL procesy. Například jsi načetl milion sázek z Kafka, provedl deduplikaci a teprve poté vložil do hlavní MergeTree tabulky.
- Cache pro live odds — kurzy se mění každou sekundu, není třeba ukládat historii, potřebuješ jen aktuální snapshot.
- Malé číselníky (do 10–15 milionů záznamů), které se znovu vytvářejí při každém spuštění skriptu.
Příklad pro cache live kurzů:
-- Tabulka pro aktuální kurzy (žije v RAM)
CREATE TABLE live_odds_cache
(
event_id UInt64, -- ID události (zápas)
market_id UInt32, -- ID trhu sázení
selection_id UInt32, -- ID výsledku
odds Decimal(10,3), -- Kurz
updated_at DateTime
)
ENGINE = Memory()
ORDER BY (event_id, market_id, selection_id); -- ORDER BY je povinný, ale index není efektivní
Vkládáme data (např. ze streamu):
-- Přišla nová kotace, vkládáme
INSERT INTO live_odds_cache VALUES (100500, 10, 200, 1.85, now());
-- Čteme aktuální kurz pro sázku
SELECT odds FROM live_odds_cache
WHERE event_id = 100500 AND market_id = 10 AND selection_id = 200;
Úskalí:
- Tabulka Memory nepodporuje slučování (merge) — pokud děláš mnoho UPDATE (přes vložení se zrušením), paměť se bude nafukovat. Použij
TRUNCATEpro vyčištění. - Při restartu ClickHouse se data ztratí. Neukládej zde nic kritického.
- Velikost tabulky je omezena dostupnou RAM. Pokud tabulka naroste na 50 GB na serveru s 64 GB RAM — server spadne.
Analogicky: Memory ENGINE je jako bílá tabule. Rychle píšeš, rychle čteš, ale po uklízečce (restart) je tabule prázdná.
2. Buffer ENGINE — bufferování INSERT před zápisem
Máš 10 000 sázek za sekundu. Každá sázka je samostatný INSERT. Pokud budeš každou zapisovat rovnou do MergeTree tabulky, ClickHouse vytvoří tisíce malých dílků (parts), což zpomalí slučování na pozadí a sníží výkon.
Buffer ENGINE řeší tento problém: shromažďuje vložení do bufferu v paměti a zapisuje je do cílové tabulky ve velkých dávkách při splnění podmínek (podle počtu řádků, objemu nebo času).
Syntaxe s parametry:
CREATE TABLE bets_buffer AS bets -- kopíruje strukturu tabulky bets
ENGINE = Buffer(
'default', -- název databáze cílové tabulky
'bets', -- název cílové tabulky (tam budou data vyprázdněna)
16, -- počet paralelních vláken pro flush
10, -- minimální zpoždění v sekundách (min_time)
100, -- maximální zpoždění v sekundách (max_time)
10000, -- minimální počet řádků pro flush
1000000, -- maximální počet řádků pro flush
10000000, -- minimální velikost v bajtech pro flush
100000000 -- maximální velikost v bajtech pro flush
);
Parametry flush (vyprázdnění bufferu na disk):
| Parametr | Hodnota | Význam |
|---|---|---|
| min_time | 10 s | Nevyprázdňovat dříve než za 10 sekund |
| max_time | 100 s | Vyprázdnit nejpozději do 100 sekund |
| min_rows | 10 000 | Pokud se nashromáždilo 10k řádků — lze vyprázdnit |
| max_rows | 1 000 000 | Pokud se nashromáždil 1 mil. řádků — urychleně vyprázdnit |
| min_bytes | 10 MB | Pokud se nashromáždilo 10 MB — lze vyprázdnit |
| max_bytes | 100 MB | Pokud se nashromáždilo 100 MB — urychleně vyprázdnit |
Jak to funguje v praxi:
- Vkládáš do
bets_buffer(rychle, jen zápis do paměti). - ClickHouse čeká, dokud se nashromáždí dostatek dat (např. 100k řádků nebo uplyne 30 sekund).
- Poté asynchronně (na pozadí) vyprázdní dávku do hlavní tabulky
bets(MergeTree). - Výsledkem je, že do
betspřicházejí velké dílky (100k řádků), což urychluje slučování na pozadí.
Proč nevkládat rovnou do MergeTree? Každý INSERT do MergeTree vytvoří mini-dílek (part). Pokud děláš 10 000 INSERT za sekundu, za minutu bude 600 000 dílků. Slučování na pozadí je nestíhá spojovat. ClickHouse začne stěžovat na Too many parts, vkládání se zpomalí.
Úskalí: Při restartu ClickHouse se buffer ztratí. Data, která se ještě nevyprázdnila do bets, zmizí. Proto používej Buffer ENGINE, jen pokud ztráta několika sekund dat není kritická (např. pro analytiku, ne pro zůstatky).
3. Null ENGINE — černá díra pro data
Null ENGINE data jednoduše pohltí. Nikam se nezapisují, neukládají, neindexují. Ale je tu trik: pokud má tabulka s Null ENGINE materializované pohledy (Materialized Views), pohledy data obdrží a zpracují je.
Vzor: Kafka → Null + Materialized View → MergeTree
Toto je klasická architektura pro vysoce zatížená vkládání z Kafka.
-- Krok 1: Přijímací tabulka (černá díra)
CREATE TABLE bets_null
(
user_id UInt64,
amount Decimal(18,2),
created_at DateTime
)
ENGINE = Null; -- nic neukládá
-- Krok 2: Cílová tabulka (tam skutečně ukládáme)
CREATE TABLE bets
(
user_id UInt64,
amount Decimal(18,2),
created_at DateTime
)
ENGINE = MergeTree()
ORDER BY (created_at, user_id);
-- Krok 3: Materializovaný pohled (most)
CREATE MATERIALIZED VIEW bets_mv TO bets AS
SELECT * FROM bets_null; -- všechna data, která se dostanou do bets_null, putují do bets
Co se nyní děje:
-- Klient (nebo Kafka consumer) vkládá do bets_null
INSERT INTO bets_null VALUES (123, 100.00, now()); -- okamžitě
-- Data procházejí přes Materialized View a ukládají se do bets
-- Samotná data se v bets_null neukládají
Proč to dělat?
bets_nullje velmi lehká tabulka, nevytváří soubory na disku.- Všichni odběratelé (Materialized Views) dostávají data současně.
- Na jednu Null tabulku lze navěsit několik Materialized Views: jeden pro surová data do MergeTree, druhý pro agregace do AggregatingMergeTree, třetí pro deduplikaci do ReplacingMergeTree.
Analogicky: Null ENGINE je jako poštovní schránka s děravým dnem. Dopisy do ní padají, ale nezdržují se. Zato všichni tví sekretáři (Materialized Views) je stihnou přečíst a přepsat do svých složek.
4. Log/TinyLog/StripeLog — jednoduché enginy pro malá data
Toto je rodina enginů pro malé tabulky (do 1–2 milionů řádků), kde není potřeba vysoký výkon a indexy.
| Engine | Vlastnosti | Kdy použít |
|---|---|---|
TinyLog |
Jeden soubor na sloupec | Velmi malé tabulky (<100k řádků), staging |
Log |
Každý sloupec v samostatném souboru, má značku (marker) pro paralelní čtení | Tabulky do 1 mil. řádků, potřeba rychlého čtení |
StripeLog |
Všechny sloupce v jednom souboru (kompaktně) | Úspora místa, vzácné čtení |
Příklad pro číselník lig (200 záznamů):
-- Číselník fotbalových lig (mění se jednou za měsíc)
CREATE TABLE leagues_ref
(
league_id UInt32,
name String,
country String,
updated_at Date
)
ENGINE = TinyLog(); -- maximálně jednoduché, žádný ORDER BY
Proč ne MergeTree? MergeTree vytváří indexy, partition, kompresi — to je pro 200 řádků zbytečné. TinyLog zabírá méně místa a je jednodušší na údržbu.
Úskalí: Tyto enginy nepodporují ALTER DELETE a ALTER UPDATE. Pokud potřebuješ změnit data — budeš muset tabulku znovu vytvořit.
5. URL ENGINE — tabulka jako HTTP endpoint
URL ENGINE umožňuje číst data přímo z HTTP zdroje (API) a dokonce do něj vkládat pomocí PUT.
CREATE TABLE currency_rates_url
(
base String,
rate Decimal(10,4),
date Date
)
ENGINE = URL('https://api.exchangerate.com/latest?base=USD', CSV)
SETTINGS
method = 'GET',
format = 'CSV',
headers = 'Authorization: Bearer token123';
Použití:
-- Čteme aktuální kurzy přímo z API
SELECT * FROM currency_rates_url;
Reálný scénář: Malá analytická úloha, kde se nechce nasazovat ETL. Například jednou za hodinu čteš kurzy měn z bezplatného API, joinuješ se sázkami a přepočítáváš částky.
Úskalí:
- Žádné indexy, každý dotaz je plné skenování zdroje.
- Pokud API vrátí chybu, dotaz spadne.
- Není vhodné pro vysoce zatížené dotazy (předpokládá se, že data jsou cacheována uvnitř ClickHouse, ne čtena z API pokaždé).
6. File ENGINE — tabulka jako soubor na disku
Umožňuje číst a zapisovat soubory v lokálním souborovém systému serveru ClickHouse. Podporuje formáty CSV, TSV, JSONEachRow, Parquet.
-- Tabulka, která čte CSV soubor
CREATE TABLE imported_players
(
user_id UInt64,
username String
)
ENGINE = File(CSV, '/var/lib/clickhouse/user_files/players.csv');
Kdy použít:
- Načítání dat ze souborů (administrátor vložil CSV s novými uživateli).
- Export výsledků dotazu do souboru pomocí
INSERT INTO ... SELECT.
Úskalí: ClickHouse musí mít přístup ke složce (obvykle je to /var/lib/clickhouse/user_files/ z bezpečnostních důvodů).
7. S3 ENGINE — přímé dotazy do S3
Čte data přímo z bucketu Amazon S3 (nebo MinIO, Yandex Object Storage). Nekopíruje data do ClickHouse.
CREATE TABLE logs_s3
(
timestamp DateTime,
message String
)
ENGINE = S3(
'https://mybucket.s3.amazonaws.com/logs/*.parquet',
'AWS_ACCESS_KEY', 'AWS_SECRET_KEY',
'Parquet'
);
Kdy použít:
- Máš terabajty logů v S3 a chceš provádět vzácné analytické dotazy bez kopírování do ClickHouse.
- Chladná data (S3 je levnější než disky ClickHouse).
Úskalí: Každý dotaz stahuje data z S3, což může být pomalé a drahé (za odchozí provoz). Vhodné pouze pro nečasté dotazy.
8. PostgreSQL ENGINE — live data z PostgreSQL
PostgreSQL ENGINE umožňuje číst a zapisovat do tabulek PostgreSQL, jako by to byly tabulky ClickHouse.
CREATE TABLE pg_players
(
user_id UInt64,
balance Decimal(18,2)
)
ENGINE = PostgreSQL(
'postgres-host:5432', -- hostitel a port
'betting', -- databáze
'players', -- tabulka v PostgreSQL
'clickhouse_user', -- uživatel
'password' -- heslo
);
Použití:
-- Čteme aktuální zůstatky z PostgreSQL
SELECT * FROM pg_players WHERE user_id = 123;
-- Dokonce můžeme dělat JOIN s tabulkami ClickHouse
SELECT b.user_id, b.amount, p.balance
FROM bets b
JOIN pg_players p ON b.user_id = p.user_id;
Kdy použít:
- Migruješ z PostgreSQL na ClickHouse postupně a některá data stále žijí ve staré DB.
- Potřebuješ live data, která jsou aktualizována externí aplikací, a nechceš dělat ETL.
Úskalí:
- Každý dotaz jde do PostgreSQL, což může být pomalé pro velké objemy.
- ClickHouse nemůže sestavit efektivní plán provedení s takovou tabulkou (No pushdown).
9. Architektonický vzor pro betting: Kafka → Buffer → MergeTree
Nyní to dáme všechno dohromady. Představ si, že od každé události sázky dostáváš 10 000+ zpráv za sekundu z Kafka. Potřebuješ je uložit do ClickHouse s minimálním zpožděním a bez vytváření tisíců malých dílků.
Hotová architektura:
-- 1. Přijímací tabulka (Null) pro Kafka
CREATE TABLE bets_kafka
(
user_id UInt64,
event_id UInt64,
amount Decimal(18,2),
bet_time DateTime
)
ENGINE = Null;
-- 2. Cílová MergeTree tabulka
CREATE TABLE bets
(
user_id UInt64,
event_id UInt64,
amount Decimal(18,2),
bet_time DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(bet_time)
ORDER BY (bet_time, user_id);
-- 3. Buffer tabulka pro vyhlazení vkládání
CREATE TABLE bets_buffer AS bets
ENGINE = Buffer('default', 'bets', 16, 5, 60, 10000, 1000000, 10000000, 100000000);
-- 4. Materialized View: Kafka → Null → Buffer (přes pohled)
CREATE MATERIALIZED VIEW bets_kafka_mv TO bets_buffer AS
SELECT * FROM bets_kafka;
-- 5. Další pohled pro agregace v reálném čase (volitelné)
CREATE MATERIALIZED VIEW bets_stats_mv TO bets_hourly_agg AS
SELECT
toStartOfHour(bet_time) AS hour,
countState() AS bet_count,
sumState(amount) AS total_amount
FROM bets_kafka
GROUP BY hour;
Tok dat:
- Kafka Connect vkládá zprávy do
bets_kafka(Null ENGINE). bets_kafka_mv(Materialized View) přesměrovává data dobets_buffer.bets_bufferhromadí v paměti dávky (např. 100k řádků nebo 60 sekund).- Buffer se vyprázdní do
bets(MergeTree) ve velkých dávkách. - Paralelně druhý Materialized View vytváří hodinové agregace pro dashboardy.
Proč je to optimální:
- Kafka zapisuje do Null (okamžitě, bez režie).
- Buffer zabraňuje množení tisíců malých dílků.
- MergeTree dostává velké dílky, merge funguje efektivně.
- Agregace se vytvářejí v reálném čase přes druhý pohled.
Co se stane, když budeš zapisovat přímo do MergeTree z Kafka: Při 10k events/sec se za minutu vytvoří 600k dílků. ClickHouse spadne s chybou Too many parts. Buffer ENGINE tomu zabrání.
Kdy který engine vybrat — tahák
| Úloha | Engine | Proč |
|---|---|---|
| Trvalé uložení, analytika | MergeTree (nebo *MergeTree) | Základ ClickHouse, indexy, komprese |
| Bufferování vysokého zatížení | Buffer | Slepení malých INSERT do velkých dávek |
| Dočasná data (session, staging) | Memory | Maximální rychlost, data nejsou kritická |
| Kafka konzument bez ukládání | Null + MV | Data jen pro pohledy |
| Malý statický číselník | TinyLog / Log | Jednoduchost, méně metadat |
| Připojení k PostgreSQL | PostgreSQL ENGINE | Živá data bez ETL |
| Vzácné dotazy do S3 | S3 ENGINE | Levné chladné úložiště |
| Import ze souborů | File ENGINE | Jednorázové kopírování |
Co dál
Seznámil jsi se se speciálními enginy, které řeší úlohy, na něž běžný MergeTree nestačí. Nyní víš:
- Memory pro cache a staging,
- Buffer pro ochranu před příliš častými INSERT,
- Null pro "rozvětvení" dat do více toků přes MV,
- URL / File / S3 / PostgreSQL pro externí data.
Další témata k prohloubení:
- Jak nastavit Kafka Engine — vestavěný konektor do Kafka bez samostatného konektoru.
- Pokročilé Materialized Views — řetězce pohledů pro složité ETL.
- Distribuované tabulky — jak shardovat data mezi servery.
Závěr: Ne všechny úlohy v ClickHouse se řeší MergeTree. Někdy je potřeba Buffer, aby se nezabil server vkládáním, někdy Null + MV, aby se data rozeslala do různých agregací, a někdy Memory pro dočasné hashování. Hlavní pravidlo: nejprve navrhni tok dat, pak vyber engine, ne naopak.
← Předchozí: Slovníky v ClickHouse: rychlý lookup bez JOIN
→ Další: Materializovaná zobrazení v ClickHouse: síla inkrementálního zpracování
— Editorial Team
Zatím žádné komentáře.