遅延こそが問題の正体だ

2026/11/01 3:38

遅延こそが問題の正体だ

RSS: https://news.ycombinator.com/rss

要約▶

Japanese Translation:

改良された要約

現代的な CPU は、処理速度の限界のために停止するのではなく、内部的な演算 (~4-8 指令/ns) に比べて遅い RAM アクセス (50–100ns) を過度に待っているために停止することが頻繁にあります。L1、L2、および L3 キャッシュが利用可能であっても、単純なデータ読み込みパターンの場合、CPU がこれらの高速キャッシュをミスし、冷たい DRAM にアクセスしてしまうことがよくあります。AMD EPYC 9124 プロセッサの単一コアでのテストにおいて、標準的な

GROUP BY
クエリは、その時間の 50% 以上をメモリの待ち時間で行っており、DRAM から約 4.5GB を読み込んでいました。レディックスパーティショニングを実装することで(データを約 4,000 のパーティションに分割し、アクティブな HashMap が高速キャッシュ内に完全に収まるようにする)、このボトルネックは大幅に減少します。最適化アプローチにより、停止時間は半分以上削減されました (~50% から ~23% に低下) 、合計 DRAM リード量は 4.5GB から 1.5GB に減少し、冷たいメモリアクセスを高速なローカル演算に変換しました。この戦略により、ハードウェアアップグレードなしで単一コアにおいて複雑な集計クエリを 30–40% まで高速に実行することが可能になります;16 コアすべてにスケーリングされた場合、システムは標準的な Parquet 処理を超えるスーパースピードを実現できます。

本文

CPU 高負荷時の最適化:キャッシュ効率とラジックスパージテーションによるアプローチ

はじめに:CPU 100% の真の意味

データベースで CPU 使用率が 100% を示しても、必ずしも全負荷で処理を行っているわけではありません。現代の CPU は複雑な機構を持ち、命令を同時並行実行することもあればアイドル状態になることもあります。

なぜ高負荷でも遅いのか?ボトルネックは「メモリー」

CPU の動作速度とメモリーアクセス速度には巨大な差があります。CPU がメモリー待ちをしている時間は「待機」ではなく、「潜在的な処理能力が未活用されている状態」です。

メモリー階層動作時間 (ns)キャパシティ1 アクセスあたりの指令数
L1 Cache~1 ns~64 KB4~8 指令
L2 Cache~3–4 ns~1 MB15–30 指令
L3 Cache~10–15 ns数百 MB 程度50–100 指令
DRAM~50–100 nsGBs 規模400–800 指令
  • CPU は約 1 ナノ秒で数命令を処理できます。
  • DRAM アクセスには 50〜100 ナノ秒かかり、CPU は数百もの命令完了できる時間を無駄にします。

問題:素朴なハッシュテーブルの非効率性

データベースでの

GROUP BY
演算に単純なハッシュテーブル(HashMap)を使う方法は一般的ですが、キャッシュ効率が極めて悪化するリスクを孕んでいます。

非効率な実装による挙動

  1. ハッシュテーブルを動的にリサイズします。
  2. レコードごとにキーチェックと更新を行います。
  3. ユニークグループが数百万ある場合、ハッシュテーブルは メインメモリー(RAM)全体 に膨張します。
  4. CPU は常時 メモリーアクセス待ち(Cold Miss) の状態になります。

デモンストレーション:素朴な実装の結果

  • 環境: CherryServers (AMD EPYC 9124 Genoa, 単一コア利用)
  • クエリ:
    SELECT count(*), UserID FROM hits GROUP BY UserID
    (約 9,900 万行)
  • データ特性: 約 1,700 万のユニーク UserID が存在するため、ハッシュテーブルが巨大化します。

テスト実行コマンド

# 単一コアでの実行設定
taskset -c 3 pivot server --config config.yaml &
# config.yaml (例):
# workers: 1
# auth: trust

# クエリの実行と計測
taskset -c 7 psql -h 127.0.0.1 -p 5440 -U pivot -c '\timing on' \
  -c "SELECT count(*) FROM (SELECT COUNT(*), UserID FROM hits GROUP BY UserID)"

測定結果(素朴実装)

  • 実行時間: 初回約 3.9 秒、2 回目約 3.6 秒
  • DRAM リード量: 約 4.5 GB

CPU 内部状態 (
perf stat
)

Performance counter stats for 'CPU(s) 3':
        8364109137      ex_no_retire.load_not_complete   #     50.1 %  backend_bound_memory
       38233214040      de_no_dispatch_per_slot.backend_stalls
        8977647849      ex_no_retire.not_complete
       11431618219      ex_ret_ops                       #     16.1 %  retiring
       
       4.030148989 seconds time elapsed

分析:

  • メモリー待ち: CPU の約 50.1% を占める(アイドル状態に近い)。
  • 実際の指令実行: 処理時間のうち僅か 16.1% に過ぎない。

ソリューション:ラジックスパージテーションによる最適化

CPU キャッシュ効果を最大化するには、データを小さなチャンクに分割して処理する必要があります。そのための技術が「ラジックスパージテーション」です。

アルゴリズムの仕組み

グループ分割を 2 つのフェーズに分けて実行します。

フェーズ 1:パーティション分割

全てのキーを 4,096 つ のパーティションに分割します。ハッシュ値の上から 12 ビット($2^{12} = 4096$)を使用してインデックスを決めます。

fn consume(&mut self, batch: RecordBatch, _sender: &mut dyn Sender<RecordBatch>) -> unary::Result<()> {
    let keys = batch.column(self.key_column).as_primitive::<Int64Type>();
    for &key in keys.values() {
        let hash = self.hasher.hash_one(key);
        // ハッシュ値の上位 12 ビットを使用 (右シフト 52 ビット)
        let partition = (hash >> 52) as usize;
        self.partitions[partition].push(&mut self.allocator, (hash, key));
    }
    Ok(())
}
  • 1 つの巨大なテーブルではなく、4,096 つの小さな配列(SlabVec)にデータが分割されます。

フェーズ 2:パーティションごとの集計

全てのデータ到着後、各パーティションごとに小型のハッシュテーブルを作成して集計を行います。

fn into_outputter(self) -> unary::Result<Option<Self::Outputter>> {
    let mut groups = Vec::new();
    for rows in &self.partitions {
        // 各パーティション用に小さな HashMap を作成
        let mut table: HashTable<(u64, i64, u64)> = HashTable::new(); 
        rows.for_each(|(hash, key)| {
            match table.entry(hash, |group| group.1 == key, |group| group.0) {
                Entry::Occupied(mut group) => group.get_mut().2 += 1,
                Entry::Vacant(slot) => { slot.insert((hash, key, 1)); }
            }
        });
        groups.extend(table.into_iter().map(|(_, key, count)| (key, count)));
    }
    Ok(Some(Outputter::new(groups)))
}

なぜ有効なのか?

  • キャッシュヒット率の向上: ハッシュテーブルサイズが小さくなり、L1/L2 キャッシュに収まりやすくなります。
  • メモリー待ちの削減: CPU が小さなデータセットをキャッシュ内で処理するため、DRAM アクセス頻度が劇的に減少します。

改善結果の確認

最適化後の実行結果と比較です。

テスト実行コマンド(最適化後)

taskset -c 7 psql -h 127.0.0.1 -p 5440 -U pivot \
  -c "SELECT count(*) FROM (SELECT COUNT(*), UserID FROM hits GROUP BY UserID)"

測定結果(最適化後)

  • 初回: 3.3 秒
  • 2 回目(キャッシュヒット): 2.7 秒

CPU 内部状態の改善 (
perf stat
)

Performance counter stats for 'CPU(s) 3':
        3790955223      ex_no_retire.load_not_complete   #     23.5 %  backend_bound_memory
       20376118172      de_no_dispatch_per_slot.backend_stalls
        5442424751      ex_no_retire.not_complete
       19350459182      ex_ret_ops                       #     32.2 %  retiring
       
       3.575992317 seconds time elapsed

比較分析:

  • メモリー待ち: 50.1% から 23.5% に低下(半分以下)。
  • 指令実行率: 16.1% から 32.2% に向上(約 2 倍)。
  • DRAM リード量: 4.5 GB から 約 1.5 GB に削減。

結論

CPU が 100% を使用している場合でも、メモリーアクセス待ちがボトルネックになっているケースは多いです。素朴な実装では CPU キャッシュを活用せず、高速 RAM への依存度が高まります。

ラジックスパージテーションなどのテクニックでデータをキャッシュ収容サイズに分割することで:

  1. DRAM アクセスを大幅削減できます。
  2. CPU の真の処理能力を引き出せます。
  3. 単一コアでも約 2.7 秒 という高性能を実現可能です。

Pivot ではこの技術をさらに進化させ、16 コアの並列実行でも同様の効率を実現しています。Parquet ファイル以上の高速さを求める場合は、ぜひ試してみてください。

同じ日のほかのニュース

一覧に戻る →

2026/11/01 4:56

HackerTube:HN で推奨される動画から構築された YouTube

## Japanese Translation: 最も重要な教訓は、hackertube.org の公開であり、これは「enshittified」化した標準的な YouTube の代替手段です。低品質なコンテンツや誇張されたサムネイルから脱却するために設計されています。AI エージェント(建設には Opus 5.5、フィルタリングには Sonnet を使用)を活用してわずか 2 時間で構築されたこのプロジェクトは、2007 年から 2026 年までの Hacker News のコメントをダウンロードし、信頼できるデータセットにフィルタリングしました。40,000 の歴史的コメントから 150 トピックにわたる 6,454 つの高品質なビデオが生み出されました。プラットフォームはデフォルトのサムネイル(全体の 50% がデフォルト)、アカウント登録やサーバーサイドのトラッキングを必要とせずブラウザ上に保存されるパーソナライズされたローカル推奨機能、そして便利なキーボードショートカットを提供しています。ユーザーはオープンデータセットをエクスポートして独自のバージョンを作成するか、エージェントを使用してさらにパーソナライズすることもでき、コミュニティが独自の信頼性のある推薦エンジン構築を可能にし、アルゴリズムによる摩擦を回避できるようになっています。

2026/11/01 4:16

TCP のUse-After-Free:CVE-2026-98432/CVSS 9.8

## 日本語翻訳: ## まとめ: 認証不要なリモートコード実行を許可する重大なセキュリティ脆弱性、CVE-2026-98432 が発生しており、Linux システムにおける Multipath TCP リッスナーを利用した攻撃が可能です。これは、Go 1.24 以降を実行している環境において特に深刻であり、デフォルトで危険なネットワーク構成を有効にできるようになっています。根本原因は不安定なメモリアクセス管理にあります:RCU(Read-Copy-Update)同期が完了する前に `kfree()` を使用して特定のデータを解放しており、安全な `kfree_rcu()` の代わりにこれを用いることで、ユーザー認証なしでシステムメモリへの操作が可能になっています。

2026/11/01 8:08

電気自動車が女性向けだった時代

## Japanese Translation: 直近の世紀の転換期に、米国自動車業界は急速な拡大を遂げ、メーカー数は 1898 年の約 50 社から 1910 年には数百社に達し、18 万 1 千台の車両を生産しました。1911 年以前、電気自動車は静かに運転でき排気ガスが出ず、バッテリースイッチで即座に始動するため、騒々しい手動クランクが不要だったという点で、ガソリン車に対して明確な優位性を誇っていました。しかし、これらのユーザーフレンドリーな機能にもかかわらず、普及には重大な技術的障壁があり、主としてバッテリーの航続距離の限界と、漏れに苦しむ不安定な鉛蓄電池によるものでした。所有者は充電に 12 時間かかるという大きな不便さに直面し、その際には水銀アーク整流器(ピーター・クーパー・ヒュイットが発明、ペルシー・H.トマスに付与された特許番号 968,896 および 1,163,706)のような巨大な設備を使用したり、車両のプラグイン方法が標準化されていなかったため予備のアダプターを携行する必要があったりと、不便さが伴いました。ゼネラル・エレクトリックはさらに、電気自動車を女性や夜間の自宅訪問をする医師にとって理想的な乗り物としてマーケティングしていました。具体的な例としては、ファニー・グッドナウが Gould Storage Battery Co. から入手した 30 セルの鉛蓄電池に依存し、専用のもので 12 時間の充電が必要となる水銀アーク整流器を使用して、米ドル 2,000(現在の価値で約 7 万 3 千ドル)で 64〜80 キロメートルの航続距離を得ていたことなどが挙げられます。この技術的な脆弱性が大量生産を防ぎ、価格を莫高にし、所有を少数の富裕層に限定していました。さらに、トーマス・エディソンは 1903 年にアルカリ電池を導入しましたが、当初は漏れを起こし、1910 年までに改善されました。エディソン自身も、自社ブランドのバッテリーモデルよりもデトロイト・エレクトリック・ビクトリアを好んでいました。ヘンリー・フォードの有効なモデル T の参入と、業界標準化されたコンポーネントの欠如により、S.R. Bailey & Co. といった初期の製造業者は 1915 年までに事業廃止に追い込まれました(約 500〜600 台の電気車両を生産)。特筆すべきは、ファニー・グッドナウの充電ステーションが商業市場が消滅した後も存続し、2022 年に遺産博物館に寄贈されるまで残ったことです。教訓は明らかです:バッテリーの耐久性を解決し、統一された充電インフラストラクチャを構築しない限り、安価でスケール可能な競争相手に対し、革新ある産業は生き延びることはできません。