使いやすいStreaming Pushとは

イ・ビョクサン氏:さて次のトピックである、Streaming Pushに移りましょう。それではServer Pushの要件を思い出してみましょう。HTTP/2規格に準拠し、使いやすいものでなければなりません。さらに未承諾のPushが可能でなければなりません。

SPDYのように、HTTP/2もServer Pushを提供しますが、そのかたちは異なります。HTTP/2では、疑似リクエストの役割を果たすPUSH_PROMISEフレームを定義しています。Pushを送信する前に、サーバーはPUSH_PROMISEをクライアントに送信し、Pushされるべきものがあることを知らせなければなりません。各Pushは、PUSH_PROMISEフレームが送信されるストリームとは、別のストリームで送信されます。

この図では、PUSH_PROMISEフレームは、2つのPushデータがStream BとCに送信されることを約束しています。この機能を使って、私たちはまず未承諾のPushを使用できるようにするために、このシーケンスを考えました。SPDY Pushとの唯一の違いは、LEGYがPushに関連するすべてのストリームで、PUSH_PROMISEを送信することです。

PUSH_PROMISEは、サブスクリプションの応答とともに送信されます。また各Pushは次のPushのために、PUSH_PROMISEを送信します。しかしHTTP/2の仕様では、PUSH_PROMISEはクライアントのリクエストストリームで使用しなければならないとされています。このPushモデルは、ブラウザやいくつかのライブラリでテストした時には動作しましたが、Server Pushの要件の1つである標準には準拠していないようです。

標準に準拠しているかの他に、もう1つ考慮すべきことがあります。例えばLINEのクライアントが従来のPushモデルを使っていて、接続はライブラリやOSが管理していたとします。Pushを得るために、LINEのクライアントはまずSubscribeリクエストを送り、それが成功したとしましょう。するとクライアントは、LEGYが必要な時にPushデータを送ってくれると信じます。

しかしクライアントは、接続を制御できないため、ともするとライブラリやOSがSubscribeリクエストを送信した接続を閉じてしまうことがあります。LEGYから見ると、クライアントは切断され、サブスクリプションは無効になります。そのため、LEGYはクライアントにPushを送信しません。クライアントはこの状況を知らず、まだサブスクリプションが生きていると信じています。これは問題です。

既存のSPDYプロトコルでは、このようなことは起こりません。それはLINEのクライアント自身がSPDYプロトコルを実装して、接続をコントロールしているからです。しかしHTTP/2ライブラリでは、LINEのクライアントがこのような問題を防ぐために、接続を完全にコントロールできるという保証はありません。クライアントは使用するライブラリにかかわらず、HTTP/2のストリームをコントロールできることはわかっています。

クライアントはいつリクエストを廃止し、一時停止して終了するかを決定できます。したがって、接続ではなくストリーム内にPushセッションを作成するのは自然なことです。そしてLEGYがPushするためには、セッション自体が終了しない限りストリームを終了してはいけません。

これは、HTTP/1.1のChunked Transferがデータストリームを可能にしたことと同じです。LEGYはSubscribeリクエストにChunked Transferで応答し、準備ができたらデータを送信できます。HTTP/2でも、同じことを1つの接続で支障なく行えます。それはHTTP/2の接続はHTTP/1.1とは異なり、複数のストリームで多重化されているからです。

HTTP/2では、HTTPメッセージボディは複数のデータフレームに分割されます。HTTP/2のデータフレームは、HTTP/1.1のChunked Transferと本質的に同じように機能するため、HTTP/2は自然にデータストリーミングをサポートします。

私たちは、HTTP/2のこのストリームの性質を利用してPushを送信し、これを「Streaming Push」と呼んでいます。これは私たちの要件を満たしているでしょうか。はい。HTTP/2は何も変更しないので標準に準拠しています。そのため、Streaming Push用のライブラリをどれでも簡単に使用できます。

未承諾のPushをどのように送るか

最後に、クライアントに未承諾のPushを送れます。これはどのような仕組みなのでしょうか。クライアントが特定のパスにストリームをオープンすると、クライアントもサーバーもこのストリームがStreaming Pushに使われていることを知ります。

LEGYはヘッダーを受け取った直後に200 OKを応答し、Pushセッションが開始されます。実際のSubscribe要求はこのセッション内のsign-onデータとして送信されます。LEGYは必要なサービスをSubscribeしてその結果をクライアントに返します。サービスがLEGYへPushデータを送信するとLEGYはクライアントに未承諾のPushを送信します。

Pushタイプに基づいてクライアントはPushの確認をクライアントに送り返せます。ロングポーリングのリクエストもストリーミングプッシュの中で同様に扱えます。sign-onデータがサブスクリプション開始時に一度だけsign-onデータが送信されるサブスクリプションとは異なり、ロングポーリングはクライアントがsign-onの結果を受けるたびに、sign-onを繰り返し送信します。なので、fetchOpsはストリーミングPushのセッション内でも、別のストリームでも動作します。

TLSv1.2とTLSv1.3

最後のテーマはTLSです。申し上げたとおり、モバイルネットワークの速度を向上させるために、TLSと自社の暗号化方式を同時に使用しています。まずTLSのハンドシェイクが遅い理由を、簡単に見ていきましょう。上位のプロトコルに関係なく、TCPをトランスポートとして使用しています。プレーンなTCPを使用する場合、送信のシーケンスは非常にシンプルです。

クライアントとサーバーは、TCPの3wayハンドシェイクを行い、1-RTTですぐにアプリケーションデータの送信が開始されます。LEGY Encryptionは、プレーンなTCP上で動作します。右の図はTLSv1.2の仕組みを示したものです。TLSは、TCPの上で動作するため3wayハンドシェイクも実行されます。

アプリケーションデータが送信される前に、TLSセッションで使用する暗号パラメーターをネゴシエートするため、TLSハンドシェイクを実行する必要があります。このネゴシエーションには2つの追加RTTが必要ですが、これは低速ネットワークでは致命的です。またモバイル環境では切断と再接続が非常に頻繁に発生します。このことが、この状況をさらに悪化させています。

幸いなことに、TLSv1.3は基本的により優れたハンドシェイク機能をサポートしており、ネゴシエーションを1RTTまで短縮しています。設定によっては、TLSv1.3は0-RTTのハンドシェイクを提供し、ネゴシエーション中にアプリケーションデータを送信できます。

サーバーにTLSv1.3を採用することで、LEGY Encryptionを避難するという懸念が解消されると思います。しかしTLSv1.3を使用することだけでは、十分ではありません。多くのクライアントが未だにTLSv1.2を使用しているためです。

TLSv1.2で、ネゴシエーションを1-RTTにできるのでしょうか。サーバーに接続する際、クライアントは2つのRTTのハンドシェイクを行い、暗号パラメーターをネゴシエートします。最初の接続時に使用した暗号パラメーターを、再度サーバーに接続する際に再利用したらどうでしょうか。

クライアントとサーバーが暗号パラメーターなどのセッション情報を記憶し、それを使用することに合意できれば、ハンドシェイクを減らせます。これはセッションのresumption、再開と言います。セッションのresumption、再開には2つの保証があります。1つがTLSv1.2がサポートするセッションID、もう1つはTLSの拡張機能として機能するセッションチケットです。

セッションIDは、暗号化パラメーターをサーバーに保存するためのものです。保存されたセッションには、それぞれIDが与えられます。この例では、新たにセッションが確立され、接続されたLEGYサーバーにID:1でセッションが保存されます。クライアントがTLSのセッションID:1でLEGYに再接続しようとすると、クライアントが別のLEGYサーバーインスタンスに接続する可能性があります。

このサーバーは、セッションID:1を知らないため、完全なTLSハンドシェイクが実行され、なんの改善もなく、2つのRTTが使われます。セッションIDが、機能するためにはすべてのLEGYインスタンスがセッションストレージを共有する必要があるのです。

セッション保存には、Redisクラスタを使っています。各セッションは、Rendezvous Hashingを使ってRedisインスタンス間でストア保存されます。

これでLEGYは、与えられたセッションIDをどのRedisインスタンスに問い合わせるべきかがわかります。この例では2つ目の接続を受けたLEGYサーバーがセッション情報の復元に成功し、1-RTTでハンドシェイクを終了しています。ご覧のとおりセッションIDはステートフルな技術なのでリソースを消費するセッションを保存する必要があります。

リソースの使用を減らすために、もう1つの手法であるセッションチケットを使用します。セッションIDとは異なり、セッションチケットはステートフルなセッション再開方法です。セッションをサーバーに保存するのではなく、LEGYはチケットと呼ばれるセッションデータを作成してクライアントに返します。チケットにはセッション情報が含まれており、LEGYはチケットからセッション情報を抽出して取得できます。

クライアントは、返送されたチケットと自分が使用するセッション情報を関連付けます。チケットは、LEGYの秘密鍵で暗号化されているため、クライアントがチケットを操作できません。セキュリティの観点から、LEGYの暗号化キーをローテーションします。またLEGYは、古いチケットをしばらくの間使用できるように鍵のウィンドウを保持しています。

将来の鍵は、鍵の不在を防ぐためにプロビジョニングされます。これにより、LEGYのインスタンス間での鍵の同期化が非常に簡単に行えます。クライアントが2回目にLEGYを接続すると、クライアントはチケットをLEGYに送信します。LEGYはそれを復号化して検証します。検証に成功した場合、LEGYはチケットから抽出されたセッション情報を使用し、クライアントはチケットに関連付けられたセッション情報を使用します。

チケットの有効期限が切れてしまった場合はフルなTLSハンドシェイクが行われます。サーバー間で唯一考慮すべきことは鍵の管理だけです。私たちは鍵管理サービス、Key Managementサービスを使って鍵を配布します。鍵はLEGYによって生成されます。すべてのLEGYインスタンスは同じロジックを実行しているため、クーロンジョブは将来の鍵生成リクエストをLEGYインスタンスの1つに送信します。

生成された鍵は、Key Managementサービスへアップロードされます。LEGYインスタンスは定期的に古い鍵を破棄し、Key Managementサービスより将来の鍵を取得してウィンドウを埋めていきます。これによって、すべてのLEGYインスタンスがチケットresumptionのために同一の鍵の使用が保証されます。

TLS改善結果

これが、私たちのTLS改善の結果です。パケットは、韓国のクライアントから米国のLEGYへのTLS接続確立時にキャプチャされたものです。最初のTLS接続バージョン1.2接続ではハンドシェイクに2つのRTTを使用します。600ミリ秒かかりました。下の2つの図はセッションIDとセッションチケットを使用することでハンドシェイクが1-RTTに縮小され、600ミリ秒から半分の300ミリ秒で済むことを示しています。

TLSv1.3は常に1-RTTでハンドシェイクを行います。セッションresumptionを使用するかどうかにかかわらず、ハンドシェイクは1-RTTで行われることがわかります。

現在、直近のクライアントにはTLSを使用しており、今後のクライアントにもモバイルと無線LANの両方でTLSを使用する予定です。その結果、LEGYへの全接続の93パーセントがTLSを使用します。

そのうち80パーセントはTLSv1.3を使用し、TLSv1.2の接続のうち20パーセントはセッションresumptionを使用します。時間が経過し、古いクライアントがアップグレードされるに連れて、TLSの使用率とTLSv1.3の比率が増加することを期待しています。

ネットワークスタックの改善は第一歩

では、今日のプレゼンテーションをまとめます。LINEが誕生してグローバルなサービスになってから10年が経ちました。次の10年も、LINEは成長し続けます。そのためにはユーザーエクスペリエンスを損なうことなく、信頼性・拡張性・セキュリティなど、さまざまな名誉を改善する必要があると考えています。そのための第一歩として、私たちはネットワークスタックの改善を試みました。

セキュリティ面では、より積極的にTLSを採用しました。しかしユーザーへの影響を考慮して、TLSv1.3を採用しながらも、TLSv1.2ではセッションresumptionの技術、セッションIDとセッションチケットを実装しました。その結果、93パーセントの接続にTLSが採用されています。

信頼性を高めるために、古くてカスタマイズされたSPDYプロトコルの廃止に着手し、HTTP/2を採用することで、標準を満たしました。85パーセントの接続がHTTP/2を使用しており、時間の経過とともに、すべてのクライアントがHTTP/2を使用するようになり、SPDYは廃止されることになると思います。拡張性を高めるために、Streaming PushというPushの仕組みを、標準をカスタマイズすることなく実装しました。

ロングポーリングやSubscribeのリクエストも、思いのままに処理できます。この仕組みを使えば、将来的にロングポーリングリクエストを実際のPushにシームレスに移行できます。グローバルなネットワーク環境は変化し続け、新しいプロトコルが次々と登場します。IPv6も、広く使われるようになりました。

HTTP/3やQUICなど、新しいプロトコルも話題に上がっています。TLSの新しいバージョンも出てくるかもしれません。コンピューターの性能が上がるに連れ、暗号化方式の中には脆弱性が問題になるものも出てくるかもしれません。ネットワークには、さまざまな要素があります。私たちは、信頼性・安定性・セキュリティが高品質なサービスの要であることを知っており、刻々と変化するネットワーク環境に対して、接続性を強化し続けていきたいと思います。

これで発表は終わりです。お楽しみいただけましたでしょうか。ご清聴ありがとうございました。