AndroidアプリにおけるFirestoreのキャッシュ増大によるHeap領域の枯渇
はじめに
Androidアプリをつくっているときに直面した不具合について、レアケースかもしれませんが、書き残します。
現象
あるとき、アプリを新しいバージョンにアップデートすると、程なくしてアプリのCPU・メモリ使用量が不安定な動作になり、ある機能が使えなくなりました。メモリは周期的に増加し、ある時間が経つと、通常時の使用量に戻るといった挙動でした。
logcatのログを確認したところ、「Heap領域が枯渇している」旨の内容が出力されていました。
タチが悪いことにアプリの更新ではなおらず、一度、アンインストールと再インストールを実行する必要がありました。
なぜ原因の特定は困難だったか
この問題は、あるバージョンにアップデートするとすべての端末で発生するわけではないという点が厄介でした。そのため、再現方法もわかりません。またログから直接的な例外やエラーといった内容は確認できず、Heap領域が不足していることしかわかりませんでした。
どうやって気づいたか
ログを確認していると気になる一文がありました。
W CursorWindow: Window is full: requested allocation 92734 bytes, free space 25060 bytes, window size 2097152 bytes
CursorWindow
は、Androidのデータベース(SQLiteなど)から取得したデータを保持するためのメモリバッファーのことだそうです。
このログは「要求されたバイト数:92734バイト、空き領域:25060バイト、ウィンドウサイズ設定値:2097152バイト」という意味になり、空き容量に対して要求されたサイズが大きすぎることを示しています。こちらは、データベース登録時ではなく、取得時に発生するようです。
とはいえ、SQLiteをつかってはいなかったので、どうしてこのようなログが出るのか不思議でした。
このログに関して調べていると、Firestoreのオフライン永続性によってメモリリークが発生することがある、といった事例を見つけました。そういえば、Firestoreを使ってバックエンドへのデータ送受信を行っていたので、「もしや?」と思いました。
このときは推測だけで、証拠をつかんだわけではありませんでした。
原因の判明
今回紹介した事象と同時に、アプリのユーザーデータが肥大化するといった現象も発生していました。このユーザーデータ肥大化現象と先ほどのログがひもづく瞬間が訪れました。
実際にこの現象が発生している端末に対して、adb shell
を使ってアプリの内部に入り、ユーザーデータを確認しました。各ディレクトリの容量は、下記のコマンドで調べました。
# du : ディレクトリやファイル容量を確認するコマンド、-k : キロバイト表示
# sort -n : 照準にソート
su du -k /data/data/com.sample.app | sort -n
すると、アプリのユーザーデータ直下にある/databases
の容量が約190MBと、正常なアプリに比べて、大きくなっていることに気がつきました。これだ…!
中身を見てみると、Firestoreに関連するキャッシュファイルが175MBと肥大化していました。このキャッシュファイルを読み込めなかったため、Heap領域の枯渇につながったのだと考えられます。
対策
アプリをリモートからアップデートする仕組み導入していましたが、その際、/database
直下にあるFirestoreに関連するキャッシュファイルを削除するようにしました。また、アプリからFirestoreのオフライン永続性を無効にするよう設定を変更しました。こららの対策で、この現象が発生することがなくなりました。
おわりに
現象自体は理解できましたが、結局のところ、根本原因は理解できていないままです。非常にレアケースかもしれませんが、意外なところに不具合の原因が隠されていることがわかり、良い経験になりました。
Discussion