surga Lab

開発したい!!

Cloud Firestoreでミニブログの構成を考える

NoSQLであるFirestoreでミニブログを作るときの構成を考えます。

Firestoreの組み方を勉強中ですので、自分の整理のためにも書いています。

ミニブログを考える

例として、ツイッターのようなつぶやきサービスについて考えます。

仕様

こんな感じでしょうか。

  • つぶやきを投稿できる
  • 他人をフォローできる
  • フォロワーを確認できる
  • 特定ユーザーのつぶやきだけを見られる
  • フォローしているユーザーのつぶやきだけを見られる

覚えておかなければいけないルール

  • 1つのドキュメントは最大1MB
  • フィールドに値は20000個まで(配列/Mapの一要素も1つとカウント)
  • ドキュメントへは1秒1回の書き込み制限がある
  • 課金はRead/Writeのドキュメント数

1つのドキュメントにまとめる

ユーザーコレクションでユーザー1人につき1ドキュメント与えられ、
ドキュメントはツイートの内容を配列として持ちます。

User
    user001{
        id:user001,
        tweets:[tweet1, tweet2, tweet3],
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }

問題点

以下の問題が挙げられます。

1MB制限/20000制限

ツイート数が増えていくと、いずれ容量/個数制限に引っかかってしまいます。

他にも問題点はありますが、後述します。

ツイートをサブコレクションにする

tweetsサブコレクションを用意し、1つのツイートを1つのドキュメントとして作成します。

サブコレクションであれば、Userドキュメントの1MB制限/20000制限に引っかからないのでうまくいきそうですね。

User
    user001{
        id:user001,
        tweets
            tweet1{
                tweetid:001,
                tweet_text:"ついーーと",
                }
            tweet2{
                tweetid:001,
                tweet_text:"ついーーと2",
                }
            tweet3{
                tweetid:001,
                tweet_text:"ついーーと3",
                }
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }

問題点

例えば全ユーザーから全てのツイートを取得しようと大変...でした。少し前まで。

今まではコレクション間を超えてQueryを出すことはできず、
db.collection("User").document("user001").collection("tweets")
のようにQueryを出す必要があったので、
全ツイートを取得するにはユーザーの数分のQueryが必要でした。

ですが最近のアップデートで、 同じコレクション名であれば横断的にQueryを出すことができるようになりました。

つまり、いけます。

qiita.com

もちろんよくある手法の通りにツイートコレクションをRoot直下に置くのもいいですね。
その際には所有者もフィードで示す必要があります。

User
    user001{
        id:user001,
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }
    user002{
        id:user002,
        followee:[user001, user003, user004],
        follower:[user001, user008, user009]
    }
Tweets
    tweet1{
        tweetid:001,
        author:user001,
        tweet_text:"ついーーと"
    }
    tweet2{
        tweetid:002,
        author:user002,
        tweet_text:"ついーーと2"
    }
    tweet3{
        tweetid:003,
        author:user003,
        tweet_text:"ついーーと3"
    }

見たところいい感じですが、問題点はまだあります。

followeeとfollowerをリストで持っていますと、
以下のような問題点が考えられます。

1MB制限/20000制限

フォロー / フォロワーに制限が出てしまいます。
2万フォロワーなんて世の中ザラにいます。私は20人にも満たないですが。

同期問題

例えばuser001がuser002のフォローを外したとします。
するとuser001ドキュメントのfolloweeからuser002を外すだけではなく、
user002ドキュメントのfollowerからuser001を外すこともしないといけません。面倒ですね。

User/ツイート/フォロー/フォロワーのコレクションをすべてRootに置く

全てをRootに置きました。

これなら1MB/20000制限にも掛かりません。

また、フォローフォロワーの更新は1つのドキュメント変更で済みます。

フォロー数の取得は
where(u'followee', u'==', u'user001')
でまとめて所得できます。

いい感じですね。

User
    user001{
        id:user001,
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }
    user002{
        id:user002,
        followee:[user001, user003, user004],
        follower:[user001, user008, user009]
    }
Tweets
    tweet1{
        tweetid:001,
        author:user001,
        tweet_text:"ついーーと"
    }
    tweet2{
        tweetid:002,
        author:user002,
        tweet_text:"ついーーと2"
    }
    tweet3{
        tweetid:003,
        author:user003,
        tweet_text:"ついーーと3"
    }
Follow
    follow1{
        followee:user001,
        follower:user002
    }
    follow2{
        followee:user001,
        follower:user002
    }
    follow3{
        followee:user001,
        follower:user002
    }

NoSQLで冗長性を持たせることは別に変なことではないので、
RootにTweetsコレクションを用意し、Userドキュメントにも同じTweetsサブコレクションを置く考え方も間違っていないと思います。

参考させていただいたサイト

Cloud Firestoreの勘所 パート2 — データ設計 – google-cloud-jp – Medium

待ち焦がれたCollectionGroupがCloud Firestoreへやってきた。 - Qiita

How to Structure Your Data | Get to Know Cloud Firestore #5 - YouTube

【Firebase】Cloud Firestoreのデータ構造の決め方をFirebaseの動画から学ぶ - Qiita