セッションのアジェンダ

大石将邦氏:こんにちは。「LINEのAndroidアプリがSPDYからHTTP/2へ移行した話」について発表します、大石と言います。よろしくお願いします。

まず、このセッションのアジェンダをお伝えします。はじめに、LINEアプリのネットワークスタックが抱えていた問題点について話します。そして、その問題点を解決するためにネットワークの刷新をどのように行ったかを話し、最後に、この刷新によってどのようなことが達成できたかについて話します。

最初に自己紹介させてください。私は大石将邦と言います。LINE Communication Platform Engineeringという部署に所属しており、コミュニケーションプラットフォーム、すなわちLINEアプリの基盤となる部分についての開発を主に担当しています。ここ最近は、LINEアプリのネットワークスタックを刷新するプロジェクトに関わっていました。それがこのセッションで話す内容です。

LINEアプリのネットワークスタックが抱えていた重大な問題

LINEアプリのネットワークスタックには、いくつか重大な問題がありました。1つはSPDYプロトコルをベースに、私たち独自のプッシュメカニズムの拡張を加えて使用していたことです。もともとSPDYプロトコルは効率的なAPI呼び出しのために、LINEアプリの初期に導入されました。しかし現在では、SPDYの後継となるHTTP/2やHTTP/3 といったプロトコルが標準化されているため、LINEアプリが使うプロトコルもこれらに置き換えられるべきです。

もう1つの問題は、LINE Androidアプリにおけるネットワークスタックのコードが、メンテナンス困難になるほど複雑化していることです。先ほど、LINEアプリでは独自のプッシュメカニズムを使用していたと話しましたが、そのために私たちはオープンソースライブラリのNettyをフォークし、独自のカスタマイズを加えることをしていました。このことが、コードのメンテナンス性を大きく下げてしまっていたのです。

LINEアプリのネットワークスタック刷新プロジェクトの3つの目標

これらの問題を解決するため、私たちはLINEアプリのネットワークスタックを刷新するプロジェクトを開始しました。プロジェクトの目標は以下の3つになります。

まず1つ目は、LINEアプリの使用するプロトコルを、SPDYからHTTP/2へと移行することです。また今回の目標ではありませんが、将来さらにHTTP/3へとアップグレードすることも考慮に入れておきます。

2つ目の目標は、標準仕様に沿うようにすることです。独自のカスタマイズは後に技術的負債となりがちなので、できるかぎり避けるようにします。

最後の目標は、このネットワークスタックの刷新を無事に完了させることです。万が一LINEアプリの通信周りで障害が起きてしまったら、非常に多くのユーザーに影響を与えてしまいます。そのため、この移行作業は慎重に進めなければなりません。

これらの目標を達成するために困難だった点がいくつかあります。まず1つは、LINEアプリ内のすべてのAPI呼び出しを、一度にSPDYからHTTP/2へと移行しなければならないことです。もう1つは、HTTP/2プロトコル上で、クライアント・サーバー間の双方向通信を実現しなければならないことです。そして最後は、このマイグレーションのリリースを、ロールバック可能なかたちで段階的に行わなければならないことです。これらの点について、今から詳しく話していきましょう。

すべてのAPI呼び出しの移行が必要な理由と問題の解決方法

まず、すべてのAPI呼び出しを一度に移行しなければならない理由ついて説明します。(スライドを指して)この図は、クライアントのある機能がサーバーに対して、なんらかのAPI呼び出しを行う時のフローを示しています。

API呼び出しの通信に関わる処理は、クライアントのネットワークスタックが担っています。この図では、SPDYプロトコルのネットワークスタックが通信を行っています。SPDYネットワークスタックは、サーバーとの間にSPDYコネクションを確立し、そのコネクション上でAPI呼び出しを行います。

あるAPI呼び出しが終わっても、SPDYコネクションはしばらくの間保持され、別のAPI呼び出しの際に再利用されます。こうすることで、1本のSPDYコネクション上で複数のAPI呼び出しを扱え、通信の効率化が行えるのです。このような仕組みをコネクションプールと呼びます。

アプリがHTTP/2のネットワークスタックを使って、APIコールを行う場合も同様です。この時、HTTP/2のネットワークスタックも、それ自身がコネクションプールを持っています。

それでは同じアプリ内で、あるAPIはSPDY経由で、別のAPIはHTTP/2経由で呼び出しを行ったらどうなるか考えてみましょう。

SPDYとHTTP/2のネットワークスタックは、それぞれがサーバーとの間にコネクションを確立します。つまりこの場合は、1つのクライアントアプリからサーバーに対して、SPDYとHTTP/2の2本のコネクションが作られるのです。

これはサーバー側から見れば、クライアントから受け取るコネクションの数が2倍になってしまうということを意味します。LINEには非常に多くのアクティブユーザーがいるので、このようにコネクションの数が突然2倍になってしまうことは許容できません。

この問題に対する解決方法として、私たちはSPDYとHTTP/2のネットワークスタックの前に、共通のネットワークレイヤーを設けることにしました。アプリ内の各機能は、SPDYもしくはHTTP/2のネットワークスタックを直接使うのではなく、この共通ネットワークレイヤーを使用するようにします。そして、この共通レイヤーがSPDYとHTTP/2のどちらを使用するかを、Featureフラグによって切り替えるのです。

これにより、アプリ内のSPDYとHTTP/2の切り替えを、一度に行えるようになりました。使われていないほうのネットワークスタックはコネクションを張らないので、サーバー側へのコネクション数が倍増するようなこともありません。

クライアント・サーバー間の双方向通信の問題

では次に、クライアント・サーバー間の双方向通信についての話をしましょう。LINEアプリでは、リアルタイムにチャットを行えるようにするなどの目的のため、クライアントとサーバー間で双方向通信を行っています。双方向通信というとWebSocketを思い浮かべる人もいるかもしれませんが、WebSocketはSPDYコネクション上では使用できません。そのため私たちは、双方向通信のためにSPDY Pushという独自のメカニズムを実装しました。

SPDY Pushは、server-initiated streamsというSPDYプロトコルの機能を用いています。

SPDY Pushでは、サーバーがクライアントに対してメッセージをプッシュする時、SPDYコネクション上のSYN_STREAMおよびDATA frameのかたちで必要なデータを送りつけます。こうすることで、クライアントはサーバーからのメッセージに即座に対応できるのです。

ところが、このSPDY Pushの実装は問題を抱えていました。SPDY Pushが用いている仕組みをサポートしている既存のネットワークライブラリは、ほとんど存在しません。そのため、私たちはNettyというオープンソースライブラリをフォークし、それに独自のカスタマイズを加える方法でSPDY Pushを実装しました。

しかし、この方法はコードの複雑化を招き、メンテナンスを困難にしてしまいました。例えば、オリジナルのNettyライブラリが後のバージョンアップによりHTTP/2をサポートしても、それを私たちのカスタマイズしたコードに反映することは非常に難しい状態でした。

このような事情もあり、私たちがLINEアプリにHTTP/2を導入する際に、新たに一から設計し直したプッシュメカニズムを実装することにしました。それがStreaming Pushです。

Streaming Pushでは、まずクライアントがサーバーに対して、HTTP POSTメソッドでのリクエストを行います。そのリクエストボディとレスポンスボディそれぞれを固定長のデータではなく、随時書き込みが行われるストリームとして扱います。つまり、HTTP POST呼び出しのリクエストボディをアップストリーム、レスポンスボディをダウンストリームとして、双方向通信を行うというわけです。

Streaming Pushで用いているこのメカニズムは、gRPCなどでも用いられている実績があります。また、HTTP/2のセマンティクスにも完全に準拠しています。そしてLINE Androidアプリにおいても、Streaming PushはOkHttpというネットワークライブラリをそのまま使用して実装されています。

このようにして、HTTP/2上の双方向通信を標準仕様に準拠したかたちで、複雑なカスタマイズを行うことなく実装できました。

ロールアウト作業の進め方

こうしてLINEアプリがHTTP/2プロトコルでサーバーと通信するためのメカニズムを実装できましたが、これをロールアウトするためにはもう少し作業が必要でした。

先ほど説明したように、SPDYとHTTP/2のどちらを使用するかは、クライアント側のFeatureフラグよって切り替えられるようにしています。私たちはこのFeatureフラグの値を、サーバーからの指示によりユーザーごと変更できるようにしました。こうすることで、「一部のユーザーに対してだけHTTP/2を使用する」という指示ができるようになりました。

まずはLINE社内のユーザーに対してのみ、HTTP/2を有効にしてテストを行いました。このテストは、最初はクライアントアプリ開発に携わっている開発者の間でだけ始め、後にすべての従業員を対象に行いました。この社内テストは1ヶ月以上の期間をかけて行い、問題点の洗い出しを行いました。

社内テストが完了したら、いよいよパブリックリリースです。問題が発生したらすぐにロールバックできるよう準備しながら、まずは5パーセントのユーザーを対象に、HTTP/2の有効化を行いました。そして徐々に対象を広げていき、1週間ほどかけてすべてのユーザーへのHTTP/2のロールアウトを行いました。幸いにもこのパブリックリリースはなにもトラブルが発生することなく、無事に完了できました。

こうして私たちは、LINE Androidアプリの使用するネットワークプロトコルをSPDYから、HTTP/2へと移行できました。HTTP/2上で双方向通信を行うための新しいプッシュメカニズムを導入することができ、これらのロールアウトをトラブルなく完了できました。

こうしてLINEアプリのネットワークスタックが刷新された結果、Androidクライアントのネットワーク周りのコードは、OkHttpとKotlinコルーチンを用いたモダンなアーキテクチャにできました。

QUICおよびHTTP/3の採用はどうすればよいか?

さて、現在はHTTP/2のさらに後継となるQUICおよびHTTP/3というプロトコルが標準化されていますが、それをLINEアプリに採用するにはどうすればよいでしょうか。

LINEアプリをSPDYからHTTP/2へと移行させるのに大きな障害となったのは、SPDY PushというSPDYのプロトコルに依存したメカニズムでした。しかし、現在使用しているStreaming Pushのメカニズムは、HTTPのセマンティクスに完全に準拠しており、このセマンティクスはHTTP/3になっても変わりません。よって、Streaming PushはHTTP/3上でも問題なく動作します。

また、現在のLINE Androidアプリの実装は、OkHttpというネットワークライブラリをそのまま使用しています。現時点ではOkHttpはQUICおよびHTTP/3をまだサポートしていませんが、将来OkHttpがそれをサポートすれば、LINE AndroidアプリはそのライブラリのバージョンアップをするだけでHTTP/3を使えるようになるはずです。

このように、できるだけ標準に従った実装を心がけることは、長期的なメンテナンスの観点からも非常に重要です。

オープンソースライブラリ“Lich”

最後に、私たちがリリースしているLichというオープンソースライブラリについて紹介します。これは今回のHTTP/2移行プロジェクトなどのために作成した、Androidアプリ向けの汎用のライブラリ集です。

今回の話題に関係するものとしては、OkHttpとApache Thriftに対し、Kotlinコルーチンに対応した拡張関数を提供するライブラリがあります。これを利用すると、例えばRangeリクエストを利用した、resume可能なダウンロードを簡単に実装できるようになります。(スライドを指して)このURLからアクセスできるので、ぜひ参考にしてみてください。

以上で私の発表を終わります。ありがとうございました。