7

I have a Message class - it contains a mass message, that can be delivered on-demand to many users, but each user can get it only once, when he demands new messages. How can I ensure that user never gets it again after it has once been delivered?


IDEA #1: Message.deliveredTo would be a Relation to _User objects. After a Message is delivered to user, he adds himself to Message.deliveredTo Relation.

When user later looks for undelivered Messages, he will find all Messages except those that he is in deliveredTo Relation:

var messagesQuery = new Parse.Query("Message");
messagesQuery.notEqualTo("deliveredTo", request.user);
messagesQuery.find() ...

IDEA #2: <messageId>_delivered is a Role that is created when Message with id <messageId> is created. The Message's ACL-read is disabled for the Role. After the Message is delivered to user, he adds himself to <messageId>_delivered Role.

When user later looks for undelivered Messages, he will find all Messages except those that he is in <messageId>_delivered Role:

var messagesQuery = new Parse.Query("Message");
messagesQuery.find({ sessionToken: request.user.getSessionToken() }) ...

IDEA #3: DeliveredMessage is a class. After a Message is delivered to user, he creates new DeliveredMessage with message field containing the Message and deliveredTo key containing the user.

When user later looks for undelivered Messages, he will find all Messages except those that are in his DeliveredMessage objects:

var messagesQuery = new Parse.Query("Message");
var deliveredQuery = new Parse.Query("DeliveredMessage");
deliveredQuery.equalTo("deliveredTo", request.user);
messagesQuery.doesNotMatchKeyInQuery("objectId", "message.objectId" /*is that even possible?*/, deliveredQuery)
messagesQuery.find() ...

The problem is, that all the options seems very inefficient and not index-friendly. Is some of them better then the others? Why?

Or do you have any other efficient idea how to implement this?

NOTE: Recipients of every Message can change over time (follower model), so it is not possible to add a pendingRecipients Relation field to Message, remove user from it when he receives the Message and use equalTo instead of notEqualTo when checking for undelivered messages.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
David Riha
  • 1,368
  • 14
  • 29

1 Answers1

1

Honestly, talking about efficiency and indexing, typical queries is a key. With assumption that the top queries from most queried to least queried are:

  • get number of undelivered message for user (recipient)
  • get list of several newest/oldest message IDs/titles for user (recipient)
  • get message content by messageID
  • put message content and recipients

A class of UndeliveredMessages will be most efficient. It should have at least a messageID and a userID. Depending on use-cases, you may want to add date of the message, may be title, etc. Indexing by userID you can retrieve number of undelivered messages without hitting a the disk.

It is similar to IDEA #3, with 2 benefits:

  • shifting calculation of undelivered messages from find query to the point when message is created, or a new follow relationship established. Population of the table can be done in background, which will result with increased latency of "delivering" messages, but keep operational requests quick.
  • size of the collection do not exponentially grows over time, as you don't need to keep all delivered messages.
Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • You mean creating `UndeliveredMessage` instance for every possible recipient when `Message` is created and deleting it after the message is delivered to the given user? That is not possible, because recipients are all followers and followers change over time. Sorry, maybe I don't understand your answer, could you please explain your idea in more detail? Do you fully understand my question? – David Riha Mar 05 '17 at 22:04
  • Do you mean you want to deliver messages retrospectively? Otherwise I don't see a problem to maintain list of the recipients. – Alex Blex Mar 05 '17 at 22:30
  • When user A follows user B, he can demand one of user B previous messages. User A can get each message only once. When user A starts following user B, he can have long history of messages that user A can demand. So, when user A starts following user B, he would have to create `UndeliveredMessage` instances for all messages that user B ever created (i.e. creating hundreds or thousands of objects per 'follow'). Creating one `UndeliveredMessage` instance, deliver it on next demand and replace with another seems complicated, as delivery of some messages is limited to specific dates or day of week. – David Riha Mar 06 '17 at 10:30
  • Okay, I have updated the answer with some explanations. The thing is, you need to find these thousands of undelivered messages to let follower know which ones he can demand in any scenario. The requirement to deliver messages on specific schedule is quite unexpected, and may be a game changer, if you describe it. Again, I strongly recommend to estimate typical queries and build schema based on it. There is no such thing as totally optimal schema, unless your objects contain only _id field. – Alex Blex Mar 06 '17 at 10:53
  • Thank you for suggestions. When user creates a message, he can specify deliverability: 'anytime' or starting and/or ending date or only on specific day of week. My implementation now and also the typical query: when user gets some messages, he creates `MessageDelivery` containing `messages` array pointer. When looking for new messages, user queries all his `MessageDelivery` and then queries `Message` objects that are deliverable on current date and are not in any of `MessageDelivery.messages`. BTW I also need to keep all `MessageDelivery` objects to provide "History of delivered messages". – David Riha Mar 06 '17 at 11:50
  • On one demand, number of messages that user can get is limited to 10. So when `find` returns lot of results, cloud code on server selects the top 10 messages (prefers messages from multiple authors, prefers newest etc) and that is what user will get and creates `MessageDelivery` containing these messages when delivery is confirmed. – David Riha Mar 06 '17 at 11:59
  • =) I knew that delivery on schedule is not the last hidden requirement. Sorry, it is too far from original question. – Alex Blex Mar 06 '17 at 12:13
  • Yes, the situation is complicated, sorry about that. It's hard to include all the details in stackoverflow post so that people are willing to read it. Thanks anyway. – David Riha Mar 06 '17 at 12:30