Unixドメインソケット(UDS)トランスポート
kevyはオプションのUnixドメインstreamリスナーを公開しています。TCPポートとまったく同じRESPセマンティクスを話すので、同一ホストのクライアントはループバックスタックを丸ごとスキップできます。
このドキュメントが必要になるとき
クライアントとサーバーが同じホストに同居しているなら、UDSが適切なトランスポートです:
- 同一ホストのクライアント — 1台のマシンにアプリとkevyが同居している、あるいはtmpfsやマウント済みソケットディレクトリを共有するコンテナ構成。
- 遅延に敏感なワークロード — コネクション数が少ない、ペイロードが小さい、あるいはTCPループバック往復のフロアが制約になっている高ファンアウトのパイプライニング。
- コンテナサイドカー — サイドカーとメインコンテナが
/runや/tmpのボリュームを共有する構成。ソケットファイルがそのままIPCハンドルになり、ポート割り当ては不要です。
ホストをまたぐクライアントには引き続きTCPが必要です。UDSのスコープはファイルシステムであり、カーネルの外には出ません。
中心となる考え方
KEVY_UNIX_SOCKETにファイルシステムのパスを設定すると、kevyはデュアルバインドします。TCPリスナーはこれまでどおり生き続け、UDSリスナーが同じシャードランタイム上で同じRESP2/3パーサーを使ってacceptします。unix://のURLか-s <path>フラグを受け取るRESPクライアントなら、設定1行で切り替えられます。UDSはループバックのrep_movs、nft_do_chain、TCPのsyscallパスを丸ごと省くため、どのワークロードでもopあたりのフロアが目に見えて下がります。
動かしてみる例
両方のトランスポートを有効にしてkevyを起動します:
KEVY_UNIX_SOCKET=/tmp/kevy.sock kevy --port 6379redis-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-client/kevy-client-async)が話すのはtcp:///kevy:///redis://と、プロセス内のmem:///file:///スキームです——unix://のURLは受け付けません。Rustからは、同一ホストのクライアントはTCPループバックで接続するか、同じ*プロセス*に住んでいるなら組み込みバックエンド(file:////mem://)でソケットを丸ごと省きます。こちらはUDSがなり得るどんな速さよりも速いのです。UDSは、他言語やエコシステムドライバの、プロセス外・同一ホストのクライアントのためのものです。
パーミッションとセキュリティ
UDSの信頼境界はファイルシステムです。UnixソケットにRESPレベルのAUTHやTLSはありません。ソケットファイルをopen(2)できる者は誰でも、FLUSHALLを含む任意のコマンドを発行できます。
- ソケットファイルの所有者。 kevyはサーバーの実行ユーザーとしてソケットを作ります。起動後に
chown/chgrpするか、ソケットを所有させたいIDでkevyを実行してください。 - パーミッションビット。 同居するクライアントプロセスが接続できるよう、デフォルトでは緩いビットでソケットを作ります。引き締めたい場合は、ソケットを制限付きディレクトリに置きます。たとえば
kevyグループ所有・0750の/run/kevy/に置けば、グループメンバーだけがconnect(2)できます。ソケットinode自体へのアクセスはディレクトリの権限が守ります。 - tmpfsかディスクか。 ほとんどのLinuxディストリビューションでは
/tmpと/runはtmpfsで、ソケットの置き場所として理想的です(connect時のディスクI/Oがありません)。実ファイルシステム上の永続パスでも動きます。inodeはランデブーポイントにすぎず、データがディスクに触れることはありません。 - 信頼ドメイン。 ソケットパスへの読み書き権限を持つアカウントは、すべて完全に認証済みとして扱ってください。クライアントごとのIDが必要なら、それはkevyより上の層(サイドカープロキシ、カーネルLSM、名前空間分離)に置く必要があります。
サーバー設定ノブ
| 環境変数 | CLIフラグ | デフォルト | 効果 |
|---|---|---|---|
KEVY_UNIX_SOCKET | (今のところenv専用) | 未設定 | bindするファイルシステムパス。未設定ならTCPのみ。 |
KEVY_BIND | --bind | 127.0.0.1 | TCPのbindアドレス。UDSのbindとは独立。 |
--port | --port | 6379 | TCPポート。UDS設定時もbindされる。 |
注意:
- パスが事前に存在してはいけません。
KEVY_UNIX_SOCKETが既存ファイルを指している場合、kevyは起動を拒否します。自分が作ったのではないパスを上書きしないためです。再起動時に掃除する(rm -f /tmp/kevy.sock)か、実行ごとのパス(/run/kevy/$(date +%s).sock)を使ってください。これは意図的な仕様です。黙ってunlinkしてしまうと、設定を誤ったkevyが他サービスのソケットを奪いかねません。 - 環境変数が設定されていれば常にデュアルバインド。 UDS専用モードはなく、TCPリスナーも必ず上がります。TCPを禁止したければ、管理下にあるループバック専用アドレスにbindし、ファイアウォールで塞いでください。
- acceptループはシャード0が所有します。 acceptされた接続は既存のシャード別ランタイムへディスパッチされるので、ソケット越しのワークロードの並列性も引き続き
--threadsで制御できます。 - io_uringパス。 Linuxで
KEVY_IO_URING=1のとき、UDSのacceptはTCPと同じio_uringインスタンスを通るmultishot accept SQEとして動き、余計なreactorコストはかかりません。TCP_NODELAYはUDSには設定されません(IPソケットではないためです)。
トレードオフ
同じkevyバイナリ上でのUDSとTCPループバックの比較:
| 観点 | UDS | TCPループバック |
|---|---|---|
| opあたりのフロア | 低い(IP/checksum/port/NAGLEなし) | 高い |
| 到達範囲 | 同一ホストのみ | 任意のホスト |
| ID | ファイルシステムのパーミッション | port + bindアドレス + AUTH |
| ライフサイクル | ディスク上のソケットファイル。再起動時に掃除が必要 | ポートのライフサイクルはカーネル管理 |
| 観測 | lsof / ss -xl | ss -tln、netstat、tcpdump |
| クライアント設定 | unix:///pathまたは-s /path | host: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-cliとredis-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」という形です。