Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Room 時代にも役に立つかも知れない SQLite の Tips / Tips for SQ...

Sansan
December 17, 2021

Room 時代にも役に立つかも知れない SQLite の Tips / Tips for SQLite which may be useful in Room-era

■イベント
【YUMEMI x Sansan Tech Meetup】Android開発におけるチャレンジを語る
https://sansan.connpass.com/event/229780/

■登壇概要

タイトル:Room 時代にも役に立つかも知れない SQLite の Tips

登壇者: 技術本部 Bill One Engineeringグループ 

辰濱 健一

▼Sansan Engineering
https://jp.corp-sansan.com/engineering/

Sansan

December 17, 2021
Tweet

More Decks by Sansan

Other Decks in Technology

Transcript

  1. Room 時代にも役に立つかも知れない SQLite の Tips 【 Y U M E

    M I x S a n s a n T e c h M e e t u p 】 A n d r o i d 開 発 に お け る チ ャ レ ン ジ を 語 る Sansan株式会社 技術本部 Bill One Engineeringグループ エンジニア 辰濱 健一
  2. Agenda - ANR について - SQLite の詳細について - クエリのパフォーマンス計測 -

    パフォーマンス改善 - Room での tips 本セッションでは SQLite が原因で ANR が出ていた問題を解決した話をします。 (Room が出ていなかったときの話です) 近年は Room が補完してくれている部分もありますが、Room のコアとなる SQLite の事も知っておくと役に立つかも知れません。
  3. • ANR とは、Application Not Responding の略 • UI スレッドが長時間(5秒以上)ブロックされると発生 •

    ANR が発生すると、ユーザーはアプリを強制終了できる ANR について
  4. ANR の検出 • Play Console の Android Vitals • Firebase

    Crashlytics (new! 2021/秋〜) ANR について
  5. ANR のスタックトレースからは、どの UI スレッドが待たされたかが分かるが、 どの処理によって待たされているのかは分からないことがある → CPU プロファイラを使ってどの処理が遅いか特定 【参考 /

    CPU Profiler を使用して CPU アクティビティを検査する】 https://developer.android.com/studio/profile/cpu-profiler SQLite まわりがあやしかったので、より具体的にどのクエリが遅いか特定 → SQLite をきちんと理解する必要があった ANR について
  6. • オープンソースで軽量なリレーショナルデータベースライブラリ • マルチプラットフォーム • SQL が使える • データは一つのファイルに格納される •

    セキュリティ機能は搭載されていない 【参考 / What Is SQLite?】 https://sqlite.org/index.html • Android OS に標準で搭載されている • 現在では Room が SQLite の抽象化レイヤーとして提供されているので、 Room を利用することが強く推奨されている SQLite について
  7. • マルチスレッド対応をしている 【参考 / Using SQLite In Multi-Threaded Applications】 https://www.sqlite.org/threadsafe.html

    • ロックはファイル単位 • テーブルや列単位のロックは存在しない • スレッドセーフだが、ロックの単位が大きいので待ち時間が多くなる → 1トランザクションごとにファイルのロックが発生している 【参考 / Appropriate Uses For SQLite】 https://www.sqlite.org/whentouse.html SQLite の詳細について
  8. • ジャーナルモード ファイルへの更新方法を指定できる (DELETE | TRUNCATE | PERSIST | MEMORY

    | WAL | OFF) 【参考 / Pragma statements supported by SQLite】 https://www.sqlite.org/pragma.html#pragma_journal_mode • WAL モード:書き込みの反映は早い(が、読み込みは遅い) • SQLIte 3.7.0 (2010-07-21) or later でしか使えない • MEMORY モード:高速(だが揮発する) • 上限に注意。テストには向いてそう SQLite の詳細について
  9. • Android OS のバージョンと SQLite バージョン • SQLite は Android

    OS にバンドルされている • Realm のように、アプリ側でバージョンを指定できない • 前述の WAL モードも API 11 からしか使えない • Room はこのあたりのバージョン差異を吸収している SQLite の詳細について 出典:android.database.sqlite / Android デベロッパー https://developer.android.com/reference/android/database/sqlite/package-summary.html
  10. • トランザクションモード • Exclusive モード(default) • 他の全てのトランザクションをロックする • データの一貫性は向上するが、パフォーマンスが出ない •

    beginTransaction ではこのモードになる • Immediate モード • 他のトランザクション書き込みはロックするが、読み込みはできる • トランザクションの並列度が増し、パフォーマンスが向上する • Phantom Read(更新される前の値の読み込み)が発生するリスクを伴う • beginTransactionNonExclusive という API で呼び出し可能 SQLite の詳細について
  11. • TransactionListener • トランザクションのコールバック を受け取ることができる • onBegin() • onCommit() •

    onRollback() • beginTransactionWithListener で Listener を渡して呼び出す • パフォーマンス測定に活用した SQLite の詳細について
  12. 前述の通り、SQLite ではファイル単 位のロックになってしまう。 実処理を短時間で行えるクエリであっ たとしても、直前にスロークエリが実 行されていたら、処理完了まではかな りの時間になってしまう。 → 次の2カ所の時間が欲しい •

    beginTransaction() → onBegin() • onBegin() -> endTransaction() クエリのパフォーマンス計測 これにより、「UX から推測される遅い処理」と「実処理の時間」を明確にする
  13. クエリのパフォーマンス計測 アプリを動かしてみたログ Main thread で DB を触っている!! ANR のトリガーになるので要改修 •

    Logcat に出力しているので、DB アクセスがリアルタイムで分かる • どの操作をしたときに、どのクエリが実行されているか把握しやすい
  14. クエリのパフォーマンス計測 アプリを動かしてみたログ db.beginTransaction() (transaction lock...) listener.onBegin() (execute ...) db.endTransaction() listener.onCommit()

    or listener.onRollbaack() 1 2 重たい処理!! 待ちが発生 → 実処理が短くても、長時間処理が返ってこないクエリが多々存在する
  15. 他にも、 • SQL の実行計画を分析(EXPLAIN)で実行計画を見る • .timer on … .timer off

    で実行時間を測る という手段も使いました。 DB Browser for SQLiteも便利 クエリのパフォーマンス計測 クエリ クエリ結果 クエリの実行情報
  16. • メインスレッドでの呼び出し • Transaction 内で不必要な処理をしていた • 複数書き込みで都度 Transaction を張っていた •

    不必要な再読込の抑制 • FirstScroll のための全件読み込み ボトルネックのパターン
  17. • メインスレッド以外に切り替える方法 • RxJava であれば、observeOn • Coroutine であれば、withContext • Room

    だとデフォルトではメインスレッドで呼び出すとクラッシュするの ですぐ分かる(安心) メインスレッドでの呼び出しへの対応
  18. • Activity#onResume で DB にアクセスしていて、 Activity に返ってくるたびに DB にアクセス →

    ViewModel などの中間層でキャッシュして、キャッシュ値を読み込む (キャッシュの更新タイミングには気をつけないといけない) 不必要な再読込の抑制
  19. • View に出していないデータの更新による再読み込み • バックグランド処理がトリガーになっていた • 対応 • テーブルを一覧と詳細で分割 •

    一覧更新を DB からの変更通知ではなく、 アプリ内イベントでトリガーに更新するようにした 不必要な再読込の抑制 一覧に出している プロパティ 変更されても 一覧は更新不要
  20. • FirstScroll のための全件読み込み • 全件読み込まないと、FirstScroll のための 情報を作れない • ページングと相性が悪く、全件読み込むしかない… •

    対応 • 全プロパティではなく、FirstScroll 構築に必要な サブセット(id と 日付)だけ一旦読み込む • 他の情報は表示時に遅延読み込み 不必要な再読込の抑制
  21. • id と FirstScroll に必要な情 報だけを全件取得する • 表示領域の id のみ、初回表

    示時に遅延読み込み • スクロールのパフォーマン スへの影響は軽微&一覧が 数万件ある場合などは大き な速度改善になった • Realm のプロパティの遅延 評価みたいですね 全件読み込みの高速化&遅延読み込み
  22. • ある程度カスタマイズができる(が、通常そんなに触らなくても良さそう) • setQueryCallback(…) • いつ、どのクエリが実行されたのかを把握するには便利 • CRUD のクエリだけなく、BEGIN TRANSACTION

    なども出る • setJournalMode(…) • デフォルトで最適なモードがあたっている(ので、気にすることはないはず) 【参考 / RoomDatabase.Builder】 https://developer.android.com/reference/androidx/room/RoomDatabase.Builder Roomdatabase.Builder
  23. • Room は Flowable や LiveData で DB の変更通知を受け取れるが、 テーブルが更新されればとにかく通知される仕組みになっている

    • 関心のないオブジェクトや列の変更でも emit されてしまい、意図しない 再読み込み(さらなる DB のアクセス)を誘発してしまう • InvalidationTracker や InvalidationTracker.Oberver の実装を見ればよくわかる • 対策 • Dao の Flowable に distinctUntilChanged() をかまして使う • LiveData の場合は MediatorLiveData で distinct して流す 【参考 / Avoid false positive notifications for observable queries】 https://medium.com/androiddevelopers/7-pro-tips-for-room-fbadea4bfbd1#5e38 Room の変更通知について
  24. まとめ - Room は SQLite のラッパーライブラリ - SQLite の知見があると Room

    の挙動をよく理解できる - Room の変更通知を過信すると、意図しない emit が含まれることがある - これからの Room の進化に期待! - 暗号化の提供…まだかな?