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

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

C686512aa23516fcb9b1565050c7a1d7?s=47 Sansan
PRO
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/

C686512aa23516fcb9b1565050c7a1d7?s=128

Sansan
PRO

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. 2014年にSansanに入社し、徳島県にある「Sansan 神山ラボ」にて勤務。 Sansan、Eight のアプリ開発を担当し、インドにも赴いて国際化対応を担当 した。 グローバル戦略統括部を経て、現在はBill Oneの国際化対応を担当。エンジニ アコミュニティの運営も行っている。 辰濱 健一

    Sansan株式会社 技術本部 Bill One Engineeringグループ エンジニア
  3. None
  4. Sansan神山ラボ

  5. Agenda - ANR について - SQLite の詳細について - クエリのパフォーマンス計測 -

    パフォーマンス改善 - Room での tips 本セッションでは SQLite が原因で ANR が出ていた問題を解決した話をします。 (Room が出ていなかったときの話です) 近年は Room が補完してくれている部分もありますが、Room のコアとなる SQLite の事も知っておくと役に立つかも知れません。
  6. ANR について

  7. • ANR とは、Application Not Responding の略 • UI スレッドが長時間(5秒以上)ブロックされると発生 •

    ANR が発生すると、ユーザーはアプリを強制終了できる ANR について
  8. ANR について ANR を発生させないためには • 通信処理やファイルの読み書きはメインスレッド で行わない • 重たい計算処理もメインスレッドで行わない

  9. ANR の検出 • Play Console の Android Vitals • Firebase

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

    CPU Profiler を使用して CPU アクティビティを検査する】 https://developer.android.com/studio/profile/cpu-profiler SQLite まわりがあやしかったので、より具体的にどのクエリが遅いか特定 → SQLite をきちんと理解する必要があった ANR について
  11. SQLite の詳細について

  12. • オープンソースで軽量なリレーショナルデータベースライブラリ • マルチプラットフォーム • SQL が使える • データは一つのファイルに格納される •

    セキュリティ機能は搭載されていない 【参考 / What Is SQLite?】 https://sqlite.org/index.html • Android OS に標準で搭載されている • 現在では Room が SQLite の抽象化レイヤーとして提供されているので、 Room を利用することが強く推奨されている SQLite について
  13. Android では Room の利用が強く推奨されている 出典:SQLite を使用してデータを保存する / Android デベロッパー https://developer.android.com/training/data-storage/sqlite

  14. • マルチスレッド対応をしている 【参考 / Using SQLite In Multi-Threaded Applications】 https://www.sqlite.org/threadsafe.html

    • ロックはファイル単位 • テーブルや列単位のロックは存在しない • スレッドセーフだが、ロックの単位が大きいので待ち時間が多くなる → 1トランザクションごとにファイルのロックが発生している 【参考 / Appropriate Uses For SQLite】 https://www.sqlite.org/whentouse.html SQLite の詳細について
  15. • ジャーナルモード ファイルへの更新方法を指定できる (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 の詳細について
  16. • 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
  17. • Android で SQLite を使うコードの例 SQLite の詳細について トランザクションの開始 トランザクションの終了

  18. • トランザクションモード • Exclusive モード(default) • 他の全てのトランザクションをロックする • データの一貫性は向上するが、パフォーマンスが出ない •

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

    onRollback() • beginTransactionWithListener で Listener を渡して呼び出す • パフォーマンス測定に活用した SQLite の詳細について
  20. クエリのパフォーマンス計測

  21. 前述の通り、SQLite ではファイル単 位のロックになってしまう。 実処理を短時間で行えるクエリであっ たとしても、直前にスロークエリが実 行されていたら、処理完了まではかな りの時間になってしまう。 → 次の2カ所の時間が欲しい •

    beginTransaction() → onBegin() • onBegin() -> endTransaction() クエリのパフォーマンス計測 これにより、「UX から推測される遅い処理」と「実処理の時間」を明確にする
  22. そこで、SQLiteTransactionListener の出番 クエリのパフォーマンス計測 db.beginTransaction() (transaction lock...) listener.onBegin() (execute ...) db.endTransaction()

    listener.onCommit() or listener.onRollbaack()
  23. クエリのパフォーマンス計測 独自のプロファイラを作った db.beginTransaction() (transaction lock...) listener.onBegin() (execute ...) db.endTransaction() listener.onCommit()

    or listener.onRollbaack() 1 2
  24. クエリのパフォーマンス計測 独自のプロファイラを実装に仕込んでいく(地道に…)

  25. クエリのパフォーマンス計測 アプリを動かしてみたログ Main thread で DB を触っている!! ANR のトリガーになるので要改修 •

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

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

    で実行時間を測る という手段も使いました。 DB Browser for SQLiteも便利 クエリのパフォーマンス計測 クエリ クエリ結果 クエリの実行情報
  28. パフォーマンス改善

  29. • メインスレッドでの呼び出し • Transaction 内で不必要な処理をしていた • 複数書き込みで都度 Transaction を張っていた •

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

    だとデフォルトではメインスレッドで呼び出すとクラッシュするの ですぐ分かる(安心) メインスレッドでの呼び出しへの対応
  31. Transaction 内で不必要な処理をしていた 加工処理などは Transaction 外に移動

  32. 複数書き込みで都度 Transaction を張っていた 一つのオブジェクトを保存する API を何度も呼ばずに List で受け取れる API も提供する

    他にも、複数のテーブルをまとめて一つの Transaction で保存するのも有効
  33. • Activity#onResume で DB にアクセスしていて、 Activity に返ってくるたびに DB にアクセス →

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

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

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

  37. • id と FirstScroll に必要な情 報だけを全件取得する • 表示領域の id のみ、初回表

    示時に遅延読み込み • スクロールのパフォーマン スへの影響は軽微&一覧が 数万件ある場合などは大き な速度改善になった • Realm のプロパティの遅延 評価みたいですね 全件読み込みの高速化&遅延読み込み
  38. これらの改善を行って、 20倍の高速化になりました!

  39. Room での tips

  40. ビルド時に生成されるクラスで SQLite を叩いている 生成されたコードを見ると 何をやっているのかよくわかる

  41. • ある程度カスタマイズができる(が、通常そんなに触らなくても良さそう) • setQueryCallback(…) • いつ、どのクエリが実行されたのかを把握するには便利 • CRUD のクエリだけなく、BEGIN TRANSACTION

    なども出る • setJournalMode(…) • デフォルトで最適なモードがあたっている(ので、気にすることはないはず) 【参考 / RoomDatabase.Builder】 https://developer.android.com/reference/androidx/room/RoomDatabase.Builder Roomdatabase.Builder
  42. @Transaction をつけると1つのトランザクションにまとめられる (ファイルへの反映回数が1回にまとまるので高速化) 複数の処理を1つのトランザクションにまとめる

  43. • Dao で SELECT する列を明示して、サブセットの型で返す テーブルのサブセットだけ取得したいとき 出典:YouTube / What’s new

    in Room (Android Dev Summit '19) https://youtu.be/_aJsh6P00c0?t=784
  44. • Repository 生成時に SupportSQLiteOpenHelper も渡す • Repository 内で SupportSQLiteOpenHelper#getWritableDatabase などを使う

    Dao で SQLite を直で触りたい場合(あるのか?)
  45. • 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 の変更通知について
  46. まとめ

  47. まとめ - Room は SQLite のラッパーライブラリ - SQLite の知見があると Room

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