12

I was investigating the Firebase Database sample for Android and realized that it stores its data in the following way:

enter image description here

I am not quite familiar with NoSQL techniques and trying to understand why we have to persist each post entity twice - at posts and user_posts correspondingly. The documentation says that this approach is called "Fan Out" and I fully agree that it might be useful to access user's posts via simple construction like databaseReference.child("user-posts").child("<user_uid>"). But why do we need the posts node then? What if we need to update some post - do we have to do it twice?

// [START write_fan_out]
private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}
// [END write_fan_out]

So I wonder... when this approach might be useful and when not? Does Firebase SDK provide any tools to keep all duplicates in sync when updating or removing data?


UPDATE: Here is the explanation received from Firebase team:

the reason the posts are duplicated is because we want to be able to quickly get all the posts belonging to a user (as you suggested) and filtering from the list of all posts ever to get the posts by one user can get pretty expensive as the number of posts expands.

This does mean that we have to update the post in two locations whenever we update it. It makes the code a little uglier but since queries are more common than writes it's better to optimize for reading the data.

I suspect that this approach might look not quite elegant but it is probably the fastest option for large data sets as long as you perform SELECT more often than UPDATE. However, for some cases I'd rather stick to other solutions recommended here.

Community
  • 1
  • 1
fraggjkee
  • 3,524
  • 2
  • 32
  • 38
  • 1
    For a good introduction, see [NoSQL data modeling](https://highlyscalable.wordpress.com/2012/03/01/nosql-data-modeling-techniques/) – Frank van Puffelen Jul 04 '16 at 15:04
  • In the "Structure your database" page from the docs, the orientation is to use flags to indicate the two way relationship (as stated in other comments), while what they're doing in this code contradicts that. I think they should add some clarification about it in the documentation, I've just sent some feedback about it in the Firebase docs page. – Eric Omine Jan 03 '17 at 14:21
  • This code is Java?! It looks like verbose Dart code. Coming here from Flutter in 2019. – SacWebDeveloper Aug 16 '19 at 16:42

2 Answers2

7

Data Fan Out is a great technique to manage massive amounts of data. If you do not use this pattern, you could have serious scaling problems in the future.

What I see from your database structure, is that you are storing the whole post information twice, and that is not a good practice. You want to store just a reference to the post under another node instead. So, you will have a node named users-postswhich will consist of user keys, and each of those keys will have a set of posts keys with value of true. To make it more clear:

enter image description here

This way, you are tracking which posts the user has written under the users-posts node; and also the user that has written each post under the posts node. Now, you may need to get a list of all users' posts. What you would have to do is to synchronize on the users-posts/USER_KEY/ node to get the keys for all the posts that the user has written, and then get more post information using the post key you just got.

Why is this database design recommended? Because you are getting much less information for each synchronization (with Firebase we are not issuing requests per-se, so I call the reading a synchronization). In your example, if you attach a listener to the user-posts/USER_KEY/ to get a list of all posts, you will also ask for ALL the information of EACH AND EVERY post they have written. With the data fan out approach you can just ask for the post information you need because you already have the key of the posts.

david-ojeda
  • 344
  • 1
  • 10
  • 1
    Thank you for the detailed answer. The approach you have suggested sounds reasonable for me. However, Firebase team decided to duplicate the entire `post` entity in their sample and they also provided some explanation on this, see my edit. – fraggjkee Jul 06 '16 at 18:44
  • 1
    erm, i was using key index approach previously until i read this post from firebase. They duplicate all the data even username and I actually prefer duplicate data because i found lookup query and object merge are tired. https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwjEgaPc55vQAhXBUrwKHddBBLoQFggaMAA&url=http%3A%2F%2Ffirebase.googleblog.com%2F2015%2F10%2Fclient-side-fan-out-for-data-consistency_73.html&usg=AFQjCNFd2jOX7mx60HqPbnVcQSvYRoDEnA&sig2=zg1IYXT5gy7VPqUI6NNMkQ&bvm=bv.138169073,d.c2I – vzhen Nov 09 '16 at 13:50
  • 3
    What you suggest is kinda the opposite of fan-out. This approach you suggest makes saving data simpler but will slow down query while there are millions of posts and you need to query them by id one by one. So fan-out (duplication) is what Firebase suggested to handle that kind of scale, see this post: https://firebase.googleblog.com/search?updated-max=2015-10-13T20:08:00-07:00&max-results=1&start=24&by-date=false – Natural Lam Nov 22 '17 at 05:10
  • The suggested approach may or may not work depending on your use-case. If you want to query on the `user-post` fields with `where(...)` or perform an `orderBy` then those fields will need to exist in the `user-posts`. So it all depends on your access patterns – geg Mar 14 '21 at 22:38
2

In my opinion this is not a good approach since you need to keep in sync those data and Firebase doesn't provide any tool to keep duplicates in sync. A good approach would be to store only the key in user-posts.

I suggest reading this, it is very interesting to understand how to structure data: https://www.firebase.com/docs/web/guide/structuring-data.html

Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
  • 3
    Just keeping keys is one approach, duplicating the entire post is another. Which one is best, depends on your scenario and willingness to have duplicate data vs load data in two steps. You could write security rules to keep the data in sync if you'd like. – Frank van Puffelen Jul 04 '16 at 15:03
  • Thanks @FrankvanPuffelen. Do you mean that there's a way to keep data in sync using just security rules? Or that I can write my security rules to keep the consistency of my duplicated data when pushing? Thanks in advance – Devid Farinelli Jul 05 '16 at 07:25
  • 3
    https://firebase.googleblog.com/search?updated-max=2015-10-13T20:08:00-07:00&max-results=1&start=24&by-date=false - this article describes some Firebase-specific things simplifying everything related to Fan Out approach it seems (multi-path updates and client-side fan-out). – fraggjkee Jul 05 '16 at 08:53
  • 1
    Security rules can do almost anything that the underlying data allows. For a good example of their power, see my recent answer on [how to keep a list of votes and voteCount in sync](http://stackoverflow.com/questions/37954217/firebase-database-security-issue/37956590#37956590). – Frank van Puffelen Jul 05 '16 at 16:53