Skip to main content
JSONカラム型は ClickHouse 25.3+ から本番環境で利用可能です。これより前のバージョンは本番利用には推奨されません。
データが JSON として届く場合、ClickHouse には、完全に型付けされたカラムから生の String まで、複数の保存方法があります。どの方法が適切かは、スキーマがどの程度予測可能か、またフィールドレベルのクエリが必要かどうかによって決まります。 Scope: このページでは、JSON データを保存する際のスキーマ設計上の判断を扱います。JSON 入出力フォーマットJSON 関数、クエリ構文は扱いません。JSONカラム型そのものについては、Use JSON where appropriate を参照してください。 Assumes: ClickHouse table creationMergeTree の基本、およびカラム型の構文を理解していることを前提としています。

クイック判断

  • すべてのフィールド の型が既知かつ安定しており、スキーマもほとんど変わらない場合 型付きカラム
  • ほとんどのフィールド は安定しているものの、一部が動的または予測しにくい場合 ハイブリッド (typed + JSON)
  • 構造全体 が動的で、レコードごとに現れたり消えたりするキーがある場合 ネイティブ JSON カラム
  • 動的フィールド がキー・バリューのペアで、値の型が一貫している場合 (例: 文字列のタグ、数値メトリクス) JSON ではなく Map
  • JSONブロブ を保存・取得するだけで、フィールドレベルのクエリ を行わない場合 不透明な String ストレージ
JSON フォーマット と JSON カラム型 を混同しないでください。JSON カラム型をまったく使わなくても、JSON形式のデータ (JSONEachRow など) を typed columns に挿入できます。ここでの判断対象は input format ではなく、カラム型です。

アプローチの詳細

型付きカラム

使用する場面: JSON の構造が設計時点で完全にわかっている場合。フィールドや型がレコードごとに変わりません。複雑なネスト構造 (オブジェクトの配列やネストしたマップ) であっても、ArrayTupleNested 型で表現できます。 トレードオフ: スキーマの変更には ALTER TABLE が必要です。想定外のフィールドは、スキーマを更新しない限り、INSERT 時にエラーなく破棄されます。
セットアップ
検証
注意点
  • JSONEachRow で JSON データを insert する際、その JSON にスキーマに存在しないフィールドが含まれている場合、ClickHouse は既定でそれらをエラーなく破棄します。代わりにエラーにしたい場合は、input_format_skip_unknown_fields0 に設定してください。

ハイブリッド (型付きカラム + JSON)

使用する場面: 主要なフィールド群 (timestamp、ID、ステータスコードなど) は安定している一方で、ペイロードの一部が動的な場合です。たとえば、ユーザー定義の属性、タグ、メタデータ、またはレコードごとに異なる拡張フィールドなどが該当します。 トレードオフ: 型付きカラムでは高いパフォーマンスを得られ、JSONカラムでは柔軟性を確保できます。一方で、JSONカラムの動的な部分には、依然として insert のオーバーヘッドとストレージコストが伴います。
セットアップ
検証
注意点
  • あらかじめ把握している JSONパス には 型ヒント を使ってください。型ヒントを使うと判別子カラムを介さず、そのパスは通常の型付きカラムと同じように格納され、同等のパフォーマンスが得られ、オーバーヘッドも発生しません。
  • クエリしないパス (デバッグ用メタデータ、内部 tracing ID など) には SKIP または SKIP REGEXP を使い、ストレージを節約して subcolumn 数を減らしてください。
  • max_dynamic_paths は、実際にクエリする distinct path の数に応じて設定してください。デフォルト値 (1024) はほとんどのケースで十分です。動的な部分が小さい場合は、より低い値にしてください。
  • max_dynamic_paths を 10,000 より大きく設定しないでください。値を大きくしすぎると resource 消費が増え、効率が低下します。
ドット付きキードットを含むキー (例: http.status_code) は、デフォルトではネストされたパスとして扱われるため、{"http.status_code": 200}{"http": {"status_code": 200}} と同じ形で格納されます。これは OTel の属性で一般的です。型ヒントを使ってドット付きパスの格納方法を制御するか、json_type_escape_dots_in_keys (25.8+) を有効にしてください。

ネイティブ JSON カラム

使用する場面: 構造が本質的に予測不能で、キーがレコードごとに現れたり消えたりする場合。ユーザー生成スキーマ、プラグインシステム、またはアップストリームのスキーマを制御できないデータレイクへのインジェストなどが該当します。 トレードオフ: 型付きカラムより挿入が遅くなります。String よりオブジェクト全体の読み取りが遅くなります。サブカラム管理によるストレージのオーバーヘッドがあります。特定のパスに対するフィールドレベルのクエリには適しています。
セットアップ
JSON カラムに JSON ドキュメント全体を挿入する場合は、JSONAsObject フォーマットを使用します。各入力行を、カラムにマッピングされる完全な JSON オブジェクトとして扱います。検証
注意事項
  • 型ヒントがない場合、ClickHouse は最初に確認した値をもとにパスごとに型を推論します。あるレコードでは score"10" (文字列) として届き、別のレコードでは 10 (整数) として届くと、そのパスには判別子カラムが作成され、クエリが遅くなります。型が分かっているパスにはヒントを追加してください。
  • パス数が max_dynamic_paths を超えると、overflow 値はクエリパフォーマンスが低下する 共有データ構造 に移されます。JSONDynamicPaths() で監視し、上限は 10,000 未満に保ってください。
  • 各動的パスは、最大 max_dynamic_types (デフォルトは 32) の異なるデータ型をサポートします。1 つのパスがこれを超えると、追加の型は共有 variant ストレージにフォールバックします。これは、同じフィールドで型の不一致が非常に多いデータでない限り、問題になることはほとんどありません。

不透明な String ストレージ

使用する場面: JSON ドキュメントを丸ごと保存・取得し、そのままアプリケーションに渡したり、アーカイブしたり、下流へ転送したりする場合。ClickHouse 内ではフィールド単位のフィルタリングや集約は行いません。 トレードオフ: 挿入が最速で、スキーマも最もシンプルです。実行時にパースする (JSONExtract ファミリーを使う) 以外にフィールドレベルのクエリはできず、これを大規模に行うと低速です。
セットアップ
検証
注意点
  • 要件が変わって後からフィールド単位のクエリが必要になった場合は、型付きカラムまたは JSON カラムを持つ新しいテーブルを作成し、データをバックフィルする必要があります。個々のフィールドをクエリする可能性が少しでもあるなら、代わりにハイブリッド方式から始めてください。
  • JSONExtract 関数は、クエリのたびに文字列をパースします。アドホックな調査には許容できますが、本番のダッシュボードや高 QPS のワークロードには向きません。
  • JSON ペイロードが大きい場合は、String カラムに圧縮 codec (ZSTD) を使うことを検討してください。高い圧縮効果が得られます。

比較

Map のほうが適している場合

動的フィールドが均質なキー・バリューのペアで構成されており、すべての値が同じ型である場合は、JSON カラムよりも Map(String, T) のほうがシンプルで効率的です。一般的な例としては、文字列タグ (Map(String, String)) 、数値メトリクス (Map(String, Float64)) 、機能フラグ (Map(String, Bool)) があります。
Map はキー単位のフィルタリング (tags['env'] = 'prod') をサポートし、JSON より低コストで保存でき、JSON type のサブカラムによるオーバーヘッドも回避できます。なお、デフォルトではキーのルックアップ時に Map が線形スキャンされます。小規模なタグセットであれば問題ありませんが、100 個以上のキーを持つ map では with_buckets シリアライゼーション を検討してください。値の型が混在している場合や構造がネストしている場合は JSON を使用し、フラットなキー・バリューのペアで値の型が uniform な場合は Map を使用してください。
最終更新日 2026年7月23日