Back

/ 5 min read

ClickHouseを使ったトレースデータの分析

OpenTelemetryでテキスト解析パイプラインのトレーシングの続編。 前回はOpenTelemetry CollectorのexporterにJaegerを設定したが、例えば複数のトレースデータをaggregationしたり、インタラクティブにクエリしながら分析するにはJaegerだと難しい場面もある。 ありがたいことにopentelemetry-collector-contribには他にも様々な外部サービス含むexporterの実装が公開されている。

今回はanalytical databaseのOSSの一つであるClickHouseをexporterとして設定し、トレースデータをClickHouseのDBに蓄積しつつpythonからクエリしてみる。

ClickHouseサーバーの起動

公式からDocker imageが提供されているのでこれ使ってサーバーを起動する。手順も公式ドキュメントが公開されており(参考)、これを元にして前回のdocker composeのファイルに以下のように加筆する。

services:
# ...
clickhouse-server:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-server
ports:
- "18123:8123"
- "19000:9000"
environment:
- CLICKHOUSE_PASSWORD=changeme
networks:
- otel-network
restart: unless-stopped

起動後、テーブルの確認をしたい時などは以下を実行するとClickHouseクライアントのインタラクティブシェルが起動する。

Terminal window
$ docker run -it --rm --network=container:clickhouse-server --entrypoint clickhouse-client clickhouse/clickhouse-server

Collectorの設定

exampleの設定を参考にCollector configを書く。

exporters:
clickhouse:
database: default
endpoint: tcp://clickhouse-server:9000?dial_timeout=10s
password: changeme
username: default
ttl: 720h
timeout: 5s
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [debug, otlp/jaeger, clickhouse]

トレースデータを流す

docker compose upして試しにリクエストを送ってトレースデータを生成。特に事前にDDLの適用などは必要無い。

Terminal window
$ curl -XPOST --header "Content-Type: application/json" localhost:8000/predict -d'{"text": "OpenAI, Inc. is an American artificial intelligence (AI) organization headquartered in San Francisco, California."}'

ClickHouseにクエリ

今回はClickHouse familyの一つで、pythonのin-process dbであるchdbからClickHouse serverにクエリする。

import chdb
query = """
SELECT
*
FROM remote(
'localhost:19000',
'default',
'otel_traces',
'default',
'changeme'
)
"""
df = chdb.query(query, "DataFrame")
print(df.iloc[0].to_dict())
# {'Timestamp': Timestamp('2025-09-07 05:56:20.722113731+0000', tz='Etc/UTC'), 'TraceId': '6cb804e12bd3e60847a5e2250ac707cf', 'SpanId': '4978923fdac9b43a', 'ParentSpanId': '81cd0b13e48b0b22', 'TraceState': '', 'SpanName': 'POST', 'SpanKind': 'Client', 'ServiceName': 'coordination-service', 'ResourceAttributes': [('service.name', 'coordination-service'), ('service.version', '1.0.0'), ('telemetry.sdk.language', 'python'), ('telemetry.sdk.name', 'opentelemetry'), ('telemetry.sdk.version', '1.36.0')], 'ScopeName': 'opentelemetry.instrumentation.requests', 'ScopeVersion': '0.57b0', 'SpanAttributes': [('http.method', 'POST'), ('http.status_code', '200'), ('http.url', '[http://summarization-service:8000/summarize](http://summarization-service:8000/summarize)')], 'Duration': 1222437530, 'StatusCode': 'Unset', 'StatusMessage': '', 'Events.Timestamp': array([], dtype='datetime64[ns]'), 'Events.Name': array([], dtype=object), 'Events.Attributes': array([], dtype=object), 'Links.TraceId': array([], dtype=object), 'Links.SpanId': array([], dtype=object), 'Links.TraceState': array([], dtype=object), 'Links.Attributes': array([], dtype=object)}

デフォルトだとotel_tracesというテーブル名でトレースデータが保管されており、スキーマはexporterのソースのこの辺りに定義されている。

例えばOpenAIのモデルにAPIコールした際のトレースの中身は以下のようにフィルタリングして取り出せる。

_df = df[df["SpanName"] == "ChatOpenAI"]
print(_df.iloc[0]["SpanAttributes"])
# [('gen_ai.completion', '{"generations":[[{"text":"","generation_info":{"finish_reason":"tool_calls","logprobs":null},"type":"ChatGeneration","message":{"lc":1,"type":"constructor","id":["langchain","schema","messages","AIMessage"],"kwargs":{"content":"","additional_kwargs":{"tool_calls":[{"id":"call_BXPN83uBQbnjT3AkyBTlaZk1","function":{"arguments":"{\\"summarized_text\\":\\"OpenAI, Inc. is a San Francisco-based American AI organization.\\"}","name":"SummarizationResponse"},"type":"function"}],"refusal":null},"response_metadata":{"token_usage":{"completion_tokens":32,"prompt_tokens":75,"total_tokens":107,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0}},"model_name":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_8bda4d3a2c","id":"chatcmpl-CD2ftyGBAZkFjxzPodhaOUBTkt2p3","service_tier":"default","finish_reason":"tool_calls","logprobs":null},"type":"ai","id":"run--4ac3c9f5-d0f0-4a22-a0be-be3b0e71761d-0","tool_calls":[{"name":"SummarizationResponse","args":{"summarized_text":"OpenAI, Inc. is a San Francisco-based American AI organization."},"id":"call_BXPN83uBQbnjT3AkyBTlaZk1","type":"tool_call"}],"usage_metadata":{"input_tokens":75,"output_tokens":32,"total_tokens":107,"input_token_details":{"audio":0,"cache_read":0},"output_token_details":{"audio":0,"reasoning":0}},"invalid_tool_calls":[]}}}]],"llm_output":{"token_usage":{"completion_tokens":32,"prompt_tokens":75,"total_tokens":107,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0}},"model_name":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_8bda4d3a2c","id":"chatcmpl-CD2ftyGBAZkFjxzPodhaOUBTkt2p3","service_tier":"default"},"run":null,"type":"LLMResult"}'), ('gen_ai.operation.name', 'chat'), ('gen_ai.prompt', '{"messages":[[{"lc":1,"type":"constructor","id":["langchain","schema","messages","HumanMessage"],"kwargs":{"content":"Summarize the following text: OpenAI, Inc. is an American artificial intelligence (AI) organization headquartered in San Francisco, California.","type":"human"}}]]}'), ('gen_ai.system', 'langchain'), ('gen_ai.usage.input_tokens', '75'), ('gen_ai.usage.output_tokens', '32'), ('gen_ai.usage.total_tokens', '107'), ('langsmith.span.kind', 'llm'), ('langsmith.trace.name', 'ChatOpenAI'), ('langsmith.trace.session_name', 'default')]

endpointのrequest/responseの中身は以下のような形でフィルタリング

_df = df[df["ScopeName"] == "opentelemetry.instrumentation.fastapi"]
print(_df.iloc[0].to_dict())
# {'Timestamp': Timestamp('2025-09-07 05:56:20.719899359+0000', tz='Etc/UTC'), 'TraceId': '6cb804e12bd3e60847a5e2250ac707cf', 'SpanId': '81cd0b13e48b0b22', 'ParentSpanId': '', 'TraceState': '', 'SpanName': 'POST [/predict](https://vscode-remote+ssh-002dremote-002bbishop-002etailf7887c-002ets-002enet.vscode-resource.vscode-cdn.net/predict)', 'SpanKind': 'Server', 'ServiceName': 'coordination-service', 'ResourceAttributes': [('service.name', 'coordination-service'), ('service.version', '1.0.0'), ('telemetry.sdk.language', 'python'), ('telemetry.sdk.name', 'opentelemetry'), ('telemetry.sdk.version', '1.36.0')], 'ScopeName': 'opentelemetry.instrumentation.fastapi', 'ScopeVersion': '0.57b0', 'SpanAttributes': [('http.flavor', '1.1'), ('http.host', '172.19.0.3:8000'), ('http.method', 'POST'), ('http.request.body.content', '{"text":"OpenAI, Inc. is an American artificial intelligence (AI) organization headquartered in San Francisco, California."}'), ('http.response.body.content', '{"prediction":"OpenAI, Inc.はサンフランシスコに本拠を置くアメリカのAI組織です。"}'), ('http.route', '/predict'), ('http.scheme', 'http'), ('http.server_name', 'localhost:8000'), ('http.status_code', '200'), ('http.target', '/predict'), ('http.url', '[http://172.19.0.3:8000/predict](http://172.19.0.3:8000/predict)'), ('http.user_agent', 'curl/8.5.0'), ('net.host.port', '8000'), ('net.peer.ip', '172.19.0.1'), ('net.peer.port', '43244')], 'Duration': 2456230410, 'StatusCode': 'Unset', 'StatusMessage': '', 'Events.Timestamp': array([], dtype='datetime64[ns]'), 'Events.Name': array([], dtype=object), 'Events.Attributes': array([], dtype=object), 'Links.TraceId': array([], dtype=object), 'Links.SpanId': array([], dtype=object), 'Links.TraceState': array([], dtype=object), 'Links.Attributes': array([], dtype=object)}

こんな感じでClickHouseに流して分析できる。

この方法であればローカル端末に閉じてトレースデータのパイプラインを構築できるメリットがあるが、閉じなくていい場合のAlternativeとしてはgooglecloudexporterを使ってCloud Traceに流してBigQueryにエクスポートするのも一案。

トレースデータのエクスポートの概要