1
votes

I'm trying to organize data for a social media app in firestore. Is it better to create a new collection for posts or put it into a subcollection of users?

The depth should be the same, but is there any advantage that one way has over the other?

Create a new collection:

posts(collection) >> user_id (document) >> userposts (subcollection) >> post_id (documents) >> ...

users (collection) >> [other user info...]

Subcollection in users:

users(collection) >> user_id (document) >> userposts (subcollection) >> post_id (documents) >> ...

2
Pick the one that makes your queries easier. There is no "right way". There is only the way that meets your app's needs and your personal preferences.Doug Stevenson

2 Answers

1
votes

Well, as others have mentioned, you should generally optimize for reads, but what 'optimize' means is going to depend on your application. If you expect that that it will be pretty infrequent that you have to return posts from several users at the same time, then I would guess it would be a just a bit easier to put the posts under the user, so you can quickly fetch all the posts from a single user. If you're doing something where there are say, common "subjects" that a lot of different users post on, and you want to present allt he posts on a subject, then my guess it will be a lot easier to put all your posts inside a single collection, rather than having to search multiple users collections to pull together all users' posts on a single subject. (Although that is not so hard really anymore either with the ability to search across multiple collecitons in a single query)

By the way, one thing to consider for a second is whether it makes any sense to put all the posts for a user inside of a single document if cost is a concern. If you do that you get all the posts in one document read and firestore charges are largely driven by docuemnt reads. However if you have to pull together posts from many users on a "subject", or if you have "threads" with posts from several different users this will approach will likely be impractical.

In response to your comment: Let's make sure we are using the same terminology. A thread is multiple posts from a number of different users on a single subject (i.e. posts and various replies). So if you have a set of users, and you are interested in seeing all posts they originated, you could keep a ThreadPosts collection, which has the originator of the thread and each post in a thread would contain a the ThreadPost, you could get a filtered collection of threads which the favorite user originated, and then you could get the posts for threads using methods like the below.

If you want to list all the threads which a favorite participated in then you would get all the posts from the favorite user, dedupe list of threadIds, and then pull in all the posts on that threadId in creationTimestamp order. You can use methods of firestore to get these collections both filtered by a field value (say, "threadId") and ordered by a fieldvalue (say, "creationTimestamp"). Here's some code (AngularFirestore in typescript) which demonstrates more or less what I mean.

 interface  ThreadPost {
   id: string;  //this is the id for this thread.
   title : string;
   originalAuthorId: string;
   creationTimestamp : number;
   lastUpdatedTimestamp: number;
 }

and then your posts Collection also at the root, might look like: [Typescript] interface Post { id: string; authorId : string threadId : string postTest: string; creationTimestamp: number; lastUpdatedTimestamp: number; }

So at the root of your db, you have 3 collections:

Users Posts PostThreads

So if you want to the all the posts from a user in createTimestamp order, you would do something like:

 async fetchPosts(userId) : Post[] {
    return getFilteredOrderedCollection("posts", "userId", ${userId}, "creationTimeStamp") 
 }

 async getFilteredOrderedCollection(collectionPath : string, filterField : string, 
                                     filterValue: string, orderByField: string) : any[] {
    let result: any[] = [];
    await AngularFirestore.collection(`${collectionPath}`).ref
      .where(`${filterField}`, "==", `${filterValue}`)
      .orderBy(orderByField).get()
      .then(function (snapshot) {
        snapshot.forEach(function (doc) {
          let p = doc.data()
          p.timestamp = Date.now();
          result.push(p)
        })
      })
      return result;
  }

And if you wanted all the posts on a thread you could call:

 async fetchThreadPosts(threadId) : Post[] {
     return getFilteredOrderedCollection("posts", "threadId", ${threadId}, 
     "creationTimeStamp") 
  }
1
votes

Save your data in the most easiest way they feel most fit for future search:

  • All queries in Cloud Firestore are cost and speed efficient
  • The onlything that affects the performance of a query you make is the number of documents you request

Major differences to consider

If you move data from your existing Realtime Database tree to Cloud Firestore documents and collections, keep in mind the following major differences between the databases that might impact how you structure data in Cloud Firestore:

  • Shallow queries offer more flexibility in hierarchical data structures
  • Complex queries offer more granularity and reduce the need for duplicate data
  • Query cursors offer more robust pagination
  • Transactions no longer require a common root for all your data, and are more efficient
  • Billing costs differ between Realtime Database and Cloud Firestore. In many cases, Cloud Firestore might be more expensive than Realtime Database, particularly if you rely on many small operations. Consider reducing the number of operations on your database and avoiding unnecessary writes. Learn more about the differences in billing between Realtime Database and Cloud Firestore