Բոլոր դասերը
ՏվյալներԱռաջադեմ12 րոպե ընթերցումԱրժե իմանալ

ClickHouse անալիտիկայի համար

Սյունակային բազա, որը միլիարդավոր իրադարձություններ ագրեգացնում է վայրկյանների ընթացքում։

  • Columnar
  • OLAP
  • Materialized views
  • Event data
Քայլ 1/5
Հարցը՝ վաճառքների ընդհանուր գումարը։ Աղյուսակում մեկ միլիարդ տող կա, ամեն մեկում՝ date, country, url և amount։

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-ները պահեք խոշոր։

sql
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)։

bash
# 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։

sql
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-ով։

sql
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-ի նման ֆայլերի վրա։

Լավագույն պրակտիկաներ

  1. ORDER BY-ը նախագծեք ձեր ամենատարածված ֆիլտրերից․ նախ ցածր cardinality ունեցող սյունակներ, ապա ժամանակը։
  2. Insert արեք մեծ batch-երով կամ միացրեք async insert-ները․ երբեք մի stream արեք մեկ տողանոց insert-ներ։
  3. Կրկնվող արժեքների համար օգտագործեք LowCardinality(String) և ամենափոքր թվային տիպերը, որոնք տեղավորում են արժեքները։
  4. Partition-ները պահեք խոշոր (սովորաբար ըստ ամսի) և օգտագործեք TTL՝ հին տվյալներն ավտոմատ ջնջելու կամ տեղափոխելու համար։
  5. Dashboard-ի հարցումները նախապես ագրեգացրեք materialized view-երով AggregatingMergeTree աղյուսակներում։
  6. Կլաստերը պաշտպանելու համար յուրաքանչյուր օգտատիրոջ համար սահմանեք հիշողության, կատարման ժամանակի և արդյունքի չափի սահմաններ։
  7. Բիզնես էնտիտիների ճշմարտության աղբյուրը պահեք ձեր 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 է։

sql
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-ը նույնական է։

sql
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-ի վրա։

Իրական իրավիճակներ

01

«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-երն ինքնուրույն կհասնեն։

02

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-ի համար։

03

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-ի հետ։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։