メインコンテンツまでスキップ
メインコンテンツまでスキップ

挿入戦略の選択

効率的なデータ取り込みは、高性能な ClickHouse デプロイメントの基盤を形成します。適切な挿入戦略を選択することで、スループット、コスト、および信頼性に大きな影響を与えることができます。このセクションでは、ワークロードに対する正しい決定を下すためのベストプラクティス、トレードオフ、および構成オプションを概説します。

注記

以下は、クライアントを介して ClickHouse にデータをプッシュしていることを前提としています。もし、s3gcs のような組み込みのテーブル関数を使用して、ClickHouse にデータをプルしている場合は、当社のガイド「"S3 の挿入と読み取りパフォーマンスの最適化"」をお勧めします。

デフォルトでの同期挿入

デフォルトでは、ClickHouse への挿入は同期的です。各挿入クエリは、メタデータとインデックスを含むストレージパートをすぐにディスク上に作成します。

クライアント側でデータをバッチすることができる場合は、同期挿入を使用してください

そうでない場合は、下記の非同期挿入を参照してください。

以下では、ClickHouse の MergeTree 挿入メカニズムについて簡単にレビューします:

Insert processes

クライアント側のステップ

最適なパフォーマンスを達成するためには、データは ①バッチ化される必要があり、バッチサイズは 最初の決定 です。

ClickHouse は挿入されたデータをディスクに整列して保存します。2 番目の決定は、② データをサーバーに送信する前に事前ソートするかどうかです。バッチが主キーのカラムで事前ソートされて到着すると、ClickHouse は⑨ソートステップをスキップでき、取り込みが早くなります。

取り込まれるデータに事前定義されたフォーマットがない場合、主な決定はフォーマットの選択です。ClickHouse は70 以上のフォーマットでのデータ挿入をサポートしています。しかし、ClickHouse コマンドラインクライアントやプログラミング言語のクライアントを使用する場合、この選択はしばしば自動的に処理されます。必要があれば、この自動選択を明示的にオーバーライドすることもできます。

次の主な決定は、④ データを ClickHouse サーバーに送信する前に圧縮するかどうかです。圧縮は転送サイズを削減し、ネットワーク効率を改善します。これにより、大規模データセットにおいてデータ転送が早くなり、帯域幅の使用量が少なくなります。

データは ⑤ ClickHouse ネットワークインターフェースに送信されます。これには、ネイティブインターフェースまたはHTTPインターフェースが含まれます(これについては後で比較します)。

サーバー側のステップ

データを⑥受信した後、ClickHouse は圧縮が使用された場合は⑦それを解凍し、元の送信形式から⑧解析します。

そのフォーマットデータからの値とターゲットテーブルのDDL ステートメントを使用して、ClickHouse は⑨メモリ内のブロックを MergeTree フォーマットで構築し、主キーのカラムがすでに事前ソートされていない場合は⑩ソートし、⑪スパース主キーインデックスを作成し、⑫カラムごとの圧縮を適用し、⑬データを新しい⑭データパートとしてディスクに書き込みます。

同期の場合はバッチ挿入を行う

上記のメカニクスは、挿入サイズに関係なく一定のオーバーヘッドを示しており、バッチサイズが取り込みスループットの最も重要な最適化要因となります。バッチ挿入は、総挿入時間に対するオーバーヘッドの割合を減少させ、処理効率を改善します。

私たちは、少なくとも1,000行のバッチでデータを挿入することを推奨しており、理想的には10,000〜100,000行の間で行うべきです。少なくて大きな挿入は、書き込まれるパーツの数を減少させ、マージ負荷を最小限に抑え、全体的なシステムリソースの使用を低減します。

同期挿入戦略が効果的であるためには、クライアント側でのバッチ処理が必要です。

クライアント側でデータをバッチ処理できない場合、ClickHouseは、サーバー側にバッチ処理を移す非同期挿入をサポートしています(参照)。

ヒント

挿入のサイズに関係なく、挿入クエリを1秒あたり約1件の挿入クエリに保つことをお勧めします。その推奨理由は、作成されたパーツが背景でより大きなパーツにマージされるため(読み取りクエリ用にデータを最適化するため)、1秒あたりにあまりにも多くの挿入クエリを送信すると、バックグラウンドのマージが新しいパーツの数に追いつかない状況が発生する可能性があるからです。しかし、非同期挿入を使用すると、1秒あたりの挿入クエリのレートを高くすることができます(非同期挿入を参照)。

冪等性のある再試行を確保する

同期挿入は 冪等 でもあります。MergeTree エンジンを使用する場合、ClickHouse はデフォルトで挿入を重複排除します。これにより、次のような曖昧な失敗ケースに対して保護が提供されます:

  • 挿入は成功したが、ネットワークの中断によりクライアントが確認応答を受け取らなかった。
  • サーバー側で挿入が失敗し、タイムアウトした。

両方のケースで、挿入を再試行するのは安全です - バッチの内容と順序が一致する限り。したがって、クライアントが一貫して再試行し、データを変更または再編成しないことが重要です。

適切な挿入ターゲットを選択する

シャードクラスタの場合、2 つのオプションがあります:

  • MergeTree または ReplicatedMergeTree テーブルに直接挿入します。これがクライアントがシャード間で負荷分散を行える場合に最も効率的なオプションです。internal_replication = true が設定されていると、ClickHouse はレプリケーションを透過的に処理します。
  • 分散テーブルに挿入します。これにより、クライアントは任意のノードにデータを送信し、ClickHouse がそれを正しいシャードに転送します。これが単純ですが、余分な転送ステップのため、わずかにパフォーマンスが低くなります。internal_replication = true は引き続き推奨されます。

ClickHouse Cloud では、すべてのノードが同じ単一のシャードに対して読み書きします。挿入は自動的にノード間でバランスが取られます。ユーザーは公開エンドポイントに挿入を送信するだけです。

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

適切な入力フォーマットの選択は、ClickHouse での効率的なデータ取り込みにとって重要です。70 を超えるサポートされているフォーマットから、最もパフォーマンスの良いオプションを選択することで、挿入速度、CPU とメモリの使用量、システム全体の効率に大きな影響を与えることができます。

柔軟性はデータエンジニアリングやファイルベースのインポートにとって有用ですが、アプリケーションはパフォーマンス重視のフォーマットを優先すべきです

  • ネイティブフォーマット (推奨):最も効率的。列指向で、サーバー側の解析が最小限に抑えられます。Go および Python クライアントでデフォルトで使用されます。
  • RowBinary:効率的な行ベースのフォーマットで、クライアント側での列指向変換が難しい場合に最適です。Java クライアントで使用されます。
  • JSONEachRow:使いやすいですが、解析コストが高いです。低ボリュームのユースケースや迅速な統合に適しています。

圧縮を使用する

圧縮はネットワークオーバーヘッドを削減し、挿入速度を上げ、ClickHouse のストレージコストを下げる重要な役割を果たします。効果的に使用すると、データフォーマットやスキーマに変更を加えることなく、取り込みパフォーマンスを向上させます。

挿入データの圧縮は、ネットワークを介して送信されるペイロードのサイズを削減し、帯域幅の使用量を最小化し、送信を加速します。

挿入において、圧縮はネイティブフォーマットと組み合わせて使用した場合に特に効果的です。このフォーマットはすでに ClickHouse の内部列指向ストレージモデルに適合しています。このセットアップでは、サーバーは効率的にデータを解凍し、最小限の変換で直接ストアできます。

スピードには LZ4、圧縮比には ZSTD を使用

ClickHouse はデータ転送中にいくつかの圧縮コーデックをサポートしています。一般的な選択肢は次のとおりです:

  • LZ4:高速で軽量。最小限の CPU オーバーヘッドでデータサイズを大幅に削減でき、高スループットの挿入に理想的で、ほとんどの ClickHouse クライアントでデフォルトとして設定されています。
  • ZSTD:圧縮比が高いが、CPU 負荷が大きい。ネットワーク転送コストが高い場合、特にクロスリージョンやクラウドプロバイダーシナリオにおいて便利ですが、クライアントサイドのコンピュートとサーバーサイドの解凍時間がわずかに増加します。

ベストプラクティス:帯域幅が制約されているか、データの出口コストが発生する場合を除き、LZ4 を使用してください。その場合は ZSTD を検討してください。

注記

FastFormats ベンチマークのテストでは、LZ4 で圧縮されたネイティブ挿入がデータサイズを 50% 以上削減し、5.6 GiB のデータセットの取り込み時間を 150 秒から 131 秒に短縮しました。同じデータセットを ZSTD に切り替えると、1.69 GiB に圧縮できましたが、サーバー側の処理時間がわずかに増加しました。

圧縮はリソース使用量を削減する

圧縮はネットワークトラフィックを削減するだけでなく、サーバーでの CPU およびメモリ効率も向上させます。圧縮データを使用すると、ClickHouse は受信するバイト数が少なく、大きな入力の解析にかかる時間が短縮されます。この利点は、特にオブザーバビリティのシナリオなど、複数のクライアントから同時に取り込む場合に重要です。

LZ4 に対する CPU とメモリの影響は控えめであり、ZSTD に対しては中程度です。負荷がかかっている場合でも、データボリュームの削減によりサーバー側の効率は向上します。

圧縮、バッチ処理、効率的な入力フォーマット(ネイティブなど)を組み合わせることで、最適な取り込みパフォーマンスが得られます。

ネイティブインターフェース(例:clickhouse-client)を使用する際、LZ4 圧縮はデフォルトで有効になっています。オプションとして、設定を通じて ZSTD に切り替えることもできます。

HTTP インターフェースでは、Content-Encoding ヘッダーを使用して圧縮を適用します(例:Content-Encoding: lz4)。ペイロード全体は送信する前に圧縮される必要があります。

低コストの場合は事前ソート

主キーでデータを挿入前に事前ソートすると、特に大規模なバッチの場合、ClickHouse での取り込み効率が向上します。

データが事前ソートされて到着した場合、ClickHouse はパート作成中の内部ソートステップをスキップまたは簡素化でき、CPU 使用量を削減し、挿入プロセスを加速します。事前ソートは、類似の値がグループ化されるため、圧縮効率を向上させます - このことで、LZ4 や ZSTD などのコーデックがより良い圧縮比を達成することができます。これは、大規模なバッチ挿入や圧縮と組み合わせると、処理オーバーヘッドと転送されるデータ量の両方を削減するため、特に有益です。

とはいえ、事前ソートはオプションの最適化であり、必須ではありません。 ClickHouse は並列処理を利用してデータを非常に効率的にソートし、多くの場合、サーバー側でのソートがクライアント側での事前ソートよりも速いか、便利です。

データがほぼ整列されている場合、またはクライアント側のリソース(CPU、メモリ)が十分で未使用の場合にのみ、事前ソートを推奨します。 遅延に敏感なユースケースや高スループットユースケース(オブザーバビリティなど)では、データが順不同で到着したり多くのエージェントから送信されたりする場合、事前ソートをスキップし、ClickHouse の内蔵パフォーマンスに頼るほうが良いです。

非同期挿入

Asynchronous inserts in ClickHouseは、クライアント側のバッチ処理が実行できない場合の強力な代替手段を提供します。これは、数百または数千のエージェントがデータ(ログ、メトリック、トレース)を持続的に、しばしば小さなリアルタイムのペイロードで送信する観測作業で特に価値があります。これらの環境でクライアント側にデータをバッファリングすると、十分に大きなバッチを送信できるようにするための中央集権的なキューが必要になるため、複雑さが増します。

注記

同期モードで多くの小さなバッチを送信することは推奨されておらず、多くのパーツが作成されることになります。これにより、クエリパフォーマンスが低下し、"too many part"エラーが発生します。

非同期挿入は、クライアントからサーバーへのバッチ処理の責任を移し、受信したデータをメモリ内バッファに書き込んだ後、構成可能なしきい値に基づいてストレージにフラッシュします。このアプローチにより、パーツ作成のオーバーヘッドが大幅に削減され、CPU使用率が低下し、高い同時接続数の下でも取り込みが効率的に保たれます。

コアの動作は、async_insert設定を介して制御されます。

Async inserts

有効化されると(1)、挿入はバッファリングされ、フラッシュ条件のいずれかが満たされるまでディスクに書き込まれません:

(1) バッファが指定サイズに達する(async_insert_max_data_size) (2) 時間のしきい値が経過する(async_insert_busy_timeout_ms)または (3) 最大挿入クエリ数が蓄積される(async_insert_max_query_number)。

このバッチ処理はクライアントには見えず、ClickHouseが複数のソースからの挿入トラフィックを効率的にマージするのを助けます。ただし、フラッシュが発生するまで、データはクエリできません。重要なことは、挿入の形状や設定の組み合わせごとに複数のバッファがあり、クラスター内ではノードごとにバッファが維持されるため、マルチテナント環境での詳細な制御が可能であることです。挿入のメカニズムは、同期挿入で説明されているものと実質的に同じです。

リターンモードの選択

非同期挿入の動作は、wait_for_async_insert設定を使用してさらに洗練されます。

1(デフォルト)に設定されている場合、ClickHouseはデータがディスクに正常にフラッシュされた後のみ、挿入を認識します。これにより強力な耐久性保証が確保され、エラーハンドリングが単純になります:フラッシュ中に何か問題が発生した場合、エラーがクライアントに返されます。このモードは、挿入の失敗を確実に追跡する必要がある場合、特にほとんどのプロダクションシナリオに推奨されます。

ベンチマークは、200または500のクライアントを実行している場合でも、適応バッチ挿入と安定したパーツ作成動作のおかげで、同時実行性にうまくスケールすることを示します。

wait_for_async_insert = 0を設定すると、「ファイアアンドフォーゲット」モードが有効になります。ここでは、サーバーはデータがバッファリングされたときに直ちに挿入を認識し、ストレージに到達するのを待ちません。

これにより、超低レイテンシの挿入と最大スループットが提供され、高速かつ重要度の低いデータに最適です。ただし、これにはトレードオフがあります:データが永続化される保証はなく、エラーはフラッシュ中のみ発生する可能性があり、挿入の失敗を追跡することが難しいです。このモードは、あなたのワークロードがデータ損失を許容できる場合にのみ使用してください。

ベンチマークも示していますが、バッファフラッシュが少ない場合(例えば、30秒ごと)にパーツの削減とCPU使用率の低下がある一方で、サイレント失敗のリスクも残ります。

私たちの強い推奨は、非同期挿入を使用する場合は async_insert=1,wait_for_async_insert=1 を使用することです。 wait_for_async_insert=0 を使用するのは非常にリスキーであり、INSERTクライアントはエラーを認識していない可能性があり、ClickHouseサーバーが書き込みを遅くしてサービスの信頼性を確保するためにバックプレッシャを作成する必要がある状況で、クライアントが迅速に書き込みを続けると潜在的なオーバーロードを引き起こす可能性があります。

デデュプリケーションと信頼性

デフォルトでは、ClickHouseは同期挿入に対して自動デデュプリケーションを行い、失敗シナリオにおいて再試行が安全になります。しかし、これは非同期挿入では明示的に有効にしない限り無効です(依存するMaterialized Viewがある場合は有効にしないことを推奨します - こちらを参照)。

実際、デデュプリケーションがオンになっていて、同じ挿入が再試行される場合(たとえば、タイムアウトやネットワークドロップによる)、ClickHouseは重複を安全に無視できます。これにより、冪等性が維持され、データの二重書き込みを回避できます。それでも、挿入の検証とスキーマ解析はバッファフラッシュ時にのみ行われるため、エラー(タイプミスマッチなど)はその時点でのみ発生することに注意する価値があります。

非同期挿入の有効化

非同期挿入は特定のユーザーまたは特定のクエリのために有効にできます:

  • ユーザーレベルで非同期挿入を有効にする。この例ではユーザー default を使用していますが、異なるユーザーを作成する場合はそのユーザー名に置き換えてください:
ALTER USER default SETTINGS async_insert = 1
  • 挿入クエリの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"

インターフェースを選択する - HTTP またはネイティブ

ネイティブ

ClickHouse はデータ取り込みのために、ネイティブインターフェースHTTP インターフェースという 2 つの主なインターフェースを提供しており、それぞれパフォーマンスと柔軟性のトレードオフがあります。ネイティブインターフェースは、clickhouse-client や Go や C++ のような選択された言語クライアントによって使用されており、パフォーマンスのために特別に設計されています。常に ClickHouse の非常に効率的なネイティブフォーマットでデータを送信し、LZ4 や ZSTD でブロック単位の圧縮をサポートし、解析やフォーマット変換などの作業をクライアントにオフロードすることでサーバー側の処理を最小限に抑えます。

さらに、MATERIALIZED および DEFAULT カラムの値のクライアント側計算を可能にし、サーバーがこれらのステップを完全にスキップできるようにします。これにより、効率が重要な高スループットな取り込みシナリオに最適なネイティブインターフェースとなります。

HTTP

多くの従来のデータベースとは異なり、ClickHouse は HTTP インターフェースもサポートしています。これに対して、互換性と柔軟性を優先します。 データは、任意のサポートされたフォーマット(JSON、CSV、Parquet など)で送信でき、Python、Java、JavaScript、Rust を含むほとんどの ClickHouse クライアントで広くサポートされています。

これは、トラフィックをロードバランサーで簡単に切り替えられるため、ClickHouse のネイティブプロトコルよりも好まれることがよくあります。ネイティブプロトコルでは、わずかにオーバーヘッドが少ないため、挿入パフォーマンスに小さな差が期待されます。

ただし、ネイティブプロトコルの深い統合が欠けており、マテリアライズされた値の計算やネイティブフォーマットへの自動変換などのクライアント側最適化を行うことができません。HTTP 挿入は、標準 HTTP ヘッダー(例:Content-Encoding: lz4)を使用して圧縮することもできますが、圧縮は個々のデータブロックではなく、ペイロード全体に適用されます。このインターフェースは、プロトコルのシンプルさ、負荷分散、または広範なフォーマット互換性が、純粋なパフォーマンスよりも重要な環境で好まれることが多いです。

これらのインターフェースの詳細な説明については、こちらをご覧ください。