これは?
Firebase Meetup #10 で 「Firestore導入前に検討したかったベスト5」 というテーマで発表したアーキテクチャ部分の話その3。
①Firestoreを中心に据えた全体設計のコンセプト
②PubSubとCloud Functionsを使って、FirestoreのCollectionをマイクロサービスに見立てた話
③パフォーマンスの劣化対策として、CacheのCollectionを作った話 ←今回
トライしたこと
まず、どんなトライをしたのか説明していきます。
この中で、今回お話するのが、赤丸をした部分です。
背景
ぼくらは、重大な問題を抱えていました。
社内の人がユーザーであるアプリケーションなので甘えていた結果、パフォーマンスが劇的に悪くなっていたのです。。
(ぼくらというかぼくだけのせい...)
状況としては、
- 元々、運用をちゃんと明瞭にシンプルにしていきたいという想いから、FirestoreのCollection設計は、正規化していた
- FirestoreではJOINしてクエリをかけられないので、正規化されているのをJOINするのは、すべて読み込み側(ブラウザ)やっていた
という感じで、つまりすべてクライアントJSでデータ結合をしていたんですね。
そしたら、1万件x1万件のジョインをするあたりから、クッソ遅くなりました。この記事は、これを解決するためにやったことのお話。
仕様
これを受けて考えたのは、 書き込みたいデータ形式と、読み出したいデータ形式の都合は違うよな! ということです。
なので、読み出しに最適化してあげればいいじゃん!と思って、真っ先に考えたのが、Backend For Frontend。クライアントのためにデータ整形してあげるAPIを挟めばいいじゃないか、と。
しかし考えてみると 「リアルタイムアップデートできないじゃん」 とおもいました。
自分でやってもいいんですが、WebSocketつかって云々カンヌンする、というのはわりと落とし穴多いし、実装スピード落ちるし、結構Firestoreの旨味減るな、と思ったわけです。
そうすると、読み出し元がFirestoreになっていることは、もはや必須要件。
だったら、Collectionも、読み出しに最適化されたものを持っていればいいじゃん、と考えました。
幸いFirestoreは書き込みをフックにCloud Functionsを起動できるので、
Firestoreに書き込まれる→Cloud Functionsが動く→Firestoreの読み出し用Collectionに書き込む
ということをしました。
もちろん、正規化された方をマスターデータとして、読み出し用CollectionはCacheと呼ぶようにしました。
良かったこと
死ぬほど早くなった。その後もすんごいデータ増えてますが、びくともしません。
だって、クライアントジョインしないなら、limitかけて取得できるので、どんなにデータ増えても性能落ちないですから、当たり前なんですよね...(クライアントジョインは全件取れないとジョインに穴できちゃうから。。)
すべての悪かったことを超越してすんごい良かった。
悪かったこと
- リアルタイムな反映が必要なやつには工夫がいる
- Cloud Functionsが動く分、リアルタイム性には微妙にばらつきがある
- (いま、USにリージョン持っているので、Tokyoリージョンなら緩和されるとおもう)
- Cacheのデバッグの難易度が高い
- 更新されてない!?何があったんだろう、という推理がある
- Cloud Functionsの設計を丁寧にやる必要がある
- 「Cloud Functionsがエラーになったら」「Cloud Functionsが重複実行されたら」「Cloud Functionsが前後してしまったら」 そういうことを想定する必要あり