LINE株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。 LINEヤフー Tech Blog

Blog


【インターンレポート】Recoilを使った「パフォーマンスランキング」のキャッシュ化

こんにちは、日本大学工学部2年の平林知明です。8月8日から6週間、LINEの就業型インターンに参加させていただきました。
この記事では、今回私が実装した機能と、インターン期間を通じて学んだことについて紹介します。

LINE証券とは?

今回、私はLINE証券の開発をしているフィナンシャル開発センター 開発1室に所属させていただきました。
LINE証券は、LINE証券が提供するスマホ投資サービスです個人的なおすすめポイントは株を一株単位から購入できて、高額な投資がなかなかできない学生でも始めやすいところです!(私も使っています!!)
Frontendチームでは、技術スタックとしてReact+TypeScriptが採用されています。

今回行ったこと

今回、私はインターンシップの期間で、APIから取得した「運用パフォーマンスの高いユーザのランキング(以下、パフォーマンスランキング)」のデータをブラウザ上に一定期間保存(キャッシュ)することで、読み込みを最適化する機能を実装しました。

パフォーマンスランキングとは?

パフォーマンスランキングとは、マイページ(ユーザーの運用成績やリアルタイムの取引履歴が公開されるページ)を公開しているユーザーの、1年間(または1ヶ月)の損益率をランキング化したものです。
パフォーマンスランキングから運用実績の良いユーザーをさがすことで、そのユーザーの保有している株や取引履歴を確認することができ、ユーザー同士が実践しながら投資を学ぶことができるようになります。

実際のパフォーマンスランキング画面です

目的

パフォーマンスランキングのデータをキャッシュする目的として、「First View(ユーザーがページを開いたときに最初に表示される領域)の描画を高速化する」ということが挙げられます。
パフォーマンスランキングは「トップページ」「ユーザーをさがす」「今月(今年)のパフォーマンスランキング」の3つのページで表示されるのですが、キャッシュをしなかった場合、データの取得はページが表示されるたびに行われます。

しかし、APIが保持するランキングデータの更新はリアルタイムで行われているわけではないので、毎回データを取得する必要性はありませんでした。
そこで、今回は取得したデータをブラウザ上にキャッシュすることで問題の解決に取り組みました。

実装方針の検討

実装方針は主にメンターの方とオンラインのホワイトボードを使って相談しながら決めました。そこでは、主に以下の2点が議題に上がりました。

取得するデータの数はどうするのか?

パフォーマンスランキングの表示データ数はページによって違うため、毎回キャッシュする件数はどうするのか?ということを考える必要がありました。
それぞれのページへの導線と必要なデータの件数は以下のとおりです。

この課題を解決するために、2つの案が挙がりました。

  • 必要となる最大件数分(100件)のキャッシュを取得する
    • メリット
      • 1回データを取得してしまえば、キャッシュの有効期限内は新しくデータを取得する必要がなくなる
    • デメリット
      • 5件や10件しか表示しないページでも100件のデータを取得するようになるので、通信量が増えてしまう
  • 必要な分だけのキャッシュを毎回取得する
    • メリット
      • 今よりも通信量が増えることは絶対にない
    • デメリット
      • せっかくキャッシュをしても、キャッシュされている数よりも多くのデータが必要になった場合は再度データを取得する必要がある

これはキャッシュの効果に直接作用することなのでかなり迷いましたが、結果的に、必要な分だけのキャッシュを毎回取得することになりました。
理由は2つあります。

まず1つ目に、本来10件のデータしか必要としない上にPVが非常に多いホーム画面で100件のデータを取得してしまうのは、余計な通信量の増加を招いてしまうからです。
たとえホームで100件のデータを取得しても、その後に取得したデータを使用するページ(具体的には、「今月(今年)のパフォーマンスランキング」)へ遷移するユーザーは一部だけなので、ほとんどのユーザーは通信量が増えるだけになってしまいます。

2つ目は、たとえキャッシュが不足していたとしても、APIからデータを取得するまでの間はある分のキャッシュを表示することで、FirstViewの表示は改善できるからです。
URL直打ちでなければ、大抵の場合はパフォーマンスランキングに辿り着くまでにトップページを経由してくるので、上図の通りキャッシュは10件あることがほとんどです。
iPhoneXでパフォーマンスランキングを開いた時、下図のようにFirstViewに必要なデータの件数は7件ですので、キャッシュの有効期限内であればFirst Viewの領域は確実に遅延なしで読み込むことができます。

キャッシュの有効期限はどうやって設定するのか?

何をキャッシュするかに加えて、いつまでキャッシュするのかも決める必要があります。
ランキングの順位変動は1日1回更新となっています。加えて、ユーザによる自身のマイページ公開要否の設定を一定のサイクルで反映しています。ですので、同一サイクル内では同じデータが返ってくる仕様になっています。

有効期限の設定手段については以下の3つが挙げられました。

  • データの有効期限をAPIのレスポンスに含めてもらう
    • メリット
      • キャッシュが最新のデータであることが保証される
      • 通信回数の最小化も期待できる
    • デメリット
      • サーバーサイドの方にも協力していただかないと実現できない
  • フロントエンド側で一律にn分の有効期限を設定する
    • メリット
      • 一番簡単にできる
    • デメリット
      • 古いデータが表示されてしまう期間ができてしまう
  • 新しいデータを毎回取得して、キャッシュはサーバーからデータを取得できるまでの間表示するだけにする(いわゆるSWR)
    • メリット
      • 目的を達成する正攻法で、フロントエンドの作業のみで実装が可能
    • デメリット
      • サーバーとの通信量は減らない

これらを検討した結果、

  • 毎回のデータ取得ごとの内容の差異があまりないから
  • 証券サービスとしての性質上、パフォーマンスランキングは取引に直接影響する情報ではないため、チャート等の他のデータと比べて常に最新のデータを表示する必然性が薄いから
  • 今後同じような実装をしようとした時、サーバーの仕様上有効期限が取得できないデータもあるので、他の実装との整合性がなくなってしまうから

以上の3つの理由により、フロントエンド側で一律にn分の有効期限を設定する方法を採用しました。

実装

仕様がある程度固まったところで、次は実装に入りました。
全体的な処理の流れは以下のの通りです。

キャッシュを保管するために使用する状態管理ライブラリには、すでにプロジェクトの他の部分で使われていたこともあり、Recoilを採用しています。
(LINE証券におけるRecoilの利用については、詳しくはこちらの記事をご確認ください!)

処理の大まかな流れを説明すると、

  1. ランキング表示モジュールは、ランキングデータ管理Hookにデータを要求する
  2. ランキングデータ管理Hookは、ランキングデータ保存Hookに、キャッシュデータが存在するか確認する
  3. ランキングデータ保存Hookは、有効なキャッシュデータを持っていたらランキングデータ管理Hookへ返す
  4. ランキングデータ管理Hookは、返ってきたキャッシュデータが必要な件数分あればランキング表示モジュールにキャッシュを返して終了
  5. そうでなければ、ランキングデータ取得HookはAPIにリクエストを送り、取得データをランキングデータ管理Hookに返す。
    1. 読み込み中の間、もし部分的にでもキャッシュが存在すればランキング表示モジュールにキャッシュを返す。
    2. 読み込みが完了したら、読み込んだデータをランキング表示モジュールへ返し、新しくキャッシュする。

といった感じです。

ランキングデータ管理フックが返すデータによって、実際に以下のような表示になります。

実装するときに気をつけたことは、キャッシュ化以前に呼び出していたものと同じ引数・返り値にすることによって、今までと変わらないインターフェースで呼び出せるようにしたことです。
これにより、呼び出し側の処理を変更せずにキャッシュ化の恩恵を受けられるようになります。もし今回のキャッシュ化する処理が使えなくなってしまった場合でも、簡単に今までの処理方法へ戻すこともできます。

結果

キャッシュ実装前後の比較です。キャッシュがなかった場合と比べてランキングデータを取得する時間が無くなったので、大幅にFirstViewが向上しました!
(差を分かりやすくするため、Chromeで6x slowdown, Fast 3Gを指定して測定しています。)

以下はテスト環境で実際の速度を計測したデータです。

キャッシュなしの場合FirstViewの表示に約120msかかっていますが、キャッシュがある場合は約50msまで短縮することができています!

おわりに

今回LINE証券のフロントエンド開発に携わらせていただいて、FrontEndチームではReactやTypeScriptなどの技術をチームで使用することによって得られる恩恵を最大限引き出そうという考えを持っている方がとても多いと感じました。そういった考えの中で、新しい技術だから使うというのではなく、使うことによってより良いプログラムを書けるようにするという意識を持つのはとても大切なことだということを学びました。また、そういった恩恵を引き出し、長期的に使用することができるプログラムを作るための知識を学ぶことの大切さも知ることができました。

今まで私はチームでの開発経験がなく、自分しか触らないコードだから動けばいいや程度の気持ちでコードを書くことが多くありました。しかし、作って終わりではなく、広く世に広がるサービスを作りたいと感じたときにはそのようなプログラムの書き方では実現できないということを、今回実際に広く世に広がっているサービスの開発に携わることで学ぶことができました。

LINEでは、知識を共有する場としてチームメンバー同士のコードレビューや勉強会などの機会が多く設けられています。
働くことでより成長することができる環境が整っていて、チームで互いに技術を高め合える、そんなチームでインターンをさせていただけたのはとても良い経験になりました!

おまけ

今回「結果」セクションでキャッシュ実装前後の比較動画を掲載しましたが、動画を作るためのソフトを持っていなかったので新しく作りました!
以下のような流れで動作しています。

  1. 比較する2つの動画をvideoタグで読み込む
  2. videoタグの内容をcanvasにレンダリング
  3. canvasのcaptureStreamメソッドを使って動画化
  4. MediaRecorder APIで録画(webmで出力)

あたらめてJavaScriptで出来ることの幅の広さに驚きました...!