GrabがAndroid向け画像キャッシュを時間考慮型LRUで最適化
Grabのエンジニアは、Androidアプリの画像キャッシュ管理を改善するために、Least Recently Used (LRU) キャッシュからTime-Aware Least Recently Used (TLRU) キャッシュへ移行し、ユーザー体験を低下させず、サーバーコストを増加させずに、ストレージをより効果的に再利用できるようにした。
キーポイント
キャッシュ戦略の移行
GrabはAndroidアプリの画像キャッシュ管理を改善するため、従来のLRUキャッシュから、時間を考慮したTLRUキャッシュへ移行した。
改善されたストレージ管理
この移行により、ストレージをより効果的に再利用(reclaim)できるようになった。
ユーザー体験とコストの維持
ストレージ管理の改善は、ユーザー体験の低下やサーバーコストの増加を伴わずに達成された。
影響分析・編集コメントを表示
影響分析
この記事は、大規模なモバイルアプリケーションにおけるリソース管理の実践的な最適化事例を示している。特定の企業(Grab)の技術的改善に焦点を当てており、業界全体を変えるような広範な影響は限定的だが、パフォーマンスとコスト効率の両立を目指すエンジニアリングチームにとって参考になる知見を提供している。
編集コメント
実務レベルの技術的改善に焦点を当てた、具体的で実用的な記事。大規模アプリのパフォーマンスチューニングに関心のある開発者にとって価値がある内容。
Grab のエンジニアは、Android アプリ内の画像キャッシュ管理を改善するため、従来の Least Recently Used (LRU) キャッシュから Time-Aware Least Recently Used (TLRU) キャッシュへと移行しました。これにより、ユーザーエクスペリエンスの低下やサーバーコストの増加を抑えつつ、より効果的にストレージを解放できるようになりました。
Grab の Android アプリでは、画像読み込みの主要フレームワークとして Glide が使用されており、ネットワーク呼び出しの削減、ロード時間の短縮、サーバーコストの低減を図るために、ローカルに画像を保存する LRU キャッシュが組み込まれていました。しかし、分析結果によると、100 MB の LRU キャッシュを使用することには重大な欠点がありました。多くのユーザーにおいてキャッシュがすぐに満杯となりパフォーマンスが低下する一方で、キャッシュサイズ制限を超えない限り画像が数ヶ月間キャッシュされたままになり、ストレージを無駄に消費するというケースも発生していました。
これらの制約を回避するため、彼らは LRU に時間ベースの有効期限機能を追加することを決定しました。TLRU は 3 つのパラメータを使用します。1 つ目は Time To Live (TTL) で、キャッシュエントリがいつ有効期限切れとみなされるかを決定します。2 つ目は最小キャッシュサイズしきい値で、キャッシュが未充足状態にある場合でも重要な画像が有効期限を迎えてもキャッシュされ続けることを保証します。3 つ目は最大キャッシュサイズで、ストレージの上限を強制するものです。
TLRU をゼロから実装するのではなく、Grab のエンジニアは Glide をフォークし、その DiskLruCache 実装を拡張することを選択しました。これにより、「成熟した、実戦で検証された基盤」を活用しています。
この実装は Android エコシステム全体で広く採用されており、クラッシュからの回復、スレッド安全性、パフォーマンス最適化といった、複製するには多大な労力を要する複雑なエッジケースを処理します。
DiskLruCache は、最終アクセス時刻の追跡機能の追加、時間ベースの排除ロジックの実装、既存ユーザーキャッシュ用の移行メカニズムの組み込みという 3 つの次元で拡張する必要がありました。
キャッシュエントリを最新のアクセス順にソートするためには最終アクセス時刻が必要であり、アプリの再起動を超えて永続化されなければなりませんでした。時間ベースの排除ロジックは、各キャッシュアクセス時に実行され、エントリを削除する前に最も最近アクセスされていないエントリが期限切れになっているかを確認します。既存キャッシュの移行における主な課題は、LRU エントリに最終アクセスタイムスタンプを割り当てることでした。ファイルシステム API には信頼できる情報源が存在しなかったため、Grab のエンジニアたちは移行タイムスタンプをすべてのエントリに割り当てることを決定しました:
このアプローチはキャッシュされたコンテンツをすべて保持し、一貫したベースラインを設定しますが、排除の完全な恩恵を実感するには TTL(Time To Live)期間の待機が必要となります。また、双方向互換性も確保しました。元の LRU 実装ではタイムスタンプ接尾辞を無視して TLRU ジョーナルファイルを読み込むことができるため、必要に応じて安全にロールバックできます。
もう一つの課題は、制御された実験に基づいて最適な設定値を見つけることでした。
私たちの成功基準は、TLRU への移行中にキャッシュヒット率が最大でも 3 パーセントポイント(pp)減少することです。例えば、キャッシュヒット率が 59% から 56% に低下した場合、サーバーリクエスト数は 7% 増加します。この閾値は、ストレージの最適化と許容可能なパフォーマンスへの影響とのバランスを取っています。
このアプローチを用いることで、アプリユーザーの 95% がキャッシュサイズを 50MB 削減し、上位 5% のユーザーではさらに大きな節約効果が見られました。これらの結果に基づき、Grab のエンジニアたちは、キャッシュヒット率を許容範囲内に保ちつつサーバーコストを増加させることなく、端末全体でテラバイト単位のストレージを回収できると推定しています。
元の投稿には、ここでは取り上げきれない LRU の動作や TLRU 実装に関する非常に貴重な詳細情報が含まれています。完全な内容を把握するためにも、必ず原文をお読みください。
著者について
セルジオ・デ・シモーネ
セルジオ・デ・シモーネはソフトウェアエンジニアです。セルジオは、シーメンス、HP、そして小規模なスタートアップなど、多様なプロジェクトや企業で25年以上にわたりソフトウェアエンジニアとして活動してきました。過去10年以上にわたって、彼の専門分野はモバイルプラットフォームおよび関連技術の開発に焦点を当てています。現在、彼はBigML, Inc.で勤務し、iOS および macOS の開発を率いています。
もっと見る 表示しない
原文を表示
To improve image cache management in their Android app, Grab engineers transitioned from a Least Recently Used (LRU) cache to a Time-Aware Least Recently Used (TLRU) cache, enabling them to reclaim storage more effectively without degrading user experience or increasing server costs.
The Grab Android app used Glide as its primary image loading framework, including an LRU cache to store images locally in order to reduce network calls, improve load times, and lower server costs. However, analytics showed that using a 100 MB LRU cache had significant shortcomings: it often filled up quickly for many users, leading to performance degradation, while in other cases, images remained cached for months if the cache never exceeded the size limit, thus wasting storage.
To circumvent these limitations, they decided to extend LRU with time-based expiration. TLRU uses three parameters: Time To Live (TTL), which determines when a cache entry is considered expired; a minimum cache size threshold, which ensures essential images remain cached even when they expire if the cache is underpopulated; and maximum cache size, which enforces the upper storage limit.
Instead of implementing TLRU from scratch, Grab engineers chose to fork Glide and extend its DiskLruCache implementation, leveraging its "mature, battle-tested foundation."
This implementation is widely adopted across the Android ecosystem and handles complex edge cases like crash recovery, thread safety, and performance optimization that would require substantial effort to replicate.
DiskLruCache needed to be extended along three dimensions by adding support for tracking last-access time, implementing time-based eviction logic, and including a migration mechanism for existing user caches.
Last-access times were required to sort cache entries by their most recent access and had to be persisted across app restarts. The time-based eviction logic ran on each cache access to check if the least recently accessed entry has expired before removing it. For existing cache migration, the main challenge was assigning last-access timestamps to LRU entries. Since filesystem APIs did not provide a reliable source, Grab engineers decided to assign the migration timestamp to all entries:
This approach preserves all cached content and establishes a consistent baseline, although it necessitates waiting one TTL period to realize the full benefits of eviction. We also ensured bidirectional compatibility - the original LRU implementation can read TLRU journal files by ignoring timestamp suffixes, enabling safe rollbacks if needed.
Another challenge was finding optimal configuration values, which were based on controlled experiments.
Our success criteria is for a cache hit ratio decrease of no more than 3 percentage points (pp) during the transition to TLRU. For instance, a decrease from 59% to 56% hit ratio would result in 7% increase in server requests. This threshold balances storage optimization with acceptable performance impact.
Using this approach, 95% of the app users saw a 50MB reduction in cache size, with the top 5% seeing even larger savings. Based on these results, Grab engineers estimated that they could reclaim terabytes of storage across devices while maintaining the cache hit ratio within acceptable limits and without increasing server costs.
The original post provides highly valuable details on LRU behavior and TLRU implementation that cannot be covered here. Make sure you read it to get the full details.
About the Author
Sergio De Simone
Sergio De Simone is a software engineer. Sergio has been working as a software engineer for over twenty five years across a range of different projects and companies, including such different work environments as Siemens, HP, and small startups. For the last 10+ years, his focus has been on development for mobile platforms and related technologies. He is currently working for BigML, Inc., where he leads iOS and macOS development.
Show moreShow less
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み