はじめに
アラートも materialized view の恩恵を受け、自動的に活用されます。
これにより、多数のアラートを実行する際の計算オーバーヘッドを削減できます。特に、アラートは通常きわめて高頻度で実行されるためです。
実行時間の短縮は、応答性とリソース消費の両面で有効です。
インクリメンタルmaterialized view とは
SELECT クエリを大幅に高速化できます。
Postgres のようなトランザクションデータベースとは異なり、ClickHouse の materialized view は保存されたスナップショットではありません。代わりに、ソーステーブルにデータブロックが挿入されるたびに、そのブロックに対してクエリを実行するトリガーとして機能します。このクエリの出力は、別のターゲットテーブルに書き込まれます。さらにデータが挿入されると、新しい部分結果が追記され、ターゲットテーブル内でマージされます。マージ後の結果は、元のデータセット全体に対して集計を実行した結果と同等です。
materialized view を使う主な目的は、ターゲットテーブルに書き込まれるデータが、集計、フィルタリング、または変換の結果になることです。ClickStack では、これらは集計専用に使用されます。こうした結果は通常、生の入力データよりもはるかに小さく、多くの場合は部分集計状態を表します。さらに、事前集計済みのターゲットテーブルはシンプルにクエリできるため、生データに対してクエリ時に同じ計算を行う場合と比べて、クエリレイテンシを大幅に低減できます。
ClickHouse の materialized view は、データがソーステーブルに流入するのに合わせて継続的に更新され、常に最新の索引のように振る舞います。これは、多くの他のデータベースにおける materialized view が静的なスナップショットであり、ClickHouse の リフレッシュ可能なマテリアライズドビュー と同様に定期的な更新が必要なのとは異なります。
インクリメンタルmaterialized view は、新しいデータが到着した際に view への変更分だけを計算し、計算を挿入時に寄せます。ClickHouse はインジェスト向けに高度に最適化されているため、挿入される各ブロックに対して view を維持する増分コストは、クエリ実行時に得られる削減効果に比べて小さく抑えられます。集計計算のコストは、読み取りのたびに毎回支払うのではなく、挿入全体に分散されます。そのため、事前集計済みの結果をクエリする方が再計算するよりもはるかに低コストであり、結果として、ペタバイト規模であっても下流の可視化に対して低い運用コストとほぼリアルタイムの性能を実現できます。
このモデルは、更新のたびに view 全体を再計算するシステムや、スケジュールされた更新に依存するシステムとは根本的に異なります。materialized view の仕組みや作成方法についてさらに詳しく知りたい場合は、上記のリンク先ガイドを参照してください。
materialized view を追加するごとに挿入時のオーバーヘッドも増えるため、使用は慎重に選ぶ必要があります。
1 つの materialized view で、異なるグルーピングに対する複数のメトリクスを計算できます。たとえば、1 分単位のバケットごとに、サービス名単位で最小値、最大値、p95 の duration を計算できます。これにより、1 つの view で 1 つだけでなく、多くの可視化に対応できます。したがって、各 view の価値を最大化し、ダッシュボードやワークフロー全体で再利用されるようにするには、メトリクスを共通の view に集約することが重要です。
高速化の対象とする可視化の選定
効果の高い可視化を特定する
- 頻繁に更新され、常時表示されるダッシュボードの可視化。たとえば、壁面ディスプレイに表示する概要レベルの監視ダッシュボードです。
- ランブックで使われる診断ワークフロー。インシデント対応中に特定のチャートを繰り返し参照し、すばやく結果を返す必要があるものです。
- HyperDX の中核的な機能。具体的には次のようなものです。
- 検索ページのヒストグラム表示。
- APM、Services、Kubernetes ビューなど、プリセットされたダッシュボードで使われる可視化。
効果と挿入時コストのバランスを取る
本番環境に移行する前に、materialized view によって生じるリソースオーバーヘッド、特に CPU 使用率、ディスク I/O、マージ処理 を必ず検証してください。materialized view は 1 つ増えるごとに挿入時の処理を増やし、追加のパーツも生成するため、マージが追いつき、パーツ数が安定した状態を維持できることを確認することが重要です。これは、オープンソース版 ClickHouse では システムテーブル と 組み込みのオブザーバビリティダッシュボード、ClickHouse Cloud では組み込みのメトリクスと ClickHouse Cloud の監視ダッシュボード を使って監視できます。過剰なパーツ数の診断と緩和については、パーツが多すぎる を参照してください。
toStartOfMinute などの関数を使って、データを時間間隔ごとにグループ化する必要があります。ただし、多くの可視化では、サービス名、スパン名、ステータスコードなど、追加の grouping key も共通して使用されます。複数の可視化で同じグループ化の次元を使う場合は、多くの場合、1 つの materialized view で対応できます。
例として (トレースの場合) :
- 時系列で見たサービス名ごとの平均 Duration -
SELECT avg(Duration), toStartOfMinute(Timestamp) as time, ServiceName FROM otel_traces GROUP BY ServiceName, time - 時系列で見たサービス名ごとのリクエスト数 -
SELECT count() count, toStartOfMinute(Timestamp) as time, ServiceName FROM otel_traces GROUP BY ServiceName, time - 時系列で見たステータスコードごとの平均 Duration -
SELECT avg(Duration), toStartOfMinute(Timestamp) as time, StatusCode FROM otel_traces GROUP BY StatusCode, time - 時系列で見たステータスコードごとのリクエスト数 -
SELECT count() count, toStartOfMinute(Timestamp) as time, StatusCode FROM otel_traces GROUP BY StatusCode, time
materialized view の作成
HyperDX 内のコンポーネントでデバッグパネルを利用できない場合は、ブラウザーのコンソールを確認できます。そこにはすべてのクエリが記録されています。
AggregatingMergeTree について、詳しい概要と例を提供しています。
以下の動画では、AggregatingMergeTree と集計関数の使い方の例を確認できます。
materialized view の例
otel_traces_1m を作成します。
otel_traces_1m_mv - の定義では、新しいデータが挿入されるたびに、これらの状態が計算されて書き込まれます:
- ターゲットテーブル。中間結果を保存するためのスキーマと集約状態の型を定義します。これらの状態をバックグラウンドで正しくマージするには、AggregatingMergeTree エンジンが必要です。
- materialized view のクエリは INSERT 時に自動的に実行されます。元のクエリと比べて、最終的な集約関数ではなく、
avgStateやquantilesStateなどの状態関数を使用します。
ClickStack で materialized view を使用する
使用するための materialized view の登録
ログソースを編集する
HyperDX で該当するログソースに移動し、設定を編集ダイアログを開きます。materialized view のセクションまでスクロールします。materialized view を追加する
Add materialized view を選択し、続いて materialized view の基盤となるデータベースとターゲットテーブルを選択します。メトリクスを選択する
ほとんどの場合、timestamp、dimension、メトリクスのカラムは自動的に推測されます。そうでない場合は、手動で指定してください。メトリクスでは、以下を対応付ける必要があります。- 元のカラム名 (例:
Duration) - materialized view 内の対応する集約カラム (例:
avg__Duration)
時間粒度を選択する
materialized view の時間粒度を選択します。たとえば 1 分です。最小日付を選択する
materialized view にデータが含まれている最小日時を指定します。これはビュー内で利用可能な最も早い timestamp を表し、インジェストが継続していることを前提とすると、通常はビューが作成された時刻です。materialized view は作成時に自動ではバックフィルされないため、作成後に挿入されたデータから生成された行のみが含まれます。
materialized view のバックフィルに関する完全なガイドは、“データのバックフィル” を参照してください。
ログソースを保存する
ログソース設定を保存します。ダッシュボードと可視化で高速化を確認する
ClickStack は、materialized view の最小タイムスタンプがクエリの時間範囲の開始時刻以下である場合にのみ、その materialized view を使用します。これにより、その view に必要なデータがすべて含まれていることが保証されます。クエリは内部的に時間ベースのサブクエリへ分割されますが、materialized view はクエリ全体に適用されるか、まったく適用されないかのいずれかです。今後の改善により、条件を満たすサブクエリに対してのみ view を選択的に使用できるようになる可能性があります。
- 最適化ステータスを確認する ダッシュボードまたは可視化を表示しているときに、稲妻または
Acceleratedアイコンを探してください。
- 緑色の稲妻 は、そのクエリが materialized view によって高速化されていることを示します。
- オレンジ色の稲妻 は、そのクエリがソーステーブルに対して実行されていることを示します。
- 最適化の詳細を確認する 稲妻アイコンをクリックすると、次の内容を示す詳細パネルが開きます。
- 使用中の materialized view: クエリに対して選択された view。推定行数も含まれます。
- スキップされた materialized view: 互換性はあるものの選択されなかった view。推定スキャンサイズも表示されます。
- 互換性のない materialized view: 使用できなかった view と、その具体的な理由。
- よくある非互換の理由を理解する 次の場合、materialized view は使用されないことがあります。
- クエリの時間範囲 の開始時刻が、view の最小タイムスタンプより前である。
- 可視化の粒度 が、view の粒度の倍数ではない。
- クエリで要求されている 集約関数 が、その view に存在しない。
- クエリで
count(if(...))のような カスタムの count 式 を使用しており、それを view の集約状態から導出できない。
可視化に対して materialized view がどのように選択されるか
EXPLAIN ESTIMATE の仕組みを使って、最も効率のよい選択肢を自動的に評価して選択します。
選択プロセスは、明確に定義された次の手順で進みます。
-
互換性を確認する
ClickStack はまず、次の点を確認して、materialized view がそのクエリに利用可能かどうかを判断します。
- 時間範囲のカバー: クエリの時間範囲が、materialized view で利用可能なデータ範囲内に完全に収まっている必要があります。
- 粒度: 可視化の時間バケットは、ビューの粒度と同じか、それより粗い必要があります。
- 集計: 要求されたメトリクスがビュー内に存在し、その集計状態から計算可能である必要があります。
-
クエリを変換する
互換性のあるビューに対しては、ClickStack はクエリを書き換え、materialized view のテーブルを対象にします。
- 集計関数は、対応する materialized columns にマッピングされます。
- 集計状態には
-Mergecombinator が適用されます。 - 時間バケット化は、ビューの粒度に合うよう調整されます。
-
最適な候補を選択する
互換性のある materialized view が複数ある場合、ClickStack は各候補に対して
EXPLAIN ESTIMATEクエリを実行し、スキャンされる推定行数とグラニュール数を比較します。推定スキャンコストが最も低いビューが選択されます。 - スムーズにフォールバックする 互換性のある materialized view がない場合、ClickStack は自動的にソーステーブルへのクエリにフォールバックします。
materialized view の選択例
otel_traces_1m:分、ServiceName、StatusCodeごとにグループ化otel_traces_1m_v2:分、ServiceName、StatusCode、SpanNameごとにグループ化
EXPLAIN ESTIMATE クエリを発行し、推定 granule 数を比較します。つまり、次のようになります。
otel_traces_1m はサイズが小さく、スキャンするグラニュール数も少ないため、自動的に選択されます。
どちらの materialized view も基となるテーブルを直接クエリするより高性能ですが、要件を満たす中で最も小さい view を選ぶことで、最高のパフォーマンスが得られます。
アラート
materialized view のバックフィル
バックフィルのアプローチ
POPULATE は避けてくださいPOPULATE コマンドを使って materialized view をバックフィルする方法は、取り込みを停止している小規模なデータセットの場合を除き、推奨されません。この処理では、POPULATE 処理の完了後に materialized view が作成されるため、ソーステーブルに挿入された行を取りこぼす可能性があります。さらに、POPULATE はすべてのデータを対象に実行されるため、大規模なデータセットでは中断やメモリ制限の影響を受けやすくなります。
INSERT INTO SELECT を使用した直接バックフィル
ビューの現在の対象範囲を確認する
バックフィルを行う前に、まず materialized VIEW にどこまでのデータがすでに含まれているかを確認します。これは、ターゲットテーブルに存在する最小の timestamp をクエリすることで確認できます。バックフィルが必要かどうかを判断する
ほとんどの ClickStack デプロイメントでは、クエリの対象は直近 24 時間のような新しいデータに集中します。このような場合、新しく作成されたビューは作成後まもなく十分に利用可能になるため、バックフィルは不要です。前の手順で返された timestamp がユースケースに対して十分古ければ、バックフィルは必要ありません。バックフィルを検討すべきなのは、次の場合に限られます。- クエリが長い過去期間にまたがることが多い。
- その期間において、そのビューがパフォーマンス上重要である。
- データセットのサイズと集計コストを踏まえて、バックフィルの実行が現実的である。
欠落している過去データをバックフィルする
バックフィルが必要な場合は、現在の最小 timestamp より前のデータを対象に、上で記録した timestamp より古いデータだけを読み取るよう変更したビューのクエリを使って、materialized VIEW のターゲットテーブルを埋めます。ターゲットテーブルは AggregatingMergeTree を使用しているため、バックフィルクエリでは最終値ではなく集計状態を挿入する必要があります。以下のクエリでは、WHERE 句を追加することで、ビュー内に存在する最も古い timestamp より前のデータだけを集計対象にしている点に注目してください。Null テーブルを使ったインクリメンタルバックフィル
INSERT INTO SELECT を使った直接的なバックフィルは現実的でなかったり、安全でなかったりすることがあります。このような場合は、インクリメンタルバックフィルのアプローチを推奨します。この方法は、インクリメンタルmaterialized view の通常の動作により近く、履歴データセット全体を一度に集計するのではなく、扱いやすいブロック単位でデータを処理します。
このアプローチが適しているのは、次のような場合です。
- バックフィルクエリがそのままでは何時間も実行されてしまう場合。
- 全体を集計した際のメモリ使用量のピークが高すぎる場合。
- バックフィル中の CPU とメモリの消費を厳密に制御したい場合。
- 中断された場合でも安全に再開できる、より耐障害性の高いプロセスが必要な場合。
バックフィル用の Null テーブルを作成する
materialized view の集計に必要なカラムだけを含む軽量な Null テーブルを作成します。これにより、I/O とメモリ使用量を最小限に抑えられます。Null テーブルに materialized view を接続する
次に、プライマリ materialized view が使用しているものと同じ集計テーブルをターゲットとする materialized view を Null テーブル上に作成します。データをインクリメンタルにバックフィルする
最後に、履歴データを Null テーブルに挿入します。materialized view はデータをブロック単位で処理し、生の行を永続化することなく、集計状態をターゲットテーブルに出力します。安全性をさらに高めるため、バックフィル用の materialized view の出力先を一時的なターゲットテーブル (たとえば
otel_traces_1m_v2) にすることも検討してください。バックフィルが正常に完了したら、パーティションを移動してプライマリのターゲットテーブルに移せます。たとえば ALTER TABLE otel_traces_1m_v2 MOVE PARTITION '2026-01-02' TO otel_traces_1m のようにします。これにより、バックフィルが中断されたり、リソース制限によって失敗したりした場合でも、簡単に復旧できます。推奨事項
粒度の選択と整合性
- 時系列チャート (x 軸が時刻の line または bar chart) : チャートで明示的に指定した粒度は、materialized view の粒度の倍数である必要があります。 たとえば、10 分粒度のチャートでは、10 分、5 分、2 分、1 分粒度の materialized view は使用できますが、20 分や 3 分粒度の VIEW は使用できません。
-
非時系列チャート (number、table、または summary chart) :
実効粒度は
(time range / 80)から算出され、HyperDX がサポートする最も近い粒度に切り上げられます。この算出された粒度も、materialized view の粒度の倍数である必要があります。
- 10 分粒度の materialized view は作成しないでください。 ClickStack はチャートとアラートで 15 分粒度をサポートしていますが、10 分粒度はサポートしていません。そのため、10 分粒度の materialized view は、一般的な 15 分粒度の可視化やアラートと互換性がありません。
- 1 分または1 時間粒度を推奨します。これらは、ほとんどのチャートおよびアラート設定と無理なく組み合わせられます。
materialized view を制限して集約する
- 1 つのログソースあたり materialized view は 20 個以下 にしてください。
- 一般的には materialized view が 10 個前後 が最適です。
- 複数の可視化で共通の次元を使う場合は、1 つの materialized view にまとめてください。
次元は慎重に選択する
- グループ化カラムを追加するたびに、ビューのサイズは大きくなります。
- クエリの柔軟性と、ストレージおよび挿入時のコストのバランスを取りましょう。
- ビューに含まれていないカラムに対するフィルタを使うと、ClickStack はソーステーブルにフォールバックします。
ヒント一般的で、ほぼ常に有用なベースラインは、サービス名と count メトリクスでグループ化した materialized viewです。これにより、検索やダッシュボードで高速なヒストグラムやサービスレベルの概要を表示できます。
集約カラムの命名規則
- パターン:
<aggFn>__<sourceColumn> - 例:
avg__Durationmax__Duration- 行数のカウントには
count__
分位点とスケッチの選択
quantilesはディスク上により大きなスケッチを生成しますが、挿入時の計算コストは低めです。quantileTDigestは挿入時の計算コストは高くなりますが、より小さなスケッチを生成するため、VIEW に対するクエリが高速になることがよくあります。
quantile(0.5)) 。生成されたスケッチは、後から別の分位点の値 (たとえば quantile(0.95)) で引き続きクエリできます。ご自身のワークロードに最適なバランスを見つけるには、実際に試してみることをおすすめします。
効果を継続的に検証する
- UI の高速化インジケーターで利用状況を確認します。
- VIEW を有効にする前後でクエリのパフォーマンスを比較します。
- リソース使用量とマージの挙動を監視します。
高度な設定
- 直近のデータは高解像度で保持し、過去のデータは粗い粒度のビューで扱う
- 概要向けのサービスレベルのビューと、詳細な診断向けの endpoint レベルのビュー
制限事項
一般的な非互換の理由
- クエリの時間範囲 クエリの時間範囲の開始時刻が materialized view の最小タイムスタンプより前の場合。VIEW は自動的にバックフィルされないため、完全にカバーしている時間範囲のクエリにしか対応できません。
-
粒度の不一致
可視化の実効粒度は、materialized view の粒度の整数倍である必要があります。具体的には次のとおりです。
- 時系列チャート (x 軸が時間の折れ線グラフまたは棒グラフ) の場合、チャートで選択した粒度は VIEW の粒度の倍数である必要があります。たとえば、10 分粒度のチャートでは、10 分、5 分、2 分、1 分の materialized view は使用できますが、20 分や 3 分粒度の VIEW は使用できません。
- 時系列以外のチャート (数値チャートまたはテーブルチャート) の場合、実効粒度は
(time range / 80)として計算し、HyperDX がサポートする最も近い粒度に切り上げたうえで、これも VIEW の粒度の倍数である必要があります。
- 未対応の集計関数 クエリが materialized view に含まれていない集計を要求している場合。使用できるのは、VIEW で明示的に計算・保存されている集計だけです。
-
カスタム count 式
count(if(...))などの式や、その他の条件付きカウントを使用するクエリは、標準的な集計状態から導出できないため、materialized view は使用できません。
設計上および運用上の制約
- 自動バックフィルなし インクリメンタルmaterialized view には、作成後に挿入されたデータしか含まれません。過去データの高速化には明示的なバックフィルが必要であり、大規模なデータセットではコストが高すぎたり、現実的でなかったりする場合があります。
- 粒度のトレードオフ 粒度が非常に細かい VIEW は、ストレージ使用量と挿入時のオーバーヘッドを増やす一方、粒度が粗い VIEW は柔軟性を下げます。粒度は、想定されるクエリパターンに合わせて慎重に選ぶ必要があります。
- 次元の爆発的増加 グループ化する次元を多く追加すると、VIEW のサイズが大幅に増加し、効果が薄れることがあります。VIEW には、一般的によく使われるグループ化用およびフィルタリング用のカラムだけを含めるべきです。
- VIEW 数のスケーラビリティの限界 materialized view を追加するたびに挿入時のオーバーヘッドが増え、マージ負荷も高まります。VIEW を作りすぎると、インジェストやバックグラウンドマージに悪影響を及ぼす可能性があります。
トラブルシューティング
materialized view が使用されていない
- 最適化モーダルを開き、「Date range not supported.」と表示されていないか確認します。
- クエリの日付範囲が materialized view の最小日付より後になっていることを確認します。
- materialized view にすべての履歴データが含まれている場合は、最小日付の設定を削除します。
- チャートの粒度が MV の粒度の倍数になっていることを確認します。
- チャートを「Auto」に設定するか、互換性のある粒度を手動で選択してみてください。
- チャートで使用している集計が MV に含まれているか確認します。
- 最適化モーダルの「Available aggregated columns」を確認します。
- group by のカラムが MV の次元カラムに含まれていることを確認します。
- 最適化モーダルの「Available group/filter columns」を確認します。
遅い materialized view クエリ
- 粒度が細かすぎるため (例: 1 秒) 、MV の行数が多くなりすぎています。
- 解決策: より粗い粒度の MV を作成します (例: 1 分または 1 時間) 。
- 次元カラムが多いため、MV のカーディナリティが高くなっています。
- 解決策: 次元カラムを、最もよく使われるものに絞ります。
- システムが各 MV に対して
EXPLAINを実行しています。 - 解決策: ほとんど使われない MV や、常にスキップされる MV を削除します。
設定エラー
- MVの設定に、少なくとも1つの集計カラムを追加してください。
- 集計対象のカラムを指定してください (ソースカラムを省略できるのは count のみです) 。
- ドロップダウンから、用意されている粒度を1つ選択してください。
- 有効な SQL の interval 形式で指定する必要があります (例:
1 hour。1 hは無効です) 。