gRPCが遅すぎる?eBPFでカーネル内で動かす!
gRPCの高速化への飽くなき追求(具体的な目標や目的なし)を続けてきましたが、まだ、遅すぎる!今回は、安全にLinuxカーネルに機能を追加できるeBPFという仕組みを使って、カーネル内で動作するgRPCサーバを実装しました。その結果、前回実装したRust版よりも2倍高速になりました!
eBPFで安全なユーザコード実行
eBPFを使えば、システムコール、パケットの受信など、カーネルで発生する様々なイベントに対して、私たちユーザが実装したコードを、カーネル内部で実行することができます。同じようにカーネルに機能を追加できるカーネルモジュールと違って、eBPFは、データ破壊など、システムの安定性に深刻な影響を与える危険なコードの実行を防ぐことができます。
eBPFで検索すると、たくさんの日本語の情報が見つかるXDPは、ネットワークインターフェイスのドライバのパケット受信時に、ユーザコードを実行する仕組みです。eBPFは、クラウドネィティブとかけ離れた、ネットワークパケットとはしゃぐエンジニアのための技術と思っていた方もいるかもしれませんが、様々なクラウドネイティブなソフトウェアでも活用されています。クラウドネィティブなあなたも、安心して読み進めて、eBPFというキーワードを会話に散りばめていきましょう。
eBPFコードは、カーネルに組み込まれる前に、無効なアドレスへのメモリアクセス、無限ループなど、システムの安定性に影響を与える動作がないかどうか検証されます。
実装
eBPFでも、Rustへの愛を貫くこともできますが、「RustでeBPFコードを実装してみた」という記事(3部作)を書けるぐらい面倒なので、標準的な手法のC言語で実装しました。
eBPFのsockhashという機能を使うことで、TCP/UDPソケットにデータが届いた際に、ユーザコードを実行することができます。ユーザコードは、受信したパケットの中身にアクセスし、必要に応じて変更して、送信することなどができます。
実装したgRPCサーバは、ユーザスペースで動作する部分とカーネルスペースで動作するeBPFコードの組み合わせになります。ユーザスペース部分は、acceptシステムコールで、gRPCクライアントとのソケット作成のみを実行します。その後、eBPFのコードが、繰り返し送られてくるgRPCのリクエストに対して、適切なレスポンスを送信します。
eBPFコードは、検証器が理解できるように、メモリにアクセスする前には、有効かどうかチェックする必要があるなど、様々な独自のお作法を守る必要があります。何もせずに、C言語のメモリ安全性という永遠の夢が実現するはずがありませんからね。そのため、eBPFを想定していない既存のライブラリは利用できません。gRPCに必要なHTTP/2のパーサをスクラッチから実装するのはいつの日にかやるとして、ベンチマークのための最小限の機能を実装しました。
今回の実装は、gRPCクライアント数が12,000の場合、goよりも3倍、前回のRustの実装よりも2倍ほど高速になりました。
まとめ
「eBPFは制限が多すぎて、実際に役立つサーバソフトウェアが実装できるのか?」という根本的な疑問はありますが、eBPFによるサーバソフトウェアを実現することで、eBPFで動作する様々なライブラリを再開発するという、この先10年間ぐらいは、エンジニアみんなの仕事を作ることができます。業界愛からの活動です。NTTでは、仲間を募集中です。連絡お待ちしています。