こんにちは!トレタでフロントエンドエンジニアをしている shira です。
この記事ではトレタO/Xというモバイルオーダー事業のうち、自分が関わっているtoCアプリで行ったパフォーマンス観点の対応について紹介します。
トレタO/Xに関しては、次の記事をご覧ください。
前提
トレタO/XではtoB向けとtoC向けのアプリケーションがあり、toBは共通、toCは各法人別という構成になっています。
この記事ではトレタO/XのtoCアプリのうち、塚田農場(エー・ピー・ホールディングス)様向けtoCアプリ (以下「toCアプリ」という。)の紹介をさせていただきます。
この記事で紹介するtoCアプリは2020年末から開発しているWebアプリケーションで、小さくリリースを繰り返し、改善してきました。
この記事では、パフォーマンス観点で行った対応の一部をご紹介します。
パフォーマンス観点の課題と対策
1. 画像が多く、読み込みに時間がかかる
課題
toCアプリでは全てのメニューに対して画像を表示しています。そのため、パフォーマンス観点で一番気にしていた点は画像の多さでした。パフォーマンスに影響することが明らかなため、初期リリース前に最低限の対応を入れたいと考えました。
対応方針
まず、画像サイズを小さくする方法を検討し、WebP を使うことにしました。
Googleによると、WebP に変換することで PNG は 26% 小さくなり、JPEG は 25〜34% 小さくなるとされています。期待大です。(参考:An image format for the Web)
WebPは基本的にモダンブラウザで利用可能ですが、対応していないブラウザのために WebP の他に PNG もしくは JPEG 画像を用意する必要がありました。
前提として、運用上変更が必要になる画像は全てContentfulに登録しています。メニューの画像も同様です。
最初は登録時にそれぞれ2種類の画像を登録する必要があると大変だな、と思っていたのですが、Contentfulだと簡単にできる方法がありましたので、その方法を紹介します。
対応方法
Contentfulでは画像のファイル形式を簡単に変更することが可能です。
参考までに、具体的なコードを記載します。
Content Delivery API でデータを取得した際、画像の情報は以下の形式で取得することができます。
(以下、ドキュメントより引用)
{ "fields": { "title": "Nyan Cat", "file": { "contentType": "image/png", "fileName": "Nyan_cat_250px_frame.png", "url": "//images.ctfassets.net/yadj1kx9rmg0/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png", "details": { "image": { "width": 250, "height": 250 }, "size": 12273 } } }, "metadata": { "tags": [ { "sys": { "type": "Link", "linkType": "Tag", "id": "nyCampaign" } } ] }, "sys": { "id": "nyancat", "type": "Asset", "space": { "sys": { "type": "Link", "linkType": "Space", "id": "yadj1kx9rmg0" } }, "createdAt": "2016-12-20T10:43:35.772Z", "updatedAt": "2016-12-20T10:43:35.772Z", "revision": 1 } }
fields.file.url
が画像のURLになります。
ファイル形式を変更したい場合、fm
パラメータを使います。WebPにするには、画像のURLに?fm=webp
をつけ [fields.file.url]?fm=webp
のようにするだけでOKです。
アプリケーションで画像を指定する際は以下のように指定しました。
<picture> <source srcset="//images.ctfassets.net/yadj1kx9rmg0/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png?fm=webp'" type="image/webp" /> <img src="//images.ctfassets.net/yadj1kx9rmg0/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png" alt="" /> </picture>
<picture>
要素内の<source>
要素に type="image/webp"
を指定し、srcset
には [fields.file.url]?fm=web
形式のURLを指定しています。img タグには WebP非対応ブラウザ向けに元々のURL fields.file.url
(PNG / JPEG 画像)を指定します。
これにより、WebPに対応しているブラウザではWebP形式の画像を表示し、そうでない場合は元々の画像を表示するようになりました。
さくっと対応できる上、効果が大きいので、とても便利でした!✨ ✨ ✨
今後の課題
WebPに対応することである程度改善できましたが、Contentful側に登録してある元々の画像が大きすぎるケースが発生しており、その点が課題です。画像を登録する方に気を付けていただく方法もありますが、同じ画像を異なるレイアウトで表示している部分もあるため、toCアプリ側での対策も検討したいと考えています。Contentful の Images API を使って、画像をリサイズできるため、デバイスやレイアウトに応じたサイズの画像を返すような改修を将来的に入れたいと考えています。 また、一部WebP未対応箇所があるため、その点も今後改修予定です。
2. 動画がすぐに再生されない
課題
toCアプリでは、注文完了時ランダムに画像や動画を表示する仕様となっています。
この機能の開発中、動画の再生が遅れるという問題が発生しました。 インターネットの速度が速い場合は問題なかったのですが、あまり良くない場合に発生するようでした。
対応方針
まず、動画素材の調整を行いました。 具体的には、動画の長さ調整や画質的に問題ない範囲での圧縮などを行いました。(この対応はデザイナーが対応してくださいました。)
アプリ側では事前読み込みが効果的ではないかという仮説のもと、試してみることにしました。 具体的には、注文完了時に表示する画像・動画をアプリ起動時にpreloadで全て取得するよう対応しました。(最初prefetchを使おうとしましたが、safariで使えないようなのでpreloadを採用しました。)
アプリ起動時にpreloadを行うことにしたのは、ユーザーが必ず開く画面だからです。
結果、注文完了画面遷移時、動画がスムーズに再生するようになりました 🎉 🎉 🎉
今後の課題
この対応によって、必要ではないファイル(画像・動画)も取得してしまうという課題は残ります。
また、前項に記載したWebP対応が preload 対応部分の画像に関しては対応できていません。
いくつか改善の余地は残っている状況ですが、その点は今後検討していきたいと考えています。
3. BFFを導入するとレスポンスが遅くなった
課題
アプリの要件が複雑になってきたこともあり、BFFを導入する方針となりました。 BFFへの切り替え対応を行い、Vercelのプレビュー環境で動作確認をしたところ、レスポンスが明らかに遅くなっていることがわかりました。
前提として、toCアプリは以下と通信を行なっていました。
- APIサーバー
- Contentful
- Firebase
BFF対応の際、APIサーバーへのリクエストとContentfulへのリクエストをBFF経由で行うようにしたのですが、そのうちAPIサーバーへのリクエストのレスポンスが明らかに遅くなってしまいました。 APIによっては、6秒弱かかっているケースもありました。
トレタO/Xは
toCアプリ <=> APIサーバー <=> 各バックエンドサービスという構成になっています。
BFFは、上記の図のtoCアプリとAPIサーバーの間に入るものとなっています。 具体的には Vercel の Serverless Functions を利用しています。 また、BFFでは一部情報をFirestoreにキャッシュして利用しているため、Firebaseとの通信も発生します。
対策
対策はいくつか考え、簡単に試せるものから試しました。 検討した案は以下になります。
- VercelのServerless FunctionsのRegionを日本にする
- まとめられるリクエストがないか見直す
- 処理中にスピナーを出す
- Firebaseをやめてupstash使う
結局上から3つを行いました。順番に、検討・対応した内容を紹介します。
VercelのServerless FunctionsのRegionを日本にする
Vercel の Serverless Functions の Region が sfo1
になっていたのを hnd1
に変更しました。sfo1
になっていたのはデフォルトのままにしていたためです。( 2021年1月14日より前に作成されたプロジェクトは、デフォルトで米国のサンフランシスコ(sfo1)になり、それ以降は新しいデフォルトの米国ワシントンDC(iad1)になるようです。 参考:Default Region)
APIサーバーは日本にあるため、この変更によりBFFとAPIサーバーの物理的距離が近くなりました。
結論、この対応は一番効果的でした。この変更により、元々の速度と大差なく動くようになりました 🎉 🎉 🎉
Regionの影響の大きさを感じました🌏
参考:Regions for Serverless Functions
まとめられるリクエストがないか見直す
リクエストを直列で飛ばすとユーザーを待たせる時間が長くなってしまうため、並列でリクエストを飛ばせる箇所がないか見直しました。すでにある程度対応済みでしたが、見直したところ2箇所程度まとめられるところがあったのでまとめました。具体的には Promise.all() を使って並列でリクエストを飛ばすようにしています。
処理中にスピナーを出す
これは根本的な改善ではありませんが以下理由で対応しました。
- BFFを挟んだことにより多少レスポンスが遅くなることはある程度仕方ないと考えられる
- そもそも、あらゆる要因によってレスポンスが遅くなる場合があるが、その際、ユーザーに対してフィードバックがないという課題があった
ちなみに、これまでの通信中の表示制御は以下のようになっていました。(フィードバックを得て改善する前提で対応していました。)
- APIとの通信時
- 体感でサクサクだったので、GETは読み込み中の表示はなし、POSTはボタンの連打防止対応を入れていた
- Contentfulとの通信時
- 体感でやや待たされると感じられるため、「読み込み中...」文言を画面中央に表示
この改善により、体験のばらつきやレスポンス遅延時の懸念がある程度解消されました。
Firebase をやめて upstash を使う
Firebase にキャッシュするのをやめて、upstash を使うと早くなるのでは?という案がありました。ただ、他プロジェクトで試した方によると、100ms くらい早くなるけど体感はあまり変わらないとのことでした。それに加え、VercelのServerless FunctionのRegionの変更によって大幅に改善していたため、この対応は見送ることにしました。
まとめ
写真を見てお腹が空いた方、O/Xが気になった方、ぜひ塚田農場様に足を運んでみてください😊
この記事では、開発中に見つかったパフォーマンス観点での懸念と対策についていくつか紹介させていただきました。
toCアプリを開発し始めて1年以上経過しました。まだまだ改善したいことはたくさんあります。最近では日々の機能追加に加え、週1日改善Dayを設ける取り組みをはじめました。長くメンテナンスしていくためにコードの見直しを進めているところです。
課題を見つけて改善するのが好きな方、一緒に改善しましせんか!