メインコンテンツへスキップ
このセクションでは、一般的なシナリオを通じて、アナライザクエリプロファイリングNullable カラムを避ける などの各種パフォーマンス改善・最適化手法をどのように活用し、ClickHouse のクエリパフォーマンスを向上させるかを説明します。

クエリのパフォーマンスを理解する

パフォーマンス最適化を考える最適なタイミングは、ClickHouse に初めてデータを取り込む前、データスキーマを設計している段階です。  とはいえ、データがどれほど増えるか、どのような種類のクエリが実行されるかを予測するのは簡単ではありません。  すでにデプロイメントがあり、改善したいクエリがいくつかある場合、まずはそれらのクエリがどのようなパフォーマンスを示しているのか、また、数ミリ秒で実行されるものもあれば、より時間がかかるものもあるのはなぜかを理解することが重要です。 ClickHouse には、クエリがどのように実行されているか、そしてその実行にどのようなリソースが消費されているかを把握するのに役立つ、豊富なツールが用意されています。  このセクションでは、そうしたツールとその使い方を見ていきます。 

一般的な考慮事項

クエリのパフォーマンスを理解するために、まずクエリの実行時に ClickHouse 内部で何が起きているのかを見ていきましょう。  以下では意図的に単純化し、一部は説明を省略しています。ここでの目的は細部まで説明することではなく、基本概念をすばやくつかんでもらうことです。詳しくは、クエリアナライザ を参照してください。  大まかに言うと、ClickHouse がクエリを実行する際には、次のような処理が行われます。 
  • クエリのパースと分析
クエリがパース・分析され、汎用的なクエリ実行プランが作成されます。 
  • クエリ最適化
クエリ実行プランが最適化され、不要なデータが削減され、クエリプランからクエリパイプラインが構築されます。 
  • クエリパイプラインの実行
データが読み込まれ、並列に処理されます。これは、ClickHouse がフィルタリング、集計、ソートといったクエリ操作を実際に実行する段階です。 
  • 最終処理
結果がマージ、ソートされ、クライアントに送信される前に最終結果としてフォーマットされます。 実際には、多くの最適化が行われています。このガイドでもそれらについてもう少し詳しく説明しますが、ひとまずは、これらの主要な概念を押さえておけば、ClickHouse がクエリを実行するときに内部で何が起きているのかを十分に理解できます。  この大まかな理解を踏まえて、次に ClickHouse が提供するツールと、それらを使ってクエリのパフォーマンスに影響するメトリクスをどのように追跡できるかを見ていきましょう。 

データセット

クエリ性能へのアプローチを説明するために、実際の例を使います。  NYC のタクシー乗車データを含む NYC Taxi データセットを使ってみましょう。まず、最適化を一切行わずに NYC Taxi データセットを取り込みます。 以下は、テーブルを作成し、S3バケットからデータを挿入するコマンドです。なお、ここではあえてデータからスキーマを推定しており、最適化はされていません。
データから自動的に推論されたテーブルスキーマを見てみましょう。

遅いクエリを見つける

クエリログ

デフォルトでは、ClickHouse は実行された各クエリに関する情報を収集し、クエリログに記録します。このデータは system.query_log テーブルに保存されます。  ClickHouse は、実行された各クエリについて、クエリの実行時間、読み取った行数、CPU、メモリ使用量、ファイルシステムキャッシュのヒット数などのリソース使用量といった統計情報を記録します。  そのため、低速なクエリを調査する際は、まずクエリログを確認するのがよいでしょう。実行に時間のかかっているクエリを簡単に特定でき、それぞれのリソース使用量も確認できます。  それでは、NYC taxi データセットで実行時間が長いクエリの上位 5 件を見つけてみましょう。
フィールド query_duration_ms は、そのクエリの実行にどれくらい時間がかかったかを示します。クエリログの結果を見ると、最初のクエリの実行に 2967ms かかっており、改善の余地があることがわかります。  また、メモリや CPU を最も多く消費しているクエリを調べることで、どのクエリがシステムに負荷をかけているのかを把握したい場合もあるでしょう。 
見つかった長時間実行されているクエリを切り分け、応答時間を把握するためにそれらを数回再実行してみましょう。  この時点では、再現性を高めるために、enable_filesystem_cache 設定を 0 にしてファイルシステムキャッシュを無効にすることが重要です。
見やすいように表に要約します。 これらのクエリで何を実現しているのか、もう少し詳しく見てみましょう。 
  • クエリ 1 は、平均速度が時速 30 マイルを超える乗車について、距離の分布を計算します。
  • クエリ 2 は、週ごとの乗車回数と平均コストを求めます。 
  • クエリ 3 は、データセット内の各移動の平均所要時間を計算します。
これらのクエリはいずれも、クエリ 1 がクエリを実行するたびに移動時間をその場で計算している点を除けば、それほど複雑な処理をしているわけではありません。しかし、どのクエリも実行に 1 秒以上かかっており、ClickHouse の世界ではこれは非常に長い時間です。これらのクエリのメモリ使用量にも注目できます。各クエリで 400 MiB 前後というのは、かなり大きなメモリ消費です。また、各クエリはいずれも同じ行数 (つまり 3 億 2904 万行) を読み取っているように見えます。このテーブルに何行あるのか、さっと確認してみましょう。
このテーブルには3億2,904万行が含まれているため、各クエリでテーブル全体のフルスキャンが行われています。

EXPLAINステートメント

実行時間の長いクエリが特定できたところで、それらがどのように実行されているかを理解しましょう。ClickHouse では、EXPLAIN ステートメントコマンドがサポートされています。これは、クエリを実際に実行することなく、すべてのクエリ実行ステージを詳細に確認できる非常に便利なツールです。ClickHouse に精通していないユーザーには情報量が多く感じられるかもしれませんが、クエリがどのように実行されるかを把握するうえで欠かせないツールです。 EXPLAINステートメントの概要とクエリ実行の分析方法については、ドキュメントに詳細なガイドがあります。ここではガイドの内容を繰り返すのではなく、クエリ実行パフォーマンスのボトルネックを特定するのに役立つコマンドをいくつか紹介します。 Explain indexes = 1 まず、EXPLAIN indexes = 1 を使用してクエリプランを確認しましょう。クエリプランは、クエリがどのように実行されるかを示すツリー構造です。クエリ内の各句がどの順序で実行されるかをここで確認できます。EXPLAIN ステートメントが返すクエリプランは、下から上に向かって読みます。 最初の長時間クエリを試してみましょう。
出力はシンプルです。クエリはまず nyc_taxi.trips_small_inferred テーブルからデータを読み取ります。次に、WHERE 句を適用して計算済みの値に基づいて行をフィルタリングします。フィルタリングされたデータは集計用に準備され、分位点が計算されます。最後に、結果がソートされて出力されます。 ここで、主キーが使用されていないことがわかります。これはテーブル作成時に主キーを定義しなかったため、当然のことです。その結果、ClickHouse はクエリに対してテーブルのフルスキャンを実行しています。 Explain Pipeline (実行計画パイプライン) EXPLAIN Pipelineは、クエリの具体的な実行戦略を示します。ここでは、先ほど確認した汎用的なクエリプランをClickHouseが実際にどのように実行したかを見ることができます。
ここで、クエリの実行に使用されたスレッド数が59であることを確認できます。これは高度な並列化を示しています。これによりクエリの実行が高速化されますが、スペックの低いマシンでは時間がかかります。並列に実行されるスレッド数が多いことが、クエリが消費するメモリ量の多さの要因となっている可能性があります。 理想的には、すべての低速クエリを同じ方法で調査し、不必要に複雑なクエリプランを特定するとともに、各クエリが読み取る行数と消費リソースを把握することが推奨されます。

進め方

本番デプロイメントで問題のあるクエリを特定するのは難しいことがあります。ClickHouse のデプロイメントでは、常に非常に多くのクエリが実行されている可能性が高いためです。  問題が発生しているユーザー、データベース、またはテーブルが分かっている場合は、system.query_logsusertablesdatabases フィールドを使って検索対象を絞り込めます。  最適化したいクエリを特定したら、そのクエリの改善に着手できます。この段階で開発者がよく犯すミスの 1 つは、複数の変更を同時に加え、場当たり的な実験を行い、結果として評価が混在してしまうことです。さらに重要なのは、何がクエリを高速化したのかを十分に理解できなくなることです。  クエリ最適化には、体系立った進め方が必要です。高度なベンチマークの話ではありません。変更がクエリ性能にどう影響するかを理解するためのシンプルな手順を用意するだけでも、大きな効果があります。  まずクエリログから遅いクエリを特定し、その後、改善の可能性を個別に調査します。クエリをテストするときは、必ずファイルシステムキャッシュを無効にしてください。 
ClickHouse は、クエリ性能を向上させるために、さまざまな段階でキャッシュを活用しています。これはクエリ性能には有益ですが、トラブルシューティング時には、潜在的な I/O ボトルネックや不適切なテーブルスキーマを見えにくくしてしまう可能性があります。そのため、テスト中はファイルシステムキャッシュをオフにすることをお勧めします。本番環境では有効にしておいてください。
最適化の候補を特定したら、それぞれが性能にどう影響するかをより正確に把握できるよう、1 つずつ実装することをお勧めします。以下は一般的な進め方を示した図です。 最後に、外れ値には注意してください。ユーザーが場当たり的に高コストなクエリを試したり、別の理由でシステムに負荷がかかっていたりして、クエリの実行が遅くなることは珍しくありません。normalized_query_hash フィールドでグループ化すると、継続的に実行されている高コストなクエリを特定できます。そうしたクエリこそ、優先的に調査すべき対象である可能性が高いでしょう。

基本的な最適化

テスト用の基盤が整ったので、最適化を始めましょう。 まず確認すべきなのは、データがどのように保存されているかです。どのデータベースでも同様に、読み取るデータが少ないほど、クエリの実行は速くなります。  データをどのように取り込んだかによっては、取り込みデータに基づいてテーブルのスキーマを推定するために、ClickHouseの機能を活用しているかもしれません。これは使い始めるには非常に便利ですが、クエリのパフォーマンスを最適化したいのであれば、ユースケースに最適な形になるよう、データスキーマを見直す必要があります。

Nullable

ベストプラクティスのドキュメントで説明しているとおり、可能な限り Nullable カラムは避けてください。データインジェストの仕組みがより柔軟になるため、つい多用したくなりますが、毎回追加のカラムを処理する必要があるため、パフォーマンスに悪影響を及ぼします。 NULL 値を持つ行を数える SQL クエリを実行すれば、実際に Nullable が必要なテーブル内のカラムを簡単に特定できます。
NULL 値を持つカラムは mta_taxpayment_type の 2 つだけです。残りのフィールドに Nullable カラムを使うべきではありません。

低カーディナリティ

String 型に対して手軽に行える最適化として、LowCardinality データ型を活用する方法があります。LowCardinality のドキュメントで説明されているとおり、ClickHouse は LowCardinality カラムに辞書エンコーディングを適用するため、クエリのパフォーマンスが大幅に向上します。  どのカラムが LowCardinality の適切な候補かを見極めるための簡単な目安として、一意の値が 10,000 未満のカラムは最適な候補です。 次の SQL クエリを使用すると、一意の値の数が少ないカラムを見つけることができます。
カーディナリティが低いため、これら4つのカラム (ratecode_idpickup_location_iddropoff_location_idvendor_id) は、LowCardinalityフィールド型に適した候補です。

データ型を最適化する

ClickHouseは多数のデータ型をサポートしています。パフォーマンスを最適化し、ディスク上のデータ使用量を削減するため、用途に合った範囲でできるだけ小さいデータ型を選択してください。  数値については、データセット内の最小値と最大値を確認し、現在の精度が実際のデータセットに見合っているかを確認できます。 
日付については、データセットに合った精度を選び、実行予定のクエリに最も適したものにしてください。

最適化を適用する

最適化したスキーマを使用する新しいテーブルを作成し、データを再度取り込みましょう。
改善を確認するため、新しいテーブルを使ってもう一度クエリを実行します。  クエリ時間とメモリ使用量の両方で、一定の改善が見られます。データスキーマを最適化したことで、データの表現に必要な総データ量が減り、その結果、メモリ消費量の改善と処理時間の短縮につながっています。  違いを確認するため、テーブルのサイズを見てみましょう。 
新しいテーブルは、前のテーブルよりもかなり小さくなっています。テーブルのディスク使用量は約34%減少しています (7.38 GiB に対して 4.89 GiB) 。

主キーの重要性

ClickHouse の主キーは、従来の多くのデータベースシステムにおける主キーとは役割が異なります。そうしたシステムでは、主キーは一意性とデータ整合性を保証します。重複する主キー値を挿入しようとすると拒否され、通常は高速なルックアップのために B-tree またはハッシュベースの索引が作成されます。  ClickHouse では、主キーの目的は異なります。主キーは一意性を保証せず、データ整合性にも寄与しません。代わりに、クエリ性能を最適化するために設計されています。主キーは、データがディスク上に格納される順序を定義し、各 granule の先頭の行へのポインタを保持するスパースインデックスとして実装されます。
ClickHouse における granule は、クエリ実行時に読み取られるデータの最小単位です。granule には、index_granularity によって決まる固定数までの行が含まれ、デフォルト値は 8192 行です。granule は連続して格納され、主キー順にソートされます。 
パフォーマンスの観点から、適切な主キーの組み合わせを選ぶことは重要です。実際、特定のクエリ群を高速化するために、同じデータを異なる table に格納し、それぞれで異なる主キーの組み合わせを使うことは珍しくありません。  Projection や materialized view など、ClickHouse がサポートするほかの選択肢を使えば、同じデータに対して異なる主キーの組み合わせを利用できます。このブログシリーズの第 2 部では、これについてさらに詳しく説明します。 

主キーを選ぶ

適切な主キーの組み合わせを選ぶのは複雑なテーマであり、最適な組み合わせを見つけるには、トレードオフを見極めながら試行錯誤が必要になる場合があります。  ここでは、ひとまず次のシンプルな指針に従います。 
  • ほとんどのクエリでフィルタに使うフィールドを選ぶ
  • カーディナリティの低いカラムから先に選ぶ 
  • タイムスタンプを含むデータセットでは時間でフィルタすることが多いため、主キーに時間ベースの要素を含めることを検討する。 
この例では、passenger_countpickup_datetimedropoff_datetime を主キーとして試します。  passenger_count のカーディナリティは小さく (一意な値は 24 個) 、低速なクエリでも使われています。また、タイムスタンプのフィールド (pickup_datetimedropoff_datetime) も、頻繁にフィルタされるため追加します。 主キーを設定した新しいテーブルを作成し、データを再度取り込みます。
次に、クエリを再実行します。3 回の実験結果をまとめ、経過時間、処理行数、メモリ消費量がどのように改善されたかを確認します。 
クエリ 1
実行 1実行 2実行 3
経過時間1.699 sec1.353 sec0.765 sec
処理行数329.04 million329.04 million329.04 million
ピークメモリ440.24 MiB337.12 MiB444.19 MiB
クエリ 2
実行 1実行 2実行 3
経過時間1.419 sec1.171 sec0.248 sec
処理行数329.04 million329.04 million41.46 million
ピークメモリ546.75 MiB531.09 MiB173.50 MiB
クエリ 3
1回目2回目3回目
経過時間1.414 sec1.188 sec0.431 sec
処理行数329.04 million329.04 million276.99 million
ピークメモリ451.53 MiB265.05 MiB197.38 MiB
実行時間とメモリ使用量の両面で、全体的に大きな改善が見られます。  クエリ 2 が主キーの恩恵を最も大きく受けています。生成されるクエリプランが以前とどう違うのかを見てみましょう。
主キーにより、テーブルのグラニュールの一部だけが選択されました。これだけでも、ClickHouse が処理するデータ量を大幅に減らせるため、クエリのパフォーマンスは大きく向上します。

次のステップ

このガイドが、ClickHouse で遅いクエリを調査する方法と、それを高速化する方法についての理解を深める一助になれば幸いです。このテーマをさらに掘り下げるには、ClickHouse がクエリを実際にどのように実行しているのかをよりよく理解するために、クエリアナライザプロファイリング も参照してください。 ClickHouse の特性に慣れてきたら、クエリを高速化するために使える、より高度な手法を学ぶために、パーティションキーデータスキッピングインデックス について読むことをお勧めします。
最終更新日 2026年6月19日