データの管理
Observability 用の ClickHouse のデプロイには、管理が必要な大規模なデータセットが不可欠です。ClickHouse はデータ管理を支援するための多くの機能を提供しています。
パーティション
ClickHouse のパーティショニングは、データをカラムまたは SQL 式に従って論理的にディスク上で分離することを可能にします。データを論理的に分離することで、各パーティションを独立して操作できるようになり、例えば削除することができます。これにより、ユーザーはパーティションを移動し、効率的にストレージ階層間でサブセットを移動することができます。また、データを期限切れにする/クラスターから効率的に削除することも可能です。
パーティショニングは、初期に PARTITION BY
句を介してテーブルで指定されます。この句には、任意のカラムの SQL 式を含めることができ、その結果が行が送信されるパーティションを定義します。

データパーツは、ディスク上の各パーティションに論理的に関連付けられ(共通のフォルダ名プレフィックスを介して)、独立してクエリを実行できます。以下の例では、デフォルトの otel_logs
スキーマは toDate(Timestamp)
の式を使用して日単位でパーティショニングされています。ClickHouse に行が挿入されると、この式は各行に対して評価され、存在する場合は結果のパーティションにルーティングされます(行がその日の最初のものであれば、パーティションが作成されます)。
パーティションに対しては、バックアップ、カラム操作、ミューテーションによるデータの変更/削除、およびインデックスクリア(例:二次インデックス)など、さまざまな操作を行うことができます。
例えば、otel_logs
テーブルが日単位でパーティショニングされていると仮定します。構造化されたログデータセットで埋められると、これは数日分のデータを含むことになります。
現在のパーティションは、シンプルなシステムテーブルクエリを使用して確認できます。
別のテーブル otel_logs_archive
を持っている場合、古いデータを格納するために使用します。このテーブルにデータを効率的に(これはメタデータの変更に過ぎません)移動できます。
これは、他の技術(INSERT INTO SELECT
を使用し、新しいターゲットテーブルにデータを書き込む)を使用する必要がある場合とは対照的です。
テーブル間のパーティション移動には、いくつかの条件が満たされる必要があります。まず、テーブルは同じ構造、パーティションキー、主キー、インデックス/プロジェクションを持っている必要があります。ALTER
DDL でパーティションを指定する方法に関する詳細なメモは、こちらで確認できます。
さらに、データはパーティション単位で効率的に削除できます。これは、代替技術(ミューテーションまたは軽量削除)よりもはるかにリソース効率が良く、推奨されるべきです。
この機能は、設定 ttl_only_drop_parts=1
を使用した際に TTL によって活用されます。詳細については TTL を使用したデータ管理 を参照してください。
アプリケーション
上記は、データがパーティション単位で効率的に移動および操作可能であることを示しています。実際には、ユーザーは Observability のユースケースでパーティション操作を最も頻繁に利用する場面が二つあります。
- 階層アーキテクチャ - ストレージ階層間でデータを移動させる(ストレージ階層を参照)ことによって、ホット・コールドアーキテクチャを構築できるようになります。
- 効率的な削除 - データが指定された TTL に達したとき(TTL を使用したデータ管理を参照)。
これら二つについて、以下で詳細に探ります。
クエリパフォーマンス
パーティションはクエリのパフォーマンスを助けることができますが、これはアクセスパターンに大きく依存します。クエリがごく少数のパーティション(理想的には1つ)だけをターゲットにする場合、パフォーマンスが向上する可能性があります。これは通常、パーティショニングキーが主キーに含まれていない場合で、なおかつそれでフィルタリングを行っている時のみ有益です。しかし、多くのパーティションをカバーする必要があるクエリは、パーティショニングを使用しない場合よりもパフォーマンスが悪化することがあります(パーツが多くなる可能性があるため)。単一のパーティションを対象とする利点は、パーティショニングキーがすでに主キーの初期項目である場合には、さらに見えづらく、ほとんど存在しないことでしょう。パーティショニングは、もし各パーティション内の値がユニークであれば、GROUP BY クエリの最適化にも利用できます。しかし、一般にユーザーは主キーが最適化されていることを確認し、特定の予測可能なデータサブセットにアクセスするパターンがあるような例外的な場合にのみクエリの最適化手法としてパーティショニングを検討するべきです。例えば、日ごとにパーティショニングを行い、ほとんどのクエリが最終日のものであるようなケースです。この動作の例についてはこちらを参照ください。
TTL(有効期限)によるデータ管理
Time-to-Live (TTL) は、ClickHouse によって駆動される Observability ソリューションにおいて、効率的なデータ保持と管理のための重要な機能です。膨大なデータが継続的に生成されている状況下において、TTL を ClickHouse に実装することで、古いデータの自動的な期限切れおよび削除が可能になり、ストレージが最適に使用され、パフォーマンスが維持されることが手動での介入なしに実現できます。この機能は、データベースを引き締めておくため、ストレージコストを削減し、常に最も関連性の高く、最新のデータに焦点を当てることで、クエリが迅速で効率的に保たれることに重要です。さらに、データライフサイクルを体系的に管理することによって、データ保持ポリシーに準拠するのに役立ちます。これにより、Observability ソリューションの全体的な持続可能性とスケーラビリティが向上します。
TTL は、ClickHouse 内でテーブルレベルまたはカラムレベルのいずれかで指定できます。
テーブルレベル TTL
ログとトレースのデフォルトスキーマには、指定された期間後にデータが期限切れとなる TTL が含まれています。これは ClickHouse エクスポータの ttl
キーの下で指定されます。例:
この構文は現在、Golang Duration 構文をサポートしています。ユーザーは h
を使用し、これがパーティショニング期間と一致するようにすることを推奨します。たとえば、日ごとにパーティショニングを行う場合、72h のように日数の倍数であることを確認してください。 これにより、TTL がテーブルに自動的に追加されます。例として ttl: 96h
の場合。
デフォルトでは、有効期限が切れた TTL を持つデータは、ClickHouse がデータパーツをマージする際に削除されます。ClickHouse がデータが期限切れであることを検出すると、オフスケジュールマージを実行します。
TTL は即座には適用されず、上で述べたようにスケジュールに基づいて適用されます。MergeTree テーブル設定 merge_with_ttl_timeout
は、削除 TTL を持つマージを繰り返す前の最小遅延を秒単位で設定します。デフォルト値は 14400 秒(4 時間)です。しかしそれは最小遅延に過ぎず、TTL マージがトリガーされるまでにさらに長い時間がかかることがあります。値が低すぎると、多くのオフスケジュールマージが実行され、多くのリソースを消費する可能性があります。ALTER TABLE my_table MATERIALIZE TTL
コマンドを使用して TTL の期限切れを強制することができます。
重要: 設定 ttl_only_drop_parts=1
を使用することを推奨します(デフォルトのスキーマによって適用されます)。この設定が有効な場合、ClickHouse はすべての行が期限切れのときにパーツ全体を削除します。部分的なクリーンアップを行うのではなく(これにはリソース集中的なミューテーションが必要)、全体を削除することで merge_with_ttl_timeout
の時間を短縮し、システムパフォーマンスへの影響を低くすることができます。データが TTL 有効期限切れを行う単位でパーティショニングされている場合(例えば日単位)、パーツは定義された間隔のデータのみを自然に含むことになります。これにより、ttl_only_drop_parts=1
を効率的に適用できることが保証されます。
カラムレベル TTL
上記の例では、テーブルレベルでデータの有効期限切れを設定しています。ユーザーは、カラムレベルでデータを期限切れにすることも可能です。データが古くなるにつれて、調査での価値がそのリソースオーバーヘッドを正当化しないカラムを削除するために使用されます。たとえば、新しい動的メタデータが挿入時に抽出されていない場合に備え、Body
カラムを保持することを推奨します(例:新しい Kubernetes ラベル)。一定期間後(例えば 1 か月)、この追加メタデータが役に立たないことが明らかになる場合があるため、Body
カラムを保持することの価値が低くなります。
以下に、30 日後に Body
カラムを削除する方法を示します。
カラムレベル TTL を指定するためには、ユーザーが独自のスキーマを指定する必要があります。これは OTel コレクタ内では指定できません。
データの再圧縮
通常、Observability データセットには ZSTD(1)
を推奨しますが、ユーザーはさまざまな圧縮アルゴリズムや圧縮レベル(例:ZSTD(3)
)を試すことができます。これをスキーマ作成時に指定することができるだけでなく、設定された期間の後に変更するように構成することもできます。コーデックや圧縮アルゴリズムが圧縮を改善する一方でクエリパフォーマンスを悪化させる場合に適切であるかもしれません。このトレードオフは、稀にしかクエリされない古いデータには容認できるかもしれませんが、最近のデータには頻繁に使用されるため適さないでしょう。
以下に、データを削除するのではなく、4 日後に ZSTD(3)
で圧縮する例を示します。
ユーザーは常に異なる圧縮レベルやアルゴリズムの挿入およびクエリパフォーマンスへの影響を評価することを推奨します。たとえば、デルタコーデックはタイムスタンプの圧縮に役立つ場合があります。しかし、これらが主キーの一部である場合、フィルタリングパフォーマンスが低下する可能性があります。
TTL の構成に関するさらなる詳細や例については、こちらをご覧ください。TTL をテーブルおよびカラムに追加および修正する方法の例については、こちらをご覧ください。ホット・ウォームアーキテクチャなどのストレージ階層を TTL が可能にする方法については、ストレージ階層を参照してください。
ストレージ階層
ClickHouse では、ユーザーは異なるディスク上にストレージ階層を作成できます。たとえば、SSD 上のホット/最近データと S3 でバックアップされた古いデータ。これにより、調査での使用が少ないため、古いデータにはより高いクエリ SLA が必要とされる安価なストレージを使用できます。
ClickHouse Cloud では、S3 にバックアップされたデータの単一コピーを使用し、SSD バックアップされたノードキャッシュがあります。したがって、ClickHouse Cloud におけるストレージ階層は必要ありません。
ストレージ階層を作成するには、ユーザーがディスクを作成し、それを使用してストレージポリシーを策定する必要があります。ボリュームはテーブル作成時に指定できます。データは、フィルレート、パーツサイズ、ボリュームの優先度に基づいて、ディスク間で自動的に移動できます。詳細な情報は、こちらをご覧ください。
データは、ALTER TABLE MOVE PARTITION
コマンドを使用して手動でディスク間で移動できますが、TTL を使用してボリューム間のデータ移動を制御することもできます。完全な例は、こちらで確認できます。
スキーマ変更の管理
ログおよびトレースのスキーマは、ユーザーが異なるメタデータやポッドラベルを持つ新しいシステムを監視するにつれて、システムのライフサイクルを通じて必然的に変化します。OTel スキーマを使用してデータを生成し、元のイベントデータを構造化形式でキャプチャすることにより、ClickHouse のスキーマはこれらの変更に対して頑丈になります。しかし、新しいメタデータが利用可能になり、クエリアクセスパターンが変化するにつれて、ユーザーはこれらの開発を反映するためにスキーマを更新したいと考えます。
スキーマ変更を行う際にダウンタイムを避けるために、ユーザーは以下のいくつかのオプションを持っています。
デフォルト値を使用する
カラムに DEFAULT
値を使ってスキーマに追加できます。INSERT 時に指定されていない場合は、指定されたデフォルトが使用されます。
スキーマ変更は、これらの新しいカラムが送信される原因となる、マテリアライズドビューの変換ロジックや OTel コレクタ構成を変更する前に行うことができます。
スキーマが変更された後、ユーザーは OTel コレクタを再構成できます。"Extracting structure with SQL"(こちらを参照)で説明されている推奨プロセスを使用している場合、OTel コレクタは、ターゲットスキーマを抽出し、その結果をストレージ用のターゲットテーブルに送信する責任を持つマテリアライズドビューに Null テーブルエンジンにデータを送信します。このビューは、ALTER TABLE ... MODIFY QUERY
構文を使用して変更することができます。次のように、OTel 構造化ログからターゲットスキーマを抽出するための対応するマテリアライズドビューを持つターゲットテーブルを仮定します。
LogAttributes から新しいカラム Size
を抽出したい場合、デフォルト値を指定して ALTER TABLE
でスキーマに追加できます。
上記の例では、デフォルトを LogAttributes
の size
キーとして指定しています(存在しない場合は 0 になります)。これは、値が挿入されていない行のクエリでは、アクセスする必要があるため、Map にアクセスしあます。そのため、遅くなります。また、定数(例えば 0)として指定することで、値がない行に対する subsequent クエリのコストを削減することができます。このテーブルをクエリすると、Map から期待通りに値が populated されていることが示されます。
すべての将来のデータに対してこの値が挿入されることを保証するために、次のように ALTER TABLE
構文を使用してマテリアライズドビューを修正できます。
以降の行は、挿入時に Size
カラムに値が populate されます。
新しいテーブルを作成する
上記のプロセスの代わりに、ユーザーは単に新しいターゲットテーブルを新しいスキーマで作成することができます。すべてのマテリアライズドビューは、上記の ALTER TABLE MODIFY QUERY
を使用して新しいテーブルを使用するように変更することができます。このアプローチにより、ユーザーはテーブルのバージョン管理を行うことができ、例えば otel_logs_v3
のようにできます。
このアプローチでは、ユーザーはクエリするための複数のテーブルを持つことになります。テーブルを横断してクエリするために、ユーザーはワイルドカードパターンを受け入れる merge
関数 を使用できます。下記は、otel_logs
テーブルの v2 および v3 をクエリする例です。
もし、ユーザーが merge
関数を使用せず、複数のテーブルを結合したユーザー向けにテーブルを公開したい場合は、Merge テーブルエンジンを使用することができます。以下はその例です。
新しいテーブルが追加されるたびに、このテーブルは EXCHANGE
テーブル構文を使用して更新できます。例えば、v4 テーブルを追加するには、新しいテーブルを作成し、前のバージョンと原子性を持って交換することができます。