Zpět na domů

Speciální enginy ClickHouse: když MergeTree není potřeba

Článek popisuje speciální enginy ClickHouse pro úlohy, kde standardní MergeTree není optimální: Memory pro dočasná data a cache, Buffer pro bufferizaci vysokofrekvenčních vložení (ochrana před Too many parts), Null pro organizaci streamového zpracování pomocí Materialized Views, Log rodina pro malé referenční tabulky, URL/File/S3 pro externí data, PostgreSQL ENGINE pro live přístup. Je uveden architektonický vzor Kafka → Buffer → MergeTree pro 10k+ událostí za sekundu.

Speciální enginy ClickHouse: Memory, Buffer, Null a další
Advertisement 728x90

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:

Google AdInline article slot
  • 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í:

Google AdInline article slot
  • 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 TRUNCATE pro 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).

Google AdInline article slot

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:

  1. Vkládáš do bets_buffer (rychle, jen zápis do paměti).
  2. ClickHouse čeká, dokud se nashromáždí dostatek dat (např. 100k řádků nebo uplyne 30 sekund).
  3. Poté asynchronně (na pozadí) vyprázdní dávku do hlavní tabulky bets (MergeTree).
  4. Výsledkem je, že do bets př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_null je 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:

  1. Kafka Connect vkládá zprávy do bets_kafka (Null ENGINE).
  2. bets_kafka_mv (Materialized View) přesměrovává data do bets_buffer.
  3. bets_buffer hromadí v paměti dávky (např. 100k řádků nebo 60 sekund).
  4. Buffer se vyprázdní do bets (MergeTree) ve velkých dávkách.
  5. 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í:
Další: Materializovaná zobrazení v ClickHouse: síla inkrementálního zpracování

— Editorial Team

Advertisement 728x90

Číst dál