世界を動かす技術を、日本語で。

インテルの超高コア数プロセッサ向けのClickHouse最適化

概要

Intelの最新サーバーCPUはコア数が飛躍的に増加し、ClickHouseのスケーラビリティ最適化が重要課題。 高コア環境ではロック競合やメモリ帯域、NUMA効果などがボトルネックとなる。 Intel ShanghaiのエンジニアがClickHouseの全43 ClickBenchクエリを体系的に解析し、最適化を実施。 主な改善点はロック競合解消、スレッドローカル化、メモリ再利用最適化など。 最適化により、個別クエリで最大10倍、全体で2〜10%の性能向上を実現。

Intel超高コアサーバー時代のClickHouse最適化

  • Intelの最新CPU は1ソケットあたり128〜288コア、ロードマップでは200コア超も視野
  • マルチソケット構成で 400コア以上 のサーバーも現実に
  • Dennardスケーリング終焉 後、クロック向上よりもコア数増加が主流
  • ClickHouseのような分析DB にとって高コア化は大きな機会と課題
  • ハードウェア全体の 並列処理能力活用 が困難になる傾向

超高コア環境の主なボトルネック

  • ロック競合 :スレッド数が増えるほど待機時間とキャッシュコヒーレンシトラフィックが急増
  • キャッシュコヒーレンス :キャッシュラインのバウンスによるCPUサイクル消費
  • メモリ帯域 :データ集約型システムでの有効活用が課題
  • スレッド協調コスト :スレッド数増加で同期コストが超線形に増加
  • NUMA効果 :ローカル・リモートメモリ間のレイテンシ/帯域差
  • ロック競合解消メモリ最適化 が性能向上の鍵

ClickHouse最適化の実践

  • Intel Xeon (Ice Lake, Sapphire Rapids, Sierra Forest, Granite Rapids)で検証
  • perf, Intel VTune などのプロファイリングツールで全43 ClickBenchクエリを解析
  • 5つの最適化領域 を特定し、包括的なスケーラビリティ向上策を実装

ボトルネック1:ロック競合

  • 理論上、Nスレッドのロック待ちコストはN^2に増大
  • 例:8→80コアでロック待ち100倍
  • mutex自体のキャッシュコヒーレンストラフィックも線形増加
  • クリティカルセクション短縮、排他ロックの細粒度化、共有状態排除が基本方針
クエリ条件キャッシュの最適化
  • 元実装 :全操作で排他ロック、全スレッドが同時にロック待ち
  • 問題点
    • 読み込み中心なのに書き込みロック多用
    • クリティカルセクションが長い
    • 重複更新が多発
  • 最適化手法
    • ダブルチェックロッキング +アトミック操作で不要なロック取得を回避
    • 共有ロックで事前判定、必要時のみ排他ロック
  • 効果
    • CPUサイクル消費76%→1%
    • ClickBench Q10/Q11のQPSが 85%/89%向上
    • 全体幾何平均で 8.1%向上

ボトルネック2:グローバルタイマーのロック競合

  • クエリプロファイラ がタイマーIDの作成/削除でグローバルロック競合を誘発
  • 元実装 :全スレッドで共有タイマー管理、ロック必須
  • 最適化手法
    • スレッドローカルストレージ でタイマーIDを各スレッドに分離
    • ロックもシステムコールも不要に
  • 効果
    • タイマー関連のロック競合が解消
    • タイマー作成/削除コストも削減
    • プロファイリングのスケーラビリティ大幅向上

ボトルネック3:メモリ最適化

  • 超高コア環境ではメモリアロケータ自体が競合点に
  • メモリ帯域の分割、割り当てパターンの非効率化が大規模環境で顕在化
  • ClickHouseの二段階ハッシュテーブル のメモリ再利用最適化が必須
  • ページフォルトや常駐メモリ使用量の削減 が主目的

まとめ

  • ロック競合解消スレッドローカル化メモリ再利用最適化 など多面的な最適化が超高コア時代のDB性能向上の鍵
  • ClickHouse本体にマージ済み で、全世界のClickHouse導入現場で恩恵
  • 個別クエリで 最大10倍、全体で 2〜10% の性能向上を達成
  • 今後もコア数増加トレンド が続く中で、さらなるスケーラビリティ最適化の重要性

※続きや他の最適化領域の詳細が必要な場合はご指示ください。

Hackerたちの意見

これが僕のお気に入りのHN投稿タイプで、間違いなくこのジャンルのクラシックになると思う。

「超高コア数システムでのメモリ最適化は、シングルスレッドのメモリ管理とは大きく異なる。メモリアロケータ自体が競合ポイントになり、メモリ帯域幅はより多くのコアに分散される。そして、小規模システムでうまくいくアロケーションパターンが、大規模になるとパフォーマンスの問題を引き起こすことがある。どれだけメモリが割り当てられ、どのようにメモリが使用されるかを意識することが重要だ。」 バイオインフォマティクスでは、最も人気のあるアラインメントアルゴリズムの一つがランダムRAMアクセスにボトルネックされている(ゲノムのBWTにおけるFMインデックス)。だから、こういうアルゴリズムがこれらのすごいシステムでどう動くのか、いつも気になるんだよね。もう10年も大規模システムのパフォーマンスを最適化する時間を取ってないけど、NUMAはすでに十分難しかったし!この新しいチップがどれだけのメモリチャネルにアクセスできるのか気になるな。

僕の期待では、すごくパフォーマンスが良いと思う!今は主にAWSで192コアのIntel、AMD、Armインスタンスでベンチマークを取ってるけど、いくつかのワークロードではGPUフレンドリーなワークロードでも、SIMDとNUMAピンニングがうまくいけば、驚くほどGPUに近いパフォーマンスが出るよ。バイオインフォマティクスについては、Intel SPRの16コアUMAスライスをNvidia H100と比較したばかりで、すぐにそれを拡張するつもりだよ:https://github.com/ashvardanian/StringWa.rs

最も理想的な配置は、そもそもメモリサブシステムを使わなくて済むことだよ。もし二つのスレッドが、何かの仕事をするためにすごくタイトなループでお互いにやり取りする必要があるなら、ほぼ確実に単一スレッドで実行できるもっと速い技術があるはず。処理コア間で情報を物理的に移動させるのが、一番コストがかかる部分なんだ。Zenチップのメモリ帯域幅は、8~10コアくらいで共有作業セットをすごくアグレッシブに使うと完全に飽和するよ。コア間通信はインフィニティファブリックを使うとL1アクセスの50~100倍遅いんだ。この現実に合わせて問題を整理する方法を見つけるのが、この種のハードウェアを活用するための最短の道だよ。自分の問題が互換性がないと認識することも、たくさんのフラストレーションを避けるのに役立つよ。もし作業セットが巨大なモノリスで階層的でなければならないなら、256コア以上のモンスター部品を非常に効果的に使うのは難しいだろうね。

この投稿は、最初のセクションだけでも素晴らしい低レベルの最適化について書かれてるね。 (ちょっと小さいことかもしれないけど…)僕の好きなC++のコーディング規約、&(参照)が型にも変数名にも属さない使い方には心が躍るよ!

型に属してると思うけど、「auto」を使ってるから独立して見えるし、「&」演算子と混同されることもあるね。個人的には、*と&は変数名のプレフィックスとして使って、型名のサフィックスとしては使わないようにしてるよ。テンプレートで型を指定する時を除いてね。

288コアって、ありえない数だね。これってAVX512対応してるのかな?シエラフォレストのチップの中にはAVX512に2xFMAがあるみたいだけど…それってかなり広いね。これをカードにしてGPUとして売るべきかも(全く新しいアイデアで、今まで試されたことがないけどね…)。

残念ながら、そうじゃない!でも明るい面として、新しいAVX2 VNNI拡張をサポートしていて、ベクトル検索のための低精度整数ドットプロダクトに役立つよ!SimSIMD(USearch内のClickHouse内)にはすでにそのSIMDカーネルがあるけど、まだベンチマークを取るためのハードウェアがないんだ :(

AVX-512はPコアだけに対応してるよ(今はAMXもね)。Eコアは256ビットのベクターしかサポートしてない。もしたくさんのデータの読み書きをするなら、Eコアのチップの方が大きなコアを持つチップよりもパフォーマンスが良くなるかも。だって、Eコアはアイドル状態が多いからね。CPUがボトルネックになるタスクでは、Pコアが圧倒的に勝つよ。

いつになったらデスクの下に288コアが置けるんだろう?

Sierra Forest(288コアのやつ)にはAVX512がないんだ。Intelはサーバー製品ラインを二つに分けたのさ。* Pコアだけのプロセッサ(今はGranite Rapids)がAVX512を持ってる。* Eコアだけのプロセッサ(今はSierra Forest)はAVX512を持ってない。一方、AMDの高コアで面積が小さい製品、例えばZen 4c(Bergamo)はAVX512をサポートしてるから、個人的にはこっちの方が楽だと思う。

幅はかなり広いけど、288コアそれぞれに8x FP32レーンがあるとしても、RTX 5090のレーン数の約10分の1に過ぎないんだ。GPUは本当に、めちゃくちゃ広いからね。

640kのRAMは完全に異常だね。2GBのストレージもそうだし、2000年も。

まあ、確かに「たくさん」って感じだけど、長い間飢えてたからね。でも、分析集計のワークロードを実行してみると、1kや10kのコアと大量のメモリ帯域幅があれば、アドホックなクエリに役立つんじゃないかって思ったりする。あとは、異常な数のウェブサイトリクエストに対応できるかも…PCIeカードのCPUはIntel Xeon Phiと合ってる気がするし、Erlangメッシュクラスターを強化できるかもって考えたこともある。https://en.m.wikipedia.org/wiki/Xeon_Phi

288コアのSKU(6900Eだったと思う)はあんまり流通してないよね。大きなクラウド向けだけじゃない?

288コアって、マジで異常な数だよね。昔、ペンティアム級のウェブ&データベースサーバーでビジネスのプラットフォームを構築して運営してたけど、2Uラックで1「コア」だったんだ。それで48Uラックに24コアだから、288コアってことは12ラック分、つまり普通のデータセンターの通路全体くらいになる。98/99年の時点で、パロアルトインターネットエクスチェンジ(俺のサーバーが2台あったところ)には数千コアしかなかったと思う。今は、そのPAIXデータセンター全体よりも多くのコアを持ってるホムラボがあるんじゃないかな。

いい仕事だね!duckdbは好きだけど、clickhouseは大規模パフォーマンスにもっと焦点を当ててる感じがする。この記事は一人の視点から書かれてると思ったけど、実際は複数の著者がいるのはちょっと変だね。何か勘違いしてたかな?

何が起こったのかはわからないけど、投稿に一人の主要な著者がいて、複数のレビュアーやサポーターもクレジットされるのは珍しくないよ。

ClickHouseはDuckDBみたいにプロセス内で動作するし、CLIでも使えるけど、何百ものノードにスケールできるから、単に大規模に限らないんだ。小規模なケースでも素晴らしい体験を提供することが、まだまだ大事な焦点なんだよね。

ClickHouseの人たちは、すごい面白いことに取り組んでるね。

やってるよ!(しかも、今人を募集中!)

Clickhouse、めっちゃいいよね。ちょっと試してみたけど、数TBのオーダーブックの変更をそのままスナップショットとして読み込んでみたんだ。ダブル圧縮(タイプ認識と一般的なやつ)がすごい効果を発揮する。サイズが小さくてクエリも早いなんて、ちょっとした調整で実現できるのが驚き。システムのデフォルト設定はほとんど変えてないのに、数十億のスナップショットを数分で集計できるよ。

スナップショットって、特定の時点でのオーダーブック全体のことを指してるの?それとも、全履歴がインスタンス化されるってこと?

インテルの最新プロセッサ世代は、サーバーのコア数を前例のないレベルに押し上げてる - グラナイトラピッズのソケットあたり128のPコアから、シエラフォレストのソケットあたり288のEコアまで、将来的にはソケットあたり200以上のコアを目指してるみたい。今のインテルCPUは、昔のデータセンターを置き換えられるかも。誰かが楽しみで、1つのCPUで1000のRed Hat Linux 6.2を並行して動かしてみるといいかもね、2000年に戻ったみたいに。

2文字のSIMDフィルタリングでパフォーマンスがかなり改善されたよ:ClickBenchのクエリQ20は35%速くなった。他の部分文字列マッチングを行うクエリも全体で約10%改善。全クエリの幾何平均は4.1%向上した。ClickBenchデータセットは約70Gだったと思うから、SSE4.1(128ビット)だけでこんなに大幅なスピードアップを測定したのは興味深い。AVX2やAVX-512すら使ってないのにね。もし後者を使ってたら、結果はどうなってたんだろう?それに、これがCPUコアのALUとメモリサブシステムのより集中した利用の副産物かもしれないとも思ったりする。例えば、単一またはペアの命令でより多くの作業を処理することで、他の無関係な命令を退役させるためのスペースが増えるみたいな。

SIMDの文字列マッチング最適化、残念ながらちょっとしたポイントを見逃してるね。パターンの最初と最後の文字をマッチさせる方が、最初の2文字をマッチさせるよりも選択的だし、コストも同じなんだ。これについてはMułaに感謝だね。http://0x80.pl/notesen/2016-11-28-simd-strfind.html#generic-...

最初の最適化を見てると、排他ロックを取得した後にダブルチェックすることでパフォーマンスが良くなるのかなって思う。全体の前提として、キャッシュアクセスは読み取りが多いから、読み取り用に排他ロックを取得しないことで、かなり大きな問題が解消されるんだよね。異なるスレッドからの重複更新の稀なケース(更新自体もあんまり頻繁じゃないし)って、ロックを排除することに比べたら大したことじゃない気がする。これらの最適化について、ベンチマークの数字を別々に見てみたいな。