はじめに
こんにちは!MUSIC開発チームで就業型インターンをしています、餌打優太です(ポートフォリオサイト:https://euchi.jp/)。現在、東京工業大学情報理工学院の修士1年生です。今まではベンチャーでテックリードとしてエンジニア組織のマネジメント、そしてごりごり実装をしてきました。今回インターンに参加させていただきたかった理由としては、そのベンチャーでの業務で学びきれなかった「負荷を意識しなければいけない大規模なシステムの開発」、「確立された開発体制」を学びたいと考えたからです。
本インターンでは自分が音楽好きなのもあってLINE MUSIC関係の開発をしているMUSIC開発チームにお世話になりました。今回はその際に開発したバッチ監視ツールについてお伝えしようと思います。
背景
LINE MUSICアプリでは、プッシュ通知やアプリ内のポップアップを3rd PartyのSaaSを使って管理しています。そこで通知内容の改善をするために通知ログを使って分析をしようとしていました。しかしそのツール内の分析は24時間程度の短期間でしか行えず、長期的なユーザーへの効果は見ることができません。また、LINE社の共通データプラットフォーム(IU)に存在しているサービス利用履歴と紐づけた分析ができないという課題がありました。
そうした背景から、SaaSで管理されているデータを社内のデータプラットフォーム上で分析するため、データ転送するバッチ処理を行なっていました。データ分析チームによる長期間にわたるより効果的な分析を可能にしたかったためです。
そのようなバッチ処理を行う中で、バッチ処理が正しく行われているか定期的に確認したり、エラーの起きたバッチ処理を素早く特定することができると便利でした。背景が長くなりましたが、今回実装するのはタイトルにもある通り、そのバッチ処理の履歴を確認するツールの作成です。いつバッチ処理が行われたかを社内で見られるツールを用意するということです。このタスクはデータアクセスからAPI作成、フロントエンドの画面作成まで幅広く取り扱うので、とても勉強になりました。
設計
全体像
詳細に入る前に全体像についてお伝えします。あくまで今回の開発内容は管理画面なので、ざっくりと図を交えてご紹介します。
バッチ処理では、SaaSで管理されているデータが社内のデータプラットフォーム上にない場合、それを移すバッチ処理が実行されます。この際バッチ処理の履歴データが作成されます。バッチ処理自体は1時間ごとに行われますが、SaaSで管理されているデータは通常一日6ファイルなので、バッチ処理の履歴データとして残るのは1日6行となります。今回の管理画面では、この履歴データを取得して表示するようにしました。
仕様書作成
まず実装するに際して仕様書を社内用Wikiに作成する必要があります。
少し脱線しますが、最初社内Wikiの充実さに驚きました。社内用ツールや開発したものの説明はとりあえずWikiで検索したら見つかる状態で大変便利でした。一般にエンジニア組織は他の職種に比べて人の流動性が高いイメージを持ってて、このようにドキュメントがしっかりあると初めて入った人にも優しく、教育コストも下がって良さそうだなと学生ながら感じていました。
さて、実際に作成した仕様書は下の画像のようになっています。お恥ずかしながら、正直こんなに丁寧にドキュメントを書いたことはありませんでした。あらかじめ仕様書を作成しFBをいただくので、開発をしてしまった後に認識齟齬が見つかるということがないのが良いと思いました。
画像内の画面はMockデータを用意して作成したものです。FigmaやMiroのようなツールでワイヤーフレームを作成することもできましたが、今回はプロトタイピングで作成しました。プロトタイピングで画面構成案を作った理由としては、フロントエンドにBootstrapが用意されていてそれほど工数がかからないこと、またMockデータを実際に取得したデータに置き換えれば画面構成案のための実装をそのまま利用でき、手間が減ることが挙げられます。
画面の概要としては、上段にバッチ処理の行われた日付やバッチ処理の状態で絞り込む欄があり、中段に検索結果の一覧の表が表示されています。下段にはページネーションを用意しました。詳しくは後述しますが、当初はバッチ処理の状態で絞り込む機能は考えてませんでした。
このような仕様書を作成して、チームメンバーの方にFBをいただきました。そして修正が出来次第、実際に実装していきました。
実装
初めてのKotlinとSpring Boot
巷ではKotlinブームですが、自分はまだ触ったことがありませんでした。それどころか、KotlinがJVM上で動くことすら知りませんでした。ちょうどいつかは触れてみたいと思っていたので、とてもいい機会となりました。
私は今までLaravelやNest.jsなどのフレームワークを触ることが多くありました。そこで初めてKotlinやSpring Bootに触れてみると、ControllerやServiceなど、今まで自分が触れたことがあるフレームワークとの共通点も多く感じました。その一方、gradleやjarファイルの扱いは経験が浅かったため、理解に時間がかかりました。どのようにjarファイルを生成してデプロイしているのか、手元でどのように実行されるかを確かめながら理解していきました。またLaravelではアノテーションを使うことがなかったため、最初コードの理解に苦労しました。特にSpring Bootのアノテーションがたくさんコード中に出てきたので、最初はドキュメントを使いながらそれらの意味を調べコードを読み解いていく、という作業が多くなりました。
そしてチームレビューなどを通して、書き方のお作法についてもいろいろ教えていただきました。また同レポジトリ内に大量な良質なコードがたくさん書かれていたので、それを参考にしながら書いていきました。おかげさまで比較的すんなりコーディングすることができました。
パフォーマンスを考える
今回のインターンで技術的に学べてよかったと思うことのうちの一つが、パフォーマンスを考えた実装です。
具体的には、まず以下のようなバッチ処理の履歴を記録するテーブルを用意していました。
そしてバッチ処理の履歴を検索するために与えられた日付に一致するような日時のレコードを取り出す必要がありました。ここで私はレコードの日時を日付に置き換えて比較をしていました。
しかしレビューを通して、updated_atにindexが張られているような状況ではDATE関数を使って絞り込むべきではないとアドバイスをいただきました。なぜなら、これではupdated_atにDate関数を適用してしまっていたことでindexを使った検索が出来なくなってしまうので、BETWEENなどで絞り込むべきだからです。
試しに約10万行のデータを用意して実行時間を測ってみると、前者は242 msなのに対し、後者は29 msでパフォーマンスが大きく向上しました。
実際のテーブルは10年利用したとしても約2万件なので実用上それほど問題はないです。しかし、工数がかからずすぐパフォーマンスを向上できるものであり、自分の学習の意味合いも込めてパフォーマンスの良い綺麗な実装をするようアドバイスされました。
また余談ですが、データが数千行の場合はそれほど上の二つのSQL文で実行時間が変わらないということが起きました。もちろんデータ数が少ないためそれほど差が開かなかったということは考えられます。しかし、EXPLAEN句を使って両者の実行計画を見てみると面白いことがわかりました。
後者のBETWEENを使うSQLに対してEXPLAINを行ったところ、SQL文を実行するときに利用できるindexを示すpossible_keyは、どちらのデータ数の場合にもupdated_atが含まれていました。しかし、SQL文を実行するときに実際に利用したindexを示すkeyは、データが約10万行の場合のみupdated_atとなっていて、数千行の場合には特にkeyはありませんでした。チームメンバーと相談したところ、これはMySQLがデータ数が少ないときはindexを使うよりテーブルスキャンをしたほうが早いと判断したため、このようになったとわかりました。元からindexを使うようになっているのではなく、統計情報をもとによしなにOptimizerが実行計画を立てると知り、大変勉強になりました。
データ数が多い場合のEXPLAINの結果
データ数が少ない場合のEXPLAINの結果
話は逸れましたが、そのほかにもDBのインデックスなどパフォーマンスに関することを多く教えていただき、大変勉強になりました。ここはどうしても独学でやってると気が付きにくい部分ですし、規模が小さいシステムだとそれほど問題視されずに気がつかないので、今回のインターンで学べて良かったです。
実際の運用を意識した改善
ここまでひとまず実装は終わったのですが、後から完成した全体像を見返すとユースケース的に使いづらい部分があるのではないかと思いました。具体的にはバッチ処理のStatusによる絞り込みがなかったということです。
このツールのよくありそうなユースケースとして、「何かバッチ処理が正しく行われてなさそうで、エラーが起きているバッチ処理がどれかを調べたい」や、「定期的にエラーの起きているバッチ処理がないかどうか調べる」などが挙げられると思います。したがって両者でもエラーの起きているバッチ処理のみに絞って表示できると便利そうです。
そこで追加の修正として、Statusでの絞り込みをできるようにすることをするのはどうかと提案させていただいて、実装させていただくこととなりました。
今まで網羅的に実装してさせていただいたのもあって、それほど工数がかからず実装することができました。
テストの大事さ
先述しました改善で、それほど工数がかからずにできた別の理由として、テストをきちんと実装していたということが挙げられます。テストにより他の部分の動作が不用意に変更されていないかが保証されるため、あとから一部分だけ変えたいというときにとても役立つと思います。新たに修正する部分のコーディングにより集中しやすく、またバグも発生しにくいのが良いところだと感じました。
私個人の経験として、今までのベンチャーでの開発ではテストは作成していませんでした。理由としては開発がスピード勝負だったこと、教育コストが掛かることが挙げられました。が、今となっては「時間がないからテストできないのではない、テストをしないから時間がないのだ」という言葉にぐうの音も出ません。
そんなテスト初心者であったため、勉強していくにあたって何か良い本はないかチームメンバーにお聞きしました。その中で「テスト駆動設計」という本をお薦めしていただきました。その本を読みつつ、テストからコードを書き始めることを意識してタスクに取り組みました。また、レビューを通して網羅的にテストするにはあと何が足りていないかをアドバイスしていただきました。勉強するだけでなく、それを実践に移しFBをいただけたので、より理解を深めることができました。
結果
完成物
実際に完成したのものの挙動は以下の通りになりました。実際に動いているものを見るのはやっぱり達成感があります。
終わりに
感想
私は大規模なシステムの開発に携わったことがなかったため、当初は不安だらけでした。しかし、メンターの方やチームメンバーの方々に丁寧に手伝っていただきながら、無事少しずつ不安やわからないことが解けていきました。Slackに連投して質問する、ということもあったのですが、優しく一つひとつ教えていただきとても嬉しかったです。PRもただ修正を指摘されるだけでなく、Tipsなどをアドバイスされるなど丁寧にレビューしていただきました。
またパフォーマンスを意識した実装を学べたことが良かったです。RefinementやRetrospectiveなどのmtgを通して、スクラムの開発体制を体験することもできました。冒頭で述べたような課題に感じていて学びたいと思ったことが、このインターンで多く学ぶことができました。
今回のブログでは監視ツールの開発について取り上げましたが、その他にもSpring Cloud Data FlowやSpring Cloud Taskに関するタスクに携わらせていただきました。Kubernetes関連は大規模な開発でないとなかなか携わることができないので大変貴重な経験となりました。
6週間と長く短い期間でしたが、充実した夏休みを送ることができました。この経験を糧として、今後も頑張っていきます。