ClickHouse անալիտիկայի համար
Սյունակային բազա, որը միլիարդավոր իրադարձություններ ագրեգացնում է վայրկյանների ընթացքում։
30 վայրկյանում
ClickHouse-ը open-source սյունակային բազա է՝ ստեղծված անալիտիկայի (OLAP) համար։ Այն յուրաքանչյուր սյունակ պահում է առանձին և սեղմված, այնպես որ միլիարդավոր տողերի վրա մեկ սյունակ գումարող հարցումը կարդում է միայն այդ սյունակը։ Այն իդեալական է իրադարձությունների տվյալների, լոգերի, մետրիկների և product analytics dashboard-ների համար։ Այն թողարկվում է ամսական, տարեկան երկու LTS թողարկումով (օրինակ՝ 26.3 LTS-ը 2026-ի մարտին), սեփական սերվերի վրա կամ որպես ClickHouse Cloud։
Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։
Ինչու է սա կարևոր
- Անալիտիկ հարցումները սկանավորում են հսկայական միջակայքեր և ագրեգացնում մի քանի սյունակ։ MySQL-ի կամ PostgreSQL-ի նման տողային բազան դրա համար կարդում է յուրաքանչյուր ամբողջական տող, և dashboard-ները կանգ են առնում, երբ իրադարձությունները հասնում են հարյուրավոր միլիոնների։
- ClickHouse-ը հաճախ իրադարձությունների տվյալները սեղմում է 5–20 անգամ և մեկ սերվերի վրա վայրկյանում մշակում միլիարդավոր տողեր, ինչը real-time dashboard-ները մատչելի է դարձնում։
- Թիմերն այն ընտրում են հաշվետվությունները տրանզակցիոն բազայից տեղափոխելու համար, որպեսզի ծանր հաշվետվությունները դադարեն դանդաղեցնել checkout-ը կամ API-ն։
- Այն ուժեղ սեփական կամ կառավարվող այլընտրանք է երրորդ կողմի անալիտիկային յուրաքանչյուր իրադարձության համար վճարելուն՝ լիարժեք SQL-ով և տվյալների լիարժեք սեփականությամբ։
Ինչպես է աշխատում
01Գաղափարը՝ սյունակներ, ոչ թե տողեր
Պատկերացրեք մեկ միլիարդ page view-ի աղյուսակ։ «Դիտումներ ըստ երկրի անցյալ շաբաթ» հարցին պատասխանելու համար տողային պահոցը պետք է կարդա յուրաքանչյուր տող՝ իր բոլոր քսան սյունակներով։ Սյունակային պահոցը յուրաքանչյուր սյունակ պահում է իր ֆայլում․ այն կարդում է միայն ամսաթիվը և երկիրը, ինչը տվյալների չնչին մասն է։ Մեկ սյունակի արժեքները նման են (շատ կրկնվող երկրներ), այնպես որ դրանք չափազանց լավ են սեղմվում, և CPU-ն կարող է դրանք մշակել batch-երով՝ vectorized հրահանգներով։ Փոխզիջումը՝ մեկ ամբողջական տող բերելը կամ թարմացնելը ավելի դանդաղ է, և ClickHouse-ը նախատեսված չէ բազմաթիվ փոքր տրանզակցիոն update-ների համար։ OLTP-ն (պատվերներ, օգտատերեր) մնում է ձեր ռելյացիոն բազայում, OLAP-ը (պատմության մասին հարցեր) գալիս է այստեղ։
- OLTP՝ առանձին գրառումների բազմաթիվ փոքր ընթերցումներ և գրառումներ՝ տրանզակցիաներով։
- OLAP՝ քիչ, բայց մեծ հարցումներ, որոնք սկանավորում և ագրեգացնում են բազմաթիվ գրառումներ։
- Vectorized execution՝ մեկ CPU գործողությամբ արժեքների բլոկի մշակում՝ մեկ առ մեկի փոխարեն։
02MergeTree աղյուսակներ և sorting key-ը
Գրեթե յուրաքանչյուր աղյուսակ օգտագործում է MergeTree ընտանիքի engine։ Յուրաքանչյուր insert գրում է անփոփոխ part՝ դասավորված ըստ ORDER BY key-ի, և ֆոնային merge-երը փոքր part-երը միավորում են ավելի մեծերի։ Primary index-ը sparse է․ այն պահում է մեկ գրառում յուրաքանչյուր granule-ի համար (լռելյայն 8,192 տող), այնպես որ բավականաչափ փոքր է հիշողության մեջ մնալու համար։ Երբ հարցումը ֆիլտրում է ORDER BY-ի առաջատար սյունակներով, ClickHouse-ը բաց է թողնում այն granule-ները, որոնք չեն կարող համընկնել։ Հետևաբար ORDER BY-ի ընտրությունը դիզայնի ամենակարևոր որոշումն է․ առաջինը դրեք ցածր cardinality ունեցող սյունակները, որոնցով միշտ ֆիլտրում եք (tenant, իրադարձության տիպ), ապա ժամանակը։ PARTITION BY-ը տվյալների կառավարման համար է (հին ամիսները ջնջելու), ոչ թե արագության․ partition-ները պահեք խոշոր։
CREATE TABLE events
(
tenant_id UInt32,
event_type LowCardinality(String),
event_time DateTime64(3, 'UTC'),
user_id UInt64,
url String,
country LowCardinality(String),
properties JSON
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (tenant_id, event_type, event_time)
TTL toDateTime(event_time) + INTERVAL 13 MONTH DELETE;
03Տվյալների ճիշտ insert
ClickHouse-ը սիրում է մեծ batch-եր և ատում է փոքր insert-ները։ Յուրաքանչյուր INSERT ստեղծում է առնվազն մեկ part, և վայրկյանում հազարավոր մեկ տողանոց insert-ները ստեղծում են հազարավոր part-եր, ինչը հրահրում է հայտնի «too many parts» սխալը։ Batch արեք client-ի կողմում (յուրաքանչյուր insert-ում հազարավորից մինչև հարյուր հազարավոր տող, մոտավորապես վայրկյանը մեկ անգամ) կամ միացրեք asynchronous insert-ները, որպեսզի սերվերը ձեր փոխարեն buffer անի փոքր գրառումները։ Տվյալները սովորաբար գալիս են հերթի միջոցով (Kafka, Redis-ի վրա հիմնված worker, HTTP collector-ներ) և flush են արվում batch-երով։ JSONEachRow, CSV, Parquet և native ձևաչափերն աջակցվում են HTTP-ով (8123 պորտ) կամ native պրոտոկոլով (9000)։
# Batch insert over HTTP from a file of JSON lines
curl -u your_user:your_password \
'http://localhost:8123/?query=INSERT%20INTO%20events%20FORMAT%20JSONEachRow' \
--data-binary @events.ndjson
-- Or let the server batch small inserts
INSERT INTO events SETTINGS async_insert = 1, wait_for_async_insert = 1
FORMAT JSONEachRow {"tenant_id":1,"event_type":"page_view","event_time":"2026-11-05 10:00:00","user_id":7,"url":"/","country":"AM","properties":{}}
04Անալիտիկ SQL
ClickHouse-ը խոսում է SQL՝ բազմաթիվ անալիտիկ հավելումներով։ uniq()-ը և uniqExact()-ը հաշվում են եզակի օգտատերերին (uniq-ը մոտավոր է և շատ ավելի արագ), quantile()-ը հաշվում է պերցենտիլներ, countIf()-ը և sumIf()-ը ֆիլտրում են ագրեգատների ներսում, իսկ windowFunnel()-ը և retention()-ը ուղղակիորեն պատասխանում են պրոդուկտային հարցերին։ Զանգվածներն ու map-երը առաջին կարգի են, և JSON տիպը կիսակառուցվածքային հատկությունները պահում է որպես տիպավորված sub-column-ներ։ Միշտ ֆիլտրեք ORDER BY-ի առաջատար սյունակներով և ժամանակային միջակայքով, ընտրեք միայն ձեզ անհրաժեշտ սյունակները (երբեք SELECT * լայն աղյուսակի վրա) և ուսումնասիրելիս օգտագործեք LIMIT։
SELECT
toStartOfDay(event_time) AS day,
uniq(user_id) AS visitors,
countIf(event_type = 'purchase') AS purchases,
round(purchases / visitors * 100, 2) AS conversion_pct
FROM events
WHERE tenant_id = 1
AND event_time >= now() - INTERVAL 30 DAY
GROUP BY day
ORDER BY day;
05Materialized view-եր արագ dashboard-ների համար
ClickHouse-ում materialized view-ը insert trigger է․ երբ տողերը ժամանում են աղբյուր աղյուսակ, view-ն իր SELECT-ն աշխատեցնում է միայն այդ նոր batch-ի վրա և արդյունքը գրում թիրախային աղյուսակ։ SummingMergeTree-ի կամ AggregatingMergeTree-ի հետ համատեղ սա նախապես ագրեգացված rollup-ները (ըստ ժամի, ըստ tenant-ի) միշտ արդի է պահում, այնպես որ dashboard-ները կարդում են հազարավոր տողեր՝ միլիարդների փոխարեն։ Գրելիս օգտագործեք -State combinator-ները, իսկ կարդալիս՝ -Merge, քանի որ տարբեր part-երի մասնակի ագրեգատները պետք է միավորվեն։ Հիշեք, որ view-ն տեսնում է միայն նոր insert-ները․ պատմական տվյալները backfill արեք առանձին INSERT INTO ... SELECT-ով։
CREATE TABLE events_hourly
(
tenant_id UInt32, hour DateTime, event_type LowCardinality(String),
events UInt64, users AggregateFunction(uniq, UInt64)
)
ENGINE = AggregatingMergeTree ORDER BY (tenant_id, event_type, hour);
CREATE MATERIALIZED VIEW events_hourly_mv TO events_hourly AS
SELECT tenant_id, toStartOfHour(event_time) AS hour, event_type,
count() AS events, uniqState(user_id) AS users
FROM events GROUP BY tenant_id, hour, event_type;
-- Read: SELECT hour, sum(events), uniqMerge(users) FROM events_hourly WHERE tenant_id = 1 GROUP BY hour;
06Շահագործում՝ մեկ node, կլաստեր կամ ամպ
Մեկ ClickHouse սերվերը կարողանում է շատ ավելին, քան թիմերի մեծ մասն է սպասում, հաճախ միլիարդավոր տողեր մեկ մեքենայի վրա՝ արագ NVMe սկավառակներով։ Բարձր հասանելիության համար օգտագործեք ReplicatedMergeTree աղյուսակներ՝ համակարգված ClickHouse Keeper-ի կողմից (ZooKeeper-ի ներկառուցված փոխարինիչը)․ ավելի շատ տարողության համար տվյալները shard արեք node-երի միջև Distributed աղյուսակով։ ClickHouse Cloud-ը տարանջատում է պահոցը (object storage) հաշվարկից և հեռացնում է այս աշխատանքի մեծ մասը՝ օգտագործման վրա հիմնված գնով։ Ինչ էլ ընտրեք՝ ստեղծեք նվազագույն արտոնություններով օգտատերեր, 8123 և 9000 պորտերը պահեք հանրային ինտերնետից դուրս, յուրաքանչյուր օգտատիրոջ համար սահմանեք հարցումների սահմաններ (max_memory_usage, max_execution_time) և backup արեք BACKUP TO S3-ով կամ ձեր provider-ի snapshot-ներով։
Օգտագործեք, երբ
- Product analytics, clickstream-ներ և իրադարձությունների tracking՝ հարյուրավոր միլիոններից մինչև տրիլիոնավոր տողերով։
- Լոգեր, մետրիկներ և trace-եր, որտեղ ֆիլտրում եք ըստ ժամանակի և արագ ագրեգացնում։
- Հաճախորդների համար dashboard-ներ, որոնք պետք է ագրեգատային հարցումներին պատասխանեն մեկ վայրկյանից պակաս ժամանակում։
- Ծանր հաշվետվությունների տեղափոխում տրանզակցիոն բազայից։
Մի՛ օգտագործեք, երբ
- Որպես հավելվածի հիմնական բազա՝ հաճախակի մեկ տողանոց update-ներով, delete-ներով և տրանզակցիաներով։
- Փոքր dataset-եր (մի քանի միլիոն տող), որտեղ լավ ինդեքսներով PostgreSQL-ն արդեն պատասխանում է միլիվայրկյաններում։
- Ծանրաբեռնվածություններ, որոնց պետք են խիստ foreign key-եր և constraint-ներ էնտիտիների միջև։
Այլընտրանքներ
- PostgreSQL (plus TimescaleDB or columnar extensions)
Պահպանում է մեկ բազա․ լավ է մինչև չափավոր ծավալներ, ավելի դանդաղ՝ մեծ սկանավորումների համար։
- BigQuery / Snowflake
Ամբողջովին կառավարվող warehouse-ներ, հիանալի են batch անալիտիկայի համար․ վճարում եք ըստ հարցման կամ հաշվարկի, ավելի բարձր latency live dashboard-ների համար։
- Apache Druid / Apache Pinot
Real-time OLAP պահոցներ՝ նախատեսված streaming ingestion-ի և օգտատիրոջ համար անալիտիկայի համար։
- DuckDB
Ներկառուցվող սյունակային engine՝ տեղային կամ մեկ պրոցեսի անալիտիկայի համար Parquet-ի նման ֆայլերի վրա։
Լավագույն պրակտիկաներ
- ORDER BY-ը նախագծեք ձեր ամենատարածված ֆիլտրերից․ նախ ցածր cardinality ունեցող սյունակներ, ապա ժամանակը։
- Insert արեք մեծ batch-երով կամ միացրեք async insert-ները․ երբեք մի stream արեք մեկ տողանոց insert-ներ։
- Կրկնվող արժեքների համար օգտագործեք LowCardinality(String) և ամենափոքր թվային տիպերը, որոնք տեղավորում են արժեքները։
- Partition-ները պահեք խոշոր (սովորաբար ըստ ամսի) և օգտագործեք TTL՝ հին տվյալներն ավտոմատ ջնջելու կամ տեղափոխելու համար։
- Dashboard-ի հարցումները նախապես ագրեգացրեք materialized view-երով AggregatingMergeTree աղյուսակներում։
- Կլաստերը պաշտպանելու համար յուրաքանչյուր օգտատիրոջ համար սահմանեք հիշողության, կատարման ժամանակի և արդյունքի չափի սահմաններ։
- Բիզնես էնտիտիների ճշմարտության աղբյուրը պահեք ձեր OLTP բազայում և սինքրոնացրեք ClickHouse-ի հետ։
Տարածված սխալներ
- Partition անել ըստ օրվա կամ ըստ օգտատիրոջ, ինչը ստեղծում է հազարավոր փոքր partition-ներ և չափազանց շատ part-եր։
- Հաճախակի ALTER TABLE ... UPDATE կամ DELETE mutation-ներ օգտագործել, ասես MySQL լինի․ դրանք վերագրում են ամբողջ part-եր։
- Սպասել, որ materialized view-ը կներառի պատմական տվյալներ, որոնք գոյություն են ունեցել մինչև դրա ստեղծումը։
- ORDER BY-ում առաջինը դնել բարձր cardinality ունեցող սյունակ (ինչպես user_id-ն կամ UUID-ն), ինչը չեզոքացնում է ինդեքսով բաց թողնելը։
- HTTP պորտը հրապարակայնորեն բացել լռելյայն օգտատիրոջով և առանց գաղտնաբառի։
Խորը վերլուծություն
07Part-եր, granule-ներ և ինչպես է հարցումը բաց թողնում տվյալները
Սկավառակի վրա յուրաքանչյուր part թղթապանակ է՝ յուրաքանչյուր սյունակի համար մեկ ֆայլով (wide format), գումարած primary.idx և mark ֆայլեր։ Mark-երը մատնացույց են անում յուրաքանչյուր granule-ի սկիզբը սեղմված սյունակային ֆայլերի ներսում։ Հարցման ժամանակ ClickHouse-ը primary.idx-ով binary search է անում այն granule-ների միջակայքը, որոնց ORDER BY արժեքները կարող են համընկնել, կարդում է միայն այդ granule-ների mark-երը և ապասեղմում միայն պահանջված սյունակների այդ բլոկները։ Data-skipping ինդեքսները (minmax, set, bloom_filter) ավելացնում են granule-ային ամփոփումներ sorting key-ից դուրս սյունակների համար։ Օգտագործեք EXPLAIN indexes = 1՝ տեսնելու, թե ընդհանուրից քանի part և granule է ընտրվել․ granule-ների 95%-ն ընտրող ֆիլտրը քողարկված full scan է։
EXPLAIN indexes = 1
SELECT count() FROM events
WHERE tenant_id = 1 AND event_time >= now() - INTERVAL 1 DAY;
-- A skipping index for a column outside ORDER BY
ALTER TABLE events ADD INDEX idx_url url TYPE bloom_filter GRANULARITY 4;
ALTER TABLE events MATERIALIZE INDEX idx_url;
08Դեդուպլիկացիա և փոփոխվող տվյալներ՝ ReplacingMergeTree և ընկերներ
ClickHouse-ը չի պարտադրում եզակի key-եր։ ReplacingMergeTree-ն պահում է վերջին տողը յուրաքանչյուր ORDER BY key-ի համար՝ ընտրված version սյունակով, բայց միայն merge-երից հետո, որոնք տեղի են ունենում անհայտ ժամանակ։ Ճշգրիտ արդյունքներ պահանջող հարցումներն օգտագործում են FINAL (որը merge է անում ընթերցման ժամանակ․ վերջին տարբերակներում այն շատ ավելի արագ է, բայց դեռ արժե) կամ ագրեգացնում են argMax(value, version)-ով։ CollapsingMergeTree-ն և VersionedCollapsingMergeTree-ն update-ները մշակում են sign -1-ով չեղարկող տող insert անելով։ Replicated աղյուսակներում insert-ները որոշակի պատուհանի համար դեդուպլիկացվում են ըստ բլոկի hash-ի, այնպես որ timeout-ից հետո նույն batch-ը կրկնելն անվտանգ է, քանի դեռ batch-ը նույնական է։
CREATE TABLE users_dim
( user_id UInt64, plan LowCardinality(String), updated_at DateTime64(3) )
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;
SELECT plan, count() FROM users_dim FINAL GROUP BY plan;
09Join-եր, dictionary-ներ և projection-ներ
ClickHouse-ը կատարում է join-եր, և նրա join planner-ը վերջին թողարկումներում շատ է բարելավվել, բայց լռելյայն hash join-ը դեռ աջ աղյուսակը կառուցում է հիշողության մեջ, այնպես որ փոքր աղյուսակը պահեք աջում և երկու կողմերն էլ ֆիլտրեք join-ից առաջ։ Դանդաղ փոփոխվող փոքր dimension աղյուսակների համար (երկրներ, պլաններ, ապրանքների անուններ) dictionary-ներն ավելի լավն են․ դրանք բեռնվում են հիշողության մեջ և հարցվում dictGet()-ով, հաճախ շատ ավելի արագ, քան join-ը։ Projection-ները նույն part-երի ներսում պահում են աղյուսակի լրացուցիչ, այլ կերպ դասավորված կամ նախապես ագրեգացված պատճեն, և optimizer-ը ավտոմատ օգտագործում է դրանք, երբ հարցումը համապատասխանում է։ Դրանք արժեն սկավառակ և insert-ի ժամանակ, այնպես որ ավելացրեք դրանք միայն ապացուցված hot հարցումների համար։
10Հորիզոնական մասշտաբավորում և կլաստերի պաշտպանություն
Նախ մասշտաբավորեք ուղղահայաց․ ավելի շատ core-եր և արագ NVMe տալիս են գրեթե գծային աճ։ Երբ ավելին է պետք, shard արեք tenancy-ին համապատասխանող key-ով (օրինակ՝ cityHash64(tenant_id)), որպեսզի հարցումների մեծ մասը դիպչի մեկ shard-ի, և յուրաքանչյուր shard ռեպլիկացրեք ReplicatedMergeTree-ով և ClickHouse Keeper-ով (երեք Keeper node՝ quorum-ի համար)։ Distributed աղյուսակը հարցումները տարածում է և միավորում արդյունքները։ Multi-tenant անալիտիկայի համար պաշտպանեք կլաստերը settings profile-ներով և quota-ներով՝ max_memory_usage, max_execution_time, max_rows_to_read և max_concurrent_queries յուրաքանչյուր օգտատիրոջ համար, գումարած row policy-ներ, որպեսզի յուրաքանչյուր tenant-ի օգտատեր տեսնի միայն իր տողերը։ Մոնիտորինգ արեք system.query_log-ը, system.parts-ը (part-եր ըստ partition-ի) և system.merges-ը, և ալերտ դրեք part-երի քանակի և replication lag-ի վրա։
Իրական իրավիճակներ
«Too many parts» սխալներ թրաֆիկի կտրուկ աճի ժամանակ
Խնդիր
Tracking ծառայությունը յուրաքանչյուր իրադարձություն insert է անում ClickHouse-ում ժամանման պահին։ Մարքեթինգային արշավի ժամանակ insert-ները սկսում են ձախողվել «Too many parts»-ով, և dashboard-ները ուշանում են։
Ինչպես լուծել
Յուրաքանչյուր մեկ տողանոց insert ստեղծում է part, և merge-երը չեն հասցնում։ Կարճաժամկետ՝ tracking օգտատիրոջ համար միացրեք async_insert = 1 և wait_for_async_insert = 1, որպեսզի սերվերը batch անի գրառումները։ Ճիշտ լուծումը՝ առջևում դրեք buffer․ tracking endpoint-ը գրում է հերթ, և worker-ը ամեն մեկ-երկու վայրկյանը մեկ insert է անում 10,000–100,000 տողանոց batch-եր։ Ստուգեք աղյուսակի PARTITION BY-ը․ օրական կամ ըստ tenant-ի partition-ները բազմապատկում են part-երը, այնպես որ անցեք ամսականի։ Հետևեք system.parts-ին՝ ակտիվ part-եր ըստ partition-ի, և ալերտ դրեք մինչև շեմերին հասնելը։ Կտրուկ աճից հետո OPTIMIZE պետք չէ․ merge-երն ինքնուրույն կհասնեն։
Dashboard-ի հարցումները դանդաղում են տվյալների աճի հետ
Խնդիր
Հաճախորդի dashboard-ը, որը ցույց է տալիս daily active user-ներն ըստ tenant-ի, գործարկման ժամանակ տևում էր 200 ms, իսկ այժմ երեք միլիարդ իրադարձությամբ տևում է 6 վայրկյան։
Ինչպես լուծել
Ստուգեք EXPLAIN indexes = 1-ը․ եթե ORDER BY-ը սկսվում է event_time-ով, յուրաքանչյուր tenant-ի հարցումը սկանավորում է բոլոր tenant-ներին։ Վերակառուցեք աղյուսակը ORDER BY (tenant_id, event_type, event_time)-ով՝ օգտագործելով INSERT INTO new SELECT FROM old և ատոմար EXCHANGE TABLES։ Ապա ավելացրեք materialized view դեպի AggregatingMergeTree աղյուսակ ժամային granularity-ով՝ uniqState(user_id)-ով, backfill արեք պատմությունը և dashboard-ը ուղղեք rollup-ին uniqMerge-ով։ Dashboard-ն այժմ կարդում է հազարավոր նախապես ագրեգացված տողեր՝ միլիարդավոր raw իրադարձությունների փոխարեն, իսկ raw տվյալները մնում են հասանելի drill-down-ի համար։
GDPR ջնջման հարցում append-only պահոցում
Խնդիր
Օգտատերը խնդրում է ջնջել իր տվյալները։ Նրա իրադարձությունները բաշխված են 13 ամսվա partition-ներում՝ բազմամիլիարդ տողանոց աղյուսակում։
Ինչպես լուծել
Օգտագործեք lightweight DELETE FROM events WHERE user_id = 12345, որը տողերն անմիջապես mask է անում և ֆիզիկապես հեռացնում հետագա merge-երի ընթացքում․ աշխատեցրեք այն ֆոնային job-ում և լոգավորեք հարցումը։ Եթե ջնջման հարցումները հաճախակի են, դրանք օրական batch արեք մեկ statement-ում WHERE user_id IN (...)-ով։ Համոզվեք, որ rollup աղյուսակներն ի սկզբանե անձնական նույնացուցիչներ չեն պարունակում․ ագրեգացրեք քանակներ և մոտավոր uniq-ներ՝ առանց raw user ID-ների։ Հիշեք backup-ների մասին․ փաստաթղթավորեք retention-ը, որպեսզի ջնջված տվյալները ժամանակի ընթացքում դուրս գան նաև backup-ներից։ Երկարաժամկետ՝ դիտարկեք իրադարձություններում pseudonymous ID-ներ՝ mapping-ը OLTP բազայում պահելով, որպեսզի mapping-ը ջնջելը անանունացնի պատմությունը։
Արագ ուղեցույց
- Հարցում աշխատեցնել shell-ից
clickhouse-client --query "SELECT version()"- Աղյուսակի չափ և սեղմում
SELECT table, formatReadableSize(sum(data_compressed_bytes)) AS c, formatReadableSize(sum(data_uncompressed_bytes)) AS u FROM system.parts WHERE active GROUP BY table- Ակտիվ part-եր ըստ partition-ի
SELECT partition, count() FROM system.parts WHERE table = 'events' AND active GROUP BY partition- Ստուգել ինդեքսի օգտագործումը
EXPLAIN indexes = 1 SELECT ...- Վերջին ամենադանդաղ հարցումները
SELECT query_duration_ms, query FROM system.query_log WHERE type = 'QueryFinish' ORDER BY query_duration_ms DESC LIMIT 10- Ակնթարթորեն ջնջել հին ամիսը
ALTER TABLE events DROP PARTITION 202410- Lightweight delete
DELETE FROM events WHERE user_id = 12345- Աղյուսակների ատոմար փոխանակում
EXCHANGE TABLES events AND events_new- Backup դեպի S3
BACKUP TABLE events TO S3('https://bucket.s3.amazonaws.com/backups/events', 'KEY_ID', 'SECRET')
Հարցեր և պատասխաններ
Q1Ինչո՞ւ է ClickHouse-ը անալիտիկայի համար այդքան ավելի արագ, քան MySQL-ը։
Երեք պատճառ աշխատում են միասին։ Այն տվյալները պահում է ըստ սյունակի, այնպես որ հարցումը կարդում է միայն իրեն անհրաժեշտ սյունակները՝ ամբողջական տողերի փոխարեն։ Այդ սյունակները ուժեղ են սեղմված, հաճախ տասն անգամ կամ ավելի, այնպես որ ավելի քիչ տվյալ է գալիս սկավառակից։ Եվ այն հարցումները կատարում է vectorized ձևով՝ մեկ CPU հրահանգով մշակելով հազարավոր արժեքների բլոկներ բոլոր core-երում։ Sparse primary index-ն այնուհետև թույլ է տալիս բաց թողնել ամբողջ granule-ներ, որոնք չեն կարող համընկնել։ MySQL-ը օպտիմիզացված է հակառակ ծանրաբեռնվածության համար՝ առանձին տողեր գտնել և թարմացնել ինդեքսով՝ տրանզակցիաներով, ինչը ClickHouse-ը վատ է անում։ Յուրաքանչյուրն արագ է այն բանում, ինչի համար ստեղծվել է։
Q2Ինչպե՞ս եք ընտրում ORDER BY key-ը։
Սկսեք հարցումներից։ Թվարկեք dashboard-ների և API կանչերի մեծ մասում օգտագործվող ֆիլտրերը։ Առաջինը դրեք այն սյունակները, որոնցով գրեթե յուրաքանչյուր հարցում ֆիլտրում է հավասարությամբ՝ դասավորված ցածրից բարձր cardinality, օրինակ՝ tenant_id և event_type, ապա միջակայքերի համար օգտագործվող ժամանակի սյունակը։ Սա թույլ է տալիս sparse index-ին բաց թողնել granule-ների մեծ մասը։ Խուսափեք UUID-ն կամ user_id-ն առաջինը դնելուց, եթե հարցումների մեծ մասը մեկ օգտատիրոջով չի ֆիլտրում։ ORDER BY-ը նաև ազդում է սեղմման վրա, քանի որ նման արժեքները հայտնվում են կողք կողքի։ Եթե մեկ աղյուսակը պետք է սպասարկի շատ տարբեր հարցումների պատերններ, ավելացրեք projection-ներ կամ երկրորդ materialized աղյուսակ՝ այլ դասավորությամբ։
Q3Կարելի՞ է ClickHouse-ում տվյալներ թարմացնել կամ ջնջել։
Այո, բայց պետք է նախագծեք այնպես, որ դա հազվադեպ անհրաժեշտ լինի։ Դասական ALTER TABLE UPDATE-ը և DELETE-ը mutation-ներ են․ դրանք ֆոնում վերագրում են ազդված part-երը, ինչը ծանր է։ Lightweight DELETE FROM-ը տողերը նշում է որպես ջնջված թաքնված mask-ով և շատ ավելի էժան է, իսկ վերջին տարբերակները ավելացնում են նաև lightweight update-ներ։ Փոփոխվող տվյալների համար սովորական պատերնը ReplacingMergeTree-ն է․ insert արեք տողի նոր տարբերակը և թողեք, որ merge-երը պահեն վերջինը, իսկ ճշգրիտ արդյունքներ պետք լինելիս կարդացեք FINAL-ով կամ argMax-ով։ Retention-ի համար օգտագործեք TTL և DROP PARTITION, որոնք ակնթարթային են։
Q4Ինչպե՞ս եք տվյալները հավելվածից ClickHouse բերում։
Խուսափեք յուրաքանչյուր իրադարձության համար request path-ում ClickHouse-ում սինքրոն գրելուց։ Հավելվածը կամ tracking endpoint-ը իրադարձություններն ուղարկում է buffer, ինչպիսին Kafka-ն է, Redis-ի վրա հիմնված հերթը կամ փոքր collector ծառայությունը, և worker-ը batch-եր է insert անում ամեն վայրկյան կամ ամեն մի քանի հազար տողը մեկ։ ClickHouse-ը կարող է նաև ուղղակիորեն սպառել Kafka-ն Kafka table engine-ով կամ ամպում՝ ClickPipes-ով։ Ռելյացիոն աղյուսակների սինքրոնիզացիայի համար օգտագործեք change data capture կամ պարբերական incremental export-ներ ըստ updated_at-ի։ Եթե ստիպված եք փոքր insert-ներ ուղարկել, միացրեք async_insert-ը, որպեսզի սերվերը batch անի դրանք։ Նպատակը քիչ, բայց մեծ insert-ներն են։
Q5Ի՞նչ է materialized view-ը ClickHouse-ում, և ինչո՞վ է այն տարբերվում PostgreSQL-ից։
PostgreSQL-ում materialized view-ը պահում է հարցման արդյունքը, և դուք այն refresh եք անում ըստ պահանջի՝ ամեն ինչ վերահաշվելով։ ClickHouse-ում դասական materialized view-ը insert trigger է․ աղբյուր աղյուսակ insert արված յուրաքանչյուր նոր բլոկ ձևափոխվում է view-ի հարցմամբ և ավելացվում թիրախային աղյուսակին։ Դա այն դարձնում է incremental և միշտ արդի, կատարյալ rollup-ների համար։ Թակարդն այն է, որ այն տեսնում է միայն նոր տվյալներ, այնպես որ պատմական տողերին պետք է ձեռքով backfill, իսկ ագրեգացիաները պետք է օգտագործեն state և merge ֆունկցիաներ, քանի որ մասնակի արդյունքները գալիս են յուրաքանչյուր insert-ով։ ClickHouse-ը նաև ունի refreshable materialized view-եր՝ պարբերական ամբողջական վերահաշվարկի համար։
Q6ClickHouse-ը data warehouse-ի փոխարինո՞ղ է։
Շատ թիմերի համար՝ կարող է լինել։ Այն աշխատում է star schema-ների, join-երի, window ֆունկցիաների և հսկայական ծավալների հետ, և նրա արագությունը այն դարձնում է լավ engine թե՛ ներքին BI-ի, թե՛ հաճախորդների համար անալիտիկայի համար։ BigQuery-ի կամ Snowflake-ի համեմատ այն սովորաբար ավելի քիչ արժե կայուն ծանրաբեռնվածության դեպքում և ավելի արագ է պատասխանում ինտերակտիվ հարցումներին, բայց ավելի շատ բան եք ինքներդ կառավարում, եթե չեք օգտագործում ClickHouse Cloud, և շատ մեծ many-to-many join-երը մոդելավորման ավելի մեծ ուշադրություն են պահանջում։ Warehouse-ները փայլում են բազմաթիվ աղբյուրների վրա բազմաթիվ վերլուծաբանների ad-hoc վերլուծության համար։ Տարածված կարգավորումն է ClickHouse-ը real-time իրադարձությունների անալիտիկայի համար՝ warehouse-ի կողքին, որը ֆինանսական և batch հաշվետվությունների համար է։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս կնախագծեիք multi-tenant, հաճախորդների համար անալիտիկա ClickHouse-ի վրա։
Ես կօգտագործեի ընդհանուր աղյուսակներ՝ tenant_id-ն որպես ORDER BY-ի առաջին սյունակ, որպեսզի յուրաքանչյուր tenant-ի հարցումները կարդան ինդեքսի նեղ միջակայք, և կshard անեի tenant_id-ի hash-ով, եթե մեկ node-ը բավարար չէ։ Յուրաքանչյուր tenant-ի հարցումներն աշխատում են առանձին օգտատիրոջ կամ settings profile-ի միջոցով՝ row policy-ներով (tenant_id = X), quota-ներով և հիշողության, կատարման ժամանակի ու կարդացված տողերի սահմաններով, որպեսզի մեկ ծանր հաճախորդը չկարողանա սովամահ անել մյուսներին։ Dashboard-ները կարդում են materialized rollup-ներից, ոչ թե raw իրադարձություններից, և API շերտը whitelist է անում հարցումների ձևերը՝ raw SQL փոխանցելու փոխարեն։ Շատ մեծ tenant-ները կարող են տեղափոխվել առանձին shard-եր։ Փոխզիջումը օպերացիոն է․ ընդհանուր աղյուսակներն արդյունավետ են, բայց խիստ սահմանափակումներ են պահանջում, իսկ table-per-tenant-ը ավելի լավ է մեկուսացնում, բայց պայթեցնում է part-երի և աղյուսակների քանակը։
S2ClickHouse, թե՞ ամպային warehouse, ինչպիսին BigQuery-ն է, նոր անալիտիկ պլատֆորմի համար։
Դա կախված է ծանրաբեռնվածության ձևից։ Օգտատիրոջ համար dashboard-ների դեպքում՝ վայրկյանից պակաս latency-ով, հարցումների բարձր concurrency-ով և իրադարձությունների կայուն հոսքով, ClickHouse-ը սովորաբար ավելի արագ և էժան է, քանի որ վճարում եք provision արված հաշվարկի համար, ոչ թե յուրաքանչյուր սկանավորված բայթի։ Բազմաթիվ վերլուծաբանների կողմից տասնյակ աղբյուրների վրա ad-hoc վերլուծության համար՝ անկանոն օգտագործմամբ և ծանր join-երով, BigQuery-ն կամ Snowflake-ը ավելի պարզ են․ capacity planning չկա, governance-ը և էկոհամակարգը հասուն են։ ClickHouse-ը պահանջում է մոդելավորման ավելի մեծ կարգապահություն (sorting key-եր, rollup-ներ, առանց հաճախակի update-ների)։ Շատ ընկերություններ օգտագործում են երկուսն էլ․ warehouse-ը ֆինանսների և batch BI-ի համար, ClickHouse-ը product analytics-ի և observability-ի համար՝ սնվելով նույն իրադարձությունների հոսքից։
S3Ինչպե՞ս երաշխավորել exactly-once ingestion Kafka-ից ClickHouse։
Իրական end-to-end exactly-once-ը դժվար է․ ձգտեք idempotent at-least-once-ի։ Առաջին տարբերակ՝ Kafka table engine-ը materialized view-ով, որը offset-ները commit է անում insert-ներից հետո, բայց ձախողումների դեպքում կարող է կրկնօրինակել։ Երկրորդ տարբերակ՝ ձեր սեփական consumer-ը, որը կրկնելիս insert է անում նույնական batch-եր, որպեսզի ReplicatedMergeTree-ի block deduplication-ը հեռացնի կրկնությունները (կամ յուրաքանչյուր batch-ի համար դրեք insert_deduplication_token)։ Երրորդ տարբերակ՝ ClickHouse Kafka Connect sink-ը կամ ClickPipes-ը, որոնք իրենց դիզայնի շրջանակում առաջարկում են exactly-once սեմանտիկա։ Բոլոր դեպքերում ավելացրեք event_id և օգտագործեք ReplacingMergeTree կամ argMax հարցումներ այն աղյուսակների համար, որտեղ կրկնօրինակները կփոխեին թվերը, և պարբերաբար համադրեք քանակները աղբյուրի հետ։ Փոխզիջումը բարդությունն է ընդդեմ ճշգրտության․ մոտավոր պրոդուկտային մետրիկների համար at-least-once-ը հաճախ ընդունելի է։
Բառարան
- OLAP
- Online analytical processing՝ մեծ հարցումներ, որոնք սկանավորում և ագրեգացնում են բազմաթիվ տողեր։
- Սյունակային պահեստավորում
- Յուրաքանչյուր սյունակի առանձին պահում, որպեսզի հարցումները կարդան միայն իրենց անհրաժեշտ սյունակները։
- MergeTree
- ClickHouse-ի հիմնական table engine ընտանիքը՝ դասավորված անփոփոխ part-եր, որոնք merge են արվում ֆոնում։
- Part
- Աղյուսակի անփոփոխ կտոր սկավառակի վրա, որը ստեղծվում է յուրաքանչյուր insert-ով և ժամանակի ընթացքում merge արվում։
- Granule
- Ամենափոքր բլոկը, որը մատնացույց է անում sparse index-ը, լռելյայն 8,192 տող։
- Sparse primary index
- Մեկ ինդեքսային գրառում յուրաքանչյուր granule-ի համար, որն օգտագործվում է ֆիլտրին չհամապատասխանող բլոկները բաց թողնելու համար։
- Materialized view
- ClickHouse-ում՝ insert trigger, որը նոր տողերը ձևափոխում և գրում է մեկ այլ աղյուսակ։
- LowCardinality
- Սյունակի wrapper, որը կրկնվող արժեքները dictionary-encode է անում՝ ավելի քիչ տարածքի և ավելի արագ հարցումների համար։
- ClickHouse Keeper
- Ռեպլիկացիայի համար ներկառուցված կոորդինացիոն ծառայություն՝ համատեղելի ZooKeeper-ի հետ։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։