メインコンテンツへスキップ
データ型 Map(K, V) は、キー・バリューのペアを格納します。 他のデータベースとは異なり、ClickHouse の map ではキーは一意ではありません。つまり、1 つの map に同じキーを持つ 2 つの要素を含めることができます。 (これは、map が内部的に Array(Tuple(K, V)) として実装されているためです。) 構文 m[k] を使用すると、map m のキー k に対応する値を取得できます。 また、m[k] は map を走査するため、この操作の実行時間は map のサイズに対して線形です。 パラメータ
  • K — Map のキーの型。Nullable、および Nullable 型をネストした LowCardinality を除く任意の型。
  • V — Map の値の型。任意の型。
map 型のカラムを持つテーブルを作成します。
Query
key2 の値を選択するには:
Query
Response
要求したキー k が map に含まれていない場合、m[k] は値型のデフォルト値を返します。たとえば、整数型では 0、文字列型では '' です。 キーが map に存在するかどうかを確認するには、関数 mapContains を使用できます。
Query
Response

Tuple を Map に変換する

Tuple() 型の値は、関数 CAST を使用して Map() 型の値にキャストできます。
Query
Response

Map のサブカラムを読み取る

Map 全体を読み取らずに済むよう、場合によってはサブカラム keysvalues を使用できます。
Query
Response

MergeTree におけるバケット化された Map シリアライゼーション

デフォルトでは、MergeTree の Map カラムは、単一の Array(Tuple(K, V)) ストリームとして保存されます。 m['key'] で 1 つのキーを読み取るには、必要なのがそのキーだけであっても、カラム全体、つまりすべての行にあるすべてのキー・バリューのペアを走査する必要があります。 キーの種類が多い Map では、これがボトルネックになります。 バケット化シリアライゼーション (with_buckets) では、キーをハッシュ化して、キー・バリューのペアを複数の独立したサブストリーム (バケット) に分割します。 クエリが m['key'] にアクセスすると、そのキーを含むバケットだけがディスクから読み取られ、ほかのバケットはすべてスキップされます。

バケット化シリアライゼーションの有効化

INSERT 時に作成されるゼロレベルのパーツでは basic シリアライゼーションのままにし、マージ後のパーツでのみ with_buckets を使用することで、挿入速度の低下を防げます:

仕組み

データパートが with_buckets シリアライゼーションで書き込まれる場合:
  1. 1行あたりの平均キー数が、ブロックの統計情報から計算されます。
  2. バケット 数は、設定された戦略によって決まります (Settings を参照) 。
  3. 各キー・バリューのペアは、キーを ハッシュ して バケット に割り当てられます: bucket = hash(key) % num_buckets
  4. 各 バケット は、それぞれ独自のキー、値、オフセットを持つ独立したサブストリームとして保存されます。
  5. buckets_info メタデータストリームに、バケット 数と統計情報が記録されます。
クエリが特定のキー (m['key']) を読み取る場合、オプティマイザは expression をキーのサブカラム (m.key_<serialized_key>) に書き換えます。 シリアライゼーション層は、要求されたキーがどの バケット に属するかを計算し、その バケット だけをディスクから読み取ります。 Map 全体を読み取る場合 (たとえば SELECT m) 、すべての バケット が読み取られ、元の Map に再構成されます。これは、複数のサブストリームの読み取りと merge のオーバーヘッドがあるため、basic シリアライゼーションより低速です。
with_buckets シリアライゼーションを使用すると、Map 値内のキーの順序が元の insert 順序と異なる場合があります。キーは ハッシュ によって バケット に分散され、insert 順ではなく バケット 順で再構成されます。basic シリアライゼーションでは、挿入された Map のキー順が保持されます。
バケット 数はパーツごとに異なる場合があります。バケット 数が異なるパーツが merge されると、新しいパーツの バケット 数は、merge 後の統計情報に基づいて再計算されます。basicwith_buckets シリアライゼーションのパーツは同じ table 内に共存でき、透過的に merge されます。

設定

パフォーマンス面のトレードオフ

次の表は、さまざまな Map サイズ (1 行あたり 10 ~ 10,000 個のキー) において、basic シリアライゼーションと比較した with_buckets のパフォーマンスへの影響をまとめたものです。バケット数は、32 を上限とする sqrt 戦略で決定しています。正確な数値は、キー/値の型、データ分布、ハードウェアに依存します。

推奨事項

  • 小規模な map (平均 32 キー未満) : basic シリアライゼーションのままにしてください。小規模な map では、バケット化のオーバーヘッドに見合う効果はありません。デフォルトの map_buckets_min_avg_size = 32 により、これは自動的に適用されます。
  • 中規模な map (32~100 キー) : クエリで個々のキーに頻繁にアクセスする場合は、sqrt 戦略の with_buckets を使用してください。単一キーのルックアップは 4~8 倍高速になります。
  • 大規模な map (100 キー以上) : with_buckets を使用してください。単一キーのルックアップは 16~49 倍高速になります。insert 速度をベースラインに近い水準に保つには、map_serialization_version_for_zero_level_parts = 'basic' を検討してください。
  • map 全体のスキャンがワークロードの大半を占める場合: basic のままにしてください。バケット化シリアライゼーションでは、全スキャン時に約 2 倍のオーバーヘッドが発生します。
  • 混合ワークロード (キーのルックアップと全スキャンが混在する場合) : ゼロレベルのパーツを basic に設定した with_buckets を使用してください。PREWHERE 最適化では、まず filter に関連するバケットだけを読み取り、その後、一致した行についてのみ map 全体を読み込むため、全体として大幅な高速化が得られます。

代替アプローチ

バケット化された Map のシリアライゼーションがユースケースに合わない場合は、キー単位のアクセス性能を向上させるための代替手法が 2 つあります。

JSONデータ型の使用

JSON データ型は、頻出する各パスを個別の動的サブカラムとして格納します。max_dynamic_paths の上限を超えたパスは、共有データ構造 に格納されます。この共有データ構造では、単一パスの読み取りを最適化するために advanced シリアライゼーションを使用できます。advanced シリアライゼーションの詳しい概要については、ブログ記事を参照してください。 異なるキーごとに異なる値型が必要な場合、キーの集合が行ごとに大きく異なる場合、または頻繁にアクセスするキーが事前に分かっており、直接サブカラムアクセスできるよう型付きパスとして宣言できる場合は、JSON を使用してください。

複数の Map カラムへの手動分片

アプリケーションレベルで、キーのハッシュに基づいて 1 つの Map を複数のカラムに手動で分割できます。
挿入時には、各キー・バリューのペアをカラム m{hash(key) % 4} に振り分けます。クエリ時には、対象のカラム m{hash('target_key') % 4}['target_key'] から読み取ります。 手動シャーディングは、多数のカラムを持つテーブルのマージ時にメモリ使用量を削減するうえで垂直マージが重要な場合や、分片数を固定して明示的に制御する必要がある場合に有効です。ほとんどのユースケースでは、自動バケット化シリアライゼーションのほうがシンプルで十分です。 関連項目
最終更新日 2026年6月12日