メインコンテンツへスキップ
効率的なデータインジェストは、高性能な ClickHouse デプロイメントの基盤です。適切な 挿入 戦略を選ぶことは、スループット、コスト、信頼性に大きく影響します。このセクションでは、ワークロードに適した判断ができるよう、ベストプラクティス、トレードオフ、設定オプションについて説明します。
以下は、クライアント経由で ClickHouse にデータをプッシュすることを前提としています。たとえば s3gcs などの組み込みテーブル関数を使って ClickHouse にデータをプルする場合は、“S3 への 挿入 と読み取りパフォーマンスの最適化” ガイドを参照することをおすすめします。

デフォルトでは同期挿入

デフォルトでは、ClickHouse への挿入は同期的に行われます。各挿入クエリでは、メタデータや索引を含むストレージパーツがディスク上に直ちに作成されます。
クライアント側でデータをバッチ化できる場合は、同期挿入を使用してくださいできない場合は、以下の非同期挿入を参照してください。
以下では、ClickHouse の MergeTree における挿入の仕組みを簡単に説明します。

クライアント側の手順

最適なパフォーマンスを得るには、データを①バッチ化する必要があり、バッチサイズが最初の判断となります。 ClickHouse は、挿入されたデータをテーブルの主キーカラムに従って順序付けし、ディスクに保存します。2 つ目の判断は、②サーバーへ送信する前にデータを事前にソートするかどうかです。バッチが主キーカラムで事前にソートされた状態で到着すれば、ClickHouse は⑩のソート手順を省略でき、インジェストを高速化できます。 取り込むデータに事前定義されたフォーマットがない場合、重要な判断はフォーマットを選ぶことです。ClickHouse は70 を超えるフォーマットでのデータ挿入をサポートしています。ただし、ClickHouse のコマンドラインクライアントや各種プログラミング言語クライアントを使う場合、この選択は自動的に処理されることがよくあります。必要であれば、この自動選択を明示的に上書きすることもできます。 次の大きな判断は、④ClickHouse サーバーへ送信する前にデータを圧縮するかどうかです。圧縮により転送サイズが小さくなり、ネットワーク効率が向上するため、特に大規模なデータセットでは、データ転送の高速化と帯域幅使用量の削減につながります。 データは⑤ClickHouse のネットワークインターフェイス、つまりネイティブまたはHTTPインターフェイスを通じて送信されます (これらはこの投稿の後半で比較します) 。

サーバー側のステップ

⑥でデータを受信した後、ClickHouse は圧縮が使用されていれば ⑦でそれを解凍し、続いて元の送信フォーマットとして ⑧でパースします。 そのフォーマットのデータに含まれる値とターゲットテーブルの DDL ステートメントを使って、ClickHouse は ⑨で MergeTree フォーマットのインメモリ ブロック を構築し、行があらかじめソートされていなければ ⑩で主キーのキーカラム順に ソート し、⑪で スパースプライマリインデックス を作成し、⑫で カラムごとの圧縮 を適用し、最後に ⑬でデータを新しい ⑭ データパーツ としてディスクに書き込みます。

同期の場合のバッチ 挿入

上記の仕組みから、insert のサイズにかかわらず一定のオーバーヘッドが発生することがわかります。そのため、取り込みスループットを最適化するうえで最も重要なのはバッチサイズです。挿入をバッチ化すると、総挿入時間に占めるオーバーヘッドの割合が小さくなり、処理効率が向上します。 データは少なくとも 1,000 行、理想的には 10,000~100,000 行のバッチで挿入することを推奨します。挿入回数を減らして 1 回あたりの insert を大きくすると、書き込まれるパーツ数が減り、マージ負荷を抑えられ、システム全体のリソース使用量も低減できます。 同期挿入の戦略を効果的に機能させるには、クライアント側でのバッチ化が必要です。 クライアント側でデータをバッチ化できない場合、ClickHouse はサーバー側でバッチ化を行う非同期挿入をサポートしています (非同期挿入を参照) 。
insert のサイズにかかわらず、INSERT クエリ数は 1 秒あたりおよそ 1 件に保つことを推奨します。これは、作成されたパーツがバックグラウンドでより大きなパーツへマージされるためです (読み取りクエリ向けにデータを最適化するため) 。そのため、1 秒あたりに送信する INSERT クエリが多すぎると、バックグラウンドでのマージが新しいパーツの生成に追いつけなくなることがあります。ただし、非同期挿入を使用する場合は、1 秒あたりの INSERT クエリ数をさらに増やすことができます (非同期挿入 を参照) 。

冪等な再試行を確実にする

同期 挿入 も 冪等 です。MergeTree エンジンを使用している場合、ClickHouse はデフォルトで 挿入 を重複排除します。これにより、たとえば次のような障害時の判断が難しいケースに対処できます。
  • 挿入 は成功したものの、ネットワークの中断によりクライアントが確認応答を受信できなかった。
  • 挿入 はサーバー側で失敗し、タイムアウトした。
どちらの場合でも、バッチの内容と順序が完全に同一である限り、挿入 を再試行 しても安全です。そのため、クライアントはデータを変更したり順序を入れ替えたりせず、常に同じ形で再試行することが重要です。

適切な挿入先を選択する

分片化されたクラスターでは、次の 2 つの選択肢があります。
  • MergeTree または ReplicatedMergeTree テーブルに直接挿入します。これは、クライアントが分片間で負荷分散を行える場合に最も効率的な方法です。internal_replication = true を指定すると、ClickHouse が透過的にレプリケーションを処理します。
  • 分散テーブル に挿入します。これにより、クライアントは任意のノードにデータを送信し、ClickHouse に正しい分片へ転送させることができます。こちらのほうが簡単ですが、転送処理が 1 段増えるぶん、パフォーマンスはわずかに低下します。internal_replication = true は引き続き推奨されます。
ClickHouse Cloud では、すべてのノードが同じ単一の分片に対して読み書きを行います。挿入はノード間で自動的に負荷分散されます。公開されているエンドポイントに送信するだけで済みます。

適切なフォーマットを選ぶ

ClickHouse でデータを効率よく取り込むには、適切な入力フォーマットを選ぶことが重要です。対応フォーマットは 70 種類以上あり、最も高性能なフォーマットを選ぶことで、挿入速度、CPU とメモリの使用量、システム全体の効率に大きく影響します。 柔軟性はデータエンジニアリングやファイルベースのインポートでは有用ですが、アプリケーションではパフォーマンス重視のフォーマットを優先すべきです
  • Native format (推奨) : 最も効率的です。列指向で、サーバー側で必要なパースも最小限です。Go クライアントおよび Python クライアントではデフォルトで使用されます。
  • RowBinary: 効率的な行ベースのフォーマットで、クライアント側で列指向への変換が難しい場合に最適です。Java クライアントで使用されます。
  • JSONEachRow: 使いやすい一方で、パースのコストが高くなります。少量データのユースケースや短期間でのインテグレーションに適しています。

圧縮を使用する

圧縮は、ネットワークのオーバーヘッドを削減し、挿入を高速化し、ClickHouse のストレージコストを抑えるうえで重要な役割を果たします。適切に活用すれば、データのフォーマットやスキーマを変更しなくても、インジェスト性能を向上させることができます。 挿入するデータを圧縮すると、ネットワーク経由で送信されるペイロードのサイズが小さくなり、帯域幅の使用量を最小限に抑えつつ、転送を高速化できます。 挿入では、圧縮は特に Nativeフォーマット と組み合わせると効果的です。これは、ClickHouse の内部的な列指向ストレージモデルにすでに適合しているためです。この構成では、サーバーはデータを効率よく展開し、最小限の変換でそのまま保存できます。

速度を重視するなら LZ4、圧縮率を重視するなら ZSTD

ClickHouse は、データ転送時に複数の圧縮コーデックをサポートしています。代表的な選択肢は次の 2 つです。
  • LZ4: 高速かつ軽量です。CPU オーバーヘッドをほとんど増やさずにデータサイズを大きく削減できるため、高スループットの 挿入 に適しており、ほとんどの ClickHouse クライアントでデフォルトとして使われています。
  • ZSTD: より高い圧縮率を得られる一方で、CPU 負荷も高くなります。リージョン間やクラウドプロバイダー間のようにネットワーク転送コストが高い場合に有効ですが、クライアント側のコンピュートとサーバー側の伸長時間がわずかに増加します。
ベストプラクティス: 帯域幅に制約がある、またはデータ送信コストがかかる場合を除き、LZ4 を使用してください。そのような場合は ZSTD を検討してください。
FastFormats benchmark のテストでは、LZ4 圧縮の Native inserts によりデータサイズが 50% 以上削減され、5.6 GiB のデータセットでインジェスト時間が 150 秒から 131 秒に短縮されました。ZSTD に切り替えると、同じデータセットは 1.69 GiB まで圧縮されましたが、サーバー側の処理時間はわずかに増加しました。

圧縮によりリソース使用量を削減できます

圧縮はネットワークトラフィックを削減するだけでなく、サーバー側の CPU とメモリの効率も向上させます。データが圧縮されていれば、ClickHouse が受信するバイト数は少なくなり、大きな入力のパースにかかる時間も短くなります。この利点は、オブザーバビリティのユースケースのように、複数のクライアントから同時実行で取り込む場合に特に重要です。 CPU とメモリに対する圧縮の影響は、LZ4 では小さく、ZSTD では中程度です。負荷がかかっている状況でも、データ量が減ることでサーバー側の効率は向上します。 圧縮をバッチ化と効率的な入力フォーマット (Native など) と組み合わせると、最適なインジェスト性能が得られます。 ネイティブインターフェイス (例: clickhouse-client) を使用する場合、LZ4 圧縮はデフォルトで有効です。必要に応じて、設定で ZSTD に切り替えることもできます。 HTTPインターフェイスでは、Content-Encoding ヘッダーを使用して圧縮を適用します (例: Content-Encoding: lz4) 。送信前にペイロード全体を圧縮しておく必要があります。

コストが低い場合は事前にソートする

挿入前にデータを主キーで事前にソートしておくと、特に大きなバッチでは、ClickHouse のインジェスト効率が向上することがあります。 データがあらかじめソートされた状態で到着すると、ClickHouse は パーツ の作成時に内部的なソート処理を省略または簡略化できるため、CPU 使用量を抑えつつ 挿入 処理を高速化できます。事前ソートは圧縮効率の向上にも役立ちます。似た値がまとまって並ぶことで、LZ4 や ZSTD などの codec がより高い圧縮率を達成しやすくなるためです。これは特に、大きなバッチ 挿入 と圧縮を組み合わせた場合に効果的で、処理オーバーヘッドと転送データ量の両方を削減できます。 ただし、事前ソートは必須ではなく、あくまで任意の最適化です。 ClickHouse は並列処理によって非常に効率よくデータをソートできるため、多くの場合、クライアント側で事前ソートするよりも、サーバー側でソートしたほうが高速または実用的です。 事前ソートを推奨するのは、データがすでにほぼ順序どおりに並んでいる場合、またはクライアント側のリソース (CPU、メモリ) に十分な余裕があり、あまり使われていない場合に限られます。 オブザーバビリティのような、レイテンシに敏感な、あるいは高スループットのユースケースでは、データが順不同で到着したり、多数のエージェントから送られてきたりすることが多いため、事前ソートは行わず、ClickHouse の組み込みパフォーマンスに任せるほうがよいことがよくあります。

非同期挿入

ClickHouse の非同期挿入は、クライアント側でのバッチ処理が現実的でない場合に有効な代替手段です。これは特にオブザーバビリティのワークロードで有用です。このようなワークロードでは、何百、何千ものエージェントが、ログ、メトリクス、トレースといったデータを、多くの場合小さなリアルタイムのペイロードで継続的に送信します。こうした環境でクライアント側にデータをバッファリングすると複雑さが増し、十分な大きさのバッチを送信できるようにするための集中管理キューが必要になります。
同期モードで多数の小さなバッチを送信することは推奨されません。多くのパーツが作成される原因になるためです。これにより、クエリ性能の低下や “too many part” エラーにつながります。
非同期挿入では、受信したデータをメモリ内バッファに書き込み、その後、設定可能なしきい値に基づいてストレージへフラッシュすることで、バッチ処理の責務をクライアントからサーバーへ移します。このアプローチにより、パーツ作成のオーバーヘッドが大幅に減り、CPU 使用率が下がり、高い同時実行性の下でもインジェストを効率的に維持できます。 中核となる動作は async_insert 設定によって制御されます。 非同期挿入は HTTP と native TCP の両方のインターフェイスでサポートされています。 有効化されている場合 (async_insert = 1) 、INSERT はバッファリングされ、次のフラッシュ条件のいずれかが満たされた時点でのみ disk に書き込まれます。 最初に到達したしきい値でフラッシュがトリガーされます。 このバッチ処理はクライアントからは見えず、ClickHouse が複数のソースからの INSERT トラフィックを効率的にマージするのに役立ちます。ただし、フラッシュが発生するまでは、そのデータをクエリすることはできません。重要なのは、INSERT の shape と settings の組み合わせごとに複数のバッファが存在し、クラスターではノードごとにバッファが維持されることです。これにより、マルチテナント環境全体で細かな制御が可能になります。それ以外の INSERT の仕組みは、synchronous inserts で説明されているものと同一です。

戻りモードの選択

非同期挿入の動作は、wait_for_async_insert 設定によってさらに細かく制御できます。 1 (デフォルト) に設定すると、ClickHouse はデータが正常にディスクにフラッシュされたあとでのみ insert の受け付けを通知します。これにより高い永続性が確保され、error 処理もシンプルになります。フラッシュ中に問題が発生した場合は、その error がクライアントに返されます。このモードは、ほとんどの本番環境、とくに insert の失敗を確実に追跡する必要がある場合に推奨されます。 ベンチマーク では、適応的な insert と安定した part 作成動作により、200 クライアントでも 500 クライアントでも、高い並行度でも良好にスケールすることが示されています。 wait_for_async_insert = 0 を設定すると、「fire-and-forget」モードが有効になります。この場合、server はデータが storage に書き込まれるのを待たず、バッファ に格納された時点で insert の受け付けを通知します。 これにより、超低 latency の insert と最大限の throughput が得られるため、高速に流入し、重要度の低いデータに適しています。ただし、これにはトレードオフがあります。データが永続化される保証はなく、error が表面化するのはフラッシュ時だけで、失敗した insert のための dead-letter queue もありません。障害を追跡するには、事後的に サーバーログ とシステムテーブルを調査する必要があります。このモードは、workload がデータ損失を許容できる場合にのみ使用してください。 ベンチマークではさらに、バッファ のフラッシュ頻度が低い場合 (たとえば 30 Seconds ごと) には、part の大幅な削減と CPU 使用率の低下も示されていますが、気づかれないまま失敗するリスクは残ります。 非同期挿入を使用する場合は、async_insert=1,wait_for_async_insert=1 を使うことを強く推奨します。wait_for_async_insert=0 の使用は非常に危険です。error が発生しても INSERT クライアントがそれを認識できない可能性があるうえ、service の信頼性を確保するために ClickHouse server 側で書き込みを抑制し、backpressure をかける必要がある状況でも、クライアントが高速な書き込みを続けることで過負荷を引き起こすおそれがあるためです。

適応型非同期 INSERT

バージョン 24.2 以降、ClickHouse ではデフォルトで適応型のフラッシュタイムアウト (async_insert_use_adaptive_busy_timeout) が使用されます。固定のフラッシュ間隔ではなく、受信データのレートに応じて、タイムアウトが最小値 (async_insert_busy_timeout_min_ms、デフォルトは 50 ms) から最大値 (async_insert_busy_timeout_max_ms、デフォルトは 200 ms、Cloud では 1000 ms) までの範囲で動的に調整されます。 データが高頻度で到着する場合、より早くフラッシュしてエンドツーエンドのレイテンシを抑えるため、タイムアウトは最小値寄りに保たれます。データがスパースな場合は、より大きなバッチを蓄積できるよう、最大値に向かって長くなります。これは、固定の長いタイムアウトを設定すると、フラッシュ可能なデータがすでに揃っていてもクライアントがその間ずっと待たされてしまうデフォルトモード (wait_for_async_insert=1) で特に有効です。

エラー処理

スキーマの検証とデータのパースは、insert の受信時ではなく、バッファのフラッシュ時に行われます。INSERT クエリ内のいずれかの行にパースまたは型のエラーがある場合、そのクエリのデータは一切フラッシュされません — クエリ全体のペイロードが拒否されます。デフォルトモード (wait_for_async_insert=1) では、エラーはクライアントに返されます。fire-and-forget モードでは、エラーはサーバーログと system.asynchronous_inserts テーブルに書き込まれます。 フラッシュが実行されるたびに、バッファ内の異なる各パーティションキーの値ごとに少なくとも 1 つのパーツが作成されます。パーティションキーのないテーブルであっても、バッファされたデータが max_insert_block_size (デフォルトは約 100 万行) を超える場合、1 回のフラッシュで複数のパーツが生成されることがあります。
非同期 INSERT を使用していても、パーティション化キーのカーディナリティが高い場合は、“パーツが多すぎる” エラーが発生することがあります。

重複排除と信頼性

デフォルトでは、ClickHouse は同期挿入に対して自動的に重複排除を行うため、障害発生時でも安全に再試行できます。ただし、非同期挿入では明示的に有効化しない限り、この機能は無効です (依存する materialized view がある場合は有効にしないでください — issue を参照) 。 実際には、重複排除が有効になっていれば、たとえばタイムアウトやネットワーク切断によって同じ挿入が再試行された場合でも、ClickHouse は重複分を安全に無視できます。これにより、冪等性を維持し、データの二重書き込みを防げます。

非同期挿入の有効化

非同期挿入は、特定のユーザーまたは特定のクエリに対して有効にできます。
  • ユーザーレベルで非同期挿入を有効にします。この例ではユーザー default を使用しています。別のユーザーを作成した場合は、そのユーザー名に置き換えてください。
    ALTER USER default SETTINGS async_insert = 1
    
  • INSERT クエリの SETTINGS 句を使用して、非同期挿入の設定を指定することもできます。
    INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
    
  • ClickHouse のプログラミング言語クライアントを使用する場合は、接続パラメーターとして非同期挿入の設定を指定することもできます。 たとえば、ClickHouse Cloud への接続に ClickHouse Java JDBCドライバーを使用する場合、JDBC 接続文字列では次のように指定できます。
    "jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,wait_for_async_insert=1"
    
非同期挿入は INSERT INTO ... SELECT クエリには適用されません。挿入に SELECT 句が含まれている場合、async_insert 設定に関係なく、クエリは常に同期的に実行されます。

シャットダウン時にバッファをフラッシュする

保留中のすべての非同期 INSERT バッファをフラッシュするには (たとえば、正常終了時やメンテナンス前) 、次を実行します。
SYSTEM FLUSH ASYNC INSERT QUEUE
これにより、サーバーが停止する前に、バッファリングされたデータがすべてストレージに書き込まれます。

Bufferテーブルとの比較

非同期挿入は、Buffer tables に代わる現代的な方式です。主な違いは次のとおりです。
  • DDL の変更は不要です。 非同期挿入は透過的に動作するため、追加のテーブルを作成するのではなく、設定を有効にするだけで利用できます。
  • クエリ形状ごとのバッファリング。 非同期挿入では、一意のクエリ形状と設定の組み合わせごとに個別のバッファが維持されるため、きめ細かな フラッシュ ポリシーを適用できます。Buffer tables では、ターゲットテーブルごとに 1 つのバッファを使用します。
  • 耐久性。 デフォルトモード (wait_for_async_insert=1) では、クライアントに確認応答が返される前に、データがディスクに書き込まれたことが確認されます。Buffer tables は fire-and-forget 型で動作するため、クラッシュ時にはバッファ内のデータが失われます。
  • クラスターでの動作。 クラスターでは、非同期挿入のバッファはノードごとに維持されます。Buffer tables では、各ノードで明示的に作成する必要があります。

インターフェイスの選択—HTTP またはネイティブ

ネイティブ

ClickHouse では、データをインジェストするための主要なインターフェイスとして、ネイティブインターフェイスHTTPインターフェイス の 2 つを利用できます。両者にはそれぞれ、パフォーマンスと柔軟性の面でトレードオフがあります。clickhouse-client や Go、C++ などの一部の言語クライアントで使用されるネイティブインターフェイスは、パフォーマンスを重視して設計されています。データは常に ClickHouse の非常に効率的な Native フォーマットで送信され、LZ4 または ZSTD によるブロック単位の圧縮をサポートします。また、パースやフォーマット変換などの処理をクライアント側にオフロードすることで、サーバー側の処理を最小限に抑えます。 さらに、MATERIALIZED および DEFAULT のカラム値をクライアント側で計算できるため、サーバーはこれらの処理を完全に省略できます。そのため、効率が重要となる高スループットなインジェストの用途では、ネイティブインターフェイスが最適です。

HTTP

多くの従来型データベースとは異なり、ClickHouse は HTTP インターフェイスもサポートしています。これに対し、HTTP は互換性と柔軟性を重視しています。 サポートされている任意のフォーマット (JSON、CSV、Parquet など) でデータを送信でき、Python、Java、JavaScript、Rust を含む大半の ClickHouse クライアントで広くサポートされています。 HTTP は、ロードバランサーを使ってトラフィックを簡単に切り替えられるため、ClickHouse のネイティブプロトコルより適していることがよくあります。一方で、ネイティブプロトコルはオーバーヘッドがやや少ないため、挿入性能にはわずかな差が出ることが想定されます。 ただし、HTTP にはネイティブプロトコルのような深い統合はなく、マテリアライズド値の計算や Native フォーマットへの自動変換といったクライアント側の最適化は行えません。HTTP による挿入でも標準的な HTTP ヘッダー (例: Content-Encoding: lz4) を使って圧縮できますが、圧縮は個々のデータブロックではなくペイロード全体に適用されます。このインターフェイスは、生の性能よりも、プロトコルのシンプルさ、ロードバランシング、幅広いフォーマット互換性が重視される環境でよく選ばれます。 これらのインターフェイスの詳細については、こちらを参照してください。
最終更新日 2026年6月10日