Optimizing ClickHouse on High-Core Count Intel CPUs

Media Item Type Metadata

Feedback

超高コア数への最適化

ボトルネックを緩和することによって最大10倍高速になるということについて興味を持つきっかけになった。
またここでは「ClickHouseを超高コア数システムで非常に適切に拡張できることを示しています。」という記述があるがスケールアップするよりもコスト当たりの効能がよくなる場合があると予想できる。これはコア数が多いほどボトルネックの影響が大きくなることから導着だせる。

*ClickHouseは、Yandex社が開発したオープンソースのカラム指向データベース管理システムです。主にオンライン分析処理(OLAP)用途に最適化されており、超高速な集計や分析クエリを大規模データセットに対して実行できます。分散アーキテクチャを採用しており、複数ノードでのスケーラブルな運用が可能です。また、リアルタイム分析や高い同時実行性にも対応しています。


専門用語の説明セクション

【基本概念】

*超高コア数プロセッサー(Ultra-high Core Count Processor)
1つのCPUソケットに100コア以上を搭載したプロセッサー。Intelの最新世代では、Granite Rapidsで128 P-cores、Sierra Forestで288 E-coresを実現。物理的な制約により、単一コアの性能向上よりもコア数増加による並列処理能力向上が主流となっている。

*スケーラビリティ(Scalability)
システムの処理能力が、リソース(CPU、メモリ、ストレージなど)の増加に応じて向上する度合い。超高コア数環境では、コア数増加に対する性能向上の効率性を指す。理想的には線形スケーリング(コア数に比例した性能向上)が望ましい。

*ボトルネック(Bottleneck)
システム全体の性能を制限する要因。超高コア数環境では、ロック競合、メモリ帯域幅、キャッシュコヒーレンス、NUMA効果などが主要なボトルネックとなる。

【並列処理関連】

*スレッド(Thread)
プログラムの実行単位の一つ。1つのプロセス内で複数のスレッドが並行実行でき、各スレッドは独立したスタックを持つが、メモリ空間を共有する。超高コア数環境では、コア数に応じてスレッド数を調整することで並列処理能力を向上させる。

*並列処理(Parallel Processing)
複数の処理を同時に実行することで、全体の処理時間を短縮する手法。データベースシステムでは、クエリの各部分を複数のスレッドで並列実行することで、スループットを向上させる。

*同時実行性(Concurrency)
複数の処理が同時に進行している状態。並列処理と似ているが、必ずしも物理的に同時実行される必要はなく、時間的に重複して実行されることを指す。

【メモリ・キャッシュ関連】

*キャッシュライン(Cache Line)
CPUキャッシュの最小単位。通常64バイトのサイズで、メモリから一度に読み込まれるデータの塊。キャッシュコヒーレンスはこの単位で管理されるため、同じキャッシュライン内の異なる変数へのアクセスでも競合が発生する。

*キャッシュミス(Cache Miss)
CPUが必要なデータをキャッシュから見つけられず、メモリから読み込む必要がある状態。キャッシュミスが頻発すると、メモリアクセスのレイテンシにより性能が大幅に低下する。

*メモリ帯域幅(Memory Bandwidth)
単位時間あたりにメモリから読み書きできるデータ量。超高コア数環境では、コア数が増えるとコアあたりの利用可能なメモリ帯域幅が減少するため、効率的なメモリ使用が重要となる。

*レイテンシ(Latency)
処理の開始から完了までにかかる時間。データベースシステムでは、クエリの応答時間やメモリアクセス時間を指す。レイテンシが低いほど、リアルタイム性の高い処理が可能となる。

【同期・ロック関連】

*排他制御(Mutual Exclusion)
複数のスレッドが同時に共有リソースにアクセスすることを防ぐ仕組み。ミューテックス、セマフォ、スピンロックなどの同期プリミティブを使用して実現する。

*デッドロック(Deadlock)
複数のスレッドが互いに相手が保持するリソースを待機し、処理が進行できなくなる状態。適切なロック順序の設計やタイムアウト機能により回避する必要がある。

*スピンロック(Spin Lock)
ロックが取得できるまで、CPUを消費しながら待機し続けるロック方式。短時間の待機には有効だが、長時間の待機ではCPUリソースを無駄に消費する。

*セマフォ(Semaphore)
リソースの利用可能数を管理する同期プリミティブ。利用可能なリソース数に応じてスレッドの実行を制御し、リソースの枯渇を防ぐ。

【データベース関連】

*OLAP(Online Analytical Processing)
オンライン分析処理。大量のデータに対して複雑な集計や分析を行う処理。ClickHouseはOLAPワークロードに特化して設計されており、高速な集計クエリを実行できる。

*カラム指向データベース(Column-oriented Database)
データを列(カラム)単位で格納するデータベース。行指向データベースと比べて、集計処理や分析処理に優れている。データ圧縮率も高く、ストレージ効率が良い。

*分散アーキテクチャ(Distributed Architecture)
複数のノード(サーバー)にデータを分散配置し、協調して処理を行うアーキテクチャ。スケーラビリティと可用性を向上させるが、データの一貫性やノード間通信の管理が複雑になる。

*クエリプラン(Query Plan)
データベースがクエリを実行する際の処理手順。最適化エンジンが生成し、インデックスの使用、結合順序、並列実行方法などを決定する。効率的なクエリプランは性能に大きく影響する。

【ハードウェア関連】

*ハイパースレッディング(Hyper-threading)
1つの物理コアで2つの論理スレッドを同時実行する技術。IntelのSMT(Simultaneous Multithreading)実装。コア数を2倍に見せかけるが、実際の性能向上は限定的。

*NUMAノード(NUMA Node)
NUMAアーキテクチャにおいて、ローカルメモリとCPUが直接接続された単位。同一NUMAノード内でのメモリアクセスは高速だが、異なるNUMAノード間のアクセスは遅くなる。

*インターコネクト(Interconnect)
CPU間やCPUとメモリ間を接続する通信路。NUMA環境では、ノード間のデータ転送に使用され、その帯域幅とレイテンシがシステム全体の性能に影響する。

【最適化関連】

*誤った共有(False Sharing)
複数のスレッドが同じキャッシュライン内の異なる変数にアクセスする現象。CPUのキャッシュコヒーレンスプロトコルはキャッシュライン単位で動作するため、独立した変数へのアクセスでも競合が発生し、パフォーマンスが大幅に低下する。

*キャッシュラインアライメント(Cache Line Alignment)
データ構造を64バイトのキャッシュライン境界に合わせて配置すること。これにより、異なる変数が同じキャッシュラインを共有することを防ぎ、誤った共有を回避できる。

*スレッドローカルストレージ(Thread Local Storage)
各スレッドが独立したデータ領域を持つ仕組み。グローバル変数ではなく、スレッドごとに専用の変数を使用することで、スレッド間の競合を完全に排除できる。

*ダブルチェックロッキング(Double-checked Locking)
ロックを取得する前に条件を二重にチェックする手法。不要なロック取得を回避し、競合を減らす効果がある。主に初期化処理やリソース生成時に有効。

*アトミック操作(Atomic Operation)
CPUが提供する不可分操作。ロックを使わずに共有データの更新を行い、ロックオーバーヘッドや競合を低減できる。

*共有ロック(Shared Lock)
複数のスレッドが同時に読み取りアクセスできるロック。書き込み時のみ排他ロックを取得することで、同時実行性を高める。

*クリティカルセクション(Critical Section)
同時に複数のスレッドが実行してはならない、共有リソースへのアクセス部分。クリティカルセクションが長いほどロック競合が激しくなり、システム全体のスループットが低下する。

*細粒度ロック(Fine-grained Locking)
大きな共有リソース全体に対して一つのロックを使うのではなく、より小さな単位ごとにロックを分割する手法。これにより同時実行性が向上し、競合を減らすことができる。

*共有状態の排除(Elimination of Shared State)
スレッド間で共有するデータやリソース自体を設計から排除し、各スレッドが独立して処理できるようにするアプローチ。これによりロックや同期の必要性がなくなり、スケーラビリティが大幅に向上する。


コアスケーリングの課題
専門家ではないのでコアスケーリングの課題のほとんどが
キャッシュのオーバーヘッド、
ロックの競合
メモリの帯域幅
擦れっとの調整
NUMAの影響
であることが興味深く参考になった。


説明

*バウンスとは、複数のコアやスレッド間でキャッシュラインが頻繁に移動(転送)する現象を指します。特に共有データへの書き込みが多い場合、キャッシュコヒーレンシプロトコルによってキャッシュラインが各コア間でやり取りされ、これがパフォーマンスの低下(レイテンシ増大や帯域消費)を引き起こします。超高コア数環境ではこのバウンスの影響が顕著になり、スケーリング効率を阻害する主な要因の一つとなります。そのため、ClickHouseのような高並列システムでは、バウンスを抑制するためのデータ構造やロック戦略の最適化が重要です。

*アムダールの法則とは、システム全体の性能向上が、並列化できない部分によって制限されることを示す法則です。具体的には、プログラムの一部しか並列化できない場合、コア数をいくら増やしても全体の速度向上には限界があることを数式で表現しています。例えば、処理の90%が並列化可能でも、残り10%が直列処理であれば、理論上どれだけコア数を増やしても最大で約10倍までしか高速化できません。超高コア数プロセッサーを活用する際には、この法則を意識し、並列化できない部分(シリアル部分)をいかに減らすかが重要となります。

*NUMA(Non-Uniform Memory Access)とは、マルチプロセッサシステムにおいて、各CPU(またはCPUソケット)が自分専用のローカルメモリを持ちつつ、他のCPUのメモリにもアクセスできるアーキテクチャです。ただし、ローカルメモリへのアクセスは高速ですが、他のCPUのメモリ(リモートメモリ)へのアクセスは遅くなります。これにより、メモリアクセスのレイテンシや帯域幅がアクセス先によって「不均一(Non-Uniform)」になるのが特徴です。超高コア数環境では、NUMAの影響を考慮しないと、意図しないリモートメモリアクセスが頻発し、パフォーマンス低下の原因となります。そのため、プロセスやスレッド、データの配置を適切に制御し、できるだけローカルメモリを利用する設計が重要です。

*マルチソケットシステムとは、1台のサーバーに複数のCPUソケット(物理的なCPUを挿す場所)が搭載されている構成を指します。各ソケットには独立したCPU(プロセッサ)が装着され、それぞれが自分専用のメモリ(ローカルメモリ)を持つことが一般的です。マルチソケット構成では、CPU間の通信やメモリアクセスが発生するため、NUMAアーキテクチャと密接に関係しています。ソケット間の通信は、同一ソケット内の通信よりもレイテンシが高く、帯域幅も制限される場合が多いため、プロセスやスレッド、データの配置を意識した設計が重要です。ClickHouseのような高並列システムでは、マルチソケット環境での最適なリソース割り当てや、リモートアクセスの最小化がパフォーマンス向上の鍵となります。

*ローカルメモリとは、各CPUやコアが直接接続されているメモリ領域のことを指します。ローカルメモリへのアクセスは、物理的な距離が近いためレイテンシが低く、帯域幅も高いという利点があります。一方、リモートメモリとは、他のCPUやソケットに接続されているメモリ領域のことです。リモートメモリへのアクセスは、CPU間のインターコネクトを経由する必要があるため、ローカルメモリに比べてレイテンシが高く、帯域幅も制限される場合があります。NUMAアーキテクチャを採用したシステムでは、プロセスやスレッドができるだけローカルメモリを利用するように設計することで、パフォーマンスの最適化が可能となります。逆に、リモートメモリアクセスが頻発すると、システム全体のスループットや応答性が大きく低下する原因となります。


5つの最適化領域

ロック競合

ロックの競合によるボトルネックの解消はロックを解除するだけでなくスレッドに合わせたロックの方法を考える必要がある。これはスケールアップすることにより変わるので調整が必要だが高速化をするなら比較的間単な手段であり。
手法としては
- ダブルチェックロッキング
(ロック取得前に条件を二重にチェックすることで、不要なロック取得を回避し、競合を減らす手法。主に初期化処理やリソース生成時に有効。)

- アトミック操作の活用
(CPUが提供するアトミック命令を利用して、ロックを使わずに共有データの更新を行う。これによりロックオーバーヘッドや競合を低減できる。)

- 共有ロックの導入
(読み取り専用の処理には複数スレッドが同時にアクセスできる共有ロック(リードロック)を使い、書き込み時のみ排他ロックを取得することで、同時実行性を高める。)

- クリティカルセクションの短縮
(ロックで保護する必要がある処理部分(クリティカルセクション)をできるだけ短くし、ロック保持時間を減らすことで競合を抑制する。処理の分割やロジックの見直しが有効。)
がある。



*キュー理論(Queueing Theory)
待ち行列やサービスシステムの挙動を数理的に解析する理論。コンピュータシステムでは、リソース(CPU、ロック、I/O など)へのアクセス待ちや競合の影響を評価する際に用いられる。スレッド数が増えると待ち時間や競合がどのように増加するかを定量的に予測できる。

*ロック競合
複数のスレッドやプロセスが同じロック(排他制御)を取得しようとして同時に待機状態になる現象。コア数が増えるほど競合が激化し、スケーラビリティの大きな障害となる。

*ミューテックス(Mutex)
「Mutual Exclusion(相互排他)」の略。複数のスレッドが同時に共有リソースへアクセスするのを防ぐための同期プリミティブ。ミューテックスの取得・解放にはカーネルやハードウェアレベルの制御が必要な場合があり、競合時にはパフォーマンス低下の要因となる。

*キャッシュコヒーレンシ(Cache Coherency)
マルチコア環境で各コアが持つキャッシュの内容が一貫性を保つように同期される仕組み。ロック変数など共有データへの頻繁な書き込みは、キャッシュラインの転送(バウンス)を引き起こし、帯域やレイテンシの面で大きなオーバーヘッドとなる。

*クリティカルセクション
同時に複数のスレッドが実行してはならない、共有リソースへのアクセス部分。クリティカルセクションが長いほどロック競合が激しくなり、システム全体のスループットが低下する。

*細粒度ロック(Fine-grained Locking)
大きな共有リソース全体に対して一つのロックを使うのではなく、より小さな単位ごとにロックを分割する手法。これにより同時実行性が向上し、競合を減らすことができる。

*共有状態の排除
スレッド間で共有するデータやリソース自体を設計から排除し、各スレッドが独立して処理できるようにするアプローチ。これによりロックや同期の必要性がなくなり、スケーラビリティが大幅に向上する。


クエリ条件キャッシュの最適化

基本概念
「使わないデータをスキップする」 - これはデータベース最適化の基本原理ですが、ClickHouseではこれをクエリ条件キャッシュとして実装している点が革新的だと思いました。

従来の問題
- 240スレッドが同時にアクセス
- 76%のCPU時間をロック競合で消費
- 読み取り中心なのに排他ロックを使用

最適化手法
1. 共有ロックによる読み取りの並列化
2. ダブルチェックロッキングで無駄な更新を回避
3. アトミック操作による高速パス
4. 早期リターンでロックを回避

技術的詳細

*クエリ条件キャッシュ(Query Condition Cache)
- 目的: WHERE句のフィルター条件に基づき、どのデータが無関係かをキャッシュ
- キー: フィルター条件のハッシュ値 + マーク範囲 + 最終マークの有無
- 特性: 読み取りが圧倒的に多く、書き込みは稀
- 効果: 同じ条件のクエリで不要なデータの読み込み・処理をスキップ

汎用性
この最適化手法は他のシステムでも応用可能:
- インデックス最適化
- パーティション分割
- ビューのマテリアライゼーション
- クエリプランのキャッシュ



スレッドローカルタイマー ID

これらからスタックによる遅延がボトルネックになることがわかった。ここで遅延が生じていたのはスケーリング前のチューニングをそのまま使用していたための可能性がある。



問題の概要
ClickHouseのクエリプロファイラーで、240個のスレッドが全て同じグローバルロックを奪い合い、実質的に1つのスレッドでしか処理できていなかった問題を解決。

何がボトルネックになっていたか?

1. グローバルロックの競合
- 240個のスレッドが全て同じロック(timer_management_lock)を奪い合い
- 1つのスレッドがロックを取得している間、他の239個のスレッドは待機
- スレッド数が増えると、待機時間が指数関数的に増加

2. 頻繁なシステムコール
- プロファイリング開始時に毎回create_new_timer()を実行
- プロファイリング終了時に毎回delete_timer()を実行
- システムコールは高コスト(カーネルモードへの切り替えが必要)

3. 共有データ構造の更新
- 全スレッドが同じ共有状態(shared_timer_state)を更新
- 共有状態への書き込みは、他のスレッドのキャッシュを無効化
- キャッシュ無効化により、メモリアクセスが遅くなる

結果:240個のスレッドが順番待ちになり、並列処理の効果が全く発揮されない状態

解決策
スレッドローカルストレージを使用して、各スレッドが独自のtimer_idを持つように変更。

最適化手法
1. スレッドローカルストレージの導入
2. 共有状態の完全排除
3. タイマーの再利用によるシステムコール削減
4. ロック不要の設計への変更

技術的詳細

*スレッドローカルストレージ(Thread Local Storage)
- 目的: 各スレッドが独立したtimer_idを保持
- 効果: グローバル同期が不要になり、ロック競合を排除
- 実装: thread_localキーワードを使用
- 利点: タイマー作成・削除のシステムコールを削減

*クエリプロファイラー(Query Profiler)
データベースシステムでクエリの実行性能を分析するためのツール。POSIXタイマーを使用して定期的にスレッドスタックをサンプリングし、パフォーマンスボトルネックを特定する。プロファイリング中は頻繁にタイマーの作成・削除が発生するため、超高コア数環境ではロック競合の原因となる。

*POSIXタイマー(POSIX Timer)
POSIX標準で定義されたタイマー機能。高精度な時間計測や定期的な処理の実行に使用される。システムコールを必要とするため、頻繁な作成・削除はオーバーヘッドが大きい。

*システムコール(System Call)
アプリケーションプログラムがオペレーティングシステムの機能を利用するためのインターフェース。タイマーの作成・削除はシステムコールを必要とし、カーネルモードへの切り替えが発生するため、頻繁な実行はパフォーマンスに影響する。

*グローバル同期(Global Synchronization)
複数のスレッドが共有リソースにアクセスする際に、全体で一つのロックを使用して同期を取る方式。シンプルだが、スレッド数が増えると競合が激化し、スケーラビリティが低下する。

パフォーマンスへの影響
- タイマー関連のロック競合ホットスポットを排除
- タイマー作成・削除のシステムコールを削減
- 超高コア数サーバーでのプロファイリングのスケーラビリティ向上
- 共有状態の排除により、スレッド同期オーバーヘッドを回避

汎用性
この最適化手法は他のシステムでも応用可能:
- ログ記録システム
- メトリクス収集
- デバッグ情報の管理
- スレッド固有のリソース管理


ボトルネック 2: メモリ管理

問題の概要
超高コア数システムでのメモリ最適化は、シングルスレッドのメモリ管理とは大きく異なる。メモリアロケーター自体が競合ポイントになり、メモリ帯域幅がより多くのコアに分割され、小規模なシステムで正常に機能する割り当てパターンは、大規模なパフォーマンスの問題を引き起こす可能性がある。

何がボトルネックになっていたか?

1. メモリアロケーターの競合
- メモリアロケーター自体が競合ポイントになる
- 複数スレッドが同時にメモリを要求すると競合が発生
- 超高コア数環境では競合が激化

2. メモリ帯域幅の分散
- メモリ帯域幅がより多くのコアに分割される
- コアあたりの利用可能なメモリ帯域が減少
- メモリアクセスのレイテンシが増加

3. 割り当てパターンの問題
- 小規模システム用の割り当てパターンが大規模で問題に
- メモリの断片化が発生
- 効率的なメモリ再利用が困難

最適化 2.1: Jemalloc メモリ再利用の最適化

問題の詳細
ClickHouseの2レベルハッシュテーブルで、集計クエリの最後に256個のサブハッシュテーブルが割り当て解除される。jemallocは、マージされたメモリブロックを将来の小さな割り当てに再利用することを妨げていた。

技術的詳細

*2レベルハッシュテーブル
- 第1レベル: 256個の静的バケット
- 第2レベル: 互いに独立して成長するハッシュテーブル
- 集計クエリで使用される大きな集計状態を管理

*Jemallocの問題
- エクステント管理の問題: 大規模な割り当てが解放されると、メモリエクステントを効率的に追跡・再利用できない
- サイズクラスの断片化: メモリが将来の割り当てパターンと一致しないサイズクラスに閉じ込められる
- メタデータのオーバーヘッド: メタデータ構造が多すぎると、効率的なメモリ結合が妨げられる
- ページフォールトの増幅: 新しい割り当てが既存のコミットされたページを再利用する代わりに、ページフォールトをトリガー


メモリ削減のためのASTクエリの書き換え

問題の詳細
ClickBenchクエリQ29で、sum(column + literal)形式の冗長な計算による過剰なメモリアクセスがボトルネックになっていた。

これらによりコーディング時の冗長な計算方法が問題になる可能性あると感じた.



ボトルネック 3: 並列処理を増やす

問題の概要
高速集計は分析データベースの中核だが、並列スレッドでのデータ集約だけでなく、ローカル結果を並行してマージすることも重要。ClickHouseの集計演算子には2つのフェーズがあり、第2フェーズのマージが適切に並列化されていないとボトルネックになる。

何がボトルネックになっていたか?

1. シリアルマージフェーズ
- 第1フェーズ: 各スレッドがデータの部分を並行処理し、ローカル結果を作成
- 第2フェーズ: すべての部分結果をマージ(これがシリアル化されていた)
- スレッドが増えると、マージする結果がより部分的になり、問題が悪化

2. ハッシュテーブル変換のシリアル化
- 単一レベルと2レベルのハッシュテーブルが混在
- 混合ハッシュテーブルの場合、単一レベルを2レベルに変換する必要
- この変換がシリアルプロセスで実行されていた

3. 並列化の不十分さ
- スレッド数を増やしても、マージフェーズが線形スケーリングしない
- シリアルセクションが並列処理の効果を相殺

最適化 3.1: ハッシュテーブル変換の並列化

問題の詳細
ClickBenchクエリQ5で、コア数が80スレッドから112スレッドに増加すると、パフォーマンスが著しく低下。パイプライン分析により、ハッシュテーブル変換におけるシリアル処理が明らかになった。

技術的詳細

*ClickHouseのハッシュテーブル
- 単一レベルハッシュテーブル: 小規模データセット用の高速フラットハッシュテーブル
- 2レベルハッシュテーブル: 256個のバケットを持つ階層型ハッシュテーブル(大規模データセット用)
- データベースは処理されたデータのサイズに基づいて適切なタイプを選択

*シリアルボトルネック
- 異なるスレッドのハッシュテーブルをマージする際の問題
- 単一レベル/2レベルの混合ハッシュテーブルの場合、最初に単一レベルを2レベルに変換する必要
- この変換がシリアルプロセスで実行されていた


最適化 3.2: 単一レベルのハッシュテーブルのマージ

問題の詳細
すべてのハッシュテーブルが単一レベルの場合も、パフォーマンスが標準以下であることを発見。

解決策
並列マージを単一レベルのケースに拡張。データセットのサイズに基づいて並列化のタイミングを決定。

最適化の実装
- すべてのハッシュテーブルが単一レベルの場合でも、合計データサイズが十分に大きい場合は並列マージを適用
- 最適な閾値を体系的なテストで決定
- 小規模なデータセットでは回帰を回避

パフォーマンスへの影響
- 単一レベルのマージシナリオのパフォーマンスが235%向上
- 最適な閾値は体系的なテストによって決定
- 小規模なデータセットでは回帰なし

最適化 3.3: キーサポートによる並列マージ

問題の詳細
大きなハッシュテーブルを持つGROUP BY操作が順次マージされていた。

解決策
前の2つの最適化を、キー付き集計(GROUP BY、COUNT(DISTINCT)など)にも適用。

パフォーマンスへの影響
- ClickBenchクエリQ8が10.3%改善
- ClickBenchクエリQ9が7.6%改善
- 他のクエリには回帰なし
- マージフェーズ中のCPU使用率が改善

汎用性
この最適化手法は他のシステムでも応用可能:
- 並列データ処理システム
- 分散集計システム
- 大規模データベースの最適化
- マルチスレッドアプリケーションの設計


これらからシステムの機能の最適化が効果的であることを知りました


ボトルネック 5: 誤った共有

問題の概要
誤った共有は、複数のスレッドが同じキャッシュライン内の変数にアクセスする場合に発生する。CPUのキャッシュコヒーレンスプロトコルはキャッシュラインの粒度で動作するため、2つの異なる変数の変更を含むキャッシュラインの変更は、コア間のコストのかかる同期を必要とする競合として扱われる。2×240 vCPUシステムでは、誤った共有により、単純なカウンターの増分がシステム全体のパフォーマンスの災害に変わる可能性がある。

何がボトルネックになっていたか?

1. キャッシュラインの物理演算
- 最新のIntelプロセッサは64バイトのキャッシュラインを使用
- キャッシュラインのいずれかのバイトが変更されると、他のコアのキャッシュで行全体を無効化する必要がある

2. 誤った共有増幅
- 240スレッドの場合、カウンタの更新ごとに、潜在的に数十のコアにわたってキャッシュラインの無効化がトリガー
- 独立した操作であるはずの操作が、キャッシュコヒーレンスプロトコルを通じてシリアル化される

3. 指数関数的な劣化
- コアの数が増えると、同じキャッシュラインに同時アクセスされる可能性が指数関数的に増加
- キャッシュミスの影響がさらに悪化

最適化 5.1: プロファイルイベントカウンターのアライメント

問題の詳細
ClickBenchクエリQ3で、2×240 vCPUシステムのCPUサイクルの36.6%がProfileEvents::incrementで費やされていた。パフォーマンスプロファイリングにより、深刻なキャッシュラインの競合が明らかになった。

技術的詳細

*プロファイルイベントカウンター
- ClickHouseの内部イベントシステムを参照
- プロファイルイベントは、詳細なクエリ実行ステップからメモリ割り当てまで、すべての内部操作を追跡
- 一般的な分析クエリでは、これらのカウンタはすべてのスレッドで数百万回インクリメント

*元の実装の問題
- キャッシュラインの境界を考慮せずに、同じメモリ領域に複数のカウンタを編成
- 1つのキャッシュラインに8つの異なるカウンターが詰め込まれていた
- 8個のカウンターが単一の64バイトキャッシュラインを共有

解決策
キャッシュラインの配置を適切に行い、各カウンタが独自の64バイトのキャッシュラインを確実に取得。

従来の実装の問題
- 複数のカウンターが同じキャッシュラインを共有していた
- 8個のカウンターが単一の64バイトキャッシュラインを共有
- 1つのカウンターを更新すると、他のカウンターも影響を受ける

最適化後の実装
- 各カウンターが独自の64バイトキャッシュラインを取得
- カウンター間に適切な間隔(パディング)を配置
- 各カウンターが排他的なキャッシュライン所有権を持つ

パフォーマンスへの影響
- ProfileEvents::incrementが36.6%から8.5%に低下
- ClickBenchクエリQ3で27.4%の改善
- コア数が多いシステムでより大きな利益
- キャッシュコヒーレンスのオーバーヘッドが超線形に増加するため、パフォーマンスの向上はコア数とともに増加

汎用性
この最適化パターンは、頻繁に更新される共有データ構造とコンパクトデータ構造に適用される。教訓は、メモリレイアウトが大規模になると重要になるということ - 8コアで正常に動作するものが、240コアでは耐え難いほど遅くなる可能性がある。

拡張可能な基盤の構築

この文書では、5つのパフォーマンスボトルネックの最適化について説明した:

1. ロック競合 - 調整オーバーヘッドは、コア数とともに指数関数的に増加
2. メモリの最適化 - コア数が増えると、コアあたりのメモリ帯域幅が減少
3. 並列処理の増加 - シリアルフェーズが主要なボトルネックになる
4. SIMDの最適化 - ブルートフォースベクトル化を超えたよりスマートなアルゴリズム
5. 誤った共有 - 誤った共有は、キャッシュ行サイズの粒度が原因で発生

これらのボトルネックと最適化は、ClickHouseだけに関するものではなく、超高コア数時代におけるデータベース最適化へのアプローチ方法における根本的な変化を表している。プロセッサがコア数の増加に向けて進化し続けるにつれて、これらの技術は拡張が必要なシステムにとって不可欠になる。

最適化により、ClickHouseはコア数の増加に応じて線形に近いスケーラビリティを実現できる。これにより、ClickHouseは、Intelやその他のハードウェアメーカーがコア数を数千に押し上げる将来の世界で、分析データベースとして繁栄することができる。

これらから、システムの最適化において機能の最適化が効果的であることを知りました

Status

Priority

Media_Type

ブログ

Collection

Citation

ClickHouse Inc., “Optimizing ClickHouse on High-Core Count Intel CPUs,” unjuno'sResearchLibrary, accessed October 7, 2026, https://archive.unjuno.org/items/show/78.

コメント