というテーマでセッションが行われました。
LINEアプリのコードは、バージョン9.18になるまでの間に3万から140万行に増えています。このような大きさになると、エンジニアリングの観点からも多くの問題が出てきます。これらの問題をどのように整理/解釈し、解決していったのでしょうか。
ライブラリの依存性
まずは「依存性」の問題。ライブラリをアップデートするときに、ほかのライブラリとの互換性を確認する必要があります。ときには、ヘッダやサーチパス、C++の標準ライブラリなどのビルドセッティングも、アップデートしなければならなくなることもあります。
そこで導入されたのが「CocoaPods」であり、これはXcodeと統合しやすいという利点があります。ただし、デフォルトの挙動を変更する必要があったため、CocoaPods の高度な使い方をする必要がありました。
2014年からはSwiftを使い始めましたが、CocoaPodsはSwiftに対応していませんでした。そこで、Swiftの依存パッケージを追加するために、「Carthage」も同時に使うことにしました。Carthageはフレームワークのプレビルドを行うため、Xcodeとの統合はほとんどできませんが、Romeによるリモートキャッシュを利用できるようになりました。
Romeのリモートキャッシュを利用することで、サーバ上でCarthageの生成物をキャッシュでき、ライブラリのアップデートがない限りリビルドをする必要がなくなりました。ただし、リモートキャッシュがポイゾニングされることがあったため、QAビルドやリリースビルドで使えないという問題がありました。
アプリの起動時間の問題
続いて、アプリの起動時間がどのように影響を受けたかという話題に。起動時間が長くなった理由の1つが、「動的フレームワーク」です。Carthageを使い始めたことにより、多くの動的フレームワークが使われるようになりました。この問題に対する同社のソリューションはシンプルで、すべて静的フレームワークにしようというものでした。
Xcodeをコンフィギュレーションしながら静的フレームワークを作るには、まず動的フレームワークのターゲットを作成し、ビルドターゲットの設定を開いて、Mach-O typeを「Static Library」に変更します。基本的にはこれで完了です。
ほとんどの動的フレームワークを静的フレームワークに変換したことで、起動時間が1.6秒ほど短縮したとのことです。
Objective-CとSwiftのブリッジング
続いて、Objective-CとSwiftのブリッジングの話題に移ります。さまざまな事情から、Objective-CのカテゴリをSwiftクラスで定義したいときがあります。しかし実際にやってみると、いくつかの理由でできないことがわかります。たとえば、クラスの定義が必要というものです。そこで、Swiftで生成されたヘッダをインポートしようとしてもうまくいきません。なぜなら、生成されたヘッダとブリッジングヘッダの間には依存関係の循環があるからです。
LINEでは別の問題として、SwiftクラスのインスタンスでカスタムObjective-Cネームが付けられているものがあり、それがブリッジングヘッダの中では使えませんでした。これらはSwiftからは見ることができません。
これまでは、こうしたインスタンスがコードベースに多数ありましたが、Swiftもこうしたものに対して改善されてきています。そのため、現在では、問題のあるクラスは1つだけになっています。
Xcodeプロジェクトの肥大化に伴う問題
Xcodeプロジェクトのサイズが大きくなるにつれて、いくつかの問題が出てきました。中でも、コンフリクトの問題はプロジェクトのサイズにかかわらず常に問題となるものですが、この問題を解決するにはプロジェクトが「読めるようになっている」必要があります。そこで採用したのが「XcodeGen」というツールです。これはXcodeプロジェクトをYAMLで記述するためのツールで、ドキュメントセッティングにコメントを書くことも可能です。ほとんどのコンフリクトはファイルの削除や追加をするときに発生しますが、XcodeGenでそれらを解消できます。
ビルド時間に影響する問題もあります。ヘッダを修正したときは、Objective-Cコードのリビルドが必要です。ツールプログラムの問題で、コードにほとんど変更がないにもかかわらず、すべてがリビルドされてしまうこともあります。
ビルド時間を改善する方法として有効なのは、まず、生成されたヘッダファイルへの変更を回避することです。変更をしてしまうと、実質的にクリーンビルドと同じだけ時間がかかります。また、Swiftの特定のエクステンションへのアクセスを制限する(つまり、アクセス修飾子のスコープを限定的にする)ことも有効です。これにより、リビルドするファイルの量を劇的に減らすことができます。
より長期的な視点での解決策として、同社ではコードベースをモジュールに分けておくという方法も取り入れています。LINEの依存関係は大きくなり過ぎていて全体を把握するのが難しく、新たなコードを追加するときにどこに入れていいのかわからないことが多くなります。また、小さなモジュールを修正したが、はたしてこのモジュールをインポートしていいのかどうか判断できないといったこともあります。さらに、LINEのエクステンションに関してバイナリサイズを減らしたいというのも動機の1つにありました(静的フレームワークを使っているため、エクステンションで大きなモジュールをインポートするのはそれなりのコストを伴うことになります)。
このようにして整理したモジュールは、「Bazel」を使ってビルドしていきます。Bazelは、Googleが開発したオープンソースのビルドシステムで、速くて再現性があるのが特徴です。Bazelについては、開発者のエクスペリエンスを改善する作業が今も続いていて、同社ではBazel自体へのコードの追加もしています。
Rogers氏は、今後もコードベースの分割をさらに進め、個々のモジュールを精査していくことを課題として挙げ、話を締めくくりました。
「自動テストとZipkinを活用したマイクロサービスの障害検知の仕組みの構築」by 伊藤宏幸氏
LINE SET TF TF Leaderの伊藤宏幸氏からは、「自動テストとZipkinを活用したマイクロサービスの障害検知の仕組みの構築」というテーマでセッションが行われました。
マイクロサービスの拡大に伴う問題
本セッションの結論は、障害をより迅速に検出することを、リリース前にすべてのバグを検出することよりも優先する自動テストだと伊藤氏は語ります。MSA=マイクロサービスアーキテクチャの時代には、いろいろと考えることが多くあります。
マイクロサービスでは、事前にすべてのバグを予見して取り除くことは事実上不可能です。一方、それゆえにバグの事前検出も必要ではあるものの、それよりもResilience(回復力)のほうが重要となってきます。
テスト自動化に、次の3つの観点を加えることは重要です。MTTR(障害が発生してから解決するまでの時間)を短くすることは有益です。そしてマイクロサービスの回復力の達成と強化、事業に貢献するといったことが重要となります。
『LINE』アプリや各種ファミリーアプリなど、現時点で約2,500のマイクロサービスが稼働しています。その理由の1つは、同社の事業が成功し、広がってきていることです。マイクロサービスが拡大すると、それに伴って、それぞれの接点であるIntegration Pointも増えていきます。そこで、多くの障害が発生してしまいます。
同社では、マイクロサービスが広がったことで2つの問題に直面しています。そのうちの1つが、障害検知の難しさです。それよりも難しいのが、どのマイクロサービスで障害が起こったのかという、根本原因を突き止めることでした。
さらに同社では、このマイクロサービスを巡って大きく3つの混乱が起きていました。マイクロサービスの開発を行うということは、各サービスだけでデプロイすることができるということです。そのため、何か障害が起きても「うちのサービスは動いているから関係ない」というようなことが起きてしまうというわけです。
テストにおいても混乱がありました。QAの人にテストをお願いするときに、それを呼び出せるUIも作ってほしいといわれることがあったそうです。
また、障害が起きたときの検出方法はどうすればいいのか? サービスを迅速に回復させるにはどうすればいいのか? ビジネスや顧客、ユーザに貢献するにはどうすればいいのか? ということがわからない人が多かったそうです。
開発プロセスを改善するための専門チーム
そこで出番となったのが、具体的には、テストの自動化やDevOpsなど自動化技術とScrum・Kanban・Leanなどのアジャイル方法論です。
SETがサポートして成功した事例の1つが、「Channel Gateway」です。これは、同社が提供しているAPIをまとめて外部に公開しているサービスのことを指します。このことは、裏を返せば、さまざまなマイクロサービスがあり混乱しているということにもなります。
テストの自動化で障害検知をするためにSETが行ったことは、マイクロサービスのMTTRの削減・改善です。具体的には、開発者にテストを書いてもらう施策を行っています。今回の「Channel Gateway」の事例では、開発者のテスト用にJUnitとSpring Bootを使用してAPIのテストが行えるようにしています。
それを作ったあとで、CIサーバ経由で定期的に実行するようにします、また、チャットで障害と回復を知らせるようにしています。
こうした施策の良かった点としては、障害検出が機能し、一部の開発者がテストコードを書いてくれるようになったことです。また、副次的な効果として、インフラの脆弱性も検知することができています。
その一方で、反省点も多かったそうです。実はこの施策は定着しませんでした。その理由は、大多数の開発者がAPIのテストコードを書いてくれなかったからです。理由を聞いたところ、開発者にとってもプロダクトマネージャーにとっても読みづらく書きづらかったという答えが返ってきました。
また、どのマイクロサービスで問題か起きたのかという根本原因がわかるしくみがありませんでした。そのため、MTTRの劇的な削減にはつながりませんでした。さらに、チームの自主性に委ねてみたところ、これが大失敗につながったと伊藤氏。一緒に教えていたのは2週間程度という期間でしたが、「これもやらなければいけないの?」という感じで、開発者がテンパってしまったそうです。
このときの反省点としては、しくみだけ提供してもプロダクト開発チームは動けなかったということでした。そこで、「Karate」を使いAPIテストの簡略化を行っています。
「Karate」とは、APIに特化したBDD(Behavior-Driven Development)スタイルのフレームワークのことです。「Karate」を選んだのは、開発チームやプロダクトマネージャーにとって読みやすく書きやすかったからでした。そこで、これまでJUnitで書いてきたものを「Karate」を使って書き換えています。
ただ書き換えるだけではなく、このときは、SETチームが一緒になり、問題解決にあたっていくようにしています。その結果、障害を30〜50パーセント削減することができました。また、同チームは自分たちで考えて解決策を見つけ、次の実装を考えるセルフオーガナイズドなチームに変貌しています。さらに、プロダクトマネージャーもテストコードを書いてもらえるようになっています。
Zipkinを使ったMTTRの削減
しかしこの状態ではまだ、マイクロサービスの障害の原因はまだ特定できていません。そこで「Zipkin」を使ったMTTRの削減に取りかかっています。
「Zipkin」とは、マイクロサービスのレイテンシや依存関係を視覚化できるサービスです。依存関係がわかるということは、障害が起きたときの場所もわかるということになります。
いくつか例がありますが、「Sebas-Report」では、テストを実行したレポートに「Zipkin」の情報を加えることで、障害発生時にどこのマイクロサービスで起こったのか特定することができます。
「Sebas-Bot」は、チャットから任意のテストを実行できるようにするチャットボットです。障害が発生して修正したときに、サクッとためしたいときなどに役に立つものです。これらを組み合わせて、MTTRの削減ができるか実験を行っています。
これにより、どのマイクロサービスで障害が起きたかわかるまで1週間ほど掛かっていたのが1〜2時間まで短縮することができました。それにより、サービス回復までの時間も劇的に速くなっています。
パンドラの箱
しかし、良いことばかりではなく、ある意味パンドラの箱を開けてしまったような状態だったといいます。これを活用するには、それぞれに「Zipkin」の設定を加える必要があります。しかし、最初に触れられたようにLINEアプリのマイクロサービスは、約2500も存在しています。当然のことながら、それらは必ずしもAPIが1つとは限りません。それに加えて、SETのメンバーは現在4名しか在籍していません。そこで、こうした問題をどうすればいいのかというのが、まさに今現在課題になっている点だと伊藤氏はいいます。
将来的なプランとしては、関連するすべてのマイクロサービスに「Zipkin」を展開し、テストスクリプトを生成する予定です。「Sebas-Bot」については、「Channel Gateway」以外のユーザも使えるように展開していきます。そして、これらを全社の製品開発チームのプロセス改善に活用していくとのこと。
伊藤氏は最後に、SETはテストの自動化は品質改善のためだけとは考えておらず、根本的な改善や技術などを活用し、多くのイノベーションを起こしてハッピーになってもらいたいと語り、セッションを締めくくりました。
いかがでしたでしょうか。LINE社のテックカンパニーとしての側面がお伝えできたと思います。詳細なレポートや資料、動画はLINE Engineering Blogで公開される予定です。ぜひこちらもご覧いただき、会場の雰囲気や盛り上がりを体感してみてください。