kevy4.0

kevyのチューニング

kevyのopあたりのコストを左右するランタイムノブのリファレンスです。CPUレイアウト、リアクターの選択、永続化、メモリ上限、ネットワークトランスポート、そしてLinux側のいくつかのレバーを扱います。

このドキュメントが必要になるとき

次のような場面で参照してください:

ノートPCでkevyを立ち上げて数字に問題がないなら、このページは不要です。デフォルトはどのワークロードでもそこそこ良い性能が出るよう調整されています。

中心となる考え方

kevyはスレッド・パー・コアのサーバーです。OSスレッドごとに1シャード、CRC16ハッシュタグで分割したシェアードナッシングのキー空間、各シャードにbusy-pollリアクター、という構成です。デフォルトは「どのワークロードでもまずまず」を狙っています。チューニングとは、シャード数・リアクター・永続化ポリシーを、実際のパフォーマンスデータがボトルネックだと示しているものに合わせる作業です。先回りしてノブを切り替えないでください。計測し、コストを特定し、変数を1つずつ変えてください。

チューニングプレイブック

CPUとシャード

ノブどこデフォルト効果
--threads N / KEVY_THREADSCLI / envオンラインコア数シャード数。OSスレッドごとに1シャード
--accept-shards KCLI全シャードがaccept最初のKシャードだけがリスナーをbindし、残りはcompute-only
CPU pinningtaskset / numactlなしシャードを固定のコア集合にロック

--threadsの選び方。 ワークロードに実際に存在する並列性に合わせてください。シングルクライアントのパイプラインベンチ(-c 1 -P 16)は1シャードを飽和させます。ここで--threads 10にすると、9つのシャードが仕事のないbusy-pollを回し、シャード0のキャッシュラインを奪うだけです。本物のマルチクライアントなら、min(cores, 想定同時クライアント数 / 4)から始めて計測してください。

--accept-shardsの選び方。 コネクション対シャード比が低い場合(スパースコネクションのワークロード。たとえば50クライアントを10シャードで受けると5 conns/shard)、イテレーションごとのbusy-pollオーバーヘッドが償却できなくなり、スループットが落ちます。経験則はceil(conns / 20)です。50 connsなら--accept-shards 3とし、3つのlistenシャードがそれぞれ約17接続を受け持ちます。残りのシャードはcompute-onlyですが、内部ディスパッチャ経由でクロスシャードの仕事は受け続けます。経験的なスイートスポットは点推定より広めです。フルスイープの結果と、クロスシャードホップの税がaccept集中の利得を上回るケースの議論はdocs/accept-shards.mdを参照してください。

CPU pinning。 ベンチ用や単一テナントのホストでは、kevyを固定コア集合にpinすると、NIC IRQ→softirq→ユーザースレッドのパスを同じL1/L2に保てます:

taskset -c 0-9 kevy --port 6004 --threads 10

同じマシン上でクライアントも動かすなら、サーバーとクライアントを互いに素なコアレンジにpinします(サーバー0-9、クライアント10-15)。コアを共有すると、リアクターの利得を吹き飛ばすスケジューラのping-pongが復活します。

リアクターの選択

プラットフォームデフォルト上書き
Linux ≥ 5.19io_uring(自動検出)KEVY_IO_URING=0でepollを強制。KEVY_IO_URING=1でio_uringを必須にし、seccompでio_uring_setupがブロックされていれば大きな音を立てて終了
macOS / *BSDkqueue設定不可
旧Linuxepolln/a

Linuxの自動検出は起動時にio_uring_setupを試します。syscallがブロックされている場合(seccompプロファイル、ロックダウンされたコンテナ)、kevyは黙ってepollにフォールバックします。黙って劣化するのではなく大きな音を立てて失敗してほしい硬化デプロイでは、KEVY_IO_URING=1を設定してください。io_uringが本当に使えない限りサーバーは起動を拒否します。逆に、再現可能なepoll対io_uringベンチのため、あるいはカーネルリグレッションの回避のためにio_uringを外したい場合は、KEVY_IO_URING=0を設定します。

KEVY_IO_URING=1 kevy --port 6004   # io_uring 必須、ブロックされていれば終了
KEVY_IO_URING=0 kevy --port 6004   # epoll 強制

永続化

AOFポリシーはappendfsync(configファイルまたはCONFIG SET)で制御します。3つの値はRedisのセマンティクスと一致します:

appendfsync耐久性コスト
always各書き込みを応答前にfsync遅延は最大。NVMeのsync遅延で律速
everysec(デフォルト)バックグラウンドスレッドで毎秒fsyncデータロス窓は最大1秒。ホットパスのコストはほぼゼロ
nofsyncしない。カーネルが自分のスケジュールでflush最速。データロス窓はページキャッシュのflush間隔

everysecのバックグラウンドfsyncは、シャードのホットパスから外れた専用bioスレッドで走るため、シャードのテール遅延がディスク遅延と結合することはありません。純粋なキャッシュや読み取りレプリカでは、--no-aofでAOFを完全に無効化する(AOFファイルは一切書かれず、バッファにも積まれない)選択肢もあります。

メモリ

ノブデフォルト何をするか
maxmemory無制限バイト単位のハードなメモリ上限。達するとエビクションポリシーが動く
maxmemory-policynoeviction上限に達したときどのキーを落とすか
maxmemory-samples5近似LRU/LFUポリシーのサンプルサイズ

エビクションポリシーはRedisをミラーしています:noevictionallkeys-lruallkeys-lfuallkeys-randomvolatile-lruvolatile-lfuvolatile-randomvolatile-ttlnoevictionは上限到達後の書き込みをOOMで失敗させるもので、プライマリストアの安全なデフォルトです。allkeys-*系は、どのキーも使い捨てにできるキャッシュ層での正解です。

maxmemory-samplesは近似ポリシーの品質とコストを調整するダイヤルです。サンプルするキーを増やすほど真のLRU/LFUに近づきますが、エビクションごとのCPUコストが増えます。デフォルトの5はほとんどのキャッシュワークロードで十分です。アクセスパターン上、エビクションが悪い犠牲者を選んでいるのが見えるなら10に上げ、エビクション自体がプロファイルに現れるときに限って3に下げてください。

ネットワーク

デフォルトのトランスポートはTCPです。クライアントが同一ホストにいるなら、Unixドメインソケットに切り替えてループバックTCPスタックを丸ごとスキップできます:

KEVY_UNIX_SOCKET=/tmp/kevy.sock kevy --port 6004
redis-cli -s /tmp/kevy.sock SET foo bar

サーバーはデュアルバインドします。TCPはリモートクライアント用に上がったままで、UDSがローカルを受け持ちます。RESPセマンティクスも、シャードランタイムも同じです。ローカルクライアントのワークロードでは利得が大きくなります(小さいペイロードサイズではループバックTCPパスが支配的なコストになるためです)。詳細な数値、権限モデル、UDSが向かないケースはdocs/uds.mdを参照してください。

bindアドレスの警告。 現時点のkevyにはAUTHもTLSもありません。非ループバックアドレス(--bind 0.0.0.0や公開インタフェース)にbindすると起動時に警告が出ます。ネットワーク上の誰でもコマンドを発行できてしまうためです。kevyはプライベートネットワーク境界の内側か、認証を終端するプロキシの背後で動かしてください。

コネクションのイントロスペクション。 INFO clientsは全シャードを合算したライブのconnected_clientsゲージを報告します。CLIENT LISTCLIENT INFOは、実在するクライアント接続ごとにRedis 7.x形の行を1つ描画します——ピアアドレス、グローバルに一意なidname、購読数、MULTIキュー深さ、入出力バッファサイズ(cmd=NULL:最後のコマンド名は追跡しません)。CLIENT SETNAMEはLIST用に接続へラベルを付けます。CLIENT KILL ID <id> | ADDR <ip:port> | LADDR <ip:port>(またはレガシーな位置引数形CLIENT KILL <ip:port>)は、ブロッキングコマンドで待機中のものも含め、一致するすべての接続を閉じます。teardownは犠牲となる接続の保留出力がはけるのを待つので、自分自身をkillした接続も自分のリプライは受け取れます。

レプリケーションと可用性

プライマリ/レプリカのトポロジで動かすときだけ関係します(docs/replication.mddocs/availability.md)。

ポートレイアウト。 各ノードは3つのプレーンを使い、デフォルトではすべてクライアントポートから導出されます:

プレーンポート備考
クライアントRESPport(例:6004)クライアントとpeersのclient-portが指すもの
レプリケーションlisten_port_base + shard_i。デフォルトのbase = port + 10000nshards個の連続ポート。レプリカもこのレンジをbindする(v3.15)
選挙elect_port_base。デフォルト = port + 200ノードごとに1本のコントロールプレーンリスナー

1台のマシンに複数インスタンスを同居させる場合は、クライアントポートを最低nshards離してください。そうしないとデフォルトのレプリケーションレンジが衝突します。FAILOVERと自動再ターゲットはport + 10000の慣習を前提にしているため、フェイルオーバーを使うデプロイではlisten_port_baseをデフォルトのままにしてください。

整合性ノブ。 2つの[replication]キーで、可用性とより強い保証を交換できます(ラダーの全体像はdocs/availability.md):

ノブデフォルト何をするか
replica_max_staleness_ms0(オフ)最後のプライマリハートビートが境界より古いレプリカは、読み出しを-STALEで拒否する。ハートビートは1Hzでストリームに乗るため、約2秒未満の境界は健全なリンクでも発火する
min_replicas_to_write0(オフ)健全なレプリカがN台未満のとき、プライマリは書き込みを-NOREPLICASで拒否する

呼び出し単位のバリアなら、常設のconfigの代わりに、ブロックする呼び出し1回分のコストで済みます。プライマリではWAIT n timeout、レプリカでのread-your-writesにはREPL.TOKEN + REPL.WAITです。どちらもtimeout 0を「永遠に待つ」と解釈し、60秒でハードキャップされます。

Linuxカーネルノブ

kevyの下にあるカーネル側のフロアを動かすホストレベルのレバーが2つあります。どちらもベンチ用・単一テナント専用です。適用する前にトレードオフを読んでください。

Spectre / BHBミティゲーション。 ミティゲーションが有効(デフォルト)のLinux 6.x系カーネルでは、すべてのsyscallがclear_bhb_loopなどの代金を払います。小ペイロードの-c 1ワークロードでは、これがkevyの実行中の単独最大のCPU消費です。カーネルコマンドラインで無効化します:

# GRUB_CMDLINE_LINUX_DEFAULT に `mitigations=off` を追加、その後:
sudo update-grub && sudo reboot
cat /proc/cmdline | grep mitigations

これが許されるのは、untrustedなコードが一切走らない単一テナントマシンだけです(ワイヤから来るLuaなし、サードパーティプラグインなし、マルチテナントコンテナなし)。マルチテナントホスト、共有CIランナー、untrustedなユーザーコードを処理するものには適用しないでください。利得は-c 1で+10〜15%程度で、ワークロードがパイプライニングするほど縮みます。

.textセグメントのヒュージページ。 kevyは自分のコードセグメントにmadvise(MADV_HUGEPAGE)を呼べます。これによりカーネルはkevyバイナリの命令を4KiBではなく2MiBページで裏付けます。利得は、ホットなdispatchループのiTLBフットプリントが小さくなることです。ランタイムコストは実質ゼロなので、/sys/kernel/mm/transparent_hugepage/enabledalwaysmadviseのLinuxホストでは有効にする価値があります。トレードオフは起動時のmadvise呼び出し1回分の小さなコストだけで、mitigations=offと違ってセキュリティ上の代償はありません。

プロファイリング

実際のシンボルに解決されるperf recordのフレームグラフを取るには、release-perfプロファイルでビルドしてください。最適化レベルはreleaseと同じで、デバッグ行テーブルだけが残ります:

cargo build --profile release-perf
./target/release-perf/kevy --port 6004 --threads 1 &
KEVY_PID=$!

perf record -F 999 -p $KEVY_PID -g --call-graph=fp -- sleep 30
perf report --stdio | head -60

# インライン展開シンボルの生アドレスを解決:
addr2line -e ./target/release-perf/kevy -f -i 0x<addr>

標準のreleaseプロファイルは行テーブルをstripするため、perfはシンボルのない生アドレスを返し、addr2line??を返します。releaseバイナリをプロファイルしないでください。まずrelease-perfでリビルドしてください。

clear_bhb_loopなどカーネル側のコストをシンボル単位で帰属させるには、fpの代わりに--call-graph=dwarfでキャプチャし、同じaddr2lineのフローを使ってください。dwarfのアンワインダは遅いですが、syscall境界を越えて正しく展開します。

トレードオフ

ノブコスト買えるもの
--threads N(上げる)Nがワークロードの並列性を超えると、遊休busy-pollシャードにCPUを浪費同時クライアント容量の増加
--threads N(下げる)クロスシャードホップの税を1シャード分回避スパースコネクションでのCPU浪費の削減
--accept-shards Kリスナーの集中。クライアントが生のconnectをするならエントリポイントが減るacceptする各シャード上で、イテレーションごとのオーバーヘッドがより多くのコネクションに償却される
KEVY_IO_URING=1(強制)seccompがio_uringをブロックしているとサーバーが起動拒否硬化ホストでepollへ黙って劣化しない
KEVY_IO_URING=0(epoll強制)io_uringのopあたりの節約を諦める再現可能なepollベースライン。カーネルリグレッションの回避
appendfsync always全書き込みがfsyncでブロックデータロスゼロの耐久性
appendfsync noデータロス窓 = ページキャッシュのflush間隔最速の書き込みパス
--no-aof永続化なしディスクI/Oの最小化。レプリカ/キャッシュ用途
maxmemory設定書き込みが失敗(noeviction)またはエビクト(allkeys-*)し得るメモリフットプリントの有界化
maxmemory-samplesを上げるエビクションごとのCPUコスト近似LRU/LFUの犠牲者選択が改善
Unixドメインソケットローカル限定。ファイルシステム権限のセキュリティモデルTCPループバックスタックをスキップ
replica_max_staleness_ms設定遅れているレプリカでの読み出しは追いつくまで失敗(-STALE読み出しステイルネスの有界化
min_replicas_to_write設定書き込み可用性がレプリカの健全性と結合(-NOREPLICAS虚空への書き込みをしない
mitigations=offSpectre / Meltdown / MDSなどのミティゲーションが全オフsyscallパスの税を取り戻す
.textへのMADV_HUGEPAGE意味のあるコストなしdispatchループのiTLBフットプリント縮小
release-perfビルドバイナリが大きくなる(デバッグ行テーブル)perfがシンボル解決できる

FAQ

--accept-shardsは常に設定すべきですか?

いいえ。このノブは、conns/shardsが低くbusy-pollの本体が償却できないスパースコネクション向けです。デンスコネクション(たとえば1000クライアント×10シャード = 100 conns/shard)では、デフォルトの「全シャードがacceptする」が正解です。リスナーを均等に広げたほうがaccept側の競合が減るからです。ceil(conns / 20)は、実際にスパースコネクションのケースになっているときだけ適用してください。

io_uringは常にepollより速いですか?

Linux ≥ 5.19で投入(submission)をバッチできるワークロードなら、はい、目に見えて速いです。古いカーネル、io_uring_setupをブロックするseccompフィルタ、バッチ機会がなくopごとに1 syscallに支配されるワークロードでは差が縮みます。自動検出が正しいデフォルトです。上書きするのは、実測に基づく理由があるか、黙ってフォールバックせず大きな音を立てて失敗すべき硬化デプロイの場合だけにしてください。

appendfsyncの本番でのスイートスポットは?

ほぼ全員にとってeverysecです。データロスを1秒に有界化し、fsyncをホットパスから外し、テール遅延への影響はほぼゼロです。alwaysを使うのは、耐久性の要件が本当にゼロデータロスを求めるときだけです(その場合、NVMeのfsync遅延がテール遅延を律速するのは受け入れることになります)。noは、AOFがウォームリスタートの高速化のためだけに存在する純粋キャッシュに限ってください。

MADV_HUGEPAGEはいつ必要ですか?

perfがホットなdispatchループ上のiTLBミスを示すとき、あるいはホストの/sys/kernel/mm/transparent_hugepage/enabledmadviseのとき(この場合、ほかにkevyをオプトインさせるものがありません)です。THPが有効なLinuxホストではコストのかからないノブなので、デフォルトの姿勢は「オンのままにしておく」です。macOS / BSDに等価物はありません。

perfレポートが生アドレスばかりです。何を間違えましたか?

cargo build --releaseのバイナリをプロファイルしています。標準のreleaseプロファイルはデバッグ行テーブルをstripするため、perfにもaddr2lineにも解決の手がかりがありません。cargo build --profile release-perfでリビルドして録り直してください。