GlideのDiskLruCacheの実装を読む

この記事では、Glideで使用されている DiskLruCache のキャッシュ情報を保存する処理の実装と仕様についてみていきます。

glide/DiskLruCache.java at v4.11.0 · bumptech/glide

DiskLruCacheの概要

DiskLruCache は、LRUでファイルのキャッシュを行うクラスです。キャッシュ情報の管理には、ジャーナルファイルとメモリ上のデータ構造を使用しています。 ジャーナルファイルについては後で述べますが、キャッシュの読み込みや書き込みを行った履歴を記録したファイルとなっています。 キャッシュの操作を行った時には、はじめにメモリ上の LinkedHashMap に保存された後、ジャーナルファイルへの保存が行われます。
DiskLruCacheインスタンスDiskLruCache.open を使用して取得することができ、 open をした時にジャーナルファイルからメモリへキャッシュ情報が読み込まれます。

ジャーナルファイルの中身

ジャーナルファイルの中身は以下のような形式になっています。

libcore.io.DiskLruCache
1
100
2
CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

はじめの4行はそれぞれ、

libcore.io.DiskLruCache : 固定の文字列
1                       : ディスクキャッシュのバージョン
100                     : アプリが指定するバージョン(Glideでは固定で1)
2                       : ValueCount 1つのkeyに紐づく値の個数(Glideでは固定で1)

を表しています。ディスクキャッシュやアプリが指定するバージョンが変わると、一度キャッシュがすべてクリアされます。

5行目以降がキャッシュ情報の取得や更新のログとなっています。アクションと付加情報がスペース区切りで1行ごとに記されていて、アクションは表に示す4種類があります。

アクション 付加情報1 付加情報2~
READ キャッシュキー -
REMOVE キャッシュキー -
DIRTY キャッシュキー -
CLEAN キャッシュキー キャッシュしている各ファイルのサイズ ValueCountの数

各アクションの意味

それぞれのアクションについて説明していきたいと思います。
READ/REMOVE は想像がつきやすいですが、キャッシュ情報の取得/削除を行った時のアクションです。
個人的には、データベースなどに関する知識があまりないため、DIRTYCLEAN が表す意味を理解するのが少し難しかったです。
DiskLruCache では、キャッシュ情報を書き換える際に DiskLruCache#edit などを使用して取得できる Editor クラスを介する必要があります。アプリ側では、取得した Editor に対してキャッシュ情報の追加や上書きを行った後に Editor#commit を呼び出すことで、実際に画像ファイルキャッシュの更新が行われます。
この時に Editor を取得してから commit を行うまでの状態が DIRTY となります。( DIRTY のログが書き込まれるのはEditor 作成時)
DIRTY となっている情報を commit し終わると CLEAN のログが書き込まれます。

キャッシュのmax sizeを超えた時の処理

何度も画像のキャッシュを行っていくと、当然ですがキャッシュサイズが増えていきmax sizeを越えることがあります。そのサイズを超えた時は、以下の処理が行われてキャッシュサイズがmax size以下に抑えられます。

  1. メモリキャッシュの古い情報から順番に消していき合計サイズがmax size以下になるようにする
  2. 無駄なログが一定量ある場合はメモリキャッシュを元にジャーナルファイルを整理する(必要なログだけ残す)

ここでいう 無駄なログ には何パターンかありますが、例えば

  • キーA の情報が 編集A(DIRTY) -> 更新A(CLEAN) -> 取得(READ) -> .. -> 更新X(CLEAN) と操作された時の最後の DIRTYまたはCLEAN 以外のログ
    • キーA の最終的な情報がキャッシュに保存されていれば良いので、それまでの更新履歴などは不要
  • キャッシュ情報を取得した時のログ(READ)

その他の処理

キャッシュ情報の追加や削除を行う時には上記のメモリキャッシュの更新とジャーナルファイルへのログの記録が主な処理となっています。
しかし、エラーが起きる可能性もあり、その場合は基本的にキャッシュ情報の削除が行われます(キャッシュは消しても問題はないはずなので安全な方にたおしている)

また、 DIRTY から CLEAN へ状態がうつる場合の処理についても少し触れておきます。
DIRTYCLEAN はそれぞれ別々のファイルとして画像を保存するようになっていて、commit 時に DIRTY 用のファイルから CLEAN 用のファイルへリネームが行われます。 glide/DiskLruCache.java at v4.11.0 · bumptech/glide

キャッシュファイルの名前の規則

DIRTY 用ファイルの名前: ${key}.${index}.tmp
CLEAN 用ファイルの名前: ${key}.${index}

glide/DiskLruCache.java at v4.11.0 · bumptech/glide

まとめ

DiskLruCache ではただ画像ファイルを保存しているだけではなく、キャッシュの操作ログも記録をしておくことでLRUとして動作することを可能にしていた。

ジャーナルファイルについてはデータベースの処理とも関連がありそうなので今後さらに調べていきたいです。