メインコンテンツまでスキップ
メインコンテンツまでスキップ

OpenTelemetryを統合してデータ収集を行う

任意の可観測性ソリューションには、ログやトレースを収集し、エクスポートする手段が必要です。そのため、ClickHouseではOpenTelemetry (OTel)プロジェクトを推奨しています。

"OpenTelemetryは、トレース、メトリクス、ログなどのテレメトリデータを生成および管理するために設計された可観測性フレームワークおよびツールキットです。"

ClickHouseやPrometheusとは異なり、OpenTelemetryは可観測性バックエンドではなく、テレメトリデータの生成、収集、管理、およびエクスポートに焦点を当てています。OpenTelemetryの初期の目的は、ユーザーが言語固有のSDKを使用してアプリケーションやシステムを容易に計測できるようにすることでしたが、OpenTelemetryコレクターを介したログの収集も含むように拡張されています。これは、テレメトリデータを受信、処理、およびエクスポートするエージェントまたはプロキシです。

ClickHouse関連コンポーネント

OpenTelemetryは、いくつかのコンポーネントで構成されています。データおよびAPI仕様、標準化されたプロトコル、およびフィールド/カラムの命名規則を提供するだけでなく、OTelはClickHouseでの可観測性ソリューションを構築するために基本となる2つの機能を提供します:

  • OpenTelemetry Collectorは、テレメトリデータを受信、処理、およびエクスポートするプロキシです。ClickHouseを使用したソリューションでは、このコンポーネントを使用して、ログの収集とバッチ処理および挿入前のイベント処理を行います。
  • Language SDKsは、仕様、API、およびテレメトリデータのエクスポートを実装しています。これらのSDKは、アプリケーションのコード内でトレースが正しく記録されることを効果的に保証し、構成要素スパンを生成し、メタデータを介してサービス間でコンテキストが伝播することを保証します。これにより、分散トレースが形成され、スパンが相関できるようになります。これらのSDKは、共通のライブラリやフレームワークを自動的に実装するエコシステムによって補完されるため、ユーザーはコードを変更する必要がなく、すぐに計測を取得できます。

ClickHouseを使用した可観測性ソリューションは、これらの2つのツールを活用します。

ディストリビューション

OpenTelemetryコレクターには、いくつかのディストリビューションがあります。ClickHouseソリューションに必要なファイルログレシーバーとClickHouseエクスポーターは、OpenTelemetry Collector Contrib Distroにのみ存在します。

このディストリビューションには多くのコンポーネントが含まれており、ユーザーはさまざまな構成を試すことができます。ただし、生産環境で実行する際は、コレクターに必要なコンポーネントのみを含めるように制限することを推奨します。これを行う理由のいくつかは次のとおりです:

  • コレクターのサイズを削減し、コレクターの展開時間を短縮します。
  • 利用可能な攻撃面を減少させることで、コレクターのセキュリティを向上させます。

カスタムコレクターを構築するには、OpenTelemetry Collector Builderを使用できます。

OTelでのデータの取り込み

コレクターのデプロイ役割

ログを収集し、ClickHouseに挿入するために、OpenTelemetry Collectorの使用を推奨します。OpenTelemetry Collectorは、以下の2つの主要な役割でデプロイできます:

  • エージェント - エージェントインスタンスは、サーバーやKubernetesノードなどのエッジでデータを収集したり、OpenTelemetry SDKを使用して計測したアプリケーションから直接イベントを受信したりします。この場合、エージェントインスタンスはアプリケーションと一緒に(サイドカーやDaemonSetとして)またはアプリケーションと同じホスト上で実行されます。エージェントは、データを直接ClickHouseに送信するか、ゲートウェイインスタンスに送信することができます。前者は、エージェントデプロイメントパターンと呼ばれています。
  • ゲートウェイ - ゲートウェイインスタンスは、通常、クラスター、データセンター、またはリージョンごとにスタンドアロンサービスを提供します。これらは、単一のOTLPエンドポイントを介してアプリケーション(または他のコレクターをエージェントとして)からイベントを受信します。通常、一連のゲートウェイインスタンスがデプロイされ、負荷分散用にアウトオブボックスのロードバランサーが使用されます。すべてのエージェントとアプリケーションがこの単一のエンドポイントに信号を送信する場合、これは一般にゲートウェイデプロイメントパターンと呼ばれます。

以下では、単純なエージェントコレクターがそのイベントをClickHouseに直接送信することを前提としています。ゲートウェイを使用する際の詳細についてはスケーリングゲートウェイを参照してください。

ログの収集

コレクターを使用する主な利点は、サービスがデータを迅速にオフロードでき、コレクターがリトライ、バッチ処理、暗号化、またはセンシティブデータフィルタリングなどの追加処理を担当できることです。

コレクターでは、receiverprocessorexporterの3つの主要な処理段階の用語を使用しています。レシーバーはデータ収集に使用され、プルまたはプッシュベースできます。プロセッサーはメッセージの変換や強化を行うことができます。エクスポーターは、データを下流サービスに送信する責任を負います。このサービスは理論的には別のコレクターでも可能ですが、以下の初期の議論ではすべてのデータが直接ClickHouseに送信されると仮定しています。

ユーザーには、利用可能な完全なレシーバー、プロセッサー、およびエクスポーターに慣れ親しむことをお勧めします。

コレクターは、ログ収集用の2つの主要なレシーバーを提供します:

OTLP経由 - この場合、ログはOpenTelemetry SDKからOTLPプロトコルを介してコレクターに直接送信(プッシュ)されます。OpenTelemetryデモはこのアプローチを採用しており、各言語のOTLPエクスポーターはローカルコレクターエンドポイントを想定しています。この場合、コレクターはOTLPレシーバーで構成する必要があります — 上記のデモの設定を参照してください。このアプローチの利点は、ログデータに自動的にトレースIDが含まれるため、ユーザーは特定のログのトレースを後で特定でき、逆も可能になることです。

このアプローチでは、ユーザーが適切な言語SDKを使用してコードを計測する必要があります。

  • Filelogレシーバーを介したスクレイピング - このレシーバーは、ディスク上のファイルを追跡し、ログメッセージを形成し、これをClickHouseに送信します。このレシーバーは、複数行メッセージの検出、ログのロールオーバーの処理、再起動への堅牢性のためのチェックポイント、および構造の抽出といった複雑なタスクを処理します。また、このレシーバーはDockerおよびKubernetesコンテナのログを追跡し、helmチャートとしてデプロイ可能であり、これらから構造を抽出してポッドの詳細を強化します

ほとんどのデプロイメントは上記のレシーバーの組み合わせを使用します。ユーザーはコレクターの文書を読み、基本的な概念と設定構造およびインストール方法に慣れることをお勧めします。

注記
ヒント: otelbin.io

otelbin.ioは、設定を検証および可視化するのに便利です。

構造化 vs 非構造化

ログは構造化または非構造化のいずれかです。

構造化ログは、httpコードやソースIPアドレスといったメタデータフィールドを定義するJSONのようなデータ形式を利用します。

非構造化ログは、通常、正規表現パターンを使用して抽出可能な固有の構造を持っているものの、ログを単に文字列として表現します。

ユーザーには、可能な限り構造化ログおよびJSON(つまりndjson)でのログを使用することをお勧めします。これにより、ClickHouseに送信する前のコレクターのプロセッサーや、挿入時にマテリアライズドビューを使用してログの処理が簡素化されます。構造化ログは、後の処理リソースを最終的に節約し、ClickHouseソリューションに必要なCPUを削減します。

例えば、構造化(JSON)および非構造化ログデータセットを提供しており、それぞれ約10m行のデータがあります。以下のリンクから入手可能です:

以下の例では、構造化データセットを使用します。このファイルをダウンロードして解凍し、以下の例を再現してください。

以下は、ファイルをディスク上で読み取り、filelogレシーバーを使用してメッセージをstdoutに出力するOTelコレクターのシンプルな設定を示しています。ログが構造化されているため、json_parserオペレーターを使用します。access-structured.logファイルへのパスを修正してください。

ClickHouseでの解析を検討する

以下の例は、ログからタイムスタンプを抽出します。これは、全ログ行をJSON文字列に変換し、その結果をLogAttributesに置くjson_parserオペレーターの使用を要求します。これは計算コストが高く、ClickHouseでより効率的に行えます - SQLを使用して構造を抽出する。同様の非構造化の例は、regex_parserを使用してこれを行うことができ、こちらにあります

config-structured-logs.yaml

ユーザーは、公式の指示に従って、コレクターをローカルにインストールできます。重要なことは、指示がfilelogレシーバーを含むために変更されていることを確認することです。例えば、otelcol_0.102.1_darwin_arm64.tar.gzの代わりに、ユーザーはotelcol-contrib_0.102.1_darwin_arm64.tar.gzをダウンロードします。リリースはこちらで確認できます。

インストールが完了したら、OTel Collectorは次のコマンドで実行できます:

構造化ログを使用している場合、メッセージは以下の形式になります:

上記は、OTelコレクターによって生成された単一のログメッセージを示しています。これらのメッセージを後のセクションでClickHouseに取り込みます。

ログメッセージの完全なスキーマや、他のレシーバーを使用している場合に存在する可能性のある追加のカラムはこちらで維持されています。ユーザーには、このスキーマに慣れ親しむことを強く推奨します。

ここでの重要な点は、ログ行自体がBodyフィールド内に文字列として保持されていますが、json_parserのおかげでAttributesフィールドにJSONが自動的に抽出されたことです。この同じオペレーターは、適切なTimestampカラムへのタイムスタンプの抽出にも使用されました。OTelでのログ処理に関する推奨事項については、処理を参照してください。

オペレーター

オペレーターは、ログ処理で利用可能な最も基本的な単位です。各オペレーターは、ファイルから行を読み取ったり、フィールドからJSONを解析したりするなど、一つの責任を果たします。オペレーターは、望ましい結果を達成するためにパイプラインで連結されます。

上記のメッセージにはTraceIDまたはSpanIDフィールドが含まれていません。もし存在する場合(例えば、ユーザーが分散トレースを実装している場合)には、上記と同じテクニックを使用してJSONから抽出できます。

ローカルまたはKubernetesログファイルの収集が必要なユーザーには、filelogレシーバーで利用可能な設定オプションや、オフセットや、複数行ログの解析がどのように処理されるかに精通することをお勧めします。

Kubernetesログの収集

Kubernetesログを収集するために、OpenTelemetryドキュメントガイドを推奨します。Kubernetes Attributes Processorは、ポッドメタデータでログやメトリクスを強化するために推奨されます。これにより、ダイナミックメタデータ(例:ラベル)が生成され、ResourceAttributesカラムに格納される可能性があります。ClickHouseは現在、このカラムに対してMap(String, String)型を使用しています。マップを使用するおよびマップからの抽出については、このタイプを処理および最適化するための詳細を参照してください。

トレースの収集

コードを計測してトレースを収集したいユーザーには、公式のOTelドキュメントを参照することを推奨します。

ClickHouseにイベントを送信するには、適切なレシーバーを介してOTLPプロトコル経由でトレースイベントを受信するOTelコレクターをデプロイする必要があります。OpenTelemetryデモは、サポートされている各言語の計測例を提供しており、イベントをコレクターに送信します。以下は、イベントをstdoutに出力するための適切なコレクター設定の一例です。

トレースをOTLPで受信する必要があるため、トレースデータを生成するためのtelemetrygenツールを使用します。インストール手順についてはこちらを参照してください。

以下の設定では、OTLPレシーバーでトレースイベントを受け取り、それをstdoutに送信します。

config-traces.xml

次のコマンドでこの設定を実行します:

トレースイベントをtelemetrygenを介してコレクターに送信します:

これにより、stdoutに出力されたトレースメッセージは以下のようになります:

上記は、OTelコレクターによって生成された単一のトレースメッセージを示しています。これらのメッセージも後のセクションでClickHouseに取り込みます。

トレースメッセージの完全なスキーマはこちらで維持されています。ユーザーには、このスキーマに慣れ親しむことを強く推奨します。

処理 - フィルタリング、変換、強化

ログイベントのタイムスタンプを設定する早期の例で示されたように、ユーザーはイベントメッセージをフィルタリング、変換、および強化したいと考えます。これは、OpenTelemetryのさまざまな機能を使用して実現できます:

  • プロセッサー - プロセッサーは、レシーバーで収集したデータを変更または変換し、エクスポーターに送信する前に処理します。プロセッサーは、コレクター構成のprocessorsセクションで設定された順序で適用されます。これはオプションですが、最小限のセットは通常推奨されます。ClickHouseでOTelコレクターを使用する際は、プロセッサーを次のように制限することをお勧めします:

    • memory_limiterは、コレクターのメモリ不足の状況を防ぐために使用されます。リソースの推定については、リソースの推定を参照してください。
    • コンテキストに基づいて強化を行うプロセッサー。例えば、Kubernetes Attributes Processorは、k8sメタデータを使用してスパン、メトリクス、およびログリソース属性を自動的に設定することを可能にします。これにより、ソースポッドIDと共にイベントが強化されます。
    • 必要に応じて、TailまたはHeadサンプリング
    • 基本的なフィルタリング - オペレーターを介してこれが行えない場合、不要なイベントをドロップします(以下を参照)。
    • バッチ処理 - ClickHouseで作業する際には、データがバッチで送信されることを保証するために不可欠です。"ClickHouseにエクスポートする"を参照してください。
  • オペレーター - オペレーターは、レシーバーで利用可能な最も基本的な処理単位です。基本的な解析がサポートされており、SeverityやTimestampなどのフィールドを設定できます。ここではJSONや正規表現の解析、イベントのフィルタリング、基本的な変換がサポートされています。イベントフィルタリングをここで行うことを推奨します。

ユーザーは、オペレーターや変換プロセッサーを使用して過剰なイベント処理を避けることをお勧めします。これらは、特にJSON解析において considerableなメモリおよびCPUオーバーヘッドを引き起こす可能性があります。ClickHouseでマテリアライズドビューを使用して、挿入時にすべての処理を行うことが可能ですが、特にk8sメタデータの追加などのコンテキストを意識した強化に関しては例外があります。詳細については、SQLを使用して構造を抽出するを参照してください。

OTelコレクターを使用して処理を行う場合は、ゲートウェイのインスタンスで変換を行い、エージェントのインスタンスで行われる作業を最小限に抑えることをお勧めします。これにより、サーバー上で実行されるエッジのエージェントに必要なリソースが可能な限り最小限に抑えられます。通常、ユーザーはフィルタリング(不必要なネットワーク使用量を最小限に抑えるため)、タイムスタンプ設定(オペレーターを介して)、およびコンテキストが必要な強化を行うことを確認します。たとえば、ゲートウェイインスタンスが異なるKubernetesクラスターに存在する場合、k8s強化はエージェントで行う必要があります。

以下の設定は、非構造化ログファイルの収集を示しています。オペレーターを使用してログ行から構造を抽出し(regex_parser)、イベントをフィルタリングし、バッチイベントを処理し、メモリ使用量を制限しています。

config-unstructured-logs-with-processor.yaml

ClickHouseへのエクスポート

エクスポーターは、データを1つまたは複数のバックエンドまたは宛先に送信します。エクスポーターはプルまたはプッシュベースのものがあり、ユーザーがClickHouseにイベントを送信するには、プッシュベースのClickHouseエクスポーターを使用する必要があります。

OpenTelemetry Collector Contribを使用

ClickHouseエクスポーターは、コアディストリビューションではなく、OpenTelemetry Collector Contribの一部です。ユーザーは、contribディストリビューションを使用するか、独自のコレクターを構築することができます。

完全な設定ファイルは以下に示します。

clickhouse-config.yaml

次の重要な設定に注意してください:

  • pipelines - 上記の設定では、pipelinesを使用していることが強調されています。これには、ログとトレース用のレシーバー、プロセッサー、およびエクスポーターのセットが含まれます。
  • endpoint - ClickHouseとの通信はendpointパラメータを介して構成されます。接続文字列tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1は、TCPを介して通信が行われることを意味します。ユーザーがトラフィックスイッチングの理由でHTTPを好む場合は、こちらに説明されているように、この接続文字列を修正してください。接続の詳細、およびこの接続文字列内にユーザー名とパスワードを指定する能力については、こちらで説明されています。

重要: 上記の接続文字列では、圧縮(lz4)と非同期挿入の両方が有効になります。両方を常に有効にすることをお勧めします。非同期の挿入に関する詳細は、バッチ処理を参照してください。圧縮は常に指定する必要があり、エクスポーターの古いバージョンではデフォルトでは有効になっていません。

  • ttl - ここでの値は、データがどのくらい保持されるかを決定します。詳細は「データの管理」を参照してください。これは、e.g. 72hのように、時間単位で指定する必要があります。以下の例では、データは2019年のものであり、ClickHouseに挿入されるとすぐに削除されるため、TTLは無効化しています。
  • traces_table_nameおよび logs_table_name - ログとトレースのテーブル名を決定します。
  • create_schema - テーブルが初回起動時にデフォルトのスキーマで作成されるかどうかを判断します。始めるためにはデフォルトでtrueです。ユーザーはこれをfalseに設定し、自分のスキーマを定義する必要があります。
  • database - ターゲットデータベース。
  • retry_on_failure - 失敗したバッチを試みるかどうかを判断する設定。
  • batch - バッチプロセッサーは、イベントをバッチとして送信することを保証します。私たちは、約5000の値と5秒のタイムアウトを推奨します。どちらかが最初に到達した場合、エクスポーターにフラッシュされるバッチが開始されます。これらの値を下げると、より低いレイテンシーパイプラインを意味し、データがより早くクエリ可能になりますが、ClickHouseに送信される接続とバッチが増えることになります。これは、ユーザーが非同期挿入を使用していない場合には推奨されません。なぜなら、それはClickHouseのパーツが多すぎる問題を引き起こす可能性があるからです。逆に、ユーザーが非同期挿入を使用している場合、クエリ可能なデータの可用性は非同期挿入設定にも依存します。ただし、データはコネクタからより早くフラッシュされます。詳細については、バッチ処理を参照してください。
  • sending_queue - 送信キューのサイズを制御します。キュー内の各項目にはバッチが含まれています。このキューが超過された場合、e.g. ClickHouseが到達不能になったがイベントの到着が続く場合、バッチがドロップされます。

ユーザーが構造化されたログファイルを抽出し、ClickHouseのローカルインスタンスを実行中で(デフォルトの認証で)、ユーザーは次のコマンドでこの設定を実行できます。

このコレクターにトレースデータを送信するには、次のコマンドをtelemetrygenツールを使用して実行します。

実行中の間に、簡単なクエリでログイベントが存在することを確認します。

デフォルトのスキーマ

デフォルトでは、ClickHouseエクスポーターは、ログとトレースの両方に対してターゲットログテーブルを作成します。これは、create_schema設定を介して無効にできます。さらに、ログとトレースの両方のテーブル名は、上記で指摘された設定を使用してデフォルトのotel_logsおよびotel_tracesから変更できます。

注記

以下のスキーマでは、TTLが72hとして有効になっているとします。

ログのデフォルトスキーマは以下に示しています(otelcol-contrib v0.102.1):

ここに示されているカラムは、こちらで文書化されているログのOTel公式仕様と一致します。

このスキーマに関する重要な注意点いくつか:

  • デフォルトでは、テーブルはPARTITION BY toDate(Timestamp)を使用して日付でパーティション分けされます。これは、有効期限が切れたデータを削除するのに効率的です。
  • TTLは、TTL toDateTime(Timestamp) + toIntervalDay(3)で設定され、コレクター設定で設定された値に対応します。ttl_only_drop_parts=1は、含まれる行がすべて期限切れになったときにのみ全体のパーツが削除されることを意味します。これは、パーツ内で行を削除するよりも効率的です。常にこれを設定することをお勧めします。詳細は、TTLによるデータ管理を参照してください。
  • テーブルは古典的なMergeTreeエンジンを使用しています。これは、ログとトレースに推奨されており、変更の必要はありません。
  • テーブルはORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId)で並べ替えられています。これは、ServiceNameSeverityTextTimestampTraceIdでフィルタリングのためにクエリが最適化されることを意味します。リストの早い側のカラムは後のものよりも早くフィルタリングされます。つまり、ServiceNameでフィルタリングすることは、TraceIdでフィルタリングするよりもかなり早くなります。ユーザーは、予想されるアクセスパターンに基づいてこの順序を変更する必要があります。詳細は、主キーの選択を参照してください。
  • 上記のスキーマは、カラムにZSTD(1)を適用します。これは、ログの最適な圧縮を提供します。ユーザーは、より良い圧縮のためにZSTDの圧縮レベル(デフォルトの1以上)を上げることができますが、これはほとんど利益をもたらしません。この値を上げると、挿入時(圧縮時)のCPUのオーバーヘッドが大きくなりますが、解凍(したがってクエリ)は比較的保持されるべきです。詳細はこちらを参照してください。Timestampには、ディスク上のサイズを削減することを目的とした追加のデルタエンコーディングが適用されています。
  • ResourceAttributesLogAttributes、およびScopeAttributesがマップであることに注意してください。ユーザーはこれらの違いに慣れておくべきです。これらのマップにアクセスし、それらの中のキーへのアクセスを最適化する方法については、マップの使用を参照してください。
  • ここにある他のタイプ、大胆にLowCardinalityとしているものは、最適化されています。注意すべきは、Bodyは私たちの例のログではJSONですが、Stringとして保存されています。
  • マップのキーと値、およびBodyカラムには、ブルームフィルタが適用されています。これにより、これらのカラムへのクエリ時間が改善されることを目指しますが、通常は必要ありません。詳細は、二次/データスキッピングインデックスを参照してください。

再度、これはこちらで文書化されているトレースのOTel公式仕様に準拠しています。ここでのスキーマは、上記のログスキーマと多くの同じ設定を採用しており、スパン専用の追加リンクカラムがあります。

ユーザーには、autoスキーマ生成を無効にし、手動でテーブルを作成することをお勧めします。これにより、主キーおよび副キーを変更する機会と、クエリパフォーマンスを最適化するための追加カラムを導入する機会が得られます。詳細については、スキーマ設計を参照してください。

挿入の最適化

ObservabilityデータをClickHouseにコレクターを介して挿入する際、高い挿入パフォーマンスと強い整合性保証を実現するために、ユーザーは単純なルールに従うべきです。OTelコレクターの設定が正しく行われていれば、次のルールは簡単に守れます。これにより、ユーザーがClickHouseを初めて使用する際に遭遇する一般的な問題も避けられます。

バッチ処理

デフォルトでは、ClickHouseに送信される各挿入は、挿入からデータとともに保存する必要がある他のメタデータを含むストレージのパーツを直ちに作成します。したがって、より少量の挿入を送信してそれぞれにより多くのデータを含めること(より多くの挿入を送信してそれぞれに少しデータしか含めないよりも)は、必要な書き込みの数を減らします。私たちは、1回の挿入で少なくとも1,000行の比較的大きなバッチのデータを挿入することをお勧めします。詳細はこちらを参照してください。

デフォルトでは、ClickHouseへの挿入は同期的であり、同一であれば冪等です。MergeTreeエンジンファミリのテーブルでは、ClickHouseはデフォルトで、自動的に挿入を重複除去します。これにより、以下のようなケースでも挿入が許容されます:

  • (1) データを受信するノードに問題がある場合、挿入クエリはタイムアウトし(またはより具体的なエラーが発生し)、応答を受け取らない。
  • (2) データがノードに書き込まれましたが、ネットワークの中断によりクエリの送信者に確認応答が返せない場合、送信者はタイムアウトまたはネットワークエラーを受け取る。

コレクターの観点からは、(1)と(2)を区別するのは難しい場合があります。ただし、いずれの場合も、未確認挿入はすぐに再試行できます。再試行された挿入クエリが同じデータを同じ順序で含む限り、ClickHouseは(未確認の)元の挿入が成功していた場合、再試行された挿入を自動的に無視します。

私たちは、上記を満たすために以前の構成で示されたバッチプロセッサの使用をお勧めします。これにより、挿入は一貫したバッチとして送信され、上記の要件を満たすことが保証されます。コレクターが高スループット(秒あたりのイベント数)を持つことが予想され、各挿入で少なくとも5000イベントを送信できる場合、通常はこれがパイプラインで必要な唯一のバッチ処理です。この場合、コレクターはバッチプロセッサのtimeoutに達する前にバッチをフラッシュし、パイプラインのエンドツーエンドのレイテンシーが低く、バッチのサイズが一貫していることを保証します。

非同期挿入を使用

通常、スループットが低いコレクターの場合、ユーザーは小さなバッチを送信しなければならず、それでも最低限のエンドツーエンドレイテンシーでClickHouseにデータが届くことを期待しています。この場合、バッチプロセッサのtimeoutが切れると、小さなバッチが送信されます。これが問題を引き起こす可能性があるときに、非同期挿入が必要です。この状況は、エージェントロールのコレクターがClickHouseに直接送信するように構成されている場合に一般的に発生します。ゲートウェイは、集約器として機能することでこの問題を緩和できます - ゲートウェイでのスケーリングを参照してください。

大きなバッチを確保できない場合、ユーザーは非同期挿入を使用してClickHouseにバッチ処理を委任できます。非同期挿入を使用すると、データが最初にバッファに挿入され、その後データベースストレージに書き込まれます。

非同期挿入が有効になっている場合、ClickHouseが①挿入クエリを受信すると、クエリのデータが②最初にメモリ内バッファに即座に書き込まれます。次に③バッファフラッシュが発生したときに、バッファのデータが並べ替えられて、データベースストレージにパーツとして書き込まれます。データがデータベースストレージにフラッシュされる前はクエリによって検索できないことに注意してください。バッファフラッシュは設定可能です

コレクターのために非同期挿入を有効にするには、接続文字列にasync_insert=1を追加します。配信保証を得るために、ユーザーはwait_for_async_insert=1(デフォルト)を使用することをお勧めします。詳細についてはこちらを参照してください。

非同期挿入からのデータは、ClickHouseのバッファがフラッシュされた後に挿入されます。この処理は、async_insert_max_data_sizeを超えた場合、または最初のINSERTクエリ以来async_insert_busy_timeout_msミリ秒経過後に発生します。async_insert_stale_timeout_msがゼロ以外の値に設定されている場合、データは最後のクエリからasync_insert_stale_timeout_msミリ秒後に挿入されます。ユーザーはこれらの設定を調整してパイプラインのエンドツーエンドレイテンシーを制御できます。バッファフラッシングを調整するために使用できる他の設定はこちらに文書化されています。一般的に、デフォルトは適切です。

適応型非同期挿入を考慮する

エージェントの数が少なく、スループットが低く、厳格なエンドツーエンドレイテンシー要件がある場合、適応型非同期挿入が役立つことがあります。一般的に、これはClickHouseのような高スループットなObservabilityユースケースには適用されません。

最後に、非同期挿入を使用する際、ClickHouseへの同期挿入に関連する以前の重複排除動作は、デフォルトでは有効になっていません。必要な場合は、設定async_insert_deduplicateを参照してください。

この機能の設定に関する詳細はこちらで入手できます。さらに詳しくはこちらを参照してください。

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

OTelコレクターをClickHouseと共に使用する際に使用可能なアーキテクチャは数種類あります。それぞれの適用可能性について説明します。

エージェントのみ

エージェントのみのアーキテクチャでは、ユーザーはOTelコレクターをエッジにエージェントとして展開します。これらはローカルアプリケーション(例:サイドカーコンテナ)からトレースを受信し、サーバーやKubernetesノードからログを収集します。このモードでは、エージェントはデータを直接ClickHouseに送信します。

このアーキテクチャは、小規模から中規模の展開に適しています。その主な利点は、追加のハードウェアを必要とせず、ClickHouseの可観測性ソリューションの全体的なリソース使用量が最小限に抑えられ、アプリケーションとコレクター間の単純なマッピングが行われることです。

ユーザーは、エージェントの数が数百を超えると、ゲートウェイベースのアーキテクチャへの移行を検討する必要があります。このアーキテクチャにはスケーリングが困難であるいくつかの欠点があります:

  • 接続スケーリング - 各エージェントがClickHouseに接続を確立します。ClickHouseは数百(場合によっては数千)の同時挿入接続を維持できますが、最終的には制約要因となり、挿入の効率が低下します。つまり、接続を維持するためにClickHouseが使用するリソースが増えます。ゲートウェイを使用することで、この接続数を最小限に抑え、挿入の効率を高めます。
  • エッジでの処理 - このアーキテクチャでは、どんな変換やイベント処理もエッジまたはClickHouse内で行う必要があります。これは制約が厳しいだけでなく、複雑なClickHouseのマテリアライズドビューを作成するか、重大なサービスに影響を与えるリソースが不足している可能性のあるエッジでの大きな計算を押し上げることを意味しています。
  • 小さなバッチとレイテンシー - エージェントコレクターは、非常に少数のイベントを個別に収集する場合があります。これは通常、納品SLAを満たすために設定された間隔でフラッシュする必要があることを意味します。これにより、コレクターが小さなバッチをClickHouseに送信する可能性があります。欠点ではありますが、非同期挿入を使用することで軽減可能です - 挿入の最適化を参照してください。

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

OTel コレクタは、上記の制限に対処するためにゲートウェイインスタンスとして展開できます。これらは、通常はデータセンターや地域ごとにスタンドアロンのサービスを提供します。これらは、単一の OTLP エンドポイントを介してアプリケーション(またはエージェント役の他のコレクタ)からイベントを受信します。通常、一連のゲートウェイインスタンスが展開され、負荷を分散させるためにアウトオブボックスのロードバランサーが使用されます。

このアーキテクチャの目的は、エージェントから計算集約的な処理をオフロードし、それによってリソースの使用量を最小限に抑えることです。これらのゲートウェイは、エージェントによって行われる必要がある変換タスクを実行できます。さらに、多くのエージェントからのイベントを集約することにより、ゲートウェイは大規模なバッチが ClickHouse に送信されるようにし、効率的な挿入を可能にします。これらのゲートウェイコレクタは、エージェントが追加され、イベントスループットが増加するにつれて容易にスケールできます。以下に、例としてゲートウェイの構成と、例の構造化ログファイルを消費する関連するエージェントの構成を示します。エージェントとゲートウェイ間の通信に OTLP が使用されていることに注意してください。

clickhouse-agent-config.yaml

clickhouse-gateway-config.yaml

これらの構成は、以下のコマンドで実行できます。

このアーキテクチャの主な欠点は、一連のコレクタの管理に関連するコストとオーバーヘッドです。

大規模なゲートウェイベースのアーキテクチャの管理に関する学習例については、この ブログ記事 を推奨します。

Kafkaの追加

読者は、上記のアーキテクチャがメッセージキューとして Kafka を使用していないことに気付くかもしれません。

メッセージバッファとして Kafka キューを使用することは、ログアーキテクチャで見られる一般的なデザインパターンであり、ELKスタックによって普及しました。これにはいくつかの利点があります。主に、より強いメッセージ配信の保証を提供し、バックプレッシャーに対処するのに役立ちます。メッセージは収集エージェントから Kafka に送信され、ディスクに書き込まれます。理論的には、クラスタ化された Kafka インスタンスはメッセージバッファとして高いスループットを提供すべきです。ディスクにデータを線形に書き込む方が、メッセージを解析して処理するよりも計算オーバーヘッドが少ないためです。たとえば、Elastic ではトークン化とインデックス作成にはかなりのオーバーヘッドがかかります。データをエージェントから遠ざけることで、ソースでのログ回転の結果としてメッセージを失うリスクも低くなります。最後に、メッセージの返信およびクロスリージョン複製機能を提供し、一部のユースケースにとって魅力的かもしれません。

しかし、ClickHouse はデータの挿入を非常に迅速に処理できます - 中程度のハードウェアで毎秒数百万行です。ClickHouse からのバックプレッシャーは まれ です。しばしば、Kafka キューを活用することは、より多くのアーキテクチャの複雑さとコストを意味します。ログには銀行取引や他のミッションクリティカルなデータと同じ配信保証が必要ないという原則を受け入れられるなら、Kafka の複雑さを避けることをお勧めします。

ただし、高い配信保証やデータを再生する能力(潜在的には複数のソースへ)を必要とする場合、Kafka は有用なアーキテクチャの追加となる可能性があります。

この場合、OTel エージェントは Kafka エクスポータ を介してデータを Kafka に送信するように構成できます。ゲートウェイインスタンスは、Kafka レシーバ を使用してメッセージを消費します。詳細については、Confluent と OTel のドキュメントをお勧めします。

リソースの見積もり

OTel コレクタのリソース要件は、イベントスループット、メッセージのサイズ、および実行される処理の量によって異なります。OpenTelemetry プロジェクトは、リソース要件を見積もるために使用できる ベンチマーク を維持しています。

私たちの経験では、3 コアと 12GB の RAM を備えたゲートウェイインスタンスは、毎秒約 60,000 イベントを処理できます。これは、フィールドの名前変更を行う最小限の処理パイプラインが責任を持つことを前提とし、正規表現は使用していないものとします。

イベントをゲートウェイに送信することを担当するエージェントインスタンスについては、毎秒のログ数に基づいてサイズを決定することをお勧めします。以下は、ユーザーが出発点として使用できるおおよその数値です:

ログ記録レートコレクタエージェントのリソース
1k/秒0.2CPU, 0.2GiB
5k/秒0.5 CPU, 0.5GiB
10k/秒1 CPU, 1GiB