0

I'm quite new to Meteor and I'm trying to figure out how to implement some use cases. One of them is an application for polls. Let's say you have a couple of users that can vote on a couple of polls (for simplicity let's assume polls are limited to yes/no questions). Each use can only vote once.

In SQL I would normalize this in 3 tables:

USER (id/name)
VOTES (user_id/poll_id/choice)
POLLS (id/question)

However in the document-store world of mongodb, it seems encouraged to store the votes in a collection with the polls, like so:

[
  {
    _id: 1234
    question: "the question"
    votes:
    [
      {
        user_id: 1
        choice: 1
      }
    ]
  }
]

If a user votes on a poll an item gets inserted in the votes array of a poll. The server side has to validate the user has not already voted. To get the total score I have to iterate over the votes, but of course that could be improved by creating a field on each poll that contains the aggregated score, which gets updated as votes are added.

However, how does this propagate to other users? Let's say a vote is added to a poll. Does the entire poll object get send over to other connected clients? Because if a lot of people or voting, the votes array can get big and will cause a lot of traffic...

But in that case, when the admin user changes the question (and therefore writes a new version of the poll object to the collection), either the update query needs to know it only should update selected fields. Or if overwriting the entire object the admin user should have the latest version of the object (since meteor is last-write-wins).

Am I missing some best practices around this or is it indeed easier to model my collections the same way I would model a normalized SQL DB?

Seba Kerckhof
  • 1,294
  • 1
  • 14
  • 23
  • 1
    See my answer to [this question](https://stackoverflow.com/questions/25344444/best-model-for-representing-many-to-many-relationships-with-attributes-in-mongod). I make the case that using a relational approach (despite the data store) is generally the best option when using meteor. – David Weldon Feb 15 '15 at 23:57
  • Thanks David. Great post and really glad to hear that it's not a sin to normalize data in mongodb. I tried to apply the mongo philosophy, but all of my data is just inherently relational. Also, it seems that using embedded data requires me to know how my data is going to be queried/used which for a large part I do not yet know. – Seba Kerckhof Feb 16 '15 at 11:13

2 Answers2

1

There are definitely pros and cons to denormalizing your data. So, to summarize the two options - You either create a separate Votes collection, or you just included votes in your Polls collection.

With a separate collection you definitely get some benefits. When iterating over a cursor (Votes.find({pollId: pollId})) using the #each template block, it is very performant - notsomuch if you are iterating over an array or collection of objects.

An issue of you decided to include the votes as an object inside of a Poll is that DDP only operates at the highest level attribute of a document. What this means is that every time a vote is added, the entire "votes" object would be sent down the line.

With a separate collection, you also get the benefit of better publication/subscription control.

That being said, if you go with just a separate collection, you will have to do some annoying things (as you mentioned) just to get the count of votes, i.e. you have to publish all the votes to the client just to count them.

A lot of developers choose a hybrid option, which would be to have a separate Votes collection, but also embed some important attributes about the votes (such as count) in the Polls collection. This becomes a bit annoying as well, meaning you will have to update two different collections everytime someone votes. In this case, I'd say you are pretty much on the right track.

Ramsay Lanier
  • 426
  • 6
  • 18
  • Thanks. As always when entering a new technology, I'm a bit uncertain on the best practices. But it seems, as is often the case, there's no silver bullet and everything has its pros and cons. The hybrid approach you describe is something that is quite common practice in SQL databases. I was trying to go with the philosophy of NoSQL document databases, but as soon as I try to use them on some real world use cases, especially combined with Meteor, they easily give me headaches and make me resort to my old practices. I'm glad to hear that's not a problem. – Seba Kerckhof Feb 16 '15 at 11:03
  • You'll find with Meteor that one of thr best and worst things about it is that there are a lot of different paths to the same outcome. Happy meteoring! – Ramsay Lanier Feb 16 '15 at 11:37
1

The Discover Meteor book uses Posts and Comments as an example and recommends denormalizing the data, so (as you suggest in your question) having separate Polls and Votes Collections with a voteCount attribute on a Poll.

The code when you vote would look something like: // update the poll with a new vote Polls.update(vote.pollId, {$inc: {votesCount: 1}}); Votes.insert(vote);

The way DDP operates is at the top level of a document so this would mean if votes were a property of a poll, every time a vote was created on a poll, the server would send the entire updated vote list of that poll out to each connected client.

dtt101
  • 2,151
  • 25
  • 21