🚀

LINEのインターンで好き勝手やって成長した話

2023/09/26に公開

これは何?

LINEのインターンに参加してめっちゃ楽しく成長できた!、という思いを書き綴っているポエムです。よしよし頑張ったねみたいな暖かい心で読んでいただけると良いと思います。

何をしたか

2023年の夏に、LINEで13日間インターンをしてきました。

LINEのインターンと言っても様々あって、技術職、企画職、デザイン職など職種ごとに分かれていて、さらに技術職の中にも就業型とハッカソン形式のインターンに分かれていたりします。ボクが参加したのは技術職のハッカソン形式のインターン(京都)です。

https://linecorp.com/ja/career/newgrads/internship/engineering/hackathon

なぜ応募したか

今自分は大学1年生(B1)で、世間的に見ればインターンに参加するには早い年齢だと思っています。それでも応募したのは、高校生の時から周り(ついっただったもので観測する範囲も含む)が結構インターンに行っていて、自分も行きたいな〜と思いながら受験生していたという経験があり、大学入ったらインターンするぞ!という意志があったからです。

バリバリのエンジニアなので参加するなら技術職なのは自明ですが、なぜハッカソンの方に応募したのかというと、そっちの方が期間が短かったからです。この夏は色んな会社の短期インターンをやりたいな〜と思っていて、2ヶ月しかない夏休みで就業型を入れるとそれだけで夏休みが終わっちゃうなと思ったので期間の短いハッカソン形式の方に応募しました。でも日程被りとかで行けたとしても2つぐらいだった上に、そもそもLINE以外落とされてしまったのでなんとも。

どんな感じのインターンか

13日間でハッカソンをやるわけですが、最初の3日間は入社に関わる手続きとか、技術オリエン(環境構築とか)などをやっていました。さらに最終日はLINEの京都開発室に出社して、成果発表会と懇親会をやっていたので、実質的な開発期間は9日間でした。

ハッカソンといえばお題が出されて、いい感じにアプリやらを作るという感じだと思いますが、今回のインターンではお題というよりも要件定義みたいな感じで作るものが示されました。

そのお題はざっくり言えば出前館とかUber Eatsみたいなサービス(クイックコマースって言うらしいです)のドライバー向けアプリで、それに関して〇〇ができること、みたいな要件がいくつか並べてあるみたいな感じでした。ただ要件を全部満たす必要があるわけではなく、作りたい機能があったらそっちを優先しても良いという感じでした。ドライバー向けアプリということでスマホから使えることが条件になってましたが、形態は問わず、WebでもiOSでもAndroidでもOKという感じでした。

インターン生4人でチームを組んで、全部で3チームできてました。別に競争じゃないよ~と言われていたので割と気楽でした。

開発の流れ

初日

開発初日はチームで趣味とか得意なこと、やりたいことを共有しました。ボクはフロントの技術を推して選考を通過したんですが、なんかその時はバックエンドを書きたい欲が高まっていた[1]のでGoでバックエンド書きたいです!パフォーマンスチューニングしたいです!みたいなことを言ってました。そしたらもともとボクはフロント人間として数えられていたにも関らず、なんかいい感じに分かれた結果ボクはバックエンド側に入れました。良かった。

まずチームとしての方向性決めとか開発計画を立てるところからスタートしました。チームとしては、楽しくやろうというのと、みんながやりたいことをできると良いねというのを目指していた気がします(うろ覚え)。開発計画としては松竹梅プランという感じで、最低限実装したいこと(梅)、次にやりたいこと(竹)、余裕があったらやること(松)に分けて書きました[2]。それをFirst Reviewとしてメンターの社員さんに見てもらって、もっとストーリーがあるといいよねみたいなアドバイスをもらいました。

2日目

開発計画を立てたら次はチーム全員でAPI設計をしました。これが決まらないとバックエンドは何もできないし、フロントエンドもやりづらいので最初にやろうという感じです。OpenAPI形式でYAMLをゴリゴリ書いて定義しました。OpenAPIって結構ネスト深くなるわりにYAMLってネスト見にくいからあんまり向いてなくない?と思ったんですが、まぁ動くのでヨシという感じですね。API定義と同時にSwagger UIをデプロイして最新のAPI定義をブラウザ上で確認できるようにしました。API定義は色んな都合で随時修正されるので、常に最新の状態が見やすい状態で確認できるというのは後々結構ありがたかったです。

3~5日目

API設計が終わったあたりからフロントエンドとバックエンドで完全に分業して進めていきました。ボクはバックエンドを担当していたので、バックエンドの方について詳しく書いていきます。

技術スタック的にはボクがやりたいと主張したGo[3]で、WebフレームワークとしてGinを使いました。

バックエンドは2人で進めていたんですが、メンバーのもう1人が結構バックエンド慣れしてる感じ[4]だったので、リポジトリとか環境のセットアップを全部丸投げしてしまいました。結局自分も後々インフラいじいじとかして関わったので許して…

環境構築出来たら、とりあえずDB設計を2人で一緒にやってました。今回はgormというORMを使おうという話だったので、gormのモデルを使って定義してって感じですね。このモデルは開発が進んでもそんなに大きく変わることはなかった気がします。保存する情報増えたからカラム増やすかぐらいな感じですかね。

技術オリエンでDBの話もあって、そこで値がENUMっぽくなるカラムはENUM型にするより定数テーブル作って外部テーブル参照制約を付けた方がいいよ、という話をされたのでそっちで作ってみました。これのうまみとして、後から選択肢を増やしたりするときに型を書き換える必要がない(migrationの必要がない)というところがあるんですが、今回はENUMの値が増えることはなかったので特にこのうまみは感じられませんでしたね。というか開発段階ではmigrationしまくっていたので、このうまみが出てくるのはハッカソンではなく、実サービスの運用段階なのではないだろうか、と思いました。

ここまででだいたい2日ぐらいです。計画とか設計に時間かけすぎじゃね?という感じがあってちょっと焦ってました。

DB設計終わったらあとはAPI実装ガリガリという感じです。API実装はかなり順調に進みました。Repository層を挟んだことで、後半になるにつれて実装スピードが上がっていったような気がします。最終的に実装したエンドポイントは確か22コでした。

6~9日目

1週目でとりあえずのものを作り終えたので、2週目は自分のやりたかったパフォーマンスチューニングをやっていました。パフォーマンスチューニングなんて何からやればいいのか全く分からないので、最初はプロファイラとか調べて導入してみたりとか、スロークエリログの取り方調べてやってみたりとか、メンターさんに教えてもらった負荷試験ツールを試してみたりとかしてました。

色々触ってみて、最終的には検証用のVMからLocustという負荷試験ツールでアプリに負荷をかけつつ、アプリのログとかスロークエリログを適当に見てヤバそうなところを改善するというサイクルを回してました。今回は、というかアプリ全般に言えそうなことですが、アプリケーションのパフォーマンスよりDBのアクセスとかが律速になることがほとんどなので、pprofみたいなプロファイラは導入してはみたんですが、あんまり参考にしませんでした[5]

負荷試験をするとエンドポイントごとのレスポンス時間の平均とかP90とかが得られるわけですが、別に特定のエンドポイントが異常に重いという感じではなく、全体的に遅いしエラーも発生しているという感じで、何を改善すればいいんだ…という気持ちになっていました。

とりあえずエラーの原因を調査していると、DBへの接続時に too many connections エラーが出ていて落ちているのが原因とわかりました。全体的にクエリがバカみたいに遅くて(primary keyによる1行SELECTが10s超えるレベル)、たぶんそれでコネクションが占有されるんだろうなぁと予想できました。なのでDBアクセスを高速化すれば自然とエラーもなくなるはず、という仮説を立ててDB高速化をしました。

DBアクセスの遅さを色々調査したところ、gormのPreloadがめちゃめちゃに重いということがわかったので、Preloadをやめる方法を模索しました。しかし自分でコネコネしてみてもよくわからず、メンターに聞いてみてもイマイチわからず、結局SQLを頑張って呼ばないようにしようという方針で行きました。

配達アルゴリズムのところで注文を全件毎回読み込むと遅いので事前にマッチングさせておいて結果をキャッシュするとか、さらにその結果のキャッシュを読む前にマッチングしているかどうかをRedisにキャッシュするとかして、ひたすらDBの読み書きを減らす方向で改善をしました。するとパフォーマンスは劇的に改善して、目標に掲げていた5000人の同時アクセスもなんとか捌けるぐらいになりました。「DBアクセスを高速化すれば自然とエラーもなくなる」という仮説通りエラーもなくなりました。

あとは適当にVM増やしてLoad Balancerで良い感じにしたりしてました。特段意識したわけではないんですが、状態を全部DBに押し付けていたのでサーバー自体はステートレスで、難なく複数台構成にできました。

そして改善を続けているとサーバー側が強くなりすぎて負荷をかける方のスペックが足りなくなってきました。幸いにもLocustはmaster/worker構造が簡単にできるような仕組みがあるのでサッと作りました。でもそもそもLocustのパフォーマンスが良くないという話もあり、まだまだ改善の余地はありそうだなぁと思いました。でも初めてこういうことやった割には結構上出来なんじゃないかと勝手に思っています。思ったからこの記事を書いています。

最終発表日

準備

最終日の前日から資料作成をしていました。お得意の資料作成スキルを発揮して、イケメンなスライドを作ってました[6]。当日の朝ぐらいに完成して、まぁ何とかなったねという感じです。ちょっと時間が余ったので、負荷計測してたりしてました。ベンチマークは回しているだけで楽しいのだ。

負荷をかけているせいなのかはわからないですが、本番サーバーが上手く動かないという事態に。でも負荷止めてもずっと上手く動いてなかったので原因は別な気も?ただ発表までに直せなかったので発表のデモはローカルにサーバーを立ててやってもらいました。

発表

という感じで準備段階でちょっとしたトラブルはあったりしたんですが、発表自体はまぁまぁ上手くいきました。デモもしっかり動いてたし、伝えたいことを伝えられた感があってよかったです。

他のチームの発表も聞いてたんですが、みんなレベル高くて流石という感じです。あとそれぞれ着目するポイントが違ったりして面白かったです。

コードレビュー会

結果発表の前にメンター陣によるコードレビュー会みたいなのが開催されてました。その場で見るというよりは、事前に見てもらって気づいたこととかをシェアしてもらうみたいな感じです。Goのエラーハンドリングがびみょいみたいな話が印象的でした。印象的だったのでここに書き残そうと思います。

Goはまず設計として例外が存在しないので、エラーが起きるかもしれないメソッドは返り値に Error 型の値を入れて、それが nil でないときにエラーが発生したということを知らせます。そしてGoの思想として、エラーは返して上に持ってきて、一番上かミドルウェアで処理することが推奨されています。

https://go.dev/blog/error-handling-and-go

それに加えて、今回はMVCモデル[7]みたいな構成なので、Model層ではModel層のエラー(gormのエラー)を返して、それをRepository層で色々処理して、Repository層で定義したエラーを返す、それに対してController層ではController層で定義したエラーを返す、最後にGinのミドルウェアかどこかでエラーハンドリングして、HTTPレスポンスなりを返す、という設計が良いね、という話でした。

でも全体的な設計とか、コードのキレイさとか、READMEの充実度とかは結構褒めてもらえました。全部ボクがやったことじゃないけど。もう1人のメンバーが神過ぎた。

一方でフロント側はボクあんま見れてなかったんですが、結構やばやばなコードが生産されてたらしくて、もっと見とけばよかったな~みたいな気持ちになりました。

結果

結果として完成度賞とパフォーマンス賞を受賞できました。嬉しいです。特に自分が頑張ったパフォーマンスのところが評価されたのが一番うれしかったです。良くわからないですが受賞したのは初?らしいです。

あとで懇親会とかで話を聞いてみたら、パフォーマンス計測だけじゃなくてボトルネックを特定して改善するというサイクルを回せたというところが高評価だったらしいです。嬉しい。

完成度賞とパフォーマンス賞を受賞した表彰状兼盾

学び・反省

バックエンドの設計的なところは自分は全然詳しくなかったんですが[8]、今回のインターンを通してちょっとわかった気がします。気がするだけかもしれませんが。

例えば今回はControllerからModel(DB)にアクセスするときにRepositoryを経由して呼び出す、という設計にしていたんですが、最初はこのRepository層の存在意義がイマイチ分かってなかったです。DBアクセスは再利用しやすい関数の組み合わせでやる[9]より、一気にガッとやった方がパフォーマンスが出たりする[10]ので、Repository層を挟むことによる抽象化の恩恵ってあんまり大きくないんじゃない?と思っていました。

開発を進めていってわかったことは、抽象化はそこまでされないけど、共通化はある程度されました。○○IDから○○を取得するみたいな一般的なユースケースは結構使うので、そういうところの共通化ではかなり役立ちました。

最終日にメンターの社員さんに色々聞いたところによるとテスタブルになる効果とかもあるらしいですね。DBをモックするのは大変だけどRepositoryをモックするのは比較的簡単なので、Modelへのアクセスに全てRepositoryを噛ませることによってテストしやすくなるらしいです。

開発する中でテストは書きたいなと思ってはいたのですが、時間もないしDBとサーバーの扱いをどうするかがよくわからなかったので結局人力でテストしてました。そういうところの経験とか知見とかがまだまだ足りないなぁと感じました。

あと最後の方はボクがパフォーマンスチューニングにお熱になっていたせいで他のことを結構人に投げていたのは申し訳ないなぁと思う気持ちがあります。それでもパフォーマンスチューニングまだまだいけた!という感じがするので奥が深いなぁと思います。今回は5000人の同時アクセスで負荷をかけて、RPS(Request per Second)はだいたい2000ぐらい出せていたのですが、世の大規模サービスからしたらまだまだだよねという感じですね。

感想

今回初めてのインターンだったんですが、非常に学びが多くて楽しいインターンだったと思います。バックエンドについての理解がグッと深まった上に、実際に使ってみるという経験が積めました。それだけでなく、LINEのプライベートクラウドを使ってインフラ構築も含めたパフォーマンスチューニングの経験を積めたのも良かったと思います。でも結局負荷試験でしか確かめてないので、実際に何千人とか何万人とかが使ってるようなサービスのチューニングもやってみたいなという気持ちになりました。あるいは自分でそういうサービスを作るか。 どっかのインターンとかに申し込んでみようかな。

ボクはバックエンドという普段触っていない領域に突っ込んでいく材料として利用しましたが、普段触っていたフロントを触っていてもきっと良い機会になったんだろうなと思います。経験豊富なメンターがほぼ常駐体制でサポートしてくれる環境なんてそうそうないので、すごい貴重な機会だったんだなぁと。こんなボクを拾ってくれてLINEさんには感謝や、、、

さいごに

一緒に頑張ってチームメンバーのみんな、特にボクの適当な開発を支えてくれたバックエンドチームのメンバー、分かりづらいであろう質問にも頑張って答えてくれたメンターのみなさん、色々と陰で支えてくださったHRのみなさんにこの場を借りて感謝したいと思います。ありがとうございました!!

脚注
  1. そろそろISUCONの参加登録の時期で、チームを組み終わったぐらいのタイミングだったので、その影響もありそう ↩︎

  2. 松竹梅って松→竹→梅の順でグレードアップすると思ってたのは内緒 ↩︎

  3. もう1人のバックエンドのチームメンバーもGoでやってみたいという感じで見解一致していたので、決して自分の意見を無理やり押し通したわけではない ↩︎

  4. 自分が全然慣れていなかったので、相対的にそう見えるだけという説もある ↩︎

  5. という感じで結局ほとんど使ってないので未だ使い方がよく分かっていない ↩︎

  6. Google Slidesを使える環境ではなかったのでWeb版Office(PowerPoint)を使って作業していたが、日本語入力しようとすると最初の文字消えるし、フォントがダサいし、挙動が怪しいしでかなり嫌だった ↩︎

  7. ただしViewはフロントエンドに押し付けているのでバックエンドはModelとControllerだけしか持たない ↩︎

  8. そんなに触っていないので当たり前 ↩︎

  9. 関数型プログラミングのやり方(だと思っている) ↩︎

  10. 例えばShopにItemが複数属している状況を考えてみる。ShopのItemを全件取得したい場合はJOINを使ってN+1問題を回避することが推奨される。しかしItemを1件だけ取得したいときはそれとは別でクエリを書く必要がある。ItemIDからItemを取得するクエリを発行する関数を作った方が共通化できる部分が多く抽象化が進むが、パフォーマンス的に問題がある。 ↩︎

GitHubで編集を提案
traP

Discussion