Skip to main content
あらゆるオブザーバビリティソリューションでは、ログとトレースを収集してエクスポートする手段が必要です。この目的のために、ClickHouse は OpenTelemetry (OTel) プロジェクト を推奨しています。 「OpenTelemetry は、トレース、メトリクス、ログなどのテレメトリーデータを作成および管理するために設計された、オブザーバビリティフレームワークおよびツールキットです。」 ClickHouse や Prometheus とは異なり、OpenTelemetry はオブザーバビリティのバックエンドではなく、テレメトリーデータの生成、収集、管理、エクスポートに重点を置いています。OpenTelemetry の当初の目的は、言語ごとの SDK を使用してアプリケーションやシステムを簡単にインストルメントできるようにすることでしたが、その後、ログの収集も含むように拡張されました。これは、テレメトリーデータを受信、処理、エクスポートするエージェントまたはプロキシである OpenTelemetry collector によって実現されます。

ClickHouse 関連コンポーネント

OpenTelemetry は複数のコンポーネントで構成されています。データや API の仕様、標準化されたプロトコル、フィールド/カラムの命名規則を提供するだけでなく、OTel は ClickHouse でオブザーバビリティ ソリューションを構築するうえで不可欠な 2 つの機能も提供します。
  • OpenTelemetry Collector は、テレメトリーデータを受信、処理、エクスポートする proxy です。ClickHouse ベースのソリューションでは、このコンポーネントをログ収集と、バッチ化および insert 前のイベント処理の両方に使用します。
  • テレメトリーデータの仕様、API、エクスポートを実装する Language SDKs です。これらの SDK は、アプリケーションコード内で trace が正しく記録されるようにし、構成要素である spans を生成するとともに、metadata を介してサービス間で context が伝播されることを保証します。これにより分散 traces が形成され、spans を相関付けられるようになります。さらに、これらの SDK は一般的なライブラリやフレームワークに自動で対応するエコシステムによって補完されているため、ユーザーはコードを変更せずに、すぐに使えるインストルメンテーションを利用できます。
ClickHouse ベースのオブザーバビリティ ソリューションでは、これら 2 つのツールをいずれも活用します。

ディストリビューション

OpenTelemetry Collector には、いくつかのディストリビューションがあります。ClickHouse ソリューションに必要な filelog receiver と ClickHouse exporter が含まれているのは、OpenTelemetry Collector Contrib Distro だけです。 このディストリビューションには多くのコンポーネントが含まれているため、さまざまな構成を試すことができます。ただし、本番環境で実行する場合は、その環境に必要なコンポーネントだけを含むように collector を絞り込むことを推奨します。その理由としては、次のようなものがあります。
  • collector のサイズを小さくして、collector のデプロイ時間を短縮できる
  • アタックサーフェスを縮小することで、collector のセキュリティを向上できる
カスタム collector は、OpenTelemetry Collector Builder を使用して構築できます。

OTel を使ったデータの取り込み

collector のデプロイメントロール

ログを収集して ClickHouse に取り込むには、OpenTelemetry Collector の使用を推奨します。OpenTelemetry Collector は、主に次の 2 つのロールでデプロイできます。
  • エージェント - エージェントインスタンスは、サーバー上や Kubernetes ノード上などのエッジでデータを収集するか、OpenTelemetry SDK でインストルメントされたアプリケーションからイベントを直接受信します。後者の場合、エージェントインスタンスはアプリケーションと一緒に、またはアプリケーションと同じホスト上で実行されます (サイドカーやデーモンセットなど) 。エージェントは、データを ClickHouse に直接送信することも、ゲートウェイインスタンスに送信することもできます。前者のケースは、Agent deployment pattern と呼ばれます。
  • ゲートウェイ - ゲートウェイインスタンスは、独立したサービス (たとえば Kubernetes 上のデプロイメント) を提供し、通常はクラスターごと、データセンターごと、またはリージョンごとに配置されます。これらは、単一の OTLP エンドポイントを介して、アプリケーション (またはエージェントとして動作する他の collector) からイベントを受信します。通常は複数のゲートウェイインスタンスがデプロイされ、組み込みのロードバランサーを使用してそれらの間で負荷を分散します。すべてのエージェントとアプリケーションがシグナルをこの単一のエンドポイントに送信する場合、これはしばしば Gateway deployment pattern と呼ばれます。
以下では、イベントを ClickHouse に直接送信するシンプルなエージェント collector を前提とします。ゲートウェイの使用方法や、どのような場合に適しているかについて詳しくは、ゲートウェイによるスケーリング を参照してください。

ログの収集

collector を使用する主な利点は、サービス側でデータをすばやく引き渡し、その後の再試行、バッチ化、暗号化、さらには機微データのフィルタリングといった追加処理を Collector に任せられることです。 Collector では、3 つの主要な処理段階として receiverプロセッサエクスポーター という用語を使用します。receiver はデータの収集に使用され、pull ベースまたは push ベースのいずれかを取れます。プロセッサ はメッセージの変換やエンリッチを行います。エクスポーター はデータを下流のサービスへ送信する役割を担います。理論上、このサービスは別の collector でも構いませんが、以下の説明では、すべてのデータが ClickHouse に直接送信される前提とします。 receiver、プロセッサ、エクスポーター の全体像について事前に把握しておくことをお勧めします。 collector は、ログ収集のために 2 つの主要な receiver を提供します。 OTLP 経由 - この場合、ログは OpenTelemetry SDKs から OTLP プロトコルを介して collector に直接送信 (push) されます。OpenTelemetry demo ではこの方式が使われており、各言語の OTLP エクスポーター はローカルの collector endpoint を前提としています。この場合、collector は OTLP receiver を使うように設定する必要があります。設定例については、上記の demo を参照してください。この方式の利点は、ログデータに Trace Ids が自動的に含まれるため、後から特定のログに対応する traces を特定したり、その逆を行ったりできることです。 この方式では、ユーザーは自身のコードを対応する言語 SDKでインストルメントする必要があります。
  • Filelog receiver によるスクレイピング - この receiver はディスク上のファイルを tail し、ログメッセージを生成して ClickHouse に送信します。この receiver は、複数行メッセージの検出、ログローテーションへの対応、再起動時の耐障害性を高めるためのチェックポイント管理、構造の抽出といった複雑な処理も担います。さらに、Docker や Kubernetes のコンテナログも tail でき、Helm チャートとしてデプロイ可能で、そこから構造を抽出し、ポッドの詳細情報でエンリッチすることもできます。
ほとんどのデプロイメントでは、上記の receiver を組み合わせて使用します。collector のドキュメントを読み、基本概念に加えて、設定構造インストール方法も理解しておくことをお勧めします。
ヒント: otelbin.iootelbin.io は、設定の検証や可視化に役立ちます。

構造化ログと非構造化ログ

ログには、構造化されたものと非構造化のものがあります。 構造化ログは、JSON などのデータフォーマットを使用し、HTTP ステータスコードや送信元 IP アドレスといったメタデータのフィールドを定義します。
非構造化ログも、通常は正規表現パターンで抽出できる何らかの固有の構造を持っていますが、ログ自体は単なる文字列として表されます。
可能であれば、構造化ログを採用し、ログを JSON (つまり ndjson) 形式で出力することを推奨します。これにより、後段で必要になるログ処理が簡単になります。たとえば、collector プロセッサを使って ClickHouse に送信する前に処理する場合でも、materialized view を使ってインサート時に処理する場合でも同様です。構造化ログを使用すると、最終的に後続の処理リソースを節約でき、ClickHouse 環境で必要な CPU を削減できます。

例として、構造化 (JSON) ログと非構造化ログのデータセットを用意しています。いずれも約 10m 行で、以下のリンクから利用できます。 以下の例では、構造化データセットを使用します。以降の例を再現するには、このファイルをダウンロードして展開しておいてください。 以下は、ディスク上のこれらのファイルを filelog receiver で読み込み、生成されたメッセージを stdout に出力する OTel Collector のシンプルな構成です。ここではログが構造化されているため、json_parser operator を使用します。access-structured.log ファイルへのパスは適宜変更してください。
パースには ClickHouse の利用を検討してください以下の例では、ログから timestamp を抽出しています。これには json_parser operator を使用する必要があります。この operator はログ行全体を JSON 文字列に変換し、その結果を LogAttributes に格納します。これは計算コストが高くなる可能性がありますが、ClickHouse ではより効率的に実行できます - SQL による構造の抽出。同等の非構造化ログの例として、regex_parser を使用して同じことを実現するものをこちらで確認できます。
config-structured-logs.yaml
collector をローカルにインストールするには、公式手順に従ってください。重要なのは、contrib distribution (filelog receiver を含む) を使用するように手順を読み替えることです。たとえば、otelcol_0.102.1_darwin_arm64.tar.gz ではなく、otelcol-contrib_0.102.1_darwin_arm64.tar.gz をダウンロードします。リリースはこちらで確認できます。 インストール後、OTel collector は次のコマンドで実行できます。
構造化ログを使用する場合、出力されるメッセージは次の形式になります。
上記は、OTel collector によって生成される単一のログメッセージを表したものです。後続のセクションでは、これらと同じメッセージを ClickHouse に取り込みます。 ログメッセージの完全なスキーマは、他の receiver を使用した場合に存在しうる追加のカラムとあわせて、こちらで管理されています。このスキーマを十分に理解しておくことを強く推奨します。 ここで重要なのは、ログ行そのものは Body フィールド内に文字列として保持される一方で、json_parser によって JSON が Attributes フィールドに自動的に抽出されていることです。同じ operator を使って、タイムスタンプも適切な Timestamp カラムに抽出されています。OTel でログを処理する際の推奨事項については、処理を参照してください。
オペレーターオペレーターは、ログ処理の最も基本的な単位です。各オペレーターは、ファイルから行を読み取る、フィールドから JSON をパースするといった単一の役割を担います。その後、目的の結果を得るために、これらのオペレーターをパイプライン内で連結して使用します。
上記のメッセージには TraceID または SpanID フィールドが含まれていません。これらが存在する場合、たとえばユーザーが分散トレーシングを実装しているケースでは、上で示したのと同じ手法を使って JSON から抽出できます。 ローカルまたは Kubernetes のログファイルを収集する必要があるユーザーは、filelog receiver で利用できる設定オプションに加え、offsets複数行ログのパースがどのように処理されるか について理解しておくことを推奨します。

Kubernetesログの収集

Kubernetesログの収集については、OpenTelemetryのドキュメントガイドを参照することを推奨します。Kubernetes Attributes Processorは、ログやメトリクスにポッドのメタデータを付与するために推奨されています。これにより、ラベルなどの動的なメタデータが生成され、ResourceAttributes カラムに格納される場合があります。ClickHouseでは現在、このカラムに Map(String, String) 型を使用しています。この型の扱い方や最適化の詳細については、Using MapsおよびExtracting from mapsを参照してください。

トレースの収集

コードをインストルメントしてトレースを収集したい場合は、公式のOTel ドキュメントに従うことをお勧めします。 イベントを ClickHouse に送信するには、適切な receiver を介して OTLP プロトコルでトレースイベントを受信する OTel collector をデプロイする必要があります。OpenTelemetry Demo には、サポート対象の各言語をインストルメントする例と、イベントを collector に送信する方法が示されています。イベントを stdout に出力する適切な collector configuration の例を以下に示します。

トレースは OTLP 経由で受信する必要があるため、トレースデータの生成には telemetrygen ツールを使用します。インストールについては、こちら の手順に従ってください。 次の構成では、トレースイベントを OTLP receiver で受信し、その後 stdout に送信します。 config-traces.xml
次のコマンドでこの設定を実行します:
telemetrygen を使用してトレースイベントを collector に送信します:
これにより、以下の例のようなトレースメッセージが stdout に出力されるようになります。
上記は、OTel collector によって生成された単一のトレースメッセージを表しています。これらと同じメッセージを、後続のセクションで ClickHouse に取り込みます。 トレースメッセージの完全なスキーマはこちらで管理されています。ユーザーには、このスキーマを十分に理解しておくことを強くお勧めします。

処理 - フィルタリング、変換、エンリッチ

前述のログイベントの timestamp 設定の例で示したように、イベントメッセージは多くの場合、フィルタリング、変換、エンリッチが必要になります。これは、OpenTelemetry のさまざまな機能を使って実現できます。
  • プロセッサ - プロセッサは、receiver が収集したデータを変更または変換し、exporter に送信する前に処理します。プロセッサは、collector 設定の processors セクションで設定した順序で適用されます。これらは必須ではありませんが、最小限のセットを使うことが一般的に推奨されています。ClickHouse と組み合わせて OTel collector を使う場合は、プロセッサを次のものに絞ることを推奨します。
    • memory_limiter は、collector でメモリ不足が発生するのを防ぐために使用します。推奨事項については Estimating Resources を参照してください。
    • コンテキストに基づくエンリッチを行う任意のプロセッサ。たとえば Kubernetes Attributes Processor を使うと、spans、メトリクス、logs の resource attributes に k8s メタデータを自動的に設定できます。たとえば、イベントに送信元のポッド ID を付与してエンリッチできます。
    • traces で必要に応じた Tail または head sampling
    • 基本的なフィルタリング - 不要なイベントを破棄します。これを operator で実行できない場合に使用します (下記参照) 。
    • Batching - データをバッチ単位で送信するために、ClickHouse では不可欠です。“Exporting to ClickHouse” を参照してください。
  • Operators - Operators は、receiver で利用できる最も基本的な処理単位です。基本的なパースがサポートされており、Severity や Timestamp などのフィールドを設定できます。ここでは JSON と regex のパースに加えて、イベントのフィルタリングや基本的な変換もサポートされています。イベントのフィルタリングはここで行うことを推奨します。
operator や transform processors を使って過度なイベント処理を行うことは避けるよう推奨します。これらは、特に JSON のパースで、メモリと CPU に大きなオーバーヘッドをもたらす可能性があります。いくつかの例外を除けば、ClickHouse では materialized view とカラムを使って、insert time にすべての処理を行うことが可能です。特に例外となるのは、コンテキストを必要とするエンリッチ、たとえば k8s メタデータの追加です。詳しくは Extracting structure with SQL を参照してください。 OTel collector で処理を行う場合は、変換はゲートウェイ インスタンスで実施し、エージェント インスタンスで行う処理は最小限に抑えることを推奨します。これにより、サーバー上で動作するエッジ側のエージェントに必要なリソースを、できるだけ小さくできます。通常、users がエージェントで行うのは、フィルタリング (不要なネットワーク使用を最小限にするため) 、timestamp の設定 (operator 経由) 、およびコンテキストを必要とするエンリッチのみです。たとえば、ゲートウェイ インスタンスが別の Kubernetes クラスターにある場合、k8s のエンリッチはエージェント側で行う必要があります。

次の設定は、非構造化ログファイルの収集例です。regex_parser を使用してログ行から構造を抽出し、イベントをフィルタリングする operator と、イベントをバッチ化してメモリ使用量を制限する プロセッサ を使用している点に注目してください。 config-unstructured-logs-with-processor.yaml

ClickHouse へのエクスポート

エクスポーターは、1 つ以上のバックエンドまたは宛先にデータを送信します。エクスポーターには、プルベースとプッシュベースがあります。イベントを ClickHouse に送信するには、プッシュベースの ClickHouse exporter を使用する必要があります。
OpenTelemetry Collector Contrib を使用するClickHouse exporter はコアディストリビューションではなく、OpenTelemetry Collector Contrib の一部です。contrib ディストリビューションを使用することも、独自の collector をビルドする こともできます。
完全な設定ファイルを以下に示します。 clickhouse-config.yaml
次の重要な設定を確認してください。
  • pipelines - 上記の設定では、pipelines を使用しています。これは receiver、processor、exporter のセットで構成され、logs 用と traces 用にそれぞれ 1 つずつ定義されています。
  • endpoint - ClickHouse との通信は endpoint パラメータで設定します。接続文字列 tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1 を指定すると、TCP 経由で通信が行われます。トラフィック切り替えの都合で HTTP を使いたい場合は、こちら の説明に従ってこの接続文字列を変更してください。ユーザー名とパスワードをこの接続文字列内で指定する方法を含む完全な接続情報についても、こちら に記載されています。
重要: 上記の接続文字列では、圧縮 (lz4) と非同期挿入の両方が有効になっている点に注意してください。どちらも常に有効にすることを推奨します。非同期挿入の詳細については Batching を参照してください。圧縮は常に明示的に指定してください。古いバージョンの exporter では、デフォルトでは有効になりません。
  • ttl - ここで指定する値で、データをどれくらい保持するかが決まります。詳細は “Managing data” を参照してください。値は 72h のように、時間単位で指定する必要があります。以下の例ではデータが 2019 年のものであり、挿入すると ClickHouse によって直ちに削除されてしまうため、有効期限 (TTL) を無効にしています。
  • traces_table_namelogs_table_name - ログテーブルとトレーステーブルの名前を指定します。
  • create_schema - 起動時にデフォルトのスキーマで table を作成するかどうかを指定します。Getting Started ではデフォルトで true です。実運用では false に設定し、独自のスキーマを定義してください。
  • database - 移行先データベース。
  • retry_on_failure - 失敗した batch を再試行するかどうかを決める設定です。
  • batch - batch processor は、イベントを batch 単位で送信するためのものです。少なくとも 10,000、timeout は 5s を推奨します (メモリに余裕があれば 100,000 まで使用できます) 。このどちらかの条件に先に達した時点で、exporter へ flush する batch が開始されます。これらの値を小さくすると、データをより早くクエリできるようになり、パイプラインの latency は下がりますが、その分 ClickHouse に送信される connections と batches は増えます。非同期挿入 を使用していない場合、ClickHouse で パーツが多すぎる 問題を引き起こす可能性があるため、これは推奨されません。一方、非同期挿入を使用している場合は、クエリ可能になるまでの時間は非同期挿入の設定にも左右されますが、データ自体はより早く connector から flush されます。詳細は Batching を参照してください。
  • sending_queue - 送信 queue のサイズを制御します。queue 内の各項目には 1 つの batch が含まれます。たとえば ClickHouse に接続できない状態でもイベントが到着し続けてこの queue の上限を超えると、batches は破棄されます。
ユーザーが構造化された log file を抽出済みで、ローカルの ClickHouse インスタンス が実行中 (デフォルトの authentication を使用) であるとすると、次のコマンドでこの設定を実行できます。
このcollectorにtraceデータを送信するには、telemetrygen ツールを使って次のコマンドを実行します。
起動したら、簡単なクエリでログイベントが存在することを確認します。

標準スキーマ

ClickStack には最適化されたデフォルトスキーマがあらかじめ用意されていますClickStack は logs、traces、metrics 向けに標準で利用できるスキーマを提供しており、最新の ClickHouse 機能 (全文検索および map-key 検索向けのテキスト索引、直接読み取りフィルタリングのための materialized columns と ALIAS arrays、block-number による行ルックアップ) を取り入れています。さらに、ロギングおよび trace のワークロードで、追加設定なしでも高い性能を発揮できることがベンチマークで確認されています。独自設計を行う際の基準として活用してください。
デフォルトでは、ClickHouse exporter は logs と traces の両方について、書き込み先のテーブルを作成します。これは create_schema 設定で無効にできます。さらに、logs テーブル名と traces テーブル名は、上記の設定を使ってデフォルトの otel_logsotel_traces から変更できます。
以下のスキーマでは、有効期限 (TTL) が 72h に設定されているものとします。
logs のデフォルトスキーマを以下に示します (otelcol-contrib v0.102.1) :
ここでのカラムは、こちらに記載されているログ向けのOTel公式仕様に対応しています。 このスキーマについて、重要な注意点がいくつかあります。
  • デフォルトでは、テーブルは PARTITION BY toDate(Timestamp) によって日付単位でパーティション化されます。これにより、有効期限が切れたデータを効率よく削除できます。
  • 有効期限 (TTL) は TTL toDateTime(Timestamp) + toIntervalDay(3) で設定され、collector の設定で指定した値に対応します。ttl_only_drop_parts=1 は、含まれるすべての行の有効期限が切れた場合にのみ、パーツ全体を削除することを意味します。これは、コストの高い delete を伴うパーツ内の行削除よりも効率的です。この設定は常に有効にすることを推奨します。詳細は TTL によるデータ管理 を参照してください。
  • テーブルは標準的な MergeTree エンジン を使用します。これはログとトレースに推奨されており、通常は変更する必要はありません。
  • テーブルは ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId) で並べ替えられます。つまり、クエリは ServiceNameSeverityTextTimestampTraceId に対するフィルタに最適化されます。リスト内で前にあるカラムほど、後ろのカラムより高速にフィルタできます。たとえば、ServiceName によるフィルタは TraceId によるフィルタより大幅に高速です。想定されるアクセスパターンに応じて、この並び順を変更してください。詳しくは 主キーの選び方 を参照してください。
  • 上記のスキーマでは、カラムに ZSTD(1) を適用しています。これはログに対して最適な圧縮を提供します。より高い圧縮率を得るために ZSTD の圧縮レベル (デフォルトの 1 より上) を上げることもできますが、効果があるケースはまれです。この値を上げると、insert time の CPU オーバーヘッド (圧縮時) は増えますが、展開処理 (したがってクエリ性能) はほぼ同程度に保たれます。詳細は こちら を参照してください。さらに、ディスク上のサイズ削減を目的として、Timestamp には追加の delta encoding も適用されています。
  • ResourceAttributesLogAttributesScopeAttributes がマップである点に注目してください。これらの違いを理解することが重要です。これらのマップへのアクセス方法と、その中のキーへのアクセスを最適化する方法については、“Using maps” を参照してください。
  • ここにある他のほとんどの型も、たとえば ServiceName の LowCardinality のように最適化されています。なお、サンプルログでは JSON である Body は、String として保存されます。
  • ブルームフィルタは、マップのキーと値、および Body カラムに適用されています。これらは、これらのカラムにアクセスするクエリの実行時間短縮を目的としていますが、通常は必須ではありません。詳しくは Secondary/Data skipping indices を参照してください。
ここでも、こちらに記載されているトレース向けの OTel 公式仕様に対応するカラムと相関付けられます。ここで用いるスキーマは、上記のログ用スキーマと同様の設定を多く採用しており、さらにスパン固有の Link カラムが追加されています。 自動スキーマ作成は無効にし、テーブルは手動で作成することを推奨します。これにより、プライマリキーとセカンダリキーを変更できるほか、クエリパフォーマンスを最適化するための追加カラムを導入することもできます。詳細については、スキーマ設計を参照してください。

挿入の最適化

強い整合性保証を維持しながら高い挿入パフォーマンスを実現するには、collector 経由でオブザーバビリティデータを ClickHouse に挿入する際に、いくつかの基本的なルールに従う必要があります。OTel collector を適切に設定していれば、以下のルールは容易に守れるはずです。これにより、ClickHouse を初めて使用するユーザーが陥りがちなよくある問題を回避できます。

バッチング

デフォルトでは、ClickHouse に送信された各 insert ごとに、ClickHouse はその insert のデータと、あわせて保存が必要なその他のメタデータを含むストレージ part を即座に作成します。そのため、1 回あたりのデータ量が少ない insert を多数送るよりも、1 回あたりのデータ量が多い insert を少数送るほうが、必要な書き込み回数を減らせます。1 回につき少なくとも 1,000 行の、比較的大きなバッチでデータを insert することを推奨します。詳細はこちらを参照してください。 デフォルトでは、ClickHouse への insert は同期的で、同一内容であれば冪等です。MergeTree engine ファミリーのテーブルでは、ClickHouse はデフォルトで自動的に insert の重複排除 を行います。つまり、たとえば次のような場合でも insert に耐性があります。
  • (1) データを受信するノードで問題が発生した場合、INSERT クエリはタイムアウトするか、より具体的なエラーを返し、確認応答は返されません。
  • (2) データはノードに書き込まれたものの、ネットワークの中断によってクエリ送信元に確認応答を返せない場合、送信側ではタイムアウトまたはネットワークエラーになります。
collector の観点では、(1) と (2) を区別するのは難しいことがあります。ただし、どちらの場合でも、確認応答のない insert はすぐに再試行できます。再試行した INSERT クエリに同じデータが同じ順序で含まれている限り、確認応答のなかった元の insert が成功していれば、ClickHouse は再試行された insert を自動的に無視します。 上記の要件を満たすため、前述の設定例で示した batch processor を使用することを推奨します。これにより、上記要件を満たす一貫した行のバッチとして insert が送信されます。collector に高スループット (1 秒あたりのイベント数) が見込まれ、各 insert で少なくとも 10,000 件のイベントを送信できる場合、通常はこれだけでパイプラインに必要なバッチングは十分です。メモリに余裕があれば、100,000 まで設定できます。この場合、collector は batch processor の timeout に達する前にバッチをフラッシュするため、パイプライン全体のエンドツーエンドのレイテンシを低く保ちつつ、バッチサイズも一定に保てます。

非同期挿入 を使用する

通常、collector のスループットが低い場合、ユーザーはより小さなバッチを送らざるを得ません。それでも、データはエンドツーエンドのレイテンシをできるだけ抑えて ClickHouse に到達することが期待されます。この場合、batch processor の timeout が期限に達すると、小さなバッチが送信されます。これは問題を引き起こす可能性があり、そのような場合に非同期挿入 が必要になります。このケースは通常、エージェントの役割の collector が ClickHouse に直接送信するよう設定されている場合に発生します。ゲートウェイは集約ポイントとして機能することで、この問題を緩和できます。詳しくは ゲートウェイによるスケーリング を参照してください。 大きなバッチを保証できない場合は、非同期挿入 を使用して、batching を ClickHouse に委譲できます。非同期挿入 では、データはまずバッファに挿入され、その後データベースストレージに後から、つまり非同期に書き込まれます。 非同期挿入 を有効化すると、ClickHouse が ① INSERT クエリを受信したとき、そのクエリのデータはまず ② 直ちにメモリ内バッファに書き込まれます。③ 次回のバッファ flush が行われると、バッファ内のデータは ソート され、part としてデータベースストレージに書き込まれます。なお、データベースストレージに flush される前のデータはクエリから検索できません。バッファ flush は設定可能です。 collector で非同期挿入 を有効にするには、接続文字列に async_insert=1 を追加します。配信保証を得るため、wait_for_async_insert=1 (デフォルト) を使用することを推奨します。詳しくは こちら を参照してください。 非同期挿入 のデータは、ClickHouse のバッファが flush されると挿入されます。これは、async_insert_max_data_size を超えた後、または最初の INSERT クエリから async_insert_busy_timeout_ms ミリ秒が経過した後のいずれかで発生します。async_insert_stale_timeout_ms が 0 以外の値に設定されている場合、最後のクエリから async_insert_stale_timeout_ms milliseconds 後にデータが挿入されます。これらの設定を調整することで、pipeline のエンドツーエンド レイテンシを制御できます。バッファ flushing の調整に使用できるその他の設定は こちら に記載されています。一般的には、デフォルト値で十分です。
適応型非同期挿入 を検討する使用する agent の数が少なく、スループットは低い一方で、エンドツーエンド レイテンシ要件が厳しい場合は、adaptive asynchronous inserts が役立つことがあります。一般に、これらは ClickHouse に見られるような高スループットのオブザーバビリティのユースケースには適していません。
最後に、ClickHouse への同期 insert に関連する従来の deduplication の動作は、非同期挿入 を使用する場合にはデフォルトでは有効になりません。必要に応じて、設定 async_insert_deduplicate を参照してください。 この機能の設定に関する完全な詳細は こちら にあり、さらに詳しい解説は こちら にあります。

デプロイメント アーキテクチャ

ClickHouse で OTel collector を使用する際には、複数のデプロイメント アーキテクチャが考えられます。以下でそれぞれの構成と、どのようなケースに適しているかを説明します。

エージェントのみ

エージェントのみのアーキテクチャでは、ユーザーは OTel collector をエッジにエージェントとしてデプロイします。これらはローカルアプリケーションからトレースを受信し (例: サイドカーコンテナーとして) 、サーバーや Kubernetes ノードからログを収集します。このモードでは、エージェントはデータを ClickHouse に直接送信します。 このアーキテクチャは、小規模から中規模のデプロイメントに適しています。最大の利点は、追加のハードウェアが不要で、アプリケーションと collector の対応関係をシンプルに保ちながら、ClickHouse オブザーバビリティソリューション全体のリソース使用量を最小限に抑えられることです。 エージェント数が数百を超えるようになったら、ゲートウェイベースのアーキテクチャへの移行を検討してください。このアーキテクチャには、スケールを難しくするいくつかの欠点があります。
  • 接続数のスケーリング - 各エージェントは ClickHouse への接続を確立します。ClickHouse は数百、場合によっては数千の同時実行 insert 接続を維持できますが、最終的にはこれが制約要因となり、insert の効率も低下します。つまり、ClickHouse は接続の維持により多くのリソースを費やすことになります。ゲートウェイを使用すると接続数を最小限に抑えられ、insert の効率も向上します。
  • エッジでの処理 - このアーキテクチャでは、あらゆる変換やイベント処理をエッジ側または ClickHouse 内で実行する必要があります。これは制約が大きいだけでなく、複雑な ClickHouse materialized view が必要になったり、重要なサービスに影響する可能性があり、しかもリソースが限られるエッジ側に大きな計算負荷をかけたりすることを意味します。
  • 小さなバッチとレイテンシー - エージェント collector は、それぞれが収集するイベント数がごく少ない場合があります。通常これは、配信 SLA を満たすために、一定の間隔で flush するよう設定する必要があることを意味します。その結果、collector が ClickHouse に小さなバッチを送信することがあります。これは欠点ではありますが、非同期挿入によって軽減できます。詳細は insert の最適化 を参照してください。

ゲートウェイによるスケーリング

上記の制約に対処するために、OTel collector はゲートウェイのインスタンスとしてデプロイできます。これにより、通常はデータセンター単位またはリージョン単位で、独立したサービスを提供できます。これらは、単一の OTLP エンドポイントを介して、アプリケーション (またはエージェントの役割の他の collector) からイベントを受信します。一般的には複数のゲートウェイインスタンスをデプロイし、標準のロードバランサーを使用してそれらの間で負荷を分散します。 このアーキテクチャの目的は、計算負荷の高い処理を agent からオフロードし、agent のリソース使用量を最小限に抑えることです。これらのゲートウェイは、本来 agent 側で実行する必要がある変換タスクを担えます。さらに、多数の agent からイベントを集約することで、ゲートウェイは大きなバッチを ClickHouse に送信でき、効率的な挿入が可能になります。これらのゲートウェイ collector は、agent の追加やイベントの throughput の増加に応じて容易にスケールできます。以下に、サンプルの構造化ログファイルを取り込む関連 agent 設定とあわせて、ゲートウェイ設定の Example を示します。agent とゲートウェイ間の通信に OTLP を使用している点に注意してください。 clickhouse-agent-config.yaml
clickhouse-gateway-config.yaml
これらの設定は、次のコマンドで実行できます。
このアーキテクチャの主な欠点は、複数の collector を管理するためのコストと運用負荷がかかることです。 関連する知見を含む、より大規模なゲートウェイベースのアーキテクチャの管理例については、このブログ記事を参照することをお勧めします。

Kafka の追加

ここまでのアーキテクチャでは、メッセージキューとして Kafka を使っていないことに気づくかもしれません。 メッセージバッファとして Kafka キューを使うのは、ログアーキテクチャでよく見られる一般的な設計パターンで、ELK stack の普及によって広まりました。これにはいくつか利点があります。主な利点は、より強固なメッセージ配信保証を実現しやすくなることと、バックプレッシャーに対処しやすくなることです。メッセージは収集エージェントから Kafka に送られ、ディスクに書き込まれます。理論上は、クラスター化された Kafka インスタンスは高スループットなメッセージバッファとして機能します。これは、メッセージを解析して処理するよりも、データをディスクに順次書き込むほうが計算オーバーヘッドが小さいためです。たとえば Elastic では、トークン化と索引付けに大きなオーバーヘッドが発生します。さらに、データをエージェントから切り離すことで、発生元でのログローテーションによってメッセージが失われるリスクも低減できます。最後に、メッセージの再生やリージョン間レプリケーションの機能も備えており、一部のユースケースでは魅力的です。 ただし、ClickHouse はデータを非常に高速に挿入できます。一般的なハードウェアでも毎秒数百万行を処理できます。ClickHouse でバックプレッシャーが発生することは まれ です。多くの場合、Kafka キューを導入すると、アーキテクチャの複雑さとコストが増します。ログには銀行取引やその他のミッションクリティカルなデータと同等の配信保証は不要だ、という前提を受け入れられるのであれば、Kafka による複雑化は避けることを推奨します。 一方で、高い配信保証やデータの再生機能 (場合によっては複数のログソースへの配信) が必要であれば、Kafka はアーキテクチャに有用な追加要素となります。 この場合、OTel エージェントは Kafka exporter を介して Kafka にデータを送信するよう設定できます。一方、ゲートウェイ インスタンスは Kafka receiver を使用してメッセージを消費します。詳細については、Confluent と OTel のドキュメントを参照することを推奨します。

リソースの見積もり

OTel collector のリソース要件は、イベントのスループット、メッセージのサイズ、実行する処理の量によって異なります。OpenTelemetry プロジェクトでは、リソース要件の見積もりに使える ベンチマーク を提供しています。 私たちの経験では、3 コアと 12GB の RAM を備えたゲートウェイ インスタンスで、1 秒あたり約 60k イベントを処理できます。これは、フィールド名の変更のみを行い、正規表現を使わない最小限のパイプラインを前提としています。 イベントをゲートウェイに送信し、イベントにタイムスタンプを設定するだけの agent インスタンスについては、想定される 1 秒あたりのログ数に基づいてサイジングすることを推奨します。以下は、その出発点として使えるおおよその値です。
最終更新日 2026年7月23日