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?

この記事は、AI時代のデータベースについて自分なりに考えた記事です。ClickHouseを「ベクトル検索もできる高速OLAP」ではなく、「RAGやLLMアプリの失敗を後から見直すためのDB」として見ています。

本文中のClickHouse機能は、2026年6月1日に公式ドキュメントと公式ブログを確認しました。SQLは設計例です。手元環境にClickHouseを入れての実測値は載せていません。

はじめに

RAGを作ると、最初はだいたいベクトル検索に目が行きます。

分かります。
RAGを作るなら、文章をembeddingにして、近い文書を検索して、LLMに渡す。
この流れはかなり自然です。

でも、実際にサービスとして考えると、ベクトル検索だけではRAGは直せません。

たとえば、ユーザーから「この回答、違います」と言われたとします。
そのときに見たいのは、近いベクトルが取れたかどうかだけではありません。

  • ユーザーが何を聞いたか
  • どの文書が検索されたか
  • その文書は本当に回答に使われたか
  • LLMの回答はユーザーの役に立ったか
  • 失敗した質問にはどんな傾向があるか
  • モデルを変えたあとに品質やコストがどう変わったか

ここまで見るなら、欲しいのは「近いベクトルを探す場所」だけではありません。
大量のイベントを速く集計できて、検索ログとユーザー行動をつなげて見られる場所が必要です。

そこでClickHouseです。

自分がこの記事で言いたいのは、ClickHouseを「ベクトルDBの代わり」として見るより、「AIアプリの失敗を分析する場所」として見るとかなり強い、という話です。

ClickHouseを入れればRAGが勝手に賢くなる、という話ではありません。プロンプト、文書分割、embeddingモデル、評価データ、UI、運用の全部が効きます。この記事では、DB側から見た設計の話に絞ります。

この記事で言いたいこと

  • AI時代のDBで増える仕事は、ベクトル検索だけではなくログ分析にもある
  • RAGは、検索候補、距離、順位、回答、評価を残さないと改善しにくい
  • ClickHouseは、ベクトル検索と大量イベント集計を同じSQLで扱えるので、RAGの観測台として使いやすい
  • PostgreSQLやMySQLを置き換えるのではなく、分析と観測をClickHouseに逃がすのが現実的

AI時代のDBに増えた仕事

昔からDBは、データを保存して検索する場所でした。
今もそこは変わりません。

ただ、LLMアプリではDBに入るものが増えました。

たとえば普通のWebサービスなら、ユーザー、投稿、注文、決済、設定みたいなテーブルを考えます。
RAGを入れると、ここに別のデータが乗ってきます。

種類
元データ ドキュメント、FAQ、記事、社内Wiki
検索用データ embedding、文書チャンク、メタデータ
実行ログ 質問、検索結果、プロンプト、回答
評価データ thumbs up/down、再質問、離脱、手動評価
コスト情報 token数、モデル名、レイテンシ、リトライ回数

この中で一番派手なのはembeddingです。
でも、運用で効くのはむしろログです。

検索が外れたのか。
検索は当たったけどLLMが使わなかったのか。
そもそも文書が足りないのか。
ユーザーの聞き方と文書の粒度が合っていないのか。

ここを見られないと、RAGの改善が感覚になります。
「なんか回答が微妙」から先に進めません。

ClickHouseをどこに置くか

自分なら、最初から全部をClickHouseに寄せるより、役割を分けます。

役割 使うもの
ユーザー、課金、権限など PostgreSQLやMySQL
文書の原本 オブジェクトストレージや既存DB
文書チャンクとembedding ClickHouse
検索ログ、LLM実行ログ、評価ログ ClickHouse
管理画面の重い集計 ClickHouse

PostgreSQLやMySQLを捨てる話ではありません。
むしろ、トランザクションが大事な部分は今まで通り普通のRDBに置きたいです。

ClickHouseは、後から大量に読むところに置きます。
RAGだと、検索候補、実行ログ、評価ログ、分析画面あたりです。

ClickHouse公式でも、PostgreSQLをシステムの記録元にし、分析をClickHouseに任せる構成が紹介されています。
この考え方はRAGにも合います。

最小構成のテーブルを考える

たとえば、社内FAQや技術記事をRAGで検索するなら、まず文書チャンクをこう置けます。

CREATE TABLE rag_chunks
(
    chunk_id UUID,
    document_id UUID,
    title String,
    url String,
    section String,
    body String,
    document_version String,
    embedding_model LowCardinality(String),
    embedding Array(Float32),
    updated_at DateTime
)
ENGINE = MergeTree
ORDER BY (document_id, section, chunk_id);

embeddingArray(Float32)で持ちます。
ClickHouseではL2DistancecosineDistanceのような距離関数を使って、ベクトル同士の近さをSQLで計算できます。

検索はこういう形です。

WITH {query_embedding:Array(Float32)} AS query_embedding
SELECT
    chunk_id,
    title,
    url,
    section,
    body,
    cosineDistance(embedding, query_embedding) AS distance
FROM rag_chunks
ORDER BY distance ASC
LIMIT 8;

まずはこれで十分です。
最初から巨大な専用構成を組まなくても、SQLで検索できる状態にしておくと試行錯誤しやすいです。

データが増えて線形検索が重くなってきたら、HNSWなどの近似検索を検討します。
ClickHouseの動画でも、HNSWのvector similarity indexを使って、線形検索から近似検索へ移る流れが紹介されています。

ClickHouse公式ドキュメントでは、vector similarity indexはClickHouse 25.8以降で利用可能とされています。古いバージョンで動かす場合は、まず利用中のClickHouseのバージョンを確認してください。

たとえば1536次元のembeddingなら、こういう形です。

CREATE TABLE rag_chunks
(
    chunk_id UUID,
    document_id UUID,
    title String,
    url String,
    section String,
    body String,
    document_version String,
    embedding_model LowCardinality(String),
    embedding Array(Float32),
    updated_at DateTime,
    CONSTRAINT embedding_length CHECK length(embedding) = 1536,
    INDEX embedding_hnsw embedding TYPE vector_similarity('hnsw', 'cosineDistance', 1536)
)
ENGINE = MergeTree
ORDER BY (document_id, section, chunk_id);

後から足すならこうです。

ALTER TABLE rag_chunks
ADD INDEX embedding_hnsw embedding
TYPE vector_similarity('hnsw', 'cosineDistance', 1536);

ALTER TABLE rag_chunks
MATERIALIZE INDEX embedding_hnsw
SETTINGS mutations_sync = 2;

ベクトル検索は、速ければ良いというものでもありません。近似検索は速度と精度の交換です。まずは少ないデータで検索結果を目で見て、評価用の質問セットを作ってから速くした方が失敗しにくいです。L2DistancecosineDistanceでは値が小さいほど近いので、ORDER BY ... ASCで使います。

また、ClickHouse公式ドキュメントでは、vector similarity indexは検索時にメモリへロードされると説明されています。大きいembeddingを大量に持つなら、indexを貼れば全部解決、とは考えない方がよさそうです。

RAGで本当に欲しいのは検索ログ

自分がClickHouseを使いたいと思う一番の理由は、検索ログです。

RAGは「検索して答える」だけならデモで終わります。
運用では、検索と回答の間にある失敗を見たいです。

たとえばイベントをこう置きます。

CREATE TABLE rag_events
(
    event_time DateTime64(3),
    request_id UUID,
    user_id String,
    session_id String,
    model String,
    prompt_version LowCardinality(String),
    question String,
    answer String,
    input_tokens UInt32,
    output_tokens UInt32,
    latency_ms UInt32,
    status LowCardinality(String),
    feedback LowCardinality(String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, request_id);

検索されたチャンクも別で持ちます。

CREATE TABLE rag_retrievals
(
    event_time DateTime64(3),
    request_id UUID,
    chunk_id UUID,
    retrieval_rank UInt8,
    distance Float32,
    title String,
    url String,
    document_version String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, request_id, retrieval_rank);

これで、RAGの動きが後から見えます。

ここで大事なのは、回答だけでなく「検索された候補」を残すことです。
RAGの失敗は、回答文だけ見ても原因が分かりません。

自分なら、最低でもこの4つは残します。

残すもの 後で分かること
request_id 質問、検索、回答、評価をつなげられる
retrieval_rank 上位候補に何が出たか分かる
distance 検索が近かったのか遠かったのか見える
document_version 古い文書で答えていないか追える

たとえば、低評価が多い質問を探します。

SELECT
    question,
    count() AS cnt,
    avg(latency_ms) AS avg_latency_ms,
    avg(input_tokens + output_tokens) AS avg_tokens
FROM rag_events
WHERE feedback = 'bad'
  AND event_time >= now() - INTERVAL 7 DAY
GROUP BY question
ORDER BY cnt DESC
LIMIT 20;

低評価の回答で、どの文書が検索されていたかも見られます。

SELECT
    r.title,
    r.url,
    count() AS bad_count,
    avg(r.distance) AS avg_distance
FROM rag_events AS e
INNER JOIN rag_retrievals AS r
    ON e.request_id = r.request_id
WHERE e.feedback = 'bad'
  AND r.retrieval_rank <= 3
  AND e.event_time >= now() - INTERVAL 7 DAY
GROUP BY
    r.title,
    r.url
ORDER BY bad_count DESC
LIMIT 20;

この結果を見ると、改善の方向が少し具体的になります。

  • いつも同じ古い文書が上に来るなら、文書更新や重み付けを見る
  • distanceが近いのに低評価なら、プロンプトや回答生成を見る
  • distanceが遠いものばかりなら、文書分割やembeddingモデルを見る
  • 特定の質問だけ失敗するなら、FAQやメタデータを足す

こういう分析は、ベクトルDB単体よりOLAPの世界に近いです。
だからClickHouseをRAGの横に置く意味があります。

失敗を4つに分けて見る

RAGの改善で一番もったいないのは、全部を「回答が悪い」にしてしまうことです。
それだと、プロンプトをいじるしかなくなります。

自分なら、まず失敗をこの4つに分けます。

失敗 ログで見るところ 直す場所
文書がない どの検索候補も遠い、同じ質問が何度も出る FAQ追加、ドキュメント整備
検索が外れた 低評価のときにdistanceが遠い chunkサイズ、embeddingモデル、メタデータ
回答生成が外れた distanceは近いのに低評価 プロンプト、引用ルール、モデル
運用がつらい latencyやtokenが急に増える モデル選定、キャッシュ、検索件数

この分け方をすると、ClickHouseで見るべきクエリも決まります。
たとえば「検索が外れた」なら、低評価かつ上位候補のdistanceが大きいものを見ます。

SELECT
    e.question,
    min(r.distance) AS best_distance,
    any(r.title) AS top_title,
    any(r.url) AS top_url
FROM rag_events AS e
INNER JOIN rag_retrievals AS r
    ON e.request_id = r.request_id
WHERE e.feedback = 'bad'
  AND r.retrieval_rank = 1
  AND e.event_time >= now() - INTERVAL 7 DAY
GROUP BY e.question
ORDER BY best_distance DESC
LIMIT 20;

逆に「回答生成が外れた」なら、上位候補は近いのに低評価になっているものを見ます。

SELECT
    e.question,
    e.answer,
    r.title,
    r.url,
    r.distance
FROM rag_events AS e
INNER JOIN rag_retrievals AS r
    ON e.request_id = r.request_id
WHERE e.feedback = 'bad'
  AND r.retrieval_rank = 1
  AND r.distance < 0.2
  AND e.event_time >= now() - INTERVAL 7 DAY
ORDER BY e.event_time DESC
LIMIT 20;

0.2は適当な例です。
実際にはembeddingモデル、正規化の有無、距離関数、データの性質で見方が変わります。
だからこそ、最初は固定のしきい値で決めつけず、distanceの分布を見たいです。

「AI時代のDB」は、LLMの失敗を説明できる必要がある

LLMアプリを作っていて怖いのは、失敗の原因がぼやけることです。

普通の機能なら、バグの場所を追いやすいです。
SQLが間違っている。
APIが落ちている。
バリデーションが抜けている。

RAGでは、もっと曖昧です。

検索対象の文書が悪いのか。
検索クエリが悪いのか。
embeddingが合っていないのか。
LLMが拾った文書を無視したのか。
ユーザーの期待と回答方針がずれているのか。

このへんを毎回ログから追うのはしんどいです。
だから、最初から分析できる形で残しておきたいです。

ClickHouseは、大量のログやイベントをあとから集計するのが得意です。
OpenAIのオブザーバビリティ事例でも、ClickHouseを使ってペタバイト規模のログを扱っている話が出ています。
自分の個人開発でそんな量は出ません。
でも、考え方は同じです。

RAGも観測できないと改善できません。

ダッシュボードで見るならこのあたり

もし管理画面を作るなら、最初はこのくらいを見たいです。

見たいもの 理由
1日の質問数 利用量を見る
p50/p95レイテンシ 体感速度を見る
token数の推移 コストを見る
feedback別の質問 失敗パターンを見る
retrieval_rank 1のdistance分布 検索の当たり具合を見る
参照された文書ランキング 使われている知識を知る
低評価に出やすい文書 古い文書や曖昧な文書を探す

たとえば、日ごとの利用量と低評価率です。

SELECT
    toDate(event_time) AS day,
    count() AS requests,
    countIf(feedback = 'bad') AS bad_requests,
    countIf(feedback = 'bad') / count() AS bad_rate,
    quantile(0.95)(latency_ms) AS p95_latency_ms
FROM rag_events
GROUP BY day
ORDER BY day;

モデル変更の前後比較もできます。

SELECT
    model,
    count() AS requests,
    avg(input_tokens + output_tokens) AS avg_tokens,
    quantile(0.95)(latency_ms) AS p95_latency_ms,
    countIf(feedback = 'good') / count() AS good_rate
FROM rag_events
WHERE event_time >= now() - INTERVAL 30 DAY
GROUP BY model
ORDER BY requests DESC;

AIアプリでは、モデルを変えたときに「なんとなく良くなった気がする」で終わりがちです。
でもログを残しておくと、遅くなったのか、安くなったのか、低評価が減ったのかを見られます。

ClickHouseが合うところ、合わないところ

ClickHouseが合いそうなのは、こういう部分です。

  • 検索ログを大量に保存する
  • LLMの実行ログを集計する
  • token数やレイテンシを分析する
  • 文書チャンクとメタデータをSQLで絞り込む
  • ベクトル検索と普通の集計を同じ場所で試す
  • PostgreSQLやMySQLの分析負荷を逃がす

逆に、何でもClickHouseに入れたいわけではありません。

  • ユーザーの残高更新
  • 注文処理
  • 認可の中心データ
  • 強い整合性が必要な書き込み
  • 1行ずつ頻繁に更新するデータ

こういうところは、PostgreSQLやMySQLの方が自然です。

AI時代のDBは、1つのDBが全部やるというより、役割分担が大事になりそうです。
RDBはサービスの状態を守る。
ClickHouseは大量のイベントを読み解く。
ベクトル検索は、その中の1機能として使う。

自分にはこの見方が一番しっくり来ています。

ClickHouseをAIスタックに入れる理由

ClickHouseをAIスタックに入れる理由は、単に「速いから」だけではないと思っています。

速いのはもちろん大事です。
でも、それだけならベクトル検索専用DBや検索エンジンも候補になります。

ClickHouseの良さは、検索した後の世界までSQLで触れるところです。

RAGでは、ベクトルで近い文書を取ったあとに、普通の条件で絞りたいことがあります。

  • 公開範囲
  • 言語
  • 更新日時
  • 製品カテゴリ
  • ユーザーの契約プラン
  • 文書の種類

さらに、検索後の結果をログとして残し、日次やモデル別に集計したいです。
このとき、構造化データと半構造化データとベクトルが別々の場所に散っていると、分析が面倒になります。

ClickHouseに寄せると、少なくとも「読む」「集計する」「比較する」は同じSQLでできます。
ここがAI時代のDBっぽいところです。

小さく始めるなら

自分なら、最初はこの順で試します。

  1. PostgreSQLやMySQLの本体データはそのままにする
  2. 文書チャンクとembeddingをClickHouseに入れる
  3. RAGの検索ログとLLM実行ログをClickHouseに入れる
  4. 低評価や遅い質問をSQLで見られるようにする
  5. データが増えてから、vector similarity indexやパーティション設計を見る

最初から「AI基盤」を作ろうとすると大きくなりすぎます。
でも、検索ログを残すだけなら始めやすいです。

そして、ログはあとから作れません。
取っていなかった検索結果は、あとで分析できません。
RAGを少しでも本番に近い形で動かすなら、回答本文だけでなく、検索された候補と距離と順位も残したいです。

まとめ

AI時代のデータベースで変わるのは、ベクトルを持てるかどうかだけではないと思います。

もちろんベクトル検索は大事です。
でも、RAGやLLMアプリを運用するなら、もっと泥くさいログも必要です。

ユーザーが何を聞いたか。
どの文書が返ったか。
LLMがどう答えたか。
どれくらい遅かったか。
どれくらいtokenを使ったか。
ユーザーは満足したか。

これを後から速く見られるDBがあると、改善がしやすくなります。

ClickHouseは、AIスタックの中で「ベクトル検索をする場所」にもなれます。
でもそれ以上に、「AIアプリを観測して改善する場所」として強いと思いました。

RAGは作って終わりではありません。
ログを見て、外れた検索を直して、古い文書を直して、モデル変更の影響を見る。

その作業をちゃんと回すためのDBとして、ClickHouseはかなり現実的な選択肢だと思います。

参考

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?