概要
- ウェブサーバの高容量化の歴史と技術進化の流れを解説
- epollやio_uringなどLinuxにおけるスケーラブルI/O手法の紹介
- kTLSやdescriptorless filesなど最新最適化技術の概要
- Rust製ウェブサーバ「tarweb」での実践例と課題
- io_uring利用時の安全性やメモリ管理の難しさに言及
世紀転換期の高容量ウェブサーバ需要とC10k問題
- 世紀転換期に 高容量ウェブサーバ の需要急増
- C10k問題論文の登場による 同時1万接続 の課題提起
- 当時の主流は プリフォーク方式 によるプロセス生成コスト削減
- 1リクエストごとにプロセス生成が一般的だった時代背景
- スレッド化、poll()/select()の導入による 軽量化とコンテキストスイッチ削減
select()/poll()の限界とepollの登場
- select()/poll()は 大量接続に非スケーラブル
- 毎ループで 巨大な配列 をカーネルに渡す必要
- Linuxの epoll (他OSではkqueue)の登場で効率化
- epollは 差分管理 でsyscallコスト削減
- メインループ例:epollで新規/読込/書込を効率管理
- ただしsyscall自体のコストが相対的に目立つ段階へ
io_uringによる非同期I/O最適化
- syscallごとにカーネルへ命令する従来方式からの脱却
- io_uring は命令をキューに書き込み、カーネルが非同期に処理
- 例:accept()をキューへ投入、完了時にキューから結果取得
- ほぼ全てのI/O操作を メモリ操作だけで完結 可能
- 忙しいサーバならsyscall不要(straceでも何も表示されない)
マルチコア時代の設計とNUMA最適化
- 現代CPUは 多コア化、理想は1コア1スレッド運用
- 各スレッドをコアにバインド、 共有リードライト構造体を回避
- NUMA構成では ローカルノードのメモリ のみ利用推奨
- リクエスト負荷の完全分散は今後の課題
メモリ割当とリスク管理
- ユーザ空間・カーネル空間の両方で メモリ割当 発生
- コネクションごとの固定チャンク割当で フラグメント防止
- カーネル側もバッファ管理が必要、socket optionで調整可能
- RAM不足回避 が安定運用の必須要件
kTLSによるカーネルTLS最適化
- kTLS はTLS暗号化/復号をカーネルにオフロード
- ハンドシェイク後は sendfile() が使え、ユーザ空間とのコピー削減
- NICのハードウェア対応時は CPU負荷の大幅削減 も可能
descriptorless filesとregister_files
- ファイルディスクリプタの ユーザ・カーネル間受け渡しコスト 削減
- register_filesによる descriptorless files 導入
- ユーザ空間で見える番号は整数値で、/proc/pid/fdには現れない
- io_uring専用で、ulimitのfd制限は適用
Rust製ウェブサーバ「tarweb」の実践
- tarweb :単一tarファイルを配信するRust製ウェブサーバ
- io_uring、 kTLS、Rustの組み合わせで最新技術を実装
- kTLS有効化にはsetsockopt()が必要、io_uring側のPRで対応
- TLSライブラリ(rustls)がハンドシェイク時にメモリ割当実施の可能性
- 1リクエストごとにsyscallゼロでHTTPS応答可能
ベンチマークと今後の課題
- 現時点で ベンチマーク未実施
- コード整備後に測定予定
io_uring利用時の安全性とメモリ管理
- io_uring はバッファの寿命管理が難しい
- 操作完了までバッファを 解放・上書き不可
- Rustのio-uring crateは 安全性保証が弱い
- Rust本来の「コンパイル通過=安全」には未到達
- pinningやborrowを活用した safer-ring crate の必要性