Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
脱Firebase. 我々はどう生きるか/Migrate from Firebase
Search
Keisuke69
November 09, 2022
Programming
7
8.8k
脱Firebase. 我々はどう生きるか/Migrate from Firebase
AWS DevDay 2022での登壇資料です。
Firebaseと言ってますが実際にはFirestoreだけです。
なお、Firebaseをdisるような内容ではありません。
Keisuke69
November 09, 2022
Tweet
Share
More Decks by Keisuke69
See All by Keisuke69
CTOから見た事業開発とプロダクト開発 / My Perspective on Business and Product Development as CTO
keisuke69
4
1.2k
波濤 / Surges
keisuke69
1
160
クロスプラットフォーム開発の真実
keisuke69
2
620
AWSでISRの実現!その謎を解明すべくAmazonの奥地へと足を踏み入れる!! / Digging how to running ISR on AWS
keisuke69
4
8.8k
様式美と絵に書いた餅、そしてそこにあるリアル
keisuke69
0
5.4k
俺のJestが動かない 2021 Spring / My Jest does not work well 2021 Spring
keisuke69
0
7.3k
フロントエンド開発者も知っておきたいAWS Lambda とサーバーレス / Serverless for frontend developers
keisuke69
6
7.9k
Pythonistaに贈るAWS Lambda入門 / AWS Lambda Essentials for Pythonista
keisuke69
2
4.6k
The Twelve-Factor App on AWS
keisuke69
16
5.1k
Other Decks in Programming
See All in Programming
[JAWS-UG横浜 #76] イケてるアップデートを宇宙いち早く紹介するよ!
maroon1st
0
460
快速入門可觀測性
blueswen
0
370
Effective Signals in Angular 19+: Rules and Helpers
manfredsteyer
PRO
0
110
RWC 2024 DICOM & ISO/IEC 2022
m_seki
0
210
Exploring: Partial and Independent Composables
blackbracken
0
100
競技プログラミングへのお誘い@阪大BOOSTセミナー
kotamanegi
0
360
【re:Growth 2024】 Aurora DSQL をちゃんと話します!
maroon1st
0
780
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
380
Go の GC の不得意な部分を克服したい
taiyow
3
790
Haze - Real time background blurring
chrisbanes
1
510
創造的活動から切り拓く新たなキャリア 好きから始めてみる夜勤オペレーターからSREへの転身
yjszk
1
130
Recoilを剥がしている話
kirik
5
6.8k
Featured
See All Featured
Building an army of robots
kneath
302
44k
KATA
mclloyd
29
14k
Writing Fast Ruby
sferik
628
61k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Thoughts on Productivity
jonyablonski
67
4.4k
Why Our Code Smells
bkeepers
PRO
335
57k
Building Your Own Lightsaber
phodgson
103
6.1k
Gamification - CAS2011
davidbonilla
80
5.1k
It's Worth the Effort
3n
183
28k
A designer walks into a library…
pauljervisheath
204
24k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
Bash Introduction
62gerente
608
210k
Transcript
Singular Perturbations Inc Keisuke Nishitani 'JSFCBTF զʑͲ͏ੜ͖Δ͔
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. ⚡
None
世界の悲しい経験を減らす。 VISION
コンピューターサイエンスがもたらす知能を 安全に関わる全ての⼈へ MISSION
None
前置き Photo by Kristina Flour on Unsplash
弊社のアプリケーション構成 • 最初期はモバイルだけであり、エンジニアがいなかったこともあ り全⾯的にFirebaseに依存 • Cloud Firestore以外にもFirebase Authentication、Cloud Storage for
Firebaseなどを利⽤ • 昨年からプロダクト構成が整理され、モバイルの作り直しや新た にWebアプリを開発 • モバイルとWebは役割の異なるアプリケーションだがデータは同 じものを参照 • AWSは⼀部のAPIと機械学習パイプラインで利⽤
弊社のアプリケーション構成 Firebase Authentication Firebase Cloud Firestore Cloud Storage for Firebase
Routing API
弊社のアプリケーション構成 その他 • 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
弊社のアプリケーション構成 Firebase Authentication Firebase Cloud Firestore Cloud Storage for Firebase
Routing API REST API Firebase Authentication Cloud Storage for Firebase Routing API
今⽇のテーマ
FirebaseのCloud Firestoreをやめた話 ※Firebaseをdisる話ではありません
そもそも、なぜやめるのか
なぜやめるのか • ユースケース・ニーズの変化 • 将来的なビジネスロードマップ上の布⽯ • 開発効率
なぜやめるのか • ユースケース・ニーズの変化 • 将来的なビジネスロードマップ上の布⽯ • 開発効率
なぜやめるのか • ユースケース・ニーズの変化 • 将来的なビジネスロードマップ上の布⽯ • 開発効率
なぜやめるのか • ユースケース・ニーズの変化 • 将来的なビジネスロードマップ上の布⽯ • 開発効率
ユースケース・ニーズの変化 • Cloud Firestoreでは対応するのが難しいケースの発⽣ • 集約 • 当初はユースケースとして不要 • 運⽤上発⽣していた集計処理は⽉次でBigQuery
(BQ) にインポートして処 理 • ユーザ操作によってその時点での集計結果を得たいという要件が発⽣ • 地理情報による検索 • 特定の地理情報でデータを柔軟にクエリする必要が発⽣ • Geographical point型があるが公式にも推奨されておらず、Geohashを 使った実装が必要
そうだ、Firestoreやめよう
では、どうするか
GraphQL vs REST API
GraphQL vs REST API • GraphQLの⼀般的な利点 • クライアントで取得したいデータを決められ、ちょっとした仕様変更なら API側の修正やリリースが不要 •
REST APIのように固定的なリクエスト/レスポンスに従うわけではなく、 必要なデータのみを含むレスポンスとなり効率がいい • 型がある • フロントエンドのためのゲートウェイとして振る舞ることも可能で、いわ ゆるフェデレーション的なことも可能 • 正直なところRESTと⽐べると利点しかない • でもREST APIを採⽤した
なぜREST APIにしたのか
GraphQLを選ばなかった理由 • エコシステムの成熟度 • ツール、ライブラリの選択肢があまりない • 開発チームが不慣れ • 有⼒なマネージド・サービスが少ない •
エンジニアが少ないのでマネージド・サービスは必須 • 安⼼して任せられる決定的なサービスが存在しない(個⼈の主観) • AWS AppSyncはVTLが⾟い • 特にデバッグ • 異論は認める
というわけでCloud Firestoreをやめて、⾃前のREST APIに移⾏する
スタック • アプリケーション • TypeScript • Nest.js • Prisma •
インフラ • AWS Fargate • Amazon Elastic Load Balancing ( Network Load Balancer ) • Amazon Aurora for PostgreSQL • PostgreSQLなのはPostGISを使うため • 開発環境はAurora Serverless • Amazon API Gateway • 認証のため
(余談) PrismaがPostGISに未対応 • PrismaはPostGISのGEOMETRY型に対応していない • PostGISが持つ地理情報を扱うための関数群をPrismaそのものでは 扱えない • $queryRawならびに$executeRawでSQLを発⾏するしかないので ORMを使う嬉しさは半減する
• このあたりは本題からはずれるので別の機会に
移⾏の仕⽅を考える (アプリケーション編)
移⾏にあたって検討したこと、決め事 • アプリケーションそのものの部分はそんなに難しい話ではない • 検討の多くはデータ格納先となるRDBに既存のデータをどう格納していくか • つまりテーブル設計 • FirestoreはNoSQLと呼ばれるタイプのDBであり、テーブルや⾏といったものがない。 •
データは『ドキュメント』として扱われ、ドキュメントをまとめたものとして『コレク ション』という概念がある • 『ドキュメント』はJSのオブジェクトのようなものでフィールドとその値で構成され、こ のフィールドは可変 • 具体的には以下のような点について検討が必要だった • ドキュメントIDをどう扱うか • サブコレクションをどう扱うか • 配列やマップといったフィールドのタイプをどう扱うか • Firebase AuthenticationとFirestoreのセキュリティルールで実現している認可をどうするか
ドキュメント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として格納するので外部キー制約も問題ない
サブコレクション • サブコレクション • 特定のドキュメントにぶら下がる形で定義されたコレクション • 配列やMapでネストする場合と異なりネストするデータのサイズが増えて も親のドキュメントのサイズが変わらない • ⼩さいデータをネストする場合は問題ないがそれなりに⼤きいものを格納
すると親ドキュメントのクエリで取得するデータサイズが⼤きくなる • FirestoreのドキュメントをRDBのレコード、コレクションをテーブ ルだと⾒⽴てるとリレーションのある別テーブルと構造としては 同じ • 別テーブルとして切り出し、コレクションの移⾏先を親テーブル とした⼦テーブルとする
配列 • サブコレクションと異なりデータのサイズ・要素数は多くないものの⼀ つのドキュメントに複数存在しているという状況が多かった • この状況でサブコレクションと同様の対応をすると⼩さすぎるテーブル が⼤量になる • 親⼦関係を持つテーブルが⼤量になるため、⼤量のJOINも発⽣ •
配列に関してはPostgreSQLは配列型があるのでこれを利⽤ • 標準SQLとしても定められている • Prismaでも普通にサポートしている • ただし、使いすぎると『正規化とは…?』という状況になるため注意 • Indexも張れる • GINインデックスで配列の各要素に張れる
Map • MapについてはPrismaもサポートしているJSON/JSONB型を検討した が、展開して列として定義 • 理由 • 正規化が崩れる(これは配列型も同じ) • クエリにRDBごとの⽅⾔が強めで、SQLの可読性が低い
• Prismaが吸収してくれる部分もあるが、$queryRawを使うケースも多いため気になる • スキーマレスになり何が格納されているか分かりづらく、型も指定できな い • 既存データではMapのキーの個数が少なく可変なものもなかった • JSON/JSONB型ではなく列として展開して格納することで、型も指 定できる
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
(余談) 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 }, … ]
認証・認可 • 認証には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を使って 実装
CASLによる認可についてはこちらに書いてます https://www.keisuke69.net/entry/2022/10/25/100315
ここまでの話を踏まえて • 実際のテーブル設計は半ば機械的に実施 • テーブルの列はドキュメントのフィールドに対応させる • 列名については既存のものをそのまま使うが慣例に従ってキャメルケース からスネークケースへと置き換え • テーブルができたらあとは必死に各テーブルへのCRUD
APIを作成
移⾏の仕⽅を考える (データ編)
移⾏過渡期のデータについて • すでに稼働しているシステムのため既存システムからのデータ移 ⾏が発⽣する • 既存データをどうサービスの中断を限りなく少なく移⾏していくか • ベタに移⾏スクリプトを⽤意して複数回にわけて実施 • upsertで実装することで何も考えずに何度も実⾏可能に
• 実際のデータ投⼊はAPIのテストも兼ねて全部API経由で実施 • 実際には集計関連の⼀部機能のみ先⾏してリリースしたため、 Cloud Functionsを⽤いて逐次RDBに反映した
移⾏の仕⽅を考える (インフラ編)
新システムのアーキテクチャ • シンプルなWebシステム構成をコンテナで実装 • 実⾏基盤はAWS Fargate • 別で存在していたAPIとほぼ同⼀の構成とした • Cloud
Storage for FirebaseからAmazon S3への移⾏は強いモチベーションがないため ⾒送った • 極⼒マネージド・サービスを利⽤ • サーバーレスを採⽤しなかった理由 • アプリの開発⽣産性の観点 • 既存のWebアプリケーションフレームワークとそのエコシステムを『そのまま』利⽤し たい • バックエンドがRDBである • AWS上で構築した理由 • 別のシステムがすでにAWSで稼働していた • 開発メンバーに他クラウドを知るものがおらず学習コストをかけたくなかった • 今回のスタックでは他クラウドを使いたいという強いモチベーションもなかった
増える運⽤ • システムのモニタリング • APIの死活監視 • DBの監視(死活、容量 etc) • DBのバックアップおよびリカバリ戦略
• これまではFirestoreのエクスポートとFunctionsで簡単に実現していた • システムの運⽤ • DBマイグレーション • アプリケーション⾃体はCI/CDで⾃動デプロイされるがPrismaのマイグレーションは ⼿動で対応中
実際の移⾏ステップ 1. 設計⽅針に従って⼀通り論理設計 2. 各テーブルのCRUDをベタに実装 3. フロントエンド(Webとモバイル)のDBアクセス部分をAPIに切り替 えつつ、⾜りないAPIの洗い出し 4. ⾜りないAPIの追加実装(テーブル設計の⾒直し含む)
5. 認証認可の実装 6. モバイルの申請 (事前に審査には出しておき当⽇は公開のみに) 7. APIリリース、DBマイグレーション、データ移⾏1回⽬ 8. Webのリリース 9. モバイルの公開と強制アップデート(強制アップデートは独⾃の仕組 み。この時点でFirestoreを⾒るユーザがいなくなる) 10. データ移⾏2回⽬
というわけで10⽉31⽇に無事に完了しました
というわけで10⽉31⽇に無事に完了しました ※実際にはその後ちょっとした不具合修正をしたので11⽉2⽇
SQL最⾼
Photo by Daniel Andrade on Unsplash
気になることとかあったらいつでも @Keisuke69までDMください Photo by Daniel Andrade on Unsplash