kevy4.0

Unixドメインソケット(UDS)トランスポート

kevyはオプションのUnixドメインstreamリスナーを公開しています。TCPポートとまったく同じRESPセマンティクスを話すので、同一ホストのクライアントはループバックスタックを丸ごとスキップできます。

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

クライアントとサーバーが同じホストに同居しているなら、UDSが適切なトランスポートです:

ホストをまたぐクライアントには引き続きTCPが必要です。UDSのスコープはファイルシステムであり、カーネルの外には出ません。

中心となる考え方

KEVY_UNIX_SOCKETにファイルシステムのパスを設定すると、kevyはデュアルバインドします。TCPリスナーはこれまでどおり生き続け、UDSリスナーが同じシャードランタイム上で同じRESP2/3パーサーを使ってacceptします。unix://のURLか-s <path>フラグを受け取るRESPクライアントなら、設定1行で切り替えられます。UDSはループバックのrep_movsnft_do_chain、TCPのsyscallパスを丸ごと省くため、どのワークロードでもopあたりのフロアが目に見えて下がります。

動かしてみる例

両方のトランスポートを有効にしてkevyを起動します:

KEVY_UNIX_SOCKET=/tmp/kevy.sock kevy --port 6379

redis-cliでUDS経由で接続します:

redis-cli -s /tmp/kevy.sock SET foo bar
# OK
redis-cli -s /tmp/kevy.sock GET foo
# "bar"

TCPの:6379も並行して生きています。データもシャードも同じです:

redis-cli -p 6379 GET foo
# "bar"

リポジトリ内のRustクライアント(kevy-clientkevy-client-async)が話すのはtcp://kevy://redis://と、プロセス内のmem://file:///スキームです——unix://のURLは受け付けません。Rustからは、同一ホストのクライアントはTCPループバックで接続するか、同じ*プロセス*に住んでいるなら組み込みバックエンド(file:///mem://)でソケットを丸ごと省きます。こちらはUDSがなり得るどんな速さよりも速いのです。UDSは、他言語やエコシステムドライバの、プロセス外・同一ホストのクライアントのためのものです。

パーミッションとセキュリティ

UDSの信頼境界はファイルシステムです。UnixソケットにRESPレベルのAUTHやTLSはありません。ソケットファイルをopen(2)できる者は誰でも、FLUSHALLを含む任意のコマンドを発行できます。

サーバー設定ノブ

環境変数CLIフラグデフォルト効果
KEVY_UNIX_SOCKET(今のところenv専用)未設定bindするファイルシステムパス。未設定ならTCPのみ。
KEVY_BIND--bind127.0.0.1TCPのbindアドレス。UDSのbindとは独立。
--port--port6379TCPポート。UDS設定時もbindされる。

注意:

トレードオフ

同じkevyバイナリ上でのUDSとTCPループバックの比較:

観点UDSTCPループバック
opあたりのフロア低い(IP/checksum/port/NAGLEなし)高い
到達範囲同一ホストのみ任意のホスト
IDファイルシステムのパーミッションport + bindアドレス + AUTH
ライフサイクルディスク上のソケットファイル。再起動時に掃除が必要ポートのライフサイクルはカーネル管理
観測lsof / ss -xlss -tlnnetstattcpdump
クライアント設定unix:///pathまたは-s /pathhost:port

スループットの利得はワークロードの形に依存します。最も得をするのは小ペイロード・低コネクション数のセル(ループバックのopあたりの税が支配的だったところ)で、CPU飽和セルの利得は小さくなります(トランスポートがフロアではなかったためです)。実測値はbench/REPORT.mdを参照してください。

FAQ

UDSとTCPを同時にbindできますか?

はい。むしろそれが唯一のモードです。KEVY_UNIX_SOCKETを設定するとUDSリスナーが追加され、TCPリスナーはそのまま生き続けます。クライアントごとに都合のよいほうを使ってください。

サーバーが「socket exists」と言って起動を拒否します。

意図的な仕様です。kevyは自分が作っていないパスをunlinkしません。設定を誤った実行が他サービスのソケットを黙って奪うのを防ぐためです。再起動前に古いファイルを消す(rm -f /tmp/kevy.sock)か、/run/kevy/$(uuidgen).sockのような実行ごとのパスを使ってください。kevyがクラッシュしてファイルが残った場合は、手で消して問題ありません。

UDSはTCPループバックよりどれくらい速いですか?

どのワークロードでも目に見えて速くなります。UDSはIPパス全体をスキップするためです。checksumなし、netfilterチェーン(nft_do_chain)なし、ループバックのrep_movsなし、パケットごとのACK往復なし。倍率は、opあたりの予算のうちループバックオーバーヘッドが占めていた割合で決まります。単一コネクション・小ペイロードのワークロードが最も大きく跳ね、CPUバウンドでパイプライニングしたセルの伸びは小さくなります。自分のワークロードでの計測は、redis-benchmark -s /tmp/kevy.sock-h 127.0.0.1の比較で行ってください。

自分のクライアントライブラリはUDSを使えますか?

エコシステムドライバの多くが使えます。redis-cliredis-benchmark-s <path>を取ります。ioredis、node-redis、redis-py、redis-rb、go-redis、lettuce、jedisはいずれもunix:///pathのURLか明示的なソケットパスオプションを受け付けます——正確なキー名は各ドライバの接続オプションのドキュメントで確認してください。リポジトリ内のkevy-client / kevy-client-asyncはUDSを話しません。同一プロセスのRustクライアントには組み込みのfile:///mem://バックエンドがどんなソケットにも勝ち、プロセスをまたぐときはTCPを使います。

全クライアントが同一ホストにあるならTCPを完全に外すべきですか?

外すこともできますが、必須ではありません。TCPを127.0.0.1にbindしたままにしても、誰も接続しなければコストはゼロですし、クライアントのUDSパスが誤設定になったときのフォールバックにもなります。よくあるデプロイは「ホットなクライアントにはUDS、redis-cliでのデバッグにはTCP」という形です。