はじめに
ClickHouseは高性能なOLAPデータベースですが、ORDER BY / PARTITION BY / LowCardinality / Nullable / JOIN など、設計上のミスが性能に直結する要素が多いデータベースでもあります。
ClickHouse社が公開した公式の Agent Skill をClaude Codeに導入し、DDL生成の挙動変化を検証しました。各ルールを引用しながら最適化されたDDLが返るようになり、設計レビューの効率が改善したため、実測結果を共有します。
対象リポジトリは ClickHouse/agent-skills です。本文中の「スキル無し」「スキル有り」の応答はいずれも実際の出力を掲載しています。
Agent Skill の概要
Agent Skill は、AIコーディングエージェントに対して特定ドメインの知識と手順をパッケージとして与える仕組みです。Claude Code / Cursor / GitHub Copilot / Windsurf / Gemini CLI / Cline / Codex など、複数のエージェントが対応しています(仕様は agentskills.io)。
ClickHouse公式が提供するSkillは6種類あります。
| Skill | 内容 |
|---|---|
clickhouse-best-practices |
スキーマ・クエリ・インジェストに関する31のベストプラクティスルール |
clickhouse-architecture-advisor |
インジェスト・JOIN・遅延データ処理などの意思決定フレームワーク |
chdb-datastore / chdb-sql
|
chdb(インプロセスClickHouse)のpandas互換APIおよびSQL API |
clickhousectl-cloud-deploy / clickhousectl-local-dev
|
clickhousectl によるデプロイ手順 |
本記事の検証対象は主に clickhouse-best-practices です。
インストール
手順
プロジェクトルートで次のコマンドを実行します。
npx --yes skills add clickhouse/agent-skills -y
-y は対話プロンプトを省略するオプションです。ユーザー全体に適用する場合は -g を追加します。
導入確認
$ head -7 .agents/skills/clickhouse-best-practices/SKILL.md
---
name: clickhouse-best-practices
description: MUST USE when reviewing ClickHouse schemas, queries, or configurations.
Contains 31 rules that MUST be checked before providing recommendations.
Always read relevant rule files and cite specific rules in responses.
license: Apache-2.0
---
description 冒頭の MUST USE when ~ がClaudeによる自動適用のトリガです。
スキル無しとスキル有りの比較
検証環境は clickhouse/clickhouse-server:26.3(LTS、本記事執筆時点で最新のLTS)を使用しました(docker run またはClickHouse Cloudでも同等に再現可能です)。
Claude Codeに入力したプロンプトは次のとおりです。
プロンプト
アクセスログ用のテーブルをClickHouseに作成してください。カラムはuser_id, event_type(page_view / click / purchase), country, path, status_code, response_ms, event_time。1日数千万件の書き込みを想定し、event_type × countryおよびuser_idによる検索が多い。
スキル無しの応答(実測・抜粋)
CREATE TABLE access_logs
(
event_time DateTime CODEC(Delta, ZSTD(1)),
user_id UInt64,
event_type Enum8('page_view'=1,'click'=2,'purchase'=3),
country LowCardinality(FixedString(2)),
path LowCardinality(String),
status_code UInt16,
response_ms UInt32,
INDEX idx_user_id user_id TYPE bloom_filter(0.01) GRANULARITY 4
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, country, event_time)
TTL event_time + INTERVAL 12 MONTH;
一般的なClickHouse知識に基づく妥当な設計ですが、ルール引用は無く、設計判断の根拠は一般論ベースの解説にとどまります。
スキル有りの応答(実測・抜粋)
Claude Codeのセッションに Skill(clickhouse-best-practices) Successfully loaded skill が表示された後、Claude Codeは SHOW DATABASES および SHOW TABLES を自動で実行して既存スキーマを確認し、次の形式で応答しました。
Rules Checked
- schema-pk-plan-before-creation - Compliant
- schema-pk-cardinality-order - Applied: event_type(3) < country(~200) < user_id(高)
- schema-pk-prioritize-filters - Applied: event_type × country と user_id どちらも対応
- schema-types-lowcardinality - Applied: event_type, country, path
- schema-types-avoid-nullable - Applied: DEFAULT 使用
- schema-types-minimize-bitwidth - Applied: UInt16 (status_code), UInt32 (response_ms)
- schema-partition-low-cardinality - Applied: toYYYYMM で月単位
- query-index-skipping-indices - Applied: user_id に bloom_filter
CREATE TABLE IF NOT EXISTS analytics.access_log
(
event_type Enum8('page_view'=1,'click'=2,'purchase'=3),
country LowCardinality(String),
user_id UInt64,
event_time DateTime,
path LowCardinality(String),
status_code UInt16,
response_ms UInt32,
INDEX idx_user_id user_id TYPE bloom_filter(0.01) GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, country, user_id, event_time);
差分
| 項目 | スキル無し | スキル有り |
|---|---|---|
| 事前調査 | なし |
SHOW DATABASES / SHOW TABLES による既存状況の確認 |
| ORDER BY | (event_type, country, event_time) |
(event_type, country, user_id, event_time) |
| user_id検索 | bloom_filterのみ | ORDER BY prefixとbloom_filterの併用 |
| 応答構造 | 自由記述 |
Rules Checked → 設計判断 → DDL の固定テンプレート |
| 根拠 | 定性的な説明 | ルール名の明示的な引用(例: schema-pk-cardinality-order) |
既存DBを自動的に調査する挙動は agent-discovery-schema ルールの適用によるものです。MCP経由で本番ClickHouseに接続する場合、agent-query-safety ルールに基づき、探索クエリに自動で LIMIT および max_execution_time が付与されます。
実行検証(ClickHouse 26.3 LTS で動作確認)
スキル有りで生成されたDDLを適用し、500,000行のサンプルデータを投入したうえで granule pruning の効きを検証します。granule pruning とは、MergeTreeが granule(既定8192行のデータブロック)ごとに保持する ORDER BY 列の min/max から、WHERE条件に合致しないgranuleを丸ごと読み飛ばすスパースインデックスの挙動を指します。
EXPLAIN indexes = 1
SELECT count() FROM analytics.access_log
WHERE event_type = 'purchase' AND country = 'JP';
結果の抜粋を示します。
ReadFromMergeTree (analytics.access_log)
Indexes:
MinMax
Condition: true
Parts: 3/3
Granules: 61/61
Partition
Condition: true
Parts: 3/3
Granules: 61/61
PrimaryKey
Keys:
event_type
country
Condition: and((country in ['JP', 'JP']), (event_type in [3, 3]))
Parts: 3/3
Granules: 7/61 ← 61グラニュール中7グラニュールのみ読み込み(約88%をスキップ)
Search Algorithm: binary search
低カーディナリティ列を先頭に配置したORDER BYにより、スパースインデックスによるグラニュール単位の読み飛ばしが機能しています。これは schema-pk-cardinality-order ルールの具体的な効果です。
user_id 単独での検索でも、PrimaryKey による絞り込み(43/61)に加えて bloom_filter のスキップインデックスがさらに 7/43 まで読み込みグラニュールを削減することを確認しました。
主要ルールの適用例
query-mv-incremental: MV + AggregatingMergeTree による集計
event_type × country × 日次の集計高速化を依頼すると、次のような出力が得られます。
CREATE TABLE analytics.events_daily
(
day Date,
event_type LowCardinality(String),
country LowCardinality(String),
hits AggregateFunction(count),
avg_ms AggregateFunction(avg, UInt32)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (event_type, country, day);
CREATE MATERIALIZED VIEW analytics.events_daily_mv
TO analytics.events_daily AS
SELECT toDate(event_time) AS day, event_type, country,
countState() AS hits, avgState(response_ms) AS avg_ms
FROM analytics.access_log
GROUP BY day, event_type, country;
SELECT day, countMerge(hits), avgMerge(avg_ms)
FROM analytics.events_daily
WHERE event_type='purchase' AND country='JP'
AND day BETWEEN today()-30 AND today()
GROUP BY day ORDER BY day;
query-join-consider-alternatives / query-join-filter-before: JOINに代わるDictionary利用
CREATE DICTIONARY analytics.dict_users (
user_id UInt64, plan String, country String
)
PRIMARY KEY user_id
SOURCE(CLICKHOUSE(DB 'analytics' TABLE 'users' USER 'default' PASSWORD 'test123'))
LIFETIME(MIN 300 MAX 600)
LAYOUT(HASHED());
SELECT dictGet('analytics.dict_users','plan',user_id) AS plan, count()
FROM analytics.access_log
WHERE event_time >= now() - INTERVAL 1 HOUR
GROUP BY plan;
insert-batch-size / insert-async-small-batches: 小規模INSERTの非同期化
SET async_insert = 1, wait_for_async_insert = 1,
async_insert_max_data_size = 10000000,
async_insert_busy_timeout_ms = 1000;
INSERT INTO analytics.access_log VALUES (...);
ALTER TABLE ... UPDATE/DELETE は insert-mutation-avoid-update / insert-mutation-avoid-delete により非推奨とされ、更新頻度が高い場合は ReplacingMergeTree(version) への変更が提案されます。
既存ClickHouseへの接続(MCP)
agent-connect-mcp.md の手順に沿って .claude/settings.json にMCPサーバを登録します。
{
"mcpServers": {
"clickhouse": {
"command": "uvx",
"args": ["mcp-clickhouse"],
"env": {
"CLICKHOUSE_HOST": "localhost",
"CLICKHOUSE_PORT": "8123",
"CLICKHOUSE_USER": "default",
"CLICKHOUSE_PASSWORD": "test123",
"CLICKHOUSE_DATABASE": "analytics"
}
}
}
}
この状態で既存テーブルの調査を依頼すると、agent-discovery-schema で定義された7ステップ(DB一覧 → テーブル → カラム → ORDER BY → スキップインデックス → サンプリング → EXPLAIN)が順に実行されます。また探索クエリには LIMIT とタイムアウトが自動付与されるため、本番環境への接続時でも誤操作のリスクを抑えられます。
まとめ
-
npx skills add clickhouse/agent-skills -yにより、1コマンドで導入可能 - 実体は
.agents/skills/配下に配置され、Claude Code用には.claude/skills/からsymlinkが張られる構成となっており、複数エージェントで共通利用が可能 - SKILLに
MUST USE指定があるため、ClickHouseに関するスキーマ・クエリ設計時に自動適用される - 応答が
Rules Checked→ 設計判断 → DDL の定型構造となり、レビュー時の根拠がルール名として明示的に残る -
schema-pk-cardinality-orderの適用により、実測でgranule pruningが約88%機能することを確認 - MCP連携により、既存ClickHouseの調査時にも
LIMIT・タイムアウト・探索フローといったガードレールが機能する
新規にClickHouseを導入するチームに加え、既存環境を運用中のチームが既存DDLや設計の妥当性をレビューする用途にも適します。ライセンスはApache 2.0であり、fork や社内ルールの追加も可能です。
今回はClickHouse公式Skillの基本的な導入手順と動作確認について書きましたが、実際に使い込むほど面白いテーマが広がりそうだと感じました。今後は応用的な観点でも検証していきたいと思います。