https://fortee.jp/iosdc-japan-2021/proposal/b1b94452-dd4b-4581-8797-f348919ee362
2021.9.17Ryo Iida (@aviciida)
View Slide
はじめに - 自己紹介飯田 諒(@aviciida)● mikanのiOSエンジニア○ 🍊 mikan歴 3年(since 2018/9)○ 🍎 iOSエンジニア歴 2年(since 2019/5)● PM/分析もやってます!● ユーザー目線でプロダクト作るの好き● Twitter活発です!@aviciida2
3mikanについてMission本質的なテクノロジー活用であらゆる人の英語学習によりそい人生の可能性を広げる「英語を扱えるようになりたい、得意になりたい」人に効果的な手法を提供し、語学が人生のボトルネックでは無くなる未来を作るのが私たちmikanです。
アプリダウンロード NO.1コアバリューが支持され国内最大級に成長4
● iOSエンジニア● Androidエンジニア● サーバーサイド・インフラ エンジニア● デザイナー( 時期を見て、週1回程度の出社を想定)社員 (まずは副業からでもOK)※基本リモート勤務ご連絡はこちらから(Twitter DMでも!)https://mikan.link/carrersmikanは絶賛採用中です!5
今日話すことmikanのiOSアプリのデータベースを組み込み型のSQLiteからCloud Firestoreに9ヶ月ほどかけて移行した時の話をします6
大前提: レガシーについて本セッションは「レガシーを置き換えていく話」ですが、mikanでは「レガシー」を前向きに捉えています。まだ知識がない中で、スピード感持って事業を進めていくための当時は最善の手段であり、そのおかげで今の500万DLがあります。(mikanではレガシーではなく「ビンテージ」と読んでます)セッション内でもできるだけ配慮した表現を使いますが、大前提として先人の方々には尊敬と感謝の念でいっぱいであることを共有しておきます🙏(特に創業者の宇佐美さんと高岡さん)7
アジェンダ① なぜSQLiteからFirestoreに移行したのか?・なぜ組み込み型SQLiteが採用されていた?・当時のツラミ・Firestoreを採用した理由・移行によって変わること② 実際の移行の話・全体観・ダブルライト・マイグレーション・クライアント側のreadロジック移行③振り返って・うまくいったこと / やればよかったこと・実際Firestore使ってみてどう?・今回の学び8
アジェンダ① なぜSQLiteからFirestoreに移行したのか?・なぜ組み込み型SQLiteが採用されていた?・当時のツラミ・Firestoreを採用した理由・移行によって変わること② 実際の移行の話・全体観・ダブルライト・マイグレーション・クライアント側のreadロジック移行③振り返って・うまくいったこと / やればよかったこと・実際Firestore使ってみてどう?・今回の学び9
組み込み型SQLite?10● DBを移行する前は、ユーザーの学習データは各端末のSQLiteデータベースに保存されており、サーバーと同期はしない形式● バックアップのために、1日の最初の起動時に、DBファイルをAWS S3の各ユーザーのバケットにアップロード
なぜか?(創業者の宇佐美さん(@usatie)に聞きました!)● 当時はまだ経験不足で、サーバーにDBを置く選択肢がなかった● クライアントサイドで完結する作りにしたかった● (結果的に、オフラインでも使える実装に)なぜ組み込み型SQLiteが採用されていた?11
データの同期・移行が不可能、もしくはかなり大変● 異OS間では不可能○ AndroidはRealmを使っている && スキーマも違うため● 同OS間では”一応”可能(だがめちゃ大変)○ まず問い合わせいただく→CSチームがS3に入る→旧端末のバケットから新端末のバケットにDBファイルをドラッグ&ドロップする→ユーザーさんがアプリ内でデータの復元をするその他● 生SQLを書く時に、エラーがランタイムにしかわからない● マスターがないため、あらゆるバージョンを想定する必要あり当時のツラミ - 組み込み型SQLite12
データ構造の一部が負債化● 従来は「Book(本)」の中に「Chapter(章)」● Chapterごとに単語を学んでいく方式。(1教材 = 1 Book)当時のツラミ - その他、旧DBについて13BookChapter
データ構造の一部が負債化● ある日、本としては1冊だけど、複数のBookを持つ教材が登場○ 1教材 = 複数 Books があり得る状態に● 単体Bookと複数Booksに対応しなければならず、コードにif文が大量発生し、負債に14before1教材= 1bookafter1教材= 複数books当時のツラミ - その他、旧DBについてbook table
mikanの求めていた要件 / 当時の状況にマッチしていた● オフライン対応● データの移行・同期● サーバーサイドが薄く、クライアントサイドが厚いチーム体制○ 上記要件を、サーバーレスに実現できるのが魅力的当時の候補● API作る: サーバーサイドのリソースの関係で厳しい...!● Realm: サーバーサイドで扱える言語がmikan(Go)とマッチしないFirestoreを採用した理由15
① 組み込み型→ネットワーク型へ● 同じIDでログインすれば、簡単にデータ移行可能に● 基本はオフライン対応可能だけど、最初は通信必要に② RDB→KVSへ● カラムの追加が簡単に● 集計が弱いため、サマリーデータを持つ必要が出てくる○ 各単語の記憶度を元にした、各教材の進捗度○ モデルのそのまま移行というわけにはいかなくなる!Firestoreへの移行によって変わること整理16サマリーデータを持っているドキュメント
アジェンダ① なぜSQLiteからFirestoreに移行したのか?・なぜ組み込み型SQLiteが採用されていた?・当時のツラミ・Firestoreを採用した理由・移行によって変わること② 実際の移行の話・全体観・ダブルライト・マイグレーション・クライアント側のreadロジック移行③振り返って・うまくいったこと / やればよかったこと・実際Firestore使ってみてどう?・今回の学び17
移行の全体観18ローカルSQLiteappPhase⓪従来writereadappPhase①ダブルライトwritereadFirestorewritePhase②マイグレーションFirestoreゴリっと移行appPhase③readロジック移行Firestorewriteread👋ローカルSQLiteGCSローカルSQLiteDBファイルをuploadapp
Phase① ダブルライト● 基本はクライアントサイドの仕事● データの書き込みを、SQLiteだけでなく、Firestoreにも● ★ ダブルライト開始日をユーザーごとに保持しておく○ マイグレーションの際、ダブルライト以降のデータは対象にしないようにするため○ ユーザーのアップデートに依存してしまい、開始タイミングがバラバラなため19appwritereadFirestorewriteローカルSQLite
Phase① ダブルライト20SQLiteへの書き込みFirestoreへの書き込みFirestoreへの書き込み
Phase② マイグレーション● ダブルライト前のデータをFirestoreに移す作業● サーバーサイドの仕事● 非同期で行う○ ユーザーによっては大変な量になるので(多ければ数年分)21Firestoreゴリっと移行GCS DBファイルをuploadapp
1. client: マイグレーション対象かどうかを判断2. client: 自分のSQLiteファイルをGoogle Cloud Storageにアップロード3. client: 自分のステータスを「マイグレーション待ち」に変更4. server: ステータスが「待ち」のユーザーに対してマイグレーションを実行22DBファイルをuploadするコードPhase② マイグレーション
Phase③ readロジック移行● クライアント側の最後の大仕事● 読み込みロジックをSQLiteからFirestoreに移行○ これ以降はFirestoreで書き込み/読み込みが成立し、SQLiteの依存からは脱却● 大変なところ○ 同期処理→非同期処理への書き換え○ RDB→KVSという変更によって、データの持ち方も変更■ 合わせてコアの学習機能のロジックも少し変更○ DBの書き込み・読み取りロジックが元々クライアントサイドに寄っていたため、対象ファイルが膨大... 23appFirestorewriteread👋ローカルSQLite
24Phase③ readロジック移行同期処理で、呼び出すだけ非同期で単語取得処理順を変更before after
25Phase③ readロジック移行
アジェンダ① なぜSQLiteからFirestoreに移行したのか?・なぜ組み込み型SQLiteが採用されていた?・当時のツラミ・Firestoreを採用した理由・移行によって変わること② 実際の移行の話・全体観・ダブルライト・マイグレーション・クライアント側のreadロジック移行③振り返って・うまくいったこと / やればよかったこと・実際Firestore使ってみてどう?・今回の学び26
自前で集客して、パブリックベータを実施できた。うまくいったこと / やってよかったこと 1/327● Why: iOSの段階的リリースは、実際は段階的ではない問題○ 少数に実際に使ってほしい● 集客は[email protected]やpush通知● 社内で潰せなかった不具合がたくさん潰せた○ 色んなユーザーのデータで触ってもらえたおかげ● ただ、600人以上はなかなか集まらず...
うまくいったこと / やってよかったこと 2/3問い合わせてくれたユーザーさんの多くに個別対応● コンソールで自ら返信対応○ 不具合の再現手順などを詳しくヒアリングして不具合修正● +α 素敵なフィードバックもいただけて、やる気が出る💪28
うまくいったこと / やってよかったこと 3/3最小単位のログを別のところで貯めておく● mikanでいうと「1単語ごとの学習ログ」○ 2021/9/17 18:50 に、appleという単語の問題を解いて、不正解○ みたいな● もし不具合などで「学習履歴」や「学習進捗」のデータが消えても、そのログがあればサマライズして、復元できる29
難しかったところ・後悔 1/4ダブルライトの不具合を見つけるのが困難● アプリ内で「Firestoreへの書き込みがうまくいっているか」を確認できない○ readをFirestoreから行うように書き換えるまでは、SQLiteのデータを参照しているため○ Firestoreへの書き込みがうまく行っていなくても検知できない○ リリース前のQAは、Firestoreのコンソールを目視で確認30
難しかったところ・後悔 1/4ダブルライトの不具合を見つけるのが困難ダブルライト期間中に起きた、本当にあった怖い話...1. 新教材をFirestoreに入稿2. 入稿の際に、field名のスペルミス3. クライアントからFSにも学習データを書き込みたいが、クライアントで指定しているキー名と異なるので、Decode Error4. ダブルライト失敗しているが、SQLiteには書き込みできているので、気づかない31
難しかったところ・後悔 2/4強制アップデートとセキュリティルールのどちらも、後になって導入セキュリティルールを後から導入したけど、強制アップデートの仕組みがないため、めちゃくちゃ苦労しました...!以下のサイクルを4周くらいしました。1. しっかりしたセキュリティルールを適用2. むかーしのバージョンで、ある違法な値が投げられることが判明3. セキュリティを2に合わせて、少し緩める(アップデートさせられないので)4. 1に戻る32
難しかったところ・後悔 3/4ABテストをできるならやるべきだったやるべき理由● 数字が上がったのか、下がったのかの判断を正しくするため○ 継続率は時期要因にも左右されるので、同時期にABしてないと、正しく分析できない● パブリックベータの人数確保も簡単だった方法● DBを触っているVCを全部複製して、片方をDB刷新版にする○ 入口から分けてあげると「従来の世界」と「DB刷新の世界」を分けられる。 33
一度に多くの問題を解決しない方がいい● 一度に多くの問題を解決しようとすると、開発項目が大きくなる● 開発項目が大きくなると、シンプルに大変で、価値を届けるのが遅くなり、検証も遅くなってしまう● 解決したい課題は、個別で対応できるかも難しかったところ・後悔 4/434
一度に多くの問題を解決しない方がいい今回解決しようとした課題は大きく分けると以下● OS間のデータ移行ができない○ SQLite ⇔ Realmのconverterを作る● 同OSでデータ同期ができない○ 今のデータ構造・スキーマのままFirestoreに移行する● アプリのデータ構造がしんどい○ SQLite、Realmのままデータ構造だけ変える難しかったところ・後悔 4/435
● 「オフライン対応」と「データ移行」が特に何もせずに実現されてるのすごい○ オフラインの永続性はデフォルトでオンになっている*○ データ移行は、Firebase Authでログインすれば完了Firestore実際使ってみて - 良いところ 1/2* https://firebase.google.com/docs/firestore/manage-data/enable-offline?hl=ja#swift36
● スケールを気にしなくていい● セキュリティルール!バリデーションのコードをDBに持たせられる○ 型だけじゃなく、その型の中でどんな値を許容するかを決められる○ 誰が書き込めるか、読み取れるか、も決められる● クライアントサイドだけで、自由にカラムを追加できるFirestore実際使ってみて - 良いところ 2/237
ドキュメントの取得速度● データは綺麗に階層構造にしてしまうとしんどくなる例: あるbookに属するwordsを全部取りたいときに、chapterでfor文回してwordsを全部取ってこないといけない(これが時間かかる)Firestore実際使ってみて - 難しいところ 1/338実際のFirestoreの階層構造books > chapters > wordsの構造になっている
● 集計弱いので、サマリーデータを持たないといけなくなる○ サマリーデータと詳細データが食い違う不整合が発生し得るFirestore実際使ってみて - 難しいところ 2/339
Firestore実際使ってみて - 難しいところ 3/340● 不整合なくデータを入れていくために必要な学習コストが高い○ security rules, transaction, batch, increment valuesなど● 値段が高い○ 今はFirestoreだけで25万円○ AWSのRDS使っていた時は、EC2やロードバランサー含め15万ほど
なんの課題を解決したいのかを明確化しよう● 「OS間のデータ移行もできるようにしたいし、データ構造変だし、データの同期もできるようにしたいし...!よし!データ構造変えながらSQLiteからFirestoreに移行しちゃおう!」○ ふわっとした課題に対しては、ふわっとした解決策になりがち。● 「我々は、今なんの課題を解決しようとしているのか?」を問い続けよう。○ 課題を常にシャープにしようプロジェクトでの学び・アドバイス 1/441
ダブルライトは要らなかった説● readが旧DBのときに、新DBへのダブルライトのバグを見つけるのは至難の業● ダブルライトやらずに、readロジックの移行の📱アップデートのタイミングでマイグレーションを走らせて、非同期でゴリっとデータを移行させるだけでよかったかも。○ 今選択している教材だけは同期的にクライアント側で移行してあげれば、起動直後の学習体験は最低限担保できるプロジェクトでの学び・アドバイス 2/442
データの構造は最初にしっかり設計しよう!● book > chapter > word の構造じゃなくてbook > chapter, wordの構造に○ chapterとwordを同じ階層に置く○ 👉 book内の全wordを取得するのがもっと高速化してた。● 後から変更しづらいので、最初の設計が大事。プロジェクトでの学び・アドバイス 3/443こんな感じにしておけば...!
セキュリティルール・強制アップデートは早めに入れておこう● DBの移行関係ないですが、強制アップデートは少なくとも早めに入れておくと便利です...!プロジェクトでの学び・アドバイス 4/444
改めて: mikanは絶賛採用中です!● iOSエンジニア● Androidエンジニア● サーバーサイド・インフラ エンジニア● デザイナー( 時期を見て、週1回程度の出社を想定)社員 (まずは副業からでもOK)※基本リモート勤務ご連絡はこちらから(Twitter DMでも!)https://mikan.link/carrers45
ありがとうございました!!46