kevy4.0

--accept-shards N — スパースなコネクションをシャードの部分集合に畳む

--accept-shards Nは、リスナーのbindとacceptのarmをシャード0..Nだけに限定するようkevyに指示します。残りのシャードはcompute-onlyのワーカーとして残り、クロスシャードの読み書きは引き続き処理します。

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

kevyの各シャードは、busy-pollの本体を持つ独自のリアクターを回しています。この本体がイテレーションごとのオーバーヘッド(wakerのdrain、inboxのチェック、acceptのarm)を償却できるのは、各イテレーションが十分な数の生産的イベントを見るときだけです。concurrent_conns / threadsがおおよそ1を下回ると、どのシャードもイテレーションの大半を遊休状態で過ごすようになり、集計スループットは同じマシン上で1〜2スレッドのサーバーが出す値を下回ります。

--accept-shards Nに手を伸ばすのは、次のような場合です。

コネクション数が多い(平均してシャードあたり1コネクション以上)、予測できない、あるいはthundering herd型のクライアントプールに駆動されている場合は、設定しないでください。そこでは「全シャードがacceptする」というデフォルトが正解です。

中心となる考え方

accept集合は起動時に静的に宣言されます。集合の内側のシャードはリスナーfdをbindし、acceptのSQEをarmします。外側のシャードはそのどちらもしないため、カーネルのSO_REUSEPORTグループがSYNをそちらへルーティングすることはありません。accept集合の外にいるシャードも、自分のキー空間スライスは所有し続け、クロスシャードのinboxもdrainし続けます。したがって、そのスライスにハッシュされる書き込みはそこへディスパッチされて実行され、応答は所有コネクションのシャードを通って戻ります。ステートレスシャードのモデルは無傷です——どのシャードもまったく同じコードパスを走り、ただ一部がたまたまコネクションを1本も持たない、というだけです。

                      clients (SO_REUSEPORT group)
                                 |
                  +--------------+--------------+
                  |              |              |
              shard 0        shard 1        shard 2          <-- accept-set (--accept-shards 3)
              listen + accept + own conns + own keyspace slice
                  |              |              |
                  +------ cross-shard dispatch -------+
                  |       |       |       |       |       |
              shard 3  shard 4  shard 5  shard 6  shard 7  shard 8  shard 9   <-- compute-only
              no listener, no accept; execute dispatched cmds, own keyspace slice

動かしてみる例

10スレッド、accept 3シャード、標準のRedisポートでlistenする場合。

kevy --threads 10 --accept-shards 3 --port 6379

環境変数での同等指定。

KEVY_THREADS=10 KEVY_ACCEPT_SHARDS=3 kevy --port 6379

TOML(kevy.toml)での同等指定。

[server]
threads = 10
accept_shards = 3
port = 6379

優先順位はCLI > env > TOML > デフォルトです。accept_shards1 <= accept_shards <= threadsを満たす必要があり、この範囲を外れると起動時に終了コード2でfail fastします。デフォルトは未設定で、その場合は全シャードがacceptし、バイナリはこのフラグのないビルドとバイト単位で同一に振る舞います。

サイジングの経験則

目安はaccept_shards ≈ ceil(conns / 20)です。経験的なスイートスポットは、各acceptシャードがおおよそ15〜25コネクションを持つプラトーです——busy-pollの本体が償却できる程度には密で、クロスシャードのディスパッチがinboxチャネルを飽和させない程度には疎、という帯域です。

同時コネクション数threads推奨--accept-shardsacceptシャードあたりのconns
1010110
2010120
50102または325または17
10010520
2001010(デフォルト、未設定)20
50162または325または17
100165または620または17
5001616(デフォルト、未設定)31

代表的な--threads 10 -c 50 -d 65536 SETワークロードでは、--accept-shards 3が50コネクションを約5/シャードから約17/シャードへ畳むことで、デフォルト比+10.6%のスループットを引き出します。同じプラトーは--accept-shards 2(25 conns/シャード)でも成立します。

トレードオフ

観点accept集合のシャードcompute-onlyのシャード
busy-pollの償却高い(自前のコネクションのfan-inがイテレーションごとの仕事を生む)クロスシャードのinboxレートに依存
コネクションごとの読み書きコストローカル、ホップなし該当なし(所有コネクションがない)
クロスシャードのディスパッチコストホップごとにチャネル送信1回チャネル受信1回 + 実行 + 応答送信1回
キー空間の所有自分のスライスを所有自分のスライスを所有
遊休時のCPU下限acceptがarmされ、io_uring_enterでブロックするURING_SPIN_LIMITまでスピンしてからブロックする
テール遅延への影響acceptのSQEがホットループに仕事を足すホットループはよりきれい。クロスシャードホップがチャネル1回分を足す

具体的には、Nを小さく選ぶほど各コネクションはホットなacceptシャードの恩恵を受けますが、クロスシャードホップを踏む書き込みの割合は高くなります(全キーのうち(threads - N) / threads)。conns/シャード ≈ 15〜25のプラトーは、busy-pollの利得が追加のホップ代を払える場所です。それを大きく下回るとディスパッチのコストが支配し、大きく上回れば未設定のデフォルトに戻っただけになります。

FAQ

--accept-shardsは常に設定すべきですか? いいえ。平均してconcurrent_conns / threads >= 1なら、デフォルト(全シャードがaccept)がすでに最適です——busy-pollの本体は助けなしに償却できます。--accept-shardsは、シャードを増やすとスループットが落ちるスパースコネクション領域のためのものです。

自分のワークロードでNをどう選べばいいですか? 目安のceil(conns / 20)から始めてください。perfgateで{ceil(conns/25), ceil(conns/20), ceil(conns/15)}にわたってNをスイープし、目標のテール遅延予算のもとでスループットが最大になるものを選びます。15〜25 conns/シャードのプラトー上ならどこでも、本番設定として安全です。

レプリケーション、AOF、永続化を壊しませんか? 壊しません。accept集合の外のシャードも、accept集合のシャードとまったく同じリアクターループ、同じTTLリーパー、同じAOFライター、同じレプリケーションのtickを走らせます。省くのはリスナーのbindとacceptのarmだけです。レプリケーションのフォロワーも、スナップショット/AOFからのリストアも、Nの値に関係なく同一に振る舞います。

accept集合の外のシャードは、どうやって書き込みを処理し続けるのですか? 自分のキー空間スライスを所有しているからです。accept集合のシャード上のクライアントコネクションが、compute-onlyシャードにハッシュされるキーへの書き込みを発行すると、accept集合のシャードは所有シャードのinboxへInbound::RequestBatchをpushします。所有シャードは自分のキー空間スライスに対してコマンドを実行し、応答は発信元シャードの応答チャネルを通って戻ります。クライアントから見ればホップは不可視です——1本のコネクション上に、1つの応答が順序どおり見えるだけです。

数を間違えたらどうなりますか? Nが小さすぎるコストは、飽和したクロスシャードinboxとテール遅延の悪化です。Nが大きすぎるコストは、元のスパースコネクションの逆転現象です。どちらも新しい値で再起動すれば回復できます——この選択に紐づくオンディスク状態はありません。