2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ClickHouse のブログに、"The most reliable network call is the one you don't make"1(一番信頼できるネットワーク呼び出しは、そもそも呼ばないこと)という文がありました。エージェントは 1 ターンで何度もデータを叩くので、リモート DB への往復はレイテンシもコストも積み上がります。そこで、ClickHouse のクエリエンジンをプロセス内に埋め込んだ chDB を「エージェントの手元データエンジン」にする、というお話です。

本記事では、AI エージェント(Claude Code)に、chDB でローカルの実データ(NYC イエロータクシー)を分析させてみます。あわせて、chDB の特長である外部ソースを取り込みなしで JOIN する使い方と、規模が必要になったときに ClickHouse へそのまま持っていけるかも試します。

1. はじめに

chDB は、ClickHouse のクエリエンジンをそのまま Python から使える組み込み(in-process)SQL エンジンです2。SQLite が DB サーバー無しでファイル 1 つで動くのと同じ発想の、分析(OLAP)用途版だと考えると分かりやすいです。もともとコミュニティ開発者が作ったものが、いまは ClickHouse ファミリーに合流しています。

エージェント文脈で chDB が推される理由は、ネットワークを挟まないことにあります。リモート DB や MCP サーバー越しだと、1 回の往復が数十ミリ秒でも、エージェントが 1 ターンで 5〜20 回叩けば無視できない遅延になります。失敗時の再試行では、コンテキストごと再送されてトークンも余分に消費します。プロセス内で完結する chDB なら、そこが関数呼び出しのコストで済む、という主張です3

今回は、まず「手元エンジンとして本当に使えるか」を実際に確かめてみます。検証ゴールは次の3つです。

# 確かめること OK の条件
1 サーバーも MCP も無しで手元の分析エンジンを用意できるか import chdb だけでローカル parquet を集計できる
2 Claude が手元 chDB で実データ分析を完走できるか 先に固定した4問の期待値に到達する(誤読しやすい設問を含む)
3 スケール時に ClickHouse へ持っていけるか 同じ集計 SQL が、データ指定の変更だけで Cloud でも同じ結果になる

結論先出し

  • pip install chdbimport chdb だけで、サーバーも MCP も無しの手元 SQL エンジンを用意できた(Windows は非対応なので WSL で実行)
  • Claude Code は約330万行の parquet を自分で SQL で叩いて 4 問を全問正解し(現金のチップが記録されない注意点も自力で発見)、地点を名前で聞くと手元の対応表 CSV を見つけて JOIN し、地点ID を地名に変えた(132 → JFK Airport)
  • chDB で書いた集計・JOIN は、データ指定を差し替えるだけで ClickHouse Cloud でもそのまま同じ結果になった。手元で試して、規模が大きくなったら ClickHouse へ移せる(集計 SQL は変えず、データの指定だけ差し替える)
  • 分析を進めるのは chDB でなくクライアント側(Claude)の判断である。chDB はそのための、サーバー不要で速いエンジンだった

2. 検証環境

項目 内容
OS Windows 11 Pro + WSL2(Ubuntu 24.04.1 LTS・glibc 2.39・Python 3.12.3)
分析エンジン chDB 4.2.0(in-process。サーバー/MCP 無し)
エージェント Claude Code(モデル claude-opus-4-8)
データ NYC イエロータクシー 2023年6月 yellow_tripdata_2023-06.parquet(3,307,234 行・19 列)4
補助データ taxi_zone_lookup.csv(地点ID → 地名の対応表)4

chDB は macOS / Linux 向けで、Windows ネイティブには対応していません。そのため本記事では WSL の Ubuntu 上で chDB を動かし、Claude Code からは wsl 経由で呼び出しています。WSL はローカルのサブプロセスなので、ネットワーク越しの呼び出しではなく、「サーバーも MCP も無しの in-process」という前提は保たれます。

3. chDB とは / セットアップ

3.1. SQLite との違い(なぜ分析に chDB か)

「サーバー不要の組み込み DB」という見た目は SQLite と同じですが、エンジンの設計が違います。

SQLite chDB
行指向・トランザクション処理(OLTP)向け 列指向・分析処理(OLAP)向け=ClickHouse
得意 1 行の読み書き・更新 大量行の集計・スキャン
外部ソース 基本は自分のファイルのみ parquet / CSV / S3 / PostgreSQL 等を直接クエリ

「1000万行のうち 1 列だけ合計する」ような分析では、列指向がその列だけを読むぶん有利です。加えて chDB は parquet や別 DB を取り込みなしで直接読めるため、手元の分析ではこの 2 点が利点になります。ただし「組み込みで手軽」という点自体は SQLite や DuckDB とも共通で、chDB ならではと言えるのは ClickHouse エンジン(列指向・1000超の関数・大規模集計)を組み込みで使えるところです。

3.2. セットアップ

WSL の Ubuntu では、システムの Python への直接 pip install が PEP 668 で拒否されるため、専用の仮想環境(venv)を作って導入します。

# WSL(Ubuntu) 上で実行
python3 -m venv ~/chdb-venv
~/chdb-venv/bin/pip install chdb

これで導入は完了です。あとは import chdb するだけで、サーバー起動もネットワーク接続もありません。なお chDB は新しめの glibc を前提にビルドされているため、WSL の Ubuntu は最近の LTS(今回は 24.04・glibc 2.39)を使うのが無難です。

スクリプトを動かすときは、システムの python3 ではなくこの仮想環境の python を明示的に指定します。そうしないと、chDB が入っていないグローバルの Python が起動して ModuleNotFoundError になります。

# count.py として保存
import chdb
# ローカル parquet をそのまま SQL で読む(file 関数)
print(chdb.query(
    "SELECT count() FROM file('yellow_tripdata_2023-06.parquet', Parquet)", "CSV"))
# 仮想環境の python で実行する(システムの python3 では chdb が見つからない)
~/chdb-venv/bin/python count.py

構成はこうなります。Claude Code が Bash 経由で WSL の Python を呼び、その中で chDB がプロセス内でクエリを実行します。

4. 手順・実行

4.1. Claude に手元 chDB で分析させる

Claude Code に、次の情報だけを渡しました。

  • 対象は NYC タクシーの parquet(パス)
  • chDB は WSL の venv にあり、wsl 経由で python を呼ぶこと
  • 質問に番号を付けて答え、可能なら別の数え方でも検算すること

出す質問は、採点が主観に流れないよう、正解(SQL と期待値)を先に固定してから渡しました。質問文には ClickHouse や chDB の関数名を入れず、素の日本語にしています。質問は難易度を散らして 4 問です。

  1. 全乗車の平均走行距離(易)
  2. 曜日別で乗車開始が最も多い曜日と件数(時系列)
  3. 乗車 1 万回以上の地点で、平均支払総額が最も高い地点とその額(2 段階の絞り込み)
  4. 平均チップ額と、その数値の解釈上の注意点

質問 4 は解釈に注意が要ります。NYC のデータでは、チップ額はカード決済でしか記録されず、現金のチップは 0 として入っています。素直に全体平均を出すと「現金客はチップを払わない」と誤読しかねません。ここに気づけるかを見ます。

4.2. 「地点を名前で」と頼むと、Claude が自分で対応表を JOIN した

質問 3 の答えは「地点ID 132」のように数値 ID で返ります。そこで同じ Claude に、今度は「乗車が多く支払総額の高い地点を、ゾーン名(地名)で答えて」と頼みました。

すると Claude は、parquet が地点 ID しか持たないことに気づき、データフォルダに置いてあった対応表 taxi_zone_lookup.csv を自分で見つけて、parquet と JOIN しました。CSV をどこかに取り込む工程は無く、両方を file() で参照してそのまま結合しています。なお、この CSV は準備段階でこちらがフォルダに置いておいたもので、Claude がネットから取得してきたわけではありません。対応表がフォルダにあること自体は、こちらからは伝えていません。「名前が必要 → 手元の対応表を突き合わせる」という段取りは Claude が自分で決めました。Claude が実行したのは、次のような JOIN です(フェデレーテッドクエリ)。

import chdb
TRIPS = "file('yellow_tripdata_2023-06.parquet', Parquet)"
ZONES = "file('taxi_zone_lookup.csv', CSVWithNames)"

sql = f"""
SELECT t.PULocationID AS id, z.Zone AS zone, z.Borough AS borough,
       count() AS trips, round(avg(t.total_amount), 2) AS avg_total
FROM {TRIPS} t
INNER JOIN {ZONES} z ON t.PULocationID = z.LocationID
GROUP BY id, zone, borough HAVING trips >= 10000
ORDER BY avg_total DESC LIMIT 5
"""
print(chdb.query(sql, "PrettyCompact"))

4.3. 同じ SQL のまま ClickHouse へ

chDB は「手元で完結」だけの道具ではありません。同じ ClickHouse の SQL 方言なので、データが大きくなったり共有が要るようになったら、集計を書いた SQL はそのまま、データの指定(file()url() / s3() / テーブル)だけ変えて ClickHouse(サーバー版 / Cloud)に持っていけます。今回は、chDB で回した集計・JOIN を、そのデータ指定だけ変えて ClickHouse Cloud で実行し、結果が一致するかを確かめました。自分の ClickHouse に接続して試すときは、接続情報(ホストや認証)をコードに書かず、環境変数などから渡すのが安全です。

5. 実行結果

5.1. 分析ループの結果(4 問すべて正解)

Claude は chDB を 2 回叩いて 4 問すべてに正解し、それぞれ別の数え方で検算までしました。先に固定した期待値と突き合わせた結果が次の表です。

質問 期待値 Claude の到達値 判定
Q1 平均走行距離 4.37 マイル 4.37 マイル 一致
Q2 最多曜日 木曜・597,888 件 木曜・597,888 件 一致
Q3 高額地点(1万回以上) 地点132・79.42 ドル 地点132・79.42 ドル 一致
Q4 平均チップ 全体3.59/カード限定4.45 ドル 全体3.59/カード限定4.45 ドル 一致

注目は Q4 です。Claude は全体平均 3.59 ドルを出したうえで、支払方法別に分解し、現金の平均チップがほぼ 0 であることから「現金のチップは記録されていない」と確かめました。そのうえで「実際に払われたチップに近いのはカード限定の 4.45 ドル」と結論しています。誤読しやすい指標に、ヒント無しで気づいた形です。

このとき Claude が使ったのは chDB の呼び出し 2 回だけで、所要は約 68 秒でした。手元にエンジンがあると、質問を受けて SQL を書き、実行し、結果を見て検算する、という往復を、待ち時間少なく回せます。

5.2. Claude が JOIN した結果(ID が地名に変わった)

Claude が自分で組み立てた JOIN の結果です。取り込み工程を挟まずに、地点ID が地名に変わりました。

JOIN 前(parquet 単独・ID のまま)

id trips avg_total(ドル)
132 180,400 79.42
138 119,810 66.24
261 16,955 33.45

JOIN 後(parquet × CSV・地名に変換)

id zone borough trips avg_total(ドル)
132 JFK Airport Queens 180,400 79.42
138 LaGuardia Airport Queens 119,810 66.24
261 World Trade Center Manhattan 16,955 33.45

平均支払総額の上位が空港(JFK・LaGuardia)だと一目で分かるようになりました。CSV をどこかの DB に取り込む準備をせずに、ファイルを 2 つ指定するだけで JOIN できています。

この JOIN を chDB の PrettyCompact 形式で出力すると、次のようになります(上位 5 件)。

   ┌──id─┬─zone─────────────────────┬─borough───┬──trips─┬─avg_total─┐
1. │ 132 │ JFK Airport              │ Queens    │ 180400 │     79.42 │
2. │ 138 │ LaGuardia Airport        │ Queens    │ 119810 │     66.24 │
3. │  70 │ East Elmhurst            │ Queens    │  14534 │     61.84 │
4. │ 261 │ World Trade Center       │ Manhattan │  16955 │     33.45 │
5. │  87 │ Financial District North │ Manhattan │  18114 │     33.18 │
   └─────┴──────────────────────────┴───────────┴────────┴───────────┘

5.3. 同じ SQL が ClickHouse でも動いた

4.1 と同じ集計を、データ指定だけ url() に変えて ClickHouse Cloud で実行したところ、結果は一致しました。集計と JOIN の SQL は同一で、変えたのはデータの指定だけです。

質問 chDB(ローカル) ClickHouse Cloud(同じ集計 SQL)
平均走行距離 4.37 マイル 4.37 マイル
高額地点(1万回以上) 地点132・79.42 ドル 地点132・79.42 ドル

federation(parquet × CSV の JOIN)も同じく Cloud でそのまま動き、地点132 が JFK Airport になりました。集計を書き直さずに、データの置き場所だけローカルから ClickHouse に移せる形です。

6. 考察

6.1. 手元エンジンとして chDB が向く場面

  • import chdb だけで、サーバーもポートも無しに約330万行の集計が動いた。エージェントに渡す「手元エンジン」としては、立ち上げの手数が少ないのが利点である
  • parquet と CSV を取り込みなしで JOIN でき、ID を地名に変換できた。分析の下ごしらえ(マスタ突き合わせ)を、ロード工程なしにその場で書ける
  • 同じ書き方で postgresql() / mysql() / s3() など別ソースも参照できるとされている5。今回はローカル CSV で確認した範囲だが、外部 DB やオブジェクトストレージへ広げられる

なお「組み込みで手軽」「ファイルを直接 SQL で読める」こと自体は DuckDB や clickhouse-local とも共通です。chDB ならではと言えるのは、ClickHouse エンジンの列指向と豊富な関数群を組み込みで使える点だと考えられます。特に ClickHouse は文字列や配列を扱う関数(arrayMap など)が多く、これらがそのままインプロセスで動くので、アドホックなデータ加工をその場で書けます。

6.2. 分析を進めたのは chDB でなくクライアント(Claude)

今回、質問から SQL を組み立て、実行し、結果を見て検算し、Q4 の誤読しやすい点に気づく、という一連の判断はすべてクライアント側の Claude が行っています。地点を名前で聞いたときに「parquet は ID しか持たない → 手元の対応表を JOIN しよう」と段取りを決めたのも Claude 自身でした。chDB はあくまで「速く安く叩けるエンジン」を提供しただけです。ここを混同して「chDB がここまでやってくれる」と読むと、別のクライアントや弱いモデルに変えたときに話が合わなくなります。chDB が担う部分と、モデルが担う部分は分けて見るのがよいと思います。

なお、5.3 の ClickHouse への持ち込み(可搬性の確認)は、chDB の性質を示すために筆者が行ったもので、エージェントの自律的な作業ではありません。

6.3. いつローカルの chDB、いつ ClickHouse か

今回の条件は「1 台のローカル環境・単一ユーザー・330万行の中規模データ」でした。この範囲は手元 chDB だけで完結し、データもマシンの外に出ません。閉じた環境やオフラインで扱いたいデータにも向きます。ここはローカルの chDB が得意な領域です。

一方、データが大きくなる・複数ユーザーやチームで同じデータを共有する・常時アクセスさせる、といった段になると、サーバー型の ClickHouse や ClickHouse Cloud の出番になります3。5.3 で確かめたとおり、集計の SQL はそのまま使えます(変えるのはデータの指定だけです)。chDB で書いた集計を、データ指定を変えるだけで Cloud でも同じ結果で動かせるので、「まず手元の chDB で試して、必要になったら ClickHouse に載せ替える」という進め方ができます。

なお、エージェントの「記憶」用途(会話や決定を貯めて引く使い方)や、モデル自体をローカルに置く構成は、chDB の別の題材です。今回は分析ループに絞ったため扱っていません。

7. まとめ

  • pip install chdbimport chdb だけで、サーバーも MCP も無しの手元分析エンジンを用意できた(Windows は WSL で実行)
  • Claude Code に渡すと、約330万行の parquet を自分で SQL で叩いて 4 問を全問正解し、地点を名前で聞けば手元の対応表 CSV を JOIN して地名に変換した。手元にエンジンがあると実行と検算の往復が短い
  • chDB で書いた SQL は、データ指定を変えるだけで ClickHouse Cloud でも同じ結果になった。手元で試して規模が大きくなったら ClickHouse へ移せる(集計 SQL は変えず、データの指定だけ差し替える)
  • 分析を進めるのはクライアント(Claude)の判断であり、chDB はそのための、サーバー不要で速いエンジンだった

手元にデータがあって、それを AI に触らせて素早く分析したい場面では、サーバーを立てる前に chDB を渡してみる、という選択が十分に成り立つと感じました。

参考

  1. ClickHouse Blog「chDB as the Agent's Local Data Engine」https://clickhouse.com/blog/chdb-agents-local-data-engine

  2. chDB 製品ページ https://clickhouse.com/chdb

  3. ClickHouse Blog「How we're building a data platform for a new user: agents」https://clickhouse.com/blog/building-a-data-platform-for-agents 2

  4. データは NYC 市タクシー&リムジン委員会(TLC)が公開する Trip Record Data。今回の parquet と zone 対応表は公式の配信元から取得した(https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2023-06.parquethttps://d37ci6vzurychx.cloudfront.net/misc/taxi_zone_lookup.csv)。データセットの解説は ClickHouse Docs にもある6 2

  5. chDB Python API reference(ClickHouse Docs)https://clickhouse.com/docs/chdb/api/python

  6. New York taxi data(ClickHouse Docs)https://clickhouse.com/docs/getting-started/example-datasets/nyc-taxi

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?