Firestoreでいいねされた数を集計する

uo
KAUCHE Tech Blog
Published in
Sep 2, 2022

こんにちは。株式会社カウシェでバックエンドエンジニアをしている@uoです。

カウシェではソーシャル機能としてシェア買いの募集に「いいね」をつける機能を提供しています。「いいねされた数」はシェア買い募集画面やシェア買い一覧などで確認することができます。

シェア買い募集画面

本記事では集約関数のないFirestoreを使って「いいね」機能を実装する場合に、「いいねされた数」のカウントをどのように設計・実装するのが良いのかを紹介したいと思います。

※ 本記事のサンプルコードはGo言語で記述しています

方法1: クライアントサイドでカウント

まずシンプルな方法として、対象のデータを全て取得しクライアントサイドでカウントする、という方法があります。

Firestoreのデータ構造は以下のように定義します。

この場合、「いいねされた数」をカウントするコードは以下となります。条件に一致するドキュメントを全て取得し、件数をカウントしています。

この方法はドキュメントの件数が少ない場合は良いのですが、件数が増えるとパフォーマンスの問題が出てきます。

パフォーマンスの問題が出る理由

Firestoreからドキュメントを取得するとき、クライアントとFirestore間の通信はServer Streaming RPCで行われます。

ドキュメントの件数分のレスポンスをFirestoreから受け取るため、件数が増えるほど処理時間も増えてしまいます。

ドキュメント数が少ない場合はシンプルに実装できるこの方法でも良いかもしれません。

方法2: いいねの数を事前にカウントする

パフォーマンスに影響がないようにするためには、事前にいいねの数をカウントしFirestoreに保存しておく必要があります。

Firestoreのincrementオペレーションを使うと、トランザクションを貼らなくても安全にカウントを増やすことができます。

ただし、この方法には問題があります。Firestoreのドキュメントへの書き込みにはソフトリミットがあり、ドキュメントへの最大継続書き込み速度は1秒あたり1回となっています。

https://firebase.google.com/docs/firestore/quotas#soft_limits

そのため、トラフィックが多すぎるとパフォーマンスの影響が出てしまいます。

方法3: 分散カウンタを使っていいねの数をカウントする

トラフィックが多い場合でも更新できるようにするためには「分散カウンタ」を利用します。

ドキュメントのサブコレクションに 「シャード」 を定義して、書き込み処理を各シャードに分散させます。書き込み先のドキュメントを増やすことで書き込み速度を増やす、という方法です。

シャードを10個作ると、単一のカウンタの10倍の書き込み処理ができるようになります。

分散カウンタでインクリメントを行うコードは以下のようになります。ランダムで書き込み先のシャードを決めてインクリメントしています。

カウントの合計を取得する場合は、以下のように全てのシャードを取得してカウントを合計します。

分散カウンタにも問題があります。

トラフィックに対してシャード数が少なすぎる場合は、再試行が必要となるトランザクションが発生し、結果的に書き込み速度が遅くなります。

反対に、シャード数が多すぎる場合は、カウントの合計を取得するときに読み取るドキュメント数が多くなり、読み取り速度が遅くなります。シャード数が多くなる場合は、非同期でシャードのカウントを合計して、合計値を別のフィールドに持たせる、といった工夫をする必要があると思います。

まとめ

Firestoreでデータ件数をカウントする方法を3つ紹介しました。

ドキュメントの件数や更新頻度を考慮し、適切な方法を選択することが大切になってきます。

カウシェでは今後「お客様同士でメッセージを送り合う機能」や「フォロー・フォロワー機能」の提供を予定しています。これらの機能でも「メッセージ数の表示」や「フォロワー数の表示」でカウンタが必要になるため、適切に使い分けていければと思っています。

最後に

カウシェではGCPを利用してバックエンドの開発を行なっています。

GCPを利用したECやソーシャル機能の開発に興味を持っていただいた場合は、ぜひ採用情報をご覧いただければと思います。

--

--