アイキャッチ画像: ClickHouse高度ガイド:最適化、マテリアライズドビュー、実戦のコツ

ClickHouse高度ガイド:最適化、マテリアライズドビュー、実戦のコツ

公開日:

読了時間: 6 min

テーマ: テクノロジー

著者: Leandro Valencia

#ClickHouse#最適化#SQL#パフォーマンス#OLAP#セルフホスト

ClickHouseを最大限に活用する方法:ORDER BY、マテリアライズドビュー、プロジェクション、コーデック、TTL、S3ティアリング、スロークエリ診断。実SQL付き。

目次

基本原則:より少ないデータを読む

以下はすべて同じアイデアのバリエーションです。ClickHouseが速いのはデータを素早く処理するからではありません——それもしますが——あなたが思うよりはるかに少ないデータを処理するからです。

あなたが発射する各クエリは、正確にどれだけ読んだかを教えてくれます:

6 rows in set. Elapsed: 0.021 sec. Processed 1.14 million rows, 8.32 MB

この行があなたの主要な指標です。テーブルが5億行あってクエリが5億行を処理するなら、最適化できていません:非常に速いフルスキャンをしているだけです。目標は常にこの数値を下げることです。

それを下げるためのツール、インパクト順:ソートキーマテリアライズドビュープロジェクションスキップインデックス、そしてパーティショニング。この順序で。

1. ソートキー:すべてを決定する決定

この記事から1つだけ持って帰るなら、このセクションにしてください。

スパースインデックスの仕組み

ClickHouseには行ごとのBツリーインデックスがありません。ORDER BYに従ってディスク上のデータを物理的にソートし、8,192行(1グラニュール)ごとにマークを保存し、その位置のキー値を記録します。インデックス全体はテラバイトあたり数メガバイトしか取りません。

ORDER BYにあるカラムでフィルタすると、ClickHouseはそのインデックスを見て、一致する値を含む可能性のあるグラニュールを特定し、それだけを読みます。それ以外は一切触れません。

帰結は直接的です:カラムがクエリを高速化するのは、それがORDER BYのプレフィックスにあるときだけです。キーが(evento, fecha, usuario_id)なら、eventoでフィルタするのは極めて速く、evento AND fechaも同様ですが、usuario_idだけでフィルタするとフルスキャンを強いられます。順序はPostgresの複合インデックスと同じように重要ですが、帰結ははるかに大きいです。

ORDER BYを選ぶ3つのルール

ルール1:常にフィルタするカラムを先に。 仮説上ではなく、実際のクエリを見てください。95%がWHERE tenant_id = ?を持つなら、tenant_idが先です。

ルール2:使用頻度が同じなら、低カーディナリティを先に。 6つの値しか持たないカラム(国)は、50,000の値を持つカラム(usuario_id)よりデータをうまくまとめます。高カーディナリティを先に置くとグラニュールが断片化し、圧縮もブロックスキップも台無しになります。

ルール3:時間カラムはほぼ常に含めますが、最初に置くのは稀です。 「すべて日付でフィルタされる」からtimestampを先頭に置きたくなります。(fecha, tenant_id, evento)より(tenant_id, evento, fecha)の方が通常良いです。日付は挿入の自然な順序とかなり相関しているからです。

よく設計されたテーブルの例:

CREATE TABLE eventos
(
    tenant_id   UInt32,
    fecha       Date,
    timestamp   DateTime CODEC(Delta, ZSTD(1)),
    evento      LowCardinality(String),
    pais        LowCardinality(String),
    usuario_id  UInt32 CODEC(Delta, ZSTD(1)),
    url         String CODEC(ZSTD(3)),
    duracion_ms UInt32 CODEC(T64, ZSTD(1))
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(fecha)
ORDER BY (tenant_id, evento, fecha, usuario_id);

プライマリキーをソートキーから分離する

あまり使われない詳細:長いORDER BYと短いPRIMARY KEYを持てます。

ENGINE = MergeTree
ORDER BY (tenant_id, evento, fecha, usuario_id)
PRIMARY KEY (tenant_id, evento)

ORDER BYは物理的順序(ひいては圧縮)を制御し、PRIMARY KEYはメモリ内インデックスに何を保存するかを制御します。ソートキーが5カラムで、最初の2つだけでフィルタするなら、これでインデックスのRAM使用量を失うことなく減らせます。

キーが機能しているかを確認する

トレースをオンにして、いくつのグラニュールが破棄されるか観察します:

SET send_logs_level = 'trace';

SELECT count() FROM eventos WHERE tenant_id = 42 AND evento = 'purchase';

ログにこういう行が現れます:

Key condition: (column 0 in [42, 42]), (column 1 in ['purchase', 'purchase'])
Selected 3/48 parts by partition key, 3 parts by primary key, 14/61035 marks by primary key

14/61035 marksはテーブルの0.02%を読んだことを意味します。それがうまく選ばれたキーです。61035/61035と出たら、フィルタがインデックスを使っていません。

2. マテリアライズドビュー:作業を挿入時に移す

最もインパクトがあり、最も誤解される機能です。

ClickHouseのマテリアライズドビューはキャッシュでも定期的にリフレッシュされるテーブルでもありません。これはインサートトリガーです:データブロックがソーステーブルに到着するたびに、そのブロックに対してクエリが実行され、結果が宛先テーブルに書き込まれます。元のデータが再び読まれることはありません。

これには重大な意味があります:マテリアライズドビューは、作成後にインサートされたデータしか見えません。 履歴は手動でバックフィルする必要があります。

標準パターン

3つのピース:ソーステーブル、集計用の宛先テーブル、そしてそれらを繋ぐビュー。

-- 1. 集計状態を持つ宛先テーブル
CREATE TABLE eventos_diarios
(
    tenant_id       UInt32,
    fecha           Date,
    evento          LowCardinality(String),
    pais            LowCardinality(String),
    total           UInt64,
    usuarios_unicos AggregateFunction(uniq, UInt32),
    duracion_media  AggregateFunction(avg, UInt32)
)
ENGINE = SummingMergeTree
ORDER BY (tenant_id, fecha, evento, pais);

-- 2. それを養うマテリアライズドビュー
CREATE MATERIALIZED VIEW mv_eventos_diarios
TO eventos_diarios
AS
SELECT
    tenant_id,
    fecha,
    evento,
    pais,
    count()                 AS total,
    uniqState(usuario_id)   AS usuarios_unicos,
    avgState(duracion_ms)   AS duracion_media
FROM eventos
GROUP BY tenant_id, fecha, evento, pais;

uniqStateavgStateに注目してください。-State接尾辞は、最終結果ではなく集計の中間状態を保存します。これが異なるブロックの集計を正しく結合できるようにするものです。

クエリするには-Merge接尾辞を使います:

SELECT
    fecha,
    evento,
    sum(total)                    AS eventos,
    uniqMerge(usuarios_unicos)    AS usuarios,
    avgMerge(duracion_media)      AS duracion
FROM eventos_diarios
WHERE tenant_id = 42
  AND fecha >= today() - 30
GROUP BY fecha, evento
ORDER BY fecha;

なぜこれがそれほど重要か: このクエリは数億行の生データではなく、数千行の事前集計を読みます。差は2倍ではなく、2〜3桁です。しかも計算はインサート時に行われるため、コストはあなたのユーザーがダッシュボードを見る瞬間に集中するのではなく、時間に分散されます。

よくある間違い:avgState()ではなくavg()を使い、クエリ時に平均の平均を取ること。平均の平均は平均ではないため、間違った結果になります。集計状態はまさにこれを避けるために存在します。

履歴のバックフィル

ビューは新しいデータしか捉えないため、ビュー作成後に同じクエリでINSERT ... SELECTを使って後ろ向きに埋めます:

INSERT INTO eventos_diarios
SELECT
    tenant_id, fecha, evento, pais,
    count()                 AS total,
    uniqState(usuario_id)   AS usuarios_unicos,
    avgState(duracion_ms)   AS duracion_media
FROM eventos
WHERE fecha < today()
GROUP BY tenant_id, fecha, evento, pais;

テーブルが大きい場合は日付範囲で行い、メモリを爆破しないようにしましょう。

リフレッシュ可能なマテリアライズドビュー

ここ数バージョンでは、完全なクエリを定期的に再実行するリフレッシュ可能なバリアントもあります:

CREATE MATERIALIZED VIEW mv_resumen
REFRESH EVERY 1 HOUR
ENGINE = MergeTree ORDER BY fecha
AS SELECT ... ;

インクリメンタルビューが苦手な、変化するディメンションテーブルとのJOINが必要な場合に有用です。より高コストですが、はるかに推論が簡単です。

3. プロジェクション:同じテーブルを2つの方法でソート

ここに古典的な問題があります:ORDER BYtenant_idでフィルタするように最適化されているが、urlでフィルタする高速なクエリも必要。物理的な順序は1つしか持てません。

プロジェクションはこれを、同じテーブルの中に異なる順序でソートされたデータの追加コピーを保存することで解決します。ClickHouseがどちらを使うかを自動的に選びます。

ALTER TABLE eventos ADD PROJECTION proj_por_url
(
    SELECT
        url,
        fecha,
        count(),
        avg(duracion_ms)
    GROUP BY url, fecha
);

-- 既存データに対してマテリアライズ
ALTER TABLE eventos MATERIALIZE PROJECTION proj_por_url;

これでurlでグループ化するクエリは、SQLを変えずにプロジェクションを使います。EXPLAINで確認できます:

EXPLAIN indexes = 1
SELECT url, count() FROM eventos WHERE fecha >= today() - 7 GROUP BY url;

プロジェクション vs. マテリアライズドビュー。 当然の疑問です。私の基準:

プロジェクションを使うのは、同じテーブルの異なる順序や派生集計として同じデータが欲しく、かつ透明である(クエリを変えない、一貫性が自動的)ことを重視する場合。

マテリアライズドビューを使うのは、データを変換したい、異なるTTLを持つテーブルに書きたい、複数段階をチェーンしたい、複数ソースを組み合わせたい場合。そして、宛先テーブルが独立して生き残るべき場合。

プロジェクションのコストはストレージ(データを2回保存する)とインサート速度です。継続的なインサートを受けるテーブルに5つのプロジェクションを追加しないでください。

4. 圧縮コーデック:最も過小評価されるチューニング

ディスク上のバイトが少ない = I/Oが少ない = クエリが速い。圧縮はストレージ節約の話ではなく、パフォーマンスの話です。

ClickHouseはデフォルトでLZ4を適用し、よく機能しますが、カラムごとに調整すると実際の改善があります。公式ドキュメントは、重要度順に推奨しています:

ZSTDをベースに。 最も良い圧縮率を提供し、ZSTD(1)はほとんどの型の良いデフォルトです。ZSTD(3)を超えるのは、インサートコストに見合うことは稀です。

Deltaは日付と整数列に。 単調な列や連続する値の差が小さい場合にとてもよく機能します。タイムスタンプや自動増分IDが正統なケースです。第一次導関数の結果が十分に小さくないなら、DoubleDeltaを試してください。

DeltaZSTDを改善する。 うまく組み合わせます:CODEC(Delta, ZSTD(1))は通常、個別よりも良くなります。

LZ4ZSTDと同程度なら。 同等の圧縮が得られるなら、LZ4はより速く減圧しCPUをより消費しないため好まれます。実運用ではZSTDがほとんどのケースで大きく勝ちます。

T64は小さな範囲やスパースデータに。 ブロック内の値の範囲が狭い場合に効果的です。乱数には避けましょう。

Gorillaはセンサー型floatに。 小さな変動のメーター読み取り用に設計されています。

実運用では:

CREATE TABLE metricas
(
    timestamp  DateTime      CODEC(Delta, ZSTD(1)),
    sensor_id  UInt32        CODEC(Delta, ZSTD(1)),
    temperatura Float32      CODEC(Gorilla, ZSTD(1)),
    estado     LowCardinality(String),
    payload    String        CODEC(ZSTD(3))
)
ENGINE = MergeTree
ORDER BY (sensor_id, timestamp);

前後で測る

SELECT
    name,
    formatReadableSize(sum(data_compressed_bytes))   AS comprimido,
    formatReadableSize(sum(data_uncompressed_bytes)) AS sin_comprimir,
    round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 2) AS ratio
FROM system.columns
WHERE table = 'metricas'
GROUP BY name
ORDER BY sum(data_compressed_bytes) DESC;

圧縮サイズでソートし、最も大きい2〜3カラムだけを攻撃します。3 MBしか占めないカラムのコーデックを最適化するのは時間の無駄です。

コンパクトパートに関する注意: compressed_sizeにゼロが見えるなら、あなたのパートがwideではなくcompactだからです(小さなインサートで発生)。min_bytes_for_wide_partmin_rows_for_wide_partで制御します。

データ型も圧縮

コーデックを触る前に、型を見直しましょう。0〜100の値にInt64ではなくUInt8を使うのは無料の8倍削減です。10,000件未満の異なる値を持つカラムにはLowCardinality(String)。日付にはStringではなくDateTime。ClickHouseのドキュメントでは、型とソートキーを最適化するだけで同じデータセットが50GBから圧縮25GBになったとしています。

5. スキップインデックス:慎重に使う

データスキッピングインデックスは、ORDER BYにないカラムでフィルタしてブロックをスキップできます。普遍的な解決策のように聞こえますが、ほぼ決してそうではありません

ALTER TABLE eventos ADD INDEX idx_pais pais TYPE set(100) GRANULARITY 4;
ALTER TABLE eventos MATERIALIZE INDEX idx_pais;

主な型:

minmax はブロックごとに最小値と最大値を保存します。適用が最も安価。ほぼ順序づけられたカラムや範囲フィルタに理想です。

set(N) はブロックごとに最大N個の異なる値を保存します。グローバルには高カーディナリティだが各ブロック内では低カーディナリティの場合に良いです。

bloom_filter は大きな値の集合への所属テスト用。配列やマップで機能します。

text はフルテキスト検索のための本当の転置インデックスです。今日推奨されるものです;古いtokenbf_v1ngrambf_v1は非推奨とマークされています。

重要な警告

公式ドキュメントはこれについて明示的であり、繰り返す価値があります:よくクエリするカラムにインデックスを追加しようとする自然な衝動は、ClickHouseでは通常間違っています。

スキップインデックスが役立つのは、プライマリキーとインデックス付きカラムの間に強い相関がある場合のみです。そのカラムの値がテーブル全体にランダムに散らばっているなら、各ブロックにいくつか含まれ、何もスキップされません。インデックスのコスト(インサートとクエリで)をゼロの利益のために払うことになります。

スキップインデックスを追加する前に、この順序で試してください:ORDER BYを変える、プロジェクションを追加する、マテリアライズドビューを作る。スキップインデックスは最後の手段であり、最初ではありません。

輝くケース:稀だが重要な値。ログテーブルのerror_codesetインデックスを付ければ、エラーのない大部分のブロックをスキップできます。

6. TTLとティアリング:自動で片付くデータ

自動保持はClickHouseの最も優れた運用機能の一つであり、最も使われないものの一つです。

自動削除

ALTER TABLE eventos MODIFY TTL fecha + INTERVAL 90 DAY;

90日より古いデータはバックグラウンドマージで消えます。cronもスクリプトも監視も不要。

経年で集計する

より面白い:古いデータを削除するのではなく解像度を下げる。

-- TTLのグループ化キーはプライマリキーのプレフィックスでなければならない
CREATE TABLE metricas_sensores
(
    timestamp   DateTime CODEC(Delta, ZSTD(1)),
    sensor_id   UInt32   CODEC(Delta, ZSTD(1)),
    temperatura Float32  CODEC(Gorilla, ZSTD(1)),
    estado      LowCardinality(String)
)
ENGINE = MergeTree
ORDER BY (sensor_id, toStartOfHour(timestamp), timestamp);

ALTER TABLE metricas_sensores MODIFY TTL
    timestamp + INTERVAL 7 DAY
        GROUP BY sensor_id, toStartOfHour(timestamp)
        SET temperatura = avg(temperatura);

1週間は秒ごとのデータ、その後は時間ごと。有用な履歴を空間のごく一部で保てます。

この詳細に注意。最初は誰もがここで失敗するからです:ClickHouseはTTLのGROUP BYのカラムがプライマリキーのプレフィックスであることを要求します。テーブルが(sensor_id, timestamp)でソートされていてtoStartOfHour(timestamp)でグループ化すると、TTL Expression GROUP BY key should be a prefix of primary keyのエラーになります。解決策は、上の例のように丸めた式をORDER BY自体に含めることです。

S3ティアリング

長期保持を低コストで実現するトリック。S3ディスクとストレージポリシーを設定します:

<!-- /etc/clickhouse-server/config.d/storage.xml -->
<clickhouse>
  <storage_configuration>
    <disks>
      <s3_frio>
        <type>s3</type>
        <endpoint>https://mi-bucket.s3.eu-west-1.amazonaws.com/clickhouse/</endpoint>
        <access_key_id>TU_ACCESS_KEY</access_key_id>
        <secret_access_key>TU_SECRET_KEY</secret_access_key>
      </s3_frio>
    </disks>
    <policies>
      <caliente_frio>
        <volumes>
          <caliente><disk>default</disk></caliente>
          <frio><disk>s3_frio</disk></frio>
        </volumes>
      </caliente_frio>
    </policies>
  </storage_configuration>
</clickhouse>

そして適用:

ALTER TABLE eventos MODIFY SETTING storage_policy = 'caliente_frio';

ALTER TABLE eventos MODIFY TTL
    fecha + INTERVAL 30 DAY TO VOLUME 'frio',
    fecha + INTERVAL 365 DAY DELETE;

直近30日はローカルSSD(高速)、残りはS3(安価)、そしてすべては1年で削除。小さなプロジェクトにとって、これが「予算的に30日しか保存できない」を「数セントで2年保存」に変えるものです。 コールドデータに対するクエリは当然遅いですが、まだ機能し、最も実行されないものです。

7. インサート:すべての人を沈める間違い

本番での問題ナンバーワンは遅いクエリではなく、Too many partsです。

INSERTはディスクに新しいパートを作ります。パートはバックグラウンドでマージされますが、マージより速くインサートすると、サーバーが書き込みを拒否するまで蓄積します。

ルール:バッチでインサート

インサートごとに少なくとも1,000行、理想的には10,000〜100,000。アプリケーションでバッファし、サイズまたは時間(先に来た方)でフラッシュします。

代替:非同期インサート

アーキテクチャが簡単にバッファできない場合——例えば、多くのプロセスが単一イベントを書く——は、ClickHouseにやらせましょう:

SET async_insert = 1;
SET wait_for_async_insert = 1;

ClickHouseはサーバー側のバッファに蓄積し、バッチでフラッシュします。wait_for_async_insert = 1ではクライアントは実際の書き込みの確認を待ち(より安全)、0では即時確認を受け取ります(より速いが、サーバーがフラッシュ前に落ちるとデータを失う可能性があります)。

async_insert_max_data_sizeasync_insert_busy_timeout_msでバッファの挙動を調整します。

診断

SELECT
    table,
    count()                                  AS num_parts,
    sum(rows)                                AS filas,
    formatReadableSize(sum(bytes_on_disk))   AS tamano
FROM system.parts
WHERE active AND database = 'creacosas'
GROUP BY table
ORDER BY num_parts DESC;

あるテーブルに数百のアクティブパートがあるなら、あなたのインサートパターンが問題です。

そして、OPTIMIZE TABLE ... FINALは解決策ではありません。大きなテーブルで非常に高価な完全マージを強制し、原因を直しません。公式ドキュメントは明示的に避けることを推奨しています。

8. 診断:何が悪いかを見つける

クエリログが最良のツール

ClickHouseは各クエリを指標とともに記録します:

SELECT
    query_duration_ms,
    formatReadableQuantity(read_rows)    AS filas_leidas,
    formatReadableSize(read_bytes)       AS bytes_leidos,
    formatReadableSize(memory_usage)     AS memoria,
    substring(query, 1, 120)             AS consulta
FROM system.query_log
WHERE type = 'QueryFinish'
  AND event_time > now() - INTERVAL 1 HOUR
ORDER BY query_duration_ms DESC
LIMIT 20;

期間でソートし、最悪のものを攻撃します。read_rowsをテーブルの合計と比較してください:近ければ、フィルタがインデックスを使っていません。

EXPLAIN

EXPLAIN indexes = 1
SELECT ... ;

どのインデックスとプロジェクションが使われ、各ステップでいくつのグラニュールが破棄されたかを示します。

PREWHERE

ClickHouseは通常この最適化を自動的に適用しますが、強制できます。PREWHEREは最初にフィルタカラムを読み、行を破棄し、その後でのみ残りのカラムを読みます:

SELECT url, payload_grande
FROM eventos
PREWHERE evento = 'error'      -- 最初にこのカラムだけ読む
WHERE duracion_ms > 5000;

小さいカラムでフィルタし、大きいカラムを選ぶ場合に特に有用です。

クエリキャッシュ

同一の繰り返しクエリを持つダッシュボードに:

SELECT ... SETTINGS use_query_cache = 1;

TTLをグローバルに設定します。銀の弾丸ではありません——クエリが文字通り繰り返される場合にのみ効きます——が、複数人が同時に見るダッシュボードではかなりの節約になります。

9. より賢いクエリ

厳密さが重要でない場合の近似集計。 uniq()はHyperLogLogを使い、uniqExact()よりはるかに速くメモリも軽いです。「ユニークユーザー」ダッシュボードでは、0.5%の誤差は無関係です。

JOINの代わりの辞書。 小さなディメンションテーブルに繰り返しJOINするなら、それをインメモリの辞書として読み込みます:

CREATE DICTIONARY dic_paises (
    codigo String,
    nombre String
)
PRIMARY KEY codigo
SOURCE(CLICKHOUSE(TABLE 'paises'))
LAYOUT(COMPLEX_KEY_HASHED())
LIFETIME(3600);

SELECT dictGet('dic_paises', 'nombre', pais) AS pais_nombre, count()
FROM eventos GROUP BY pais;

dictGetはインメモリのハッシュの検索です:JOINより桁違いに速いです。

コンビネータ関数。 -If-Array-Mergeはサブクエリ全体を避けます:

SELECT
    fecha,
    countIf(evento = 'purchase')                      AS compras,
    countIf(evento = 'signup')                        AS registros,
    avgIf(duracion_ms, evento = 'pageview')           AS duracion_pageview
FROM eventos
GROUP BY fecha;

3回ではなく1回のデータ走査ですみます。

コンバージョンファネルのwindowFunnel 標準SQLでファネルを計算するのは痛苦です。ここでは関数です:

SELECT
    nivel,
    count() AS usuarios
FROM (
    SELECT
        usuario_id,
        windowFunnel(3600)(
            timestamp,
            evento = 'pageview',
            evento = 'signup',
            evento = 'purchase'
        ) AS nivel
    FROM eventos
    WHERE fecha >= today() - 30
    GROUP BY usuario_id
)
GROUP BY nivel
ORDER BY nivel;

私の最適化チェックリスト

遅いClickHouseに取り組む順序、インパクト/労力比で:

  1. system.query_logを見る、本当に遅いクエリを見つける。盲目で最適化しない。
  2. ORDER BY最も頻繁なフィルタを提供しているか確認。そうでなければ、テーブルを作り直す。痛いが、最も結果が出る。
  3. データ型を見直す。 LowCardinality、適切なサイズの整数、不要なNullableを除去。
  4. マテリアライズドビューを作る、ダッシュボードを養う集計用。
  5. インサートパターンを検証。 アクティブパートを数える。多すぎればバッチ化。
  6. コーデックを適用、最も大きい2〜3カラムに。
  7. プロジェクションを追加、2番目に一般的なアクセスパターン用。
  8. TTLとティアリングを設定、ストレージコストが制御不能に成長しないように。
  9. スキップインデックス、上記すべてでは足りず、実際の相関がある場合のみ。

ステップ1〜4で通常問題の90%が解決します。ステップ9に頻繁にいるなら、問題はおそらくスキーマ設計にあり、インデックスの不足ではありません。

最適化に関する最終的な考察

私がClickHouseで今までやった最良の最適化は、テーブルを削除することでした。

誰も直接クエリしない生イベントテーブルがありました:全員がマテリアライズドビューを使っていました。「念のため」そこにあり、ディスクの大部分を占め、マージを遅くしていました。30日のTTLを付けたら、インサート、クエリ、バックアップのすべてが改善しました。

ClickHouseでの誘惑はチューニングです。コーデック、インデックス、プロジェクション、マージ設定など、レバーが多すぎるため、15%の利益のためにパラメータ調整に何週間も費やすのは簡単です。ほぼ常に、目の前にあって10倍をもたらす設計の決定があります:選び方を間違えたソートキー、存在しないマテリアライズドビュー、あるいは保存すべきでないデータです。

パラメータの前に設計を最適化してください。 そして常に測る:各クエリの最後の「processed N rows」の行が、設定がどれほど優雅でも真実を語ります。


よくある質問

ClickHouseでORDER BYをどう選びますか?

ほぼすべてのクエリでフィルタするカラムを先頭に置き、使用頻度が同じなら高カーディナリティより低カーディナリティを前にします。send_logs_level='trace'で選ばれたマークの数を見て結果を検証してください。

マテリアライズドビューとプロジェクションの違いは?

マテリアライズドビューは独立したテーブルに書き込み、インサート時に発火し、変換や独自のTTLを許可します。プロジェクションは同じテーブル内に代替コピーを保存し、ClickHouseがクエリを変えずに自動的に選びます。

なぜClickHouseが「Too many parts」エラーを出すのですか?

小さすぎるバッチで、または頻繁すぎるインサートをしています。少なくとも1,000行のバッチにまとめるか、async_insert = 1を有効にしてください。

OPTIMIZE TABLE FINALを使うべきですか?

一般論として、いいえ。非常に高価なマージを強制し、問題の原因を直しません。公式ドキュメントは避けることを推奨しています。

どの圧縮コーデックを使うべきですか?

一般的なベースにはZSTD(1)、シーケンシャルなタイムスタンプと整数にはZSTDと組み合わせたDelta、センサーfloatにはGorilla。常に前後でsystem.columnsで測ってください。

ClickHouseのストレージコストをどう下げますか?

古いデータを削除するTTL、履歴の解像度を下げるGROUP BY付きTTL、そしてコールドデータをS3に移す階層型ストレージポリシー。

関連記事

興味を持ちそうな関連コンテンツを探し続けましょう

ClickHouse高度ガイド:最適化、マテリアライズドビュー、実戦のコツ