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

並列レプリカ

Beta feature. Learn more.

はじめに

ClickHouseはクエリを非常に迅速に処理しますが、これらのクエリはどのように複数のサーバーに分散および並列化されるのでしょうか?

このガイドでは、まずClickHouseがどのように分散テーブルを介してクエリを複数のシャードに分配するか、次にクエリがその実行のために複数のレプリカをどのように活用できるかについて説明します。

シャーディングアーキテクチャ

共有何もないアーキテクチャでは、クラスタは一般的に複数のシャードに分割され、各シャードには全データのサブセットが含まれます。分散テーブルはこれらのシャードの上に存在し、完全なデータの統一ビューを提供します。

読み取りはローカルテーブルに送信できます。クエリの実行は指定されたシャードだけで行われるか、分散テーブルに送信され、その場合は各シャードが指定されたクエリを実行します。分散テーブルがクエリされたサーバーは、データを集計し、クライアントに応答します:

sharded archtiecture

上の図は、クライアントが分散テーブルをクエリしたときに何が起こるかを示しています:

  1. SELECTクエリは、ノード上の分散テーブルにランダムに送信されます (ラウンドロビン戦略を介して、またはロードバランサーによって特定のサーバーにルーティングされた後)。このノードは、今後コーディネーターとして機能します。

  2. ノードは、分散テーブルによって指定された情報を介して、クエリを実行する必要がある各シャードを特定し、クエリを各シャードに送信します。

  3. 各シャードはデータをローカルで読み、フィルタリングし、集計し、その後、コーディネーターにマージ可能な状態を返します。

  4. コーディネートノードはデータをマージし、クライアントに応答を送信します。

レプリカが混ざる場合、プロセスはほぼ同様で、唯一の違いは各シャードからの単一のレプリカのみがクエリを実行することです。これにより、より多くのクエリを並列に処理できるようになります。

非シャーディングアーキテクチャ

ClickHouse Cloudは、上記のアーキテクチャとは非常に異なるアーキテクチャを持っています。 (詳細については "ClickHouse Cloud Architecture" を参照してください)。計算とストレージの分離、および実質的に無限のストレージにより、シャードの必要性は重要性を減少させます。

以下の図はClickHouse Cloudのアーキテクチャを示しています:

non sharded architecture

このアーキテクチャでは、レプリカをほぼ瞬時に追加および削除でき、高いクラスターのスケーラビリティを確保します。ClickHouse Keeperクラスター(右に示されています)は、メタデータの単一の真実のソースを確保します。レプリカはClickHouse Keeperクラスターからメタデータを取得し、すべてが同じデータを維持します。データ自体はオブジェクトストレージに保存され、SSDキャッシュによりクエリが高速化されます。

ただし、クエリの実行を複数のサーバーに分散するには、どうすればよいのでしょうか? シャーディングアーキテクチャでは、各シャードがデータのサブセットに対してクエリを実行できるため、それは非常に明白でした。シャーディングがない場合、これはどのように機能するのでしょうか?

並列レプリカの導入

複数のサーバーを通じてクエリ実行を並列化するには、まずコーディネーターとして機能するサーバーを指定できる必要があります。コーディネーターは、実行される必要があるタスクのリストを作成し、それらがすべて実行され、集約され、結果がクライアントに返されることを保証します。ほとんどの分散システムと同様に、これは初期クエリを受け取ったノードの役割となります。また、作業の単位を定義する必要があります。シャーディングアーキテクチャでは、作業の単位はシャードであり、データのサブセットです。並列レプリカでは、グラニュールと呼ばれるテーブルの小さな部分を作業の単位として使用します。

次に、以下の図を使って、実践でどのように機能するかを見てみましょう:

Parallel replicas

並列レプリカを使用すると:

  1. クライアントからのクエリは、ロードバランサーを通過した後、1つのノードに送信されます。このノードはこのクエリのコーディネーターになります。

  2. ノードは各パートのインデックスを分析し、処理すべき適切なパーツとグラニュールを選択します。

  3. コーディネーターは、異なるレプリカに割り当てることができるグラニュールのセットに作業負荷を分割します。

  4. 各グラニュールセットは対応するレプリカによって処理され、完了したときにマージ可能な状態がコーディネーターに送信されます。

  5. 最後に、コーディネーターはすべてのレプリカからの結果をマージし、クライアントに応答を返します。

上記のステップは、理論における並列レプリカの機能を概説しています。 しかし、実際には、そうしたロジックが完璧に機能することを妨げる多くの要因があります:

  1. 一部のレプリカが利用できない場合があります。

  2. ClickHouseにおけるレプリケーションは非同期であり、一部のレプリカは、ある時点で同じパーツを持っていないかもしれません。

  3. レプリカ間の遅延は何らかの方法で処理する必要があります。

  4. ファイルシステムキャッシュは各レプリカのアクティビティに基づいて異なるため、ランダムなタスク割り当てがキャッシュの局所性の観点から最適なパフォーマンスを実現できない可能性があります。

これらの要因を克服する方法については、以下のセクションで探ります。

アナウンスメント

上記のリストの(1)および(2)の問題に対処するために、アナウンスメントの概念を導入しました。以下の図を使って、これがどのように機能するかを視覚化してみましょう:

Announcements
  1. クライアントからのクエリは、ロードバランサーを通過した後、1つのノードに送信されます。このノードがこのクエリのコーディネーターになります。

  2. コーディネートノードは、クラスター内のすべてのレプリカからアナウンスメントを取得するリクエストを送信します。レプリカは、テーブルの現在のパーツのセットに対してやや異なるビューを持つ可能性があります。そのため、正しくスケジュールされた決定を避けるためにこの情報を収集する必要があります。

  3. コーディネートノードはアナウンスメントを使用して、異なるレプリカに割り当てることができるグラニュールのセットを定義します。例えば、ここでは、パート3のグラニュールがレプリカ2に割り当てられなかったことが確認できます。なぜなら、このレプリカがそのアナウンスメントにこのパートを提供しなかったからです。また、レプリカ3にタスクが割り当てられなかったことにも注意してください。なぜなら、このレプリカがアナウンスメントを提供しなかったからです。

  4. 各レプリカが自分のグラニュールのサブセットに対してクエリを処理し、マージ可能な状態をコーディネーターに送信した後、コーディネーターは結果をマージし、応答をクライアントに送信します。

動的コーディネーション

遅延の問題に対処するために、動的コーディネーションを追加しました。これは、すべてのグラニュールが一度のリクエストでレプリカに送信されるのではなく、各レプリカがコーディネーターに新しいタスク(処理すべきグラニュールのセット)を要求できることを意味します。コーディネーターは、受信したアナウンスメントに基づいてレプリカにグラニュールセットを提供します。

すべてのレプリカがすべてのパーツでアナウンスメントを送信した段階にいると仮定しましょう。

以下の図は、動的コーディネーションがどのように機能するかを視覚化しています:

Dynamic Coordination - part 1
  1. レプリカは、コーディネーターノードにタスクを処理できることを知らせ、処理できる作業量を指定することもできます。

  2. コーディネーターはレプリカにタスクを割り当てます。

Dynamic Coordination - part 2
  1. レプリカ1と2は非常に迅速にタスクを完了します。レプリカは、コーディネーターからさらに別のタスクを要求します。

  2. コーディネーターは、レプリカ1と2に新しいタスクを割り当てます。

Dynamic Coordination - part 3
  1. すべてのレプリカはタスクの処理を完了しました。タスクをさらに要求します。

  2. コーディネーターはアナウンスメントを使用して、処理する残りのタスクを確認しますが、残りのタスクはありません。

  3. コーディネーターはレプリカにすべてが処理されたことを伝えます。これからマージ可能な状態をすべてマージし、クエリに応答します。

キャッシュの局所性の管理

最後の潜在的な問題は、キャッシュの局所性をどのように扱うかです。もしクエリが複数回実行される場合、どのようにして同じタスクを同じレプリカにルーティングするかを確保できるのでしょうか?前の例では、以下のタスクが割り当てられました:

レプリカ 1レプリカ 2レプリカ 3
パート 1g1, g6, g7g2, g4, g5g3
パート 2g1g2, g4, g5g3
パート 3g1, g6g2, g4, g5g3

同じタスクが同じレプリカに割り当てられるようにするために、2つのことが行われます。パート + グラニュールのセット(タスク)のハッシュが計算されます。そして、タスク割り当てに対してレプリカ数の剰余が適用されます。

これは理論上は良いことに思えますが、実際には、一つのレプリカに突発的な負荷がかかるか、ネットワークの劣化が発生した場合、特定のタスクを実行するために一貫して使用される同じレプリカによって遅延が発生する可能性があります。max_parallel_replicasがレプリカ数より少ない場合、クエリの実行にはランダムなレプリカが選択されます。

タスクの奪取

もし一部のレプリカが他のレプリカよりタスクを処理するのが遅い場合、他のレプリカはそのレプリカに属するはずのタスクをハッシュで「奪う」ことを試みて、遅延を減少させます。

制限事項

この機能には既知の制限がありますが、その主要なものはこのセクションに記載されています。

注記

もし以下に示した制限のいずれでもない問題が発生し、並列レプリカが原因と思われる場合は、comp-parallel-replicasラベルを使用してGitHubで問題をオープンしてください。

制限事項説明
複雑なクエリ現在、並列レプリカは単純なクエリにはかなりうまく機能します。CTE、サブクエリ、JOIN、非平坦クエリなどの複雑さがクエリ性能に悪影響を及ぼす可能性があります。
小規模なクエリ多くの行を処理しないクエリを実行する場合、複数のレプリカで実行すると、レプリカ間のコーディネーションのネットワーク時間がクエリ実行に追加のサイクルをもたらす可能性があるため、パフォーマンスが向上しない場合があります。これらの問題を制限するために、設定を使用することができます:parallel_replicas_min_number_of_rows_per_replica
FINALで並列レプリカは無効
高いカーディナリティデータと複雑な集計多くのデータを送信する必要がある高いカーディナリティの集計が、クエリを著しく遅くする可能性があります。
新しいアナライザーとの互換性新しいアナライザーは、特定のシナリオでクエリ実行を大幅に遅くしたり、早くしたりする可能性があります。
設定説明
enable_parallel_replicas0: 無効
1: 有効
2: 並列レプリカの使用を強制します。使用されない場合は例外を投げます。
cluster_for_parallel_replicas並列レプリケーションに使用するクラスタ名。ClickHouse Cloudを使用している場合は、defaultを使用します。
max_parallel_replicas複数のレプリカでクエリ実行に使用する最大レプリカ数。クラスター内のレプリカ数より少ない数が指定されている場合、ノードはランダムに選択されます。この値は、水平スケーリングを考慮してオーバーコミットされることもあります。
parallel_replicas_min_number_of_rows_per_replica処理する必要がある行数に基づいて使用されるレプリカ数を制限します。使用されるレプリカの数は、次のように定義されます:
推定読み取り行数 / 最小行数(レプリカあたり)
allow_experimental_analyzer0: 古いアナライザーを使用
1: 新しいアナライザーを使用します。

並列レプリカの動作は使用するアナライザーによって変わる可能性があります。

並列レプリカの問題調査

各クエリに使用されている設定を確認するには、system.query_log テーブルを使用できます。また、system.events テーブルを見ることで、サーバー上で発生したすべてのイベントを確認できます。さらに、clusterAllReplicas テーブル関数を使用して、すべてのレプリカ上のテーブルを確認できます(クラウドユーザーの場合は、defaultを使用します)。

SELECT
   hostname(),
   *
FROM clusterAllReplicas('default', system.events)
WHERE event ILIKE '%ParallelReplicas%'

<details>
<summary>レスポンス</summary>
```response title="レスポンス"
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasHandleRequestMicroseconds      │   438 │ レプリカからのマークのリクエスト処理にかかった時間                                               │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   558 │ レプリカアナウンスメントの処理にかかった時間                                                         │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasReadUnassignedMarks            │   240 │ すべてのレプリカでスケジュールされた未割り当てマークの合計                                          │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasReadAssignedForStealingMarks   │     4 │ 一貫したハッシュによってスチール用にスケジュールされたマークが割り当てられた合計                     │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasStealingByHashMicroseconds     │     5 │ ハッシュによってスチール用のセグメント収集にかかった時間                                            │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasProcessingPartsMicroseconds    │     5 │ データパーツ処理にかかった時間                                                                     │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     3 │ 孤立したセグメントの収集にかかった時間                                                              │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasUsedCount                      │     2 │ タスクベースの並列レプリカでクエリを実行するために使用されたレプリカの数                           │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasAvailableCount                 │     6 │ タスクベースの並列レプリカでクエリを実行するために使用可能なレプリカの数                          │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasHandleRequestMicroseconds      │   698 │ レプリカからのマークのリクエスト処理にかかった時間                                               │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   644 │ レプリカアナウンスメントの処理にかかった時間                                                         │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasReadUnassignedMarks            │   190 │ すべてのレプリカでスケジュールされた未割り当てマークの合計                                          │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasReadAssignedForStealingMarks   │    54 │ 一貫したハッシュによってスチール用にスケジュールされたマークが割り当てられた合計                     │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasStealingByHashMicroseconds     │     8 │ ハッシュによってスチール用のセグメント収集にかかった時間                                            │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasProcessingPartsMicroseconds    │     4 │ データパーツ処理にかかった時間                                                                     │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     2 │ 孤立したセグメントの収集にかかった時間                                                              │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasUsedCount                      │     2 │ タスクベースの並列レプリカでクエリを実行するために使用されたレプリカの数                           │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasAvailableCount                 │     6 │ タスクベースの並列レプリカでクエリを実行するために使用可能なレプリカの数                          │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasHandleRequestMicroseconds      │   620 │ レプリカからのマークのリクエスト処理にかかった時間                                               │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   656 │ レプリカアナウンスメントの処理にかかった時間                                                         │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasReadUnassignedMarks            │     1 │ すべてのレプリカでスケジュールされた未割り当てマークの合計                                          │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasReadAssignedForStealingMarks   │     1 │ 一貫したハッシュによってスチール用にスケジュールされたマークが割り当てられた合計                     │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasStealingByHashMicroseconds     │     4 │ ハッシュによってスチール用のセグメント収集にかかった時間                                            │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasProcessingPartsMicroseconds    │     3 │ データパーツ処理にかかった時間                                                                     │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     1 │ 孤立したセグメントの収集にかかった時間                                                              │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasUsedCount                      │     2 │ タスクベースの並列レプリカでクエリを実行するために使用されたレプリカの数                           │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasAvailableCount                 │    12 │ タスクベースの並列レプリカでクエリを実行するために使用可能なレプリカの数                          │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasHandleRequestMicroseconds      │   696 │ レプリカからのマークのリクエスト処理にかかった時間                                               │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   717 │ レプリカアナウンスメントの処理にかかった時間                                                         │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasReadUnassignedMarks            │     2 │ すべてのレプリカでスケジュールされた未割り当てマークの合計                                          │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasReadAssignedForStealingMarks   │     2 │ 一貫したハッシュによってスチール用にスケジュールされたマークが割り当てられた合計                     │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasStealingByHashMicroseconds     │    10 │ ハッシュによってスチール用のセグメント収集にかかった時間                                            │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasProcessingPartsMicroseconds    │     6 │ データパーツ処理にかかった時間                                                                     │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     2 │ 孤立したセグメントの収集にかかった時間                                                              │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasUsedCount                      │     2 │ タスクベースの並列レプリカでクエリを実行するために使用されたレプリカの数                           │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasAvailableCount                 │    12 │ タスクベースの並列レプリカでクエリを実行するために使用可能なレプリカの数                          │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘

</details>

[`system.text_log`](/operations/system-tables/text_log) テーブルには、並列レプリカを使用したクエリの実行に関する情報も含まれています:

```sql title="クエリ"
SELECT message
FROM clusterAllReplicas('default', system.text_log)
WHERE query_id = 'ad40c712-d25d-45c4-b1a1-a28ba8d4019c'
ORDER BY event_time_microseconds ASC

<details>
<summary>レスポンス</summary>
```response title="レスポンス"
┌─message────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ (from 54.218.178.249:59198) SELECT * FROM session_events WHERE type='type2' LIMIT 10 SETTINGS allow_experimental_parallel_reading_from_replicas=2; (stage: Complete)                                                                                       │
│ クエリ SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 to stage Complete │
│ アクセスが許可されました: SELECT(clientId, sessionId, pageId, timestamp, type) ON default.session_events                                                                                                                                                             │
│ クエリ SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') to stage WithMergeableState only analyze │
│ アクセスが許可されました: SELECT(clientId, sessionId, pageId, timestamp, type) ON default.session_events                                                                                                                                                             │
│ クエリ SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') from stage FetchColumns to stage WithMergeableState only analyze │
│ クエリ SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 to stage WithMergeableState only analyze │
│ アクセスが許可されました: SELECT(clientId, sessionId, pageId, timestamp, type) ON default.session_events                                                                                                                                                             │
│ クエリ SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 from stage FetchColumns to stage WithMergeableState only analyze │
│ クエリ SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 from stage WithMergeableState to stage Complete │
│ リクエストしたレプリカの数 (100) は、クラスター内で利用可能な実際の数 (6) よりも大きいです。クエリの実行には後者の数を使用します。                                                                                                       │
│ 初期リクエストはレプリカ 4 から: 2 パーツ: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
レプリカ 4 から受信                                                                                                   │
│ 読み取り状態が完全に初期化されています: part all_0_2_1 with ranges [(0, 182)] in replicas [4]; part all_3_3_0 with ranges [(0, 62)] in replicas [4]                                                                                                            │
│ 初期リクエストを送信しました: 1 レプリカ数: 6                                                                                                                                                                                                                 │
│ 初期リクエストはレプリカ 2 から: 2 パーツ: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
レプリカ 2 から受信                                                                                                   │
│ 初期リクエストを送信しました: 2 レプリカ数: 6                                                                                                                                                                                                                 │
│ レプリカ 4 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 4 に 1 パーツ: [part all_0_2_1 with ranges [(128, 182)]] に応答を返します。終了: false; mine_marks=0, stolen_by_hash=54, stolen_rest=0                                                                                                       │
│ 初期リクエストはレプリカ 1 から: 2 パーツ: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
レプリカ 1 から受信                                                                                                   │
│ 初期リクエストを送信しました: 3 レプリカ数: 6                                                                                                                                                                                                                 │
│ レプリカ 4 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 4 に 2 パーツ: [part all_0_2_1 with ranges [(0, 128)], part all_3_3_0 with ranges [(0, 62)]] に応答を返します。終了: false; mine_marks=0, stolen_by_hash=0, stolen_rest=190                                                                  │
│ 初期リクエストはレプリカ 0 から: 2 パーツ: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
レプリカ 0 から受信                                                                                                   │
│ 初期リクエストを送信しました: 4 レプリカ数: 6                                                                                                                                                                                                                 │
│ 初期リクエストはレプリカ 5 から: 2 パーツ: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
レプリカ 5 から受信                                                                                                   │
│ 初期リクエストを送信しました: 5 レプリカ数: 6                                                                                                                                                                                                                 │
│ レプリカ 2 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 2 に 0 パーツ: [] に応答を返します。終了: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ 初期リクエストはレプリカ 3 から: 2 パーツ: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
レプリカ 3 から受信                                                                                                   │
│ 初期リクエストを送信しました: 6 レプリカ数: 6                                                                                                                                                                                                                 │
│ 読むべき総行数: 2000000                                                                                                                                                                                                                                │
│ レプリカ 5 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 5 に 0 パーツ: [] に応答を返します。終了: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ レプリカ 0 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 0 に 0 パーツ: [] に応答を返します。終了: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ レプリカ 1 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 1 に 0 パーツ: [] に応答を返します。終了: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ レプリカ 3 からのリクエストを処理中、最小マークサイズは240です                                                                                                                                                                                                 │
│ レプリカ 3 に 0 パーツ: [] に応答を返します。終了: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ (c-crimson-vd-86-server-rdhnsx3-0.c-crimson-vd-86-server-headless.ns-crimson-vd-86.svc.cluster.local:9000) 読み取るデータが十分であるため、クエリをキャンセルします。                                                                                              │
│ 81920 行を読み取り、5.16 MiB を 0.013166 秒で読み取り、6222087.194288318 行/sec., 391.63 MiB/sec.                                                                                                                                                                   │
│ 調整完了: 統計: レプリカ 0 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; レプリカ 1 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; レプリカ 2 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; レプリカ 3 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; レプリカ 4 - {requests: 3 marks: 244 assigned_to_me: 0 stolen_by_hash: 54 stolen_unassigned: 190}; レプリカ 5 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0} │
│ クエリのピークメモリ使用量: 1.81 MiB。                                                                                                                                                                                                                   │
│ 0.024095586 秒で処理されました。                                                                                                                                                                                                                              │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

</details>

最後に、`EXPLAIN PIPELINE` を使用することもできます。これにより、ClickHouse がクエリをどのように実行し、実行にどのリソースが使用されるかが強調表示されます。以下のクエリを例に見てみましょう:

```sql
SELECT count(), uniq(pageId) , min(timestamp), max(timestamp) 
FROM session_events 
WHERE type='type3' 
GROUP BY toYear(timestamp) LIMIT 10

並列レプリカなしでのクエリパイプラインを見てみましょう:

```sql title="EXPLAIN PIPELINE (並列レプリカなし)"
EXPLAIN PIPELINE graph = 1, compact = 0 
SELECT count(), uniq(pageId) , min(timestamp), max(timestamp) 
FROM session_events 
WHERE type='type3' 
GROUP BY toYear(timestamp) 
LIMIT 10 
SETTINGS allow_experimental_parallel_reading_from_replicas=0 
FORMAT TSV;

<Image img={image_8} size="lg" alt="EXPLAIN without parallel_replica" />

並列レプリカありの場合:

```sql title="EXPLAIN PIPELINE (並列レプリカあり)"
EXPLAIN PIPELINE graph = 1, compact = 0 
SELECT count(), uniq(pageId) , min(timestamp), max(timestamp) 
FROM session_events 
WHERE type='type3' 
GROUP BY toYear(timestamp) 
LIMIT 10 
SETTINGS allow_experimental_parallel_reading_from_replicas=2 
FORMAT TSV;

<Image img={image_9} size="lg" alt="EXPLAIN with parallel_replica"/>