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

脱Firebase. 我々はどう生きるか/Migrate from Firebase

Keisuke69
November 09, 2022

脱Firebase. 我々はどう生きるか/Migrate from Firebase

AWS DevDay 2022での登壇資料です。
Firebaseと言ってますが実際にはFirestoreだけです。
なお、Firebaseをdisるような内容ではありません。

Keisuke69

November 09, 2022
Tweet

More Decks by Keisuke69

Other Decks in Programming

Transcript

  1. Singular Perturbations Inc
    Keisuke Nishitani
    ୤ 'JSFCBTF
    զʑ͸Ͳ͏ੜ͖Δ͔

    View Slide

  2. CTO at Singular Perturbations Inc
    Keisuke Nishitani
    @Keisuke69
    Programming is a creative work.
    🎨
    Love Music

    Love Camping

    Blog: https://www.keisuke69.net/
    💻
    Everything will be serverless.

    View Slide

  3. View Slide

  4. 世界の悲しい経験を減らす。
    VISION

    View Slide

  5. コンピューターサイエンスがもたらす知能を
    安全に関わる全ての⼈へ
    MISSION

    View Slide

  6. View Slide

  7. 前置き
    Photo by Kristina Flour on Unsplash

    View Slide

  8. 弊社のアプリケーション構成
    • 最初期はモバイルだけであり、エンジニアがいなかったこともあ
    り全⾯的にFirebaseに依存
    • Cloud Firestore以外にもFirebase Authentication、Cloud Storage for
    Firebaseなどを利⽤
    • 昨年からプロダクト構成が整理され、モバイルの作り直しや新た
    にWebアプリを開発
    • モバイルとWebは役割の異なるアプリケーションだがデータは同
    じものを参照
    • AWSは⼀部のAPIと機械学習パイプラインで利⽤

    View Slide

  9. 弊社のアプリケーション構成
    Firebase
    Authentication
    Firebase
    Cloud Firestore
    Cloud Storage
    for Firebase
    Routing API

    View Slide

  10. 弊社のアプリケーション構成
    その他
    • Web
    • AWS Amplify Consoleで
    ホスティング
    • AWS AppSyncも利⽤
    • Routing API
    • AWS Fargate
    • 機械学習パイプライン
    • AWS StepFunctions、AWS Lambda
    、AWS Fargateあたりを活⽤
    Firebase
    Authentication
    Firebase
    Cloud Firestore
    Cloud Storage
    for Firebase
    Routing API

    View Slide

  11. 弊社のアプリケーション構成
    Firebase
    Authentication
    Firebase
    Cloud Firestore
    Cloud Storage
    for Firebase
    Routing API
    REST API
    Firebase
    Authentication
    Cloud Storage
    for Firebase
    Routing API

    View Slide

  12. 今⽇のテーマ

    View Slide

  13. FirebaseのCloud Firestoreをやめた話
    ※Firebaseをdisる話ではありません

    View Slide

  14. そもそも、なぜやめるのか

    View Slide

  15. なぜやめるのか
    • ユースケース・ニーズの変化
    • 将来的なビジネスロードマップ上の布⽯
    • 開発効率

    View Slide

  16. なぜやめるのか
    • ユースケース・ニーズの変化
    • 将来的なビジネスロードマップ上の布⽯
    • 開発効率

    View Slide

  17. なぜやめるのか
    • ユースケース・ニーズの変化
    • 将来的なビジネスロードマップ上の布⽯
    • 開発効率

    View Slide

  18. なぜやめるのか
    • ユースケース・ニーズの変化
    • 将来的なビジネスロードマップ上の布⽯
    • 開発効率

    View Slide

  19. ユースケース・ニーズの変化
    • Cloud Firestoreでは対応するのが難しいケースの発⽣
    • 集約
    • 当初はユースケースとして不要
    • 運⽤上発⽣していた集計処理は⽉次でBigQuery (BQ) にインポートして処

    • ユーザ操作によってその時点での集計結果を得たいという要件が発⽣
    • 地理情報による検索
    • 特定の地理情報でデータを柔軟にクエリする必要が発⽣
    • Geographical point型があるが公式にも推奨されておらず、Geohashを
    使った実装が必要

    View Slide

  20. そうだ、Firestoreやめよう

    View Slide

  21. では、どうするか

    View Slide

  22. GraphQL vs REST API

    View Slide

  23. GraphQL vs REST API
    • GraphQLの⼀般的な利点
    • クライアントで取得したいデータを決められ、ちょっとした仕様変更なら
    API側の修正やリリースが不要
    • REST APIのように固定的なリクエスト/レスポンスに従うわけではなく、
    必要なデータのみを含むレスポンスとなり効率がいい
    • 型がある
    • フロントエンドのためのゲートウェイとして振る舞ることも可能で、いわ
    ゆるフェデレーション的なことも可能
    • 正直なところRESTと⽐べると利点しかない
    • でもREST APIを採⽤した

    View Slide

  24. なぜREST APIにしたのか

    View Slide

  25. GraphQLを選ばなかった理由
    • エコシステムの成熟度
    • ツール、ライブラリの選択肢があまりない
    • 開発チームが不慣れ
    • 有⼒なマネージド・サービスが少ない
    • エンジニアが少ないのでマネージド・サービスは必須
    • 安⼼して任せられる決定的なサービスが存在しない(個⼈の主観)
    • AWS AppSyncはVTLが⾟い
    • 特にデバッグ
    • 異論は認める

    View Slide

  26. というわけでCloud Firestoreをやめて、⾃前のREST APIに移⾏する

    View Slide

  27. スタック
    • アプリケーション
    • TypeScript
    • Nest.js
    • Prisma
    • インフラ
    • AWS Fargate
    • Amazon Elastic Load Balancing ( Network Load Balancer )
    • Amazon Aurora for PostgreSQL
    • PostgreSQLなのはPostGISを使うため
    • 開発環境はAurora Serverless
    • Amazon API Gateway
    • 認証のため

    View Slide

  28. (余談) PrismaがPostGISに未対応
    • PrismaはPostGISのGEOMETRY型に対応していない
    • PostGISが持つ地理情報を扱うための関数群をPrismaそのものでは
    扱えない
    • $queryRawならびに$executeRawでSQLを発⾏するしかないので
    ORMを使う嬉しさは半減する
    • このあたりは本題からはずれるので別の機会に

    View Slide

  29. 移⾏の仕⽅を考える
    (アプリケーション編)

    View Slide

  30. 移⾏にあたって検討したこと、決め事
    • アプリケーションそのものの部分はそんなに難しい話ではない
    • 検討の多くはデータ格納先となるRDBに既存のデータをどう格納していくか
    • つまりテーブル設計
    • FirestoreはNoSQLと呼ばれるタイプのDBであり、テーブルや⾏といったものがない。
    • データは『ドキュメント』として扱われ、ドキュメントをまとめたものとして『コレク
    ション』という概念がある
    • 『ドキュメント』はJSのオブジェクトのようなものでフィールドとその値で構成され、こ
    のフィールドは可変
    • 具体的には以下のような点について検討が必要だった
    • ドキュメントIDをどう扱うか
    • サブコレクションをどう扱うか
    • 配列やマップといったフィールドのタイプをどう扱うか
    • Firebase AuthenticationとFirestoreのセキュリティルールで実現している認可をどうするか

    View Slide

  31. ドキュメントID
    • ドキュメントID: Firestoreで使われるID。デフォルトでは“06XWvXOqtUmLR2BnC7fZ” のような⽂字列
    • RDBでID⽣成をDBのシーケンスなどの機能に任せたい場合は数値の型になる
    • 加えて、別の値を設定するとなるとコレクション間のリレーション全てを書き換える必要が出てくる
    • プライマリーキーとなるID列を⽂字列型で⽤意し、既存データはドキュメントIDをそのまま移⾏、新規
    データは新たにキーを⽣成する
    • UUIDなどが考えられるが、CUIDを採⽤
    参考): ⼀意な識別⼦の⽣成でUUID/ULID/CUID/Nano IDなど検討してみた
    https://www.keisuke69.net/entry/2022/08/01/140656
    • 型はTEXT型を採⽤
    • 選択肢としてはCHAR、VARCHAR、TEXT
    • ID列なので結果的に固定⻑
    • 公式ドキュメントにも記載の通りPostgreSQLだとTEXTとVARCHARはパフォーマンス的には同等
    • 他のDBと異なりCHARが⼀番遅いのでCHARを使う利点はない
    • 実際のところ⽂字数を制限したところで⾒積もりが楽になるくらいだと思われることと、Prismaも対応しているため
    TEXTを使う
    • 基本的に既存のドキュメントIDをそのままIDとして格納するので外部キー制約も問題ない

    View Slide

  32. サブコレクション
    • サブコレクション
    • 特定のドキュメントにぶら下がる形で定義されたコレクション
    • 配列やMapでネストする場合と異なりネストするデータのサイズが増えて
    も親のドキュメントのサイズが変わらない
    • ⼩さいデータをネストする場合は問題ないがそれなりに⼤きいものを格納
    すると親ドキュメントのクエリで取得するデータサイズが⼤きくなる
    • FirestoreのドキュメントをRDBのレコード、コレクションをテーブ
    ルだと⾒⽴てるとリレーションのある別テーブルと構造としては
    同じ
    • 別テーブルとして切り出し、コレクションの移⾏先を親テーブル
    とした⼦テーブルとする

    View Slide

  33. 配列
    • サブコレクションと異なりデータのサイズ・要素数は多くないものの⼀
    つのドキュメントに複数存在しているという状況が多かった
    • この状況でサブコレクションと同様の対応をすると⼩さすぎるテーブル
    が⼤量になる
    • 親⼦関係を持つテーブルが⼤量になるため、⼤量のJOINも発⽣
    • 配列に関してはPostgreSQLは配列型があるのでこれを利⽤
    • 標準SQLとしても定められている
    • Prismaでも普通にサポートしている
    • ただし、使いすぎると『正規化とは…?』という状況になるため注意
    • Indexも張れる
    • GINインデックスで配列の各要素に張れる

    View Slide

  34. Map
    • MapについてはPrismaもサポートしているJSON/JSONB型を検討した
    が、展開して列として定義
    • 理由
    • 正規化が崩れる(これは配列型も同じ)
    • クエリにRDBごとの⽅⾔が強めで、SQLの可読性が低い
    • Prismaが吸収してくれる部分もあるが、$queryRawを使うケースも多いため気になる
    • スキーマレスになり何が格納されているか分かりづらく、型も指定できな

    • 既存データではMapのキーの個数が少なく可変なものもなかった
    • JSON/JSONB型ではなく列として展開して格納することで、型も指
    定できる

    View Slide

  35. Mapの配列
    • 配列の要素としてMap型のデータを持っているケース
    • 『⾏持ちテーブル』などと呼ばれる形式に変換
    [
    {
    "name": "Scott",
    "age": 30
    },
    {
    "name": "John",
    "age": 35
    },
    {
    "name": "Bill",
    "age": 25
    }
    ]
    列名 データ型 備考
    id text 親テーブルの主キー
    index integer いわゆる配列の要素番号に相当
    name text Mapに含まれるキー
    age integer Mapに含まれるキー
    id index name age
    AAA 0 Scott 30
    AAA 1 John 35
    AAA 2 Bill 25

    View Slide

  36. (余談) PostGISとGEOMETRY型
    • 今回の事例ではMapの配列が使われて
    いたのは多くが位置情報
    • このケースはPostGISのGEOMETRY型の列
    を⽤意するだけで解決する
    • 例えば右のようにLineStringのデータを格
    納していた場合、
    GEOMETRY(‘LineString’,4326)の列を⽤意
    するだけでいい
    [
    {
    "latitude": -73.993433,
    "longitude": 40.736274
    },
    {
    "latitude": -73.993632,
    "longitude": 40.736007
    },
    {
    "latitude": -73.984937,
    "longitude": 40.732353
    },
    {
    "latitude": -73.986374,
    "longitude": 40.730382
    },

    ]

    View Slide

  37. 認証・認可
    • 認証にはFirebase Authenticationを利⽤しており、認可はFirestoreのセ
    キュリティルールで実装していたので移⾏に伴い⾃前で実装する必要性
    • Firebase Authenticationでサインインすると取得できるID Tokenをサーバー
    サイドに送り、サーバーサイドでそのTokenを検証、問題なければログ
    イン済ユーザ情報を元に権限チェック
    • 任意のJWTライブラリを⽤いて検証することが可能
    • 送られてきたID Tokenをデコードするとペイロードにsubおよびuser_idというキー
    とその値があるのでそれを⽤いてRDBに保存したユーザ情報をクエリし権限情報
    を取得
    • 今回は認証は引き続きFirebase Authentication、トークン検証は
    API Gateaway + Lambda Authorizer、認可はNest.jsのGuardとCASLを使って
    実装

    View Slide

  38. CASLによる認可についてはこちらに書いてます
    https://www.keisuke69.net/entry/2022/10/25/100315

    View Slide

  39. ここまでの話を踏まえて
    • 実際のテーブル設計は半ば機械的に実施
    • テーブルの列はドキュメントのフィールドに対応させる
    • 列名については既存のものをそのまま使うが慣例に従ってキャメルケース
    からスネークケースへと置き換え
    • テーブルができたらあとは必死に各テーブルへのCRUD APIを作成

    View Slide

  40. 移⾏の仕⽅を考える
    (データ編)

    View Slide

  41. 移⾏過渡期のデータについて
    • すでに稼働しているシステムのため既存システムからのデータ移
    ⾏が発⽣する
    • 既存データをどうサービスの中断を限りなく少なく移⾏していくか
    • ベタに移⾏スクリプトを⽤意して複数回にわけて実施
    • upsertで実装することで何も考えずに何度も実⾏可能に
    • 実際のデータ投⼊はAPIのテストも兼ねて全部API経由で実施
    • 実際には集計関連の⼀部機能のみ先⾏してリリースしたため、
    Cloud Functionsを⽤いて逐次RDBに反映した

    View Slide

  42. 移⾏の仕⽅を考える
    (インフラ編)

    View Slide

  43. 新システムのアーキテクチャ
    • シンプルなWebシステム構成をコンテナで実装
    • 実⾏基盤はAWS Fargate
    • 別で存在していたAPIとほぼ同⼀の構成とした
    • Cloud Storage for FirebaseからAmazon S3への移⾏は強いモチベーションがないため
    ⾒送った
    • 極⼒マネージド・サービスを利⽤
    • サーバーレスを採⽤しなかった理由
    • アプリの開発⽣産性の観点
    • 既存のWebアプリケーションフレームワークとそのエコシステムを『そのまま』利⽤し
    たい
    • バックエンドがRDBである
    • AWS上で構築した理由
    • 別のシステムがすでにAWSで稼働していた
    • 開発メンバーに他クラウドを知るものがおらず学習コストをかけたくなかった
    • 今回のスタックでは他クラウドを使いたいという強いモチベーションもなかった

    View Slide

  44. 増える運⽤
    • システムのモニタリング
    • APIの死活監視
    • DBの監視(死活、容量 etc)
    • DBのバックアップおよびリカバリ戦略
    • これまではFirestoreのエクスポートとFunctionsで簡単に実現していた
    • システムの運⽤
    • DBマイグレーション
    • アプリケーション⾃体はCI/CDで⾃動デプロイされるがPrismaのマイグレーションは
    ⼿動で対応中

    View Slide

  45. 実際の移⾏ステップ
    1. 設計⽅針に従って⼀通り論理設計
    2. 各テーブルのCRUDをベタに実装
    3. フロントエンド(Webとモバイル)のDBアクセス部分をAPIに切り替
    えつつ、⾜りないAPIの洗い出し
    4. ⾜りないAPIの追加実装(テーブル設計の⾒直し含む)
    5. 認証認可の実装
    6. モバイルの申請 (事前に審査には出しておき当⽇は公開のみに)
    7. APIリリース、DBマイグレーション、データ移⾏1回⽬
    8. Webのリリース
    9. モバイルの公開と強制アップデート(強制アップデートは独⾃の仕組
    み。この時点でFirestoreを⾒るユーザがいなくなる)
    10. データ移⾏2回⽬

    View Slide

  46. というわけで10⽉31⽇に無事に完了しました

    View Slide

  47. というわけで10⽉31⽇に無事に完了しました
    ※実際にはその後ちょっとした不具合修正をしたので11⽉2⽇

    View Slide

  48. SQL最⾼

    View Slide

  49. Photo by Daniel Andrade on Unsplash

    View Slide

  50. 気になることとかあったらいつでも
    @Keisuke69までDMください
    Photo by Daniel Andrade on Unsplash

    View Slide