1

I have two Firestore collections, Users and Posts. Below are simplified examples of what the typical document in each contains.

enter image description here *Note that the document IDs in the friends subcollection are equal to the document ID of the corresponding user documents. Optionally, I could also add a uid field to the friends documents and/or the Users documents. Also, there is a reason not relevant to this question that we have friends as a subcollection to each user, but if need-be we change it into a unified root-level Friends collection.

This setup makes it very easy to query for posts, sorted chronologically, by any given user by simply looking for Posts documents whose owner field is equal to the document reference of that user.

I achieve this in iOS/Swift with the following, though we are building this app for iOS, Android, and web.

guard let uid = Auth.auth().currentUser?.uid else {
    print("No UID")
    return
}
let firestoreUserRef = firestore.collection("Users").document(uid)
firestorePostsQuery = firestore.collection("Posts").whereField("owner", isEqualTo: firestoreUserRef).order(by: "timestamp", descending: true).limit(to: 25)

My question is how to query Posts documents that have owner values contained in the user's friends subcollection, sorted chronologically. In other words, how to get the posts belonging to the user's friends, sorted chronologically.

For a real-world example, consider Twitter, where a given user's feed is populated by all tweets that have an owner property whose value is contained in the user's following list, sorted chronologically.

Now, I know from the documentation that Firestore does not support logical OR queries, so I can't just chain all of the friends together. Even if I could, that doesn't really seem like an optimal approach for anyone with more than a small handful of friends.

The only option I can think of is to create a separate query for each friend. There are several problems with this, however. The first being the challenges presenting (in a smooth manner) the results from many asynchronous fetches. The second being that I can't merge the data into chronological order without re-sorting the set manually on the client every time one of the query snapshots is updated (i.e., real-time update).

Is it possible to build the query I am describing, or am I going to have to go this less-than optimal approach? This seems like a fairly common query use-case, so I'll be surprised if there is not a way to do this.

willbattel
  • 1,040
  • 1
  • 10
  • 37
  • 1
    You can't do this in a single query. You will have to iterate somewhere and make multiple queries to collect everything you need, as you've already discovered. – Doug Stevenson Jan 16 '19 at 03:33
  • @DougStevenson I was worried this might be the case. Any user with more than a handful of friends is really going to be out of luck. I don't think I can go this route at all because of the sheer number of queries I'd have to make and sort and re-sort over and over again. There isn't a way I can restructure my data to make this more feasible, is there? – willbattel Jan 16 '19 at 04:06
  • Doing multiple queries is not really an issue. The biggest issue is the number of documents you read. That's the limiting factor. As long as you don't read any more documents than you actually need, you're doing OK. – Doug Stevenson Jan 16 '19 at 04:22
  • @DougStevenson even if having tons of queries aren't an issue, it still creates the issue you mentioned about number of documents read. If I have 200 queries, one for each friend, then I need to read at least 200 documents- and realistically a lot more than that to make sure I am able to display in chronological order. Now I'm fetching thousands of documents when I'm only trying to display the most recent 10. That's not very efficient. – willbattel Jan 16 '19 at 05:08
  • If you need to place a limit on the number of documents read, that can only be done with a single query on a single collection in Firestore. Firestore is not the best solution for every job, so be open to other alternatives that might meet the needs of your query. – Doug Stevenson Jan 16 '19 at 05:11
  • See https://stackoverflow.com/questions/48028440/how-to-accomplish-where-in-query-in-cloud-firestore and https://stackoverflow.com/questions/46849222/firestore-query-by-item-in-array-of-document for more info, unfortunately to the same tune of Doug's comments and Ron's answer. – Frank van Puffelen Jan 16 '19 at 05:46
  • @FrankvanPuffelen hmm.. thank you. I suppose I'll have to build an additional collection specific to this query that bridges the gap- but I'm having a really hard time believing that such a common case has no built-in support in Firestore. Most socially-orientated apps make such queries, and even though most don't use Firestore, I can't imagine they're using wildly different data structures. – willbattel Jan 16 '19 at 06:05
  • Have a look at https://firefeed.io/ for an example of how you can build a social app on a NoSQL database. That app is modeled after how twitter worked at the time: lots of data duplication to speed up reads. – Frank van Puffelen Jan 16 '19 at 14:22

1 Answers1

2

The sort chronologically is easy provided you are using a Unix timestamp, e.g. 1547608677790 using the .orderBy method. However, that leaves you with a potential mountain of queries to iterate through (one per friend).

So, I think you want to re-think the data store schema.

Take advantage of Cloud Functions for Firebase Triggers. When a new post is written, have a cloud function calculate who all should see it. Each user could have an array-type property containing all unread-posts, read-posts, etc.

Something like that would be fast and least taxing.

Ronnie Royston
  • 16,778
  • 6
  • 77
  • 91
  • This makes sense but I don't think I could use an array-type property, because of the document size limits imposed by Firestore. However, I suppose I could deploy this same concept as a subcollection on each Post document, where each document in the subcollection corresponds to a User who should be able to see the post. – willbattel Jan 16 '19 at 03:42
  • The max size of a `document` is identical to a `field value`, i.e. if an array is too small, so is a document. See [Maximum size for a document](https://firebase.google.com/docs/firestore/quotas). Note 500 pages of text equals one megabyte (the field size limit), or 1,000,000 characters. – Ronnie Royston Jan 16 '19 at 03:47
  • I'm not sure I'm following your comment. An array wouldn't be a good solution if there are a potentially unlimited number of Posts the user should see. It could be put into a subcollection. – willbattel Jan 16 '19 at 03:55