Advice: Reservation System with RavenDB

89 views
Skip to first unread message

Khalid Abuhakmeh

unread,
Apr 17, 2014, 1:45:52 PM4/17/14
to rav...@googlegroups.com
I have to build a quick reservation system and was wondering what the best approach would be and would like the community's advice.

Here are the requirements

1. There are days
2. These days have particular time slots
3. Each slot can only have 6 participants

Note: The date and times are not particularly important. You could even replace them with Cars and seats if that makes it mentally easier :)

I have a basic idea brewing in my head to avoid concurrency issues but I don't want to seed any thoughts or approaches before seeing what you guys think.

Thanks,

Khalid 

Chris Marisic

unread,
Apr 17, 2014, 2:45:29 PM4/17/14
to rav...@googlegroups.com
Car { 
Id: 1
Seats[]: { a,b,c,d,e,f }
}

And then you pull out seats as they're reserved.

Assuming you expect multiple people trying to cram into car1 you just need to keep re-applying the reserve-seat-car1 command with retry mechanics until you successfully write or that seats is empty and you're out of stock.

If you care about ensuring FIFO, that the first 6 people are guaranteed the seats. You need to stack a queue in the middle to serialize the command sequence to FIFO. If you don't use the queue, theoretically a person who was earlier might miss a seat. Say all 6 people try to register at the same time. So of course 6 concurrent requests, atleast 1 user is in a retry loop. A 7th person could come along and end up pulling the 6th seat, even though they were the 7th person sequentially without a queue you have no guaranteed order.

The "con" to this solution is you need to build every single car known to the system up front, in advance. This can eventually break down over the flow of time, especially if a car can be re-used with different times. Going back to a calendar analogy, you might need to create either CalendarDays or CalendarWeek objects upfront. This can break down when you have Users * 52 calendarWeeks (or 365days) * 100 years, is a very large growth. If your time span is limited it works out really well.

I built a scheduling system using CalendarWeeks in this exact manner and it worked great. In the near future I will be replacing a narrow focus calendar with a global calendar. Since i have no understanding upfront of how many seats are available (or i would need to carve blocks out 1440 minutes per day) i'm actually going to store this data in sql for scheduling. Then i'll store calendar weeks or calendar month objects for view presentation in raven. Using sql & raven I can avoid pre-seeding anything.

Kijana Woodard

unread,
Apr 17, 2014, 6:25:07 PM4/17/14
to rav...@googlegroups.com
Off the cuff, if you know what the slots are, you don't have to pre-gen.

slots/20140417/21

For the 21st slot of the day.

So when people reserve:

Load("slots/20140417/21"). 
If null, create/store. 
slot.AddReservation(reservationCommand). 
Turn on optimistic concurrency. SaveChanges. Retry on fail.


--
You received this message because you are subscribed to the Google Groups "RavenDB - 2nd generation document database" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Lee

unread,
Apr 17, 2014, 7:08:58 PM4/17/14
to rav...@googlegroups.com
Create documents using following schema: -

participants/20140417/13/

where 20140417 is the date and 13 is the slot.  Let raven auto generate the integer after the slash.  If the auto generated integer is greater than 6, you know you are full on that slot.

Khalid Abuhakmeh

unread,
Apr 18, 2014, 9:48:15 AM4/18/14
to rav...@googlegroups.com
@Chris Yeah that seems overly complex for my situation.

@Lee the Hilo generator will skip numbers, so it isn't a reliable counter to dependend on. I could skip from 1 directly to 33 IIRC.

@Kijana Funny, that's exactly what I was thinking for the most part. Great minds thinks a like ;) Also congrats on your RavenConf talk. I'm gonna try to make it next year promise :)

When I use optimistic concurrency is it best to reload the document for the Event/Car?

Does this look about right to you guys (I know there is a slight possibility for an infinite loop and that the session could potentially hit the request limit, this is not production code :))


using (var session = Db.OpenSession()) {
session.Advanced.UseOptimisticConcurrency = true;
var slot = session.Load<Slot>(slotId);
var stop = slot.IsFull;
while (!stop) {
try {
var reservation = new Reservation(User);
session.Store(reservation);
slot.Add(reservation);
session.SaveChanges();

Flash.Success("Hooray!", "you are in buddy!");

stop = true;
catch(ConcurrencyException) {
slot = session.Load<Slot>(slotId);
stop = slot.IsFull;
Flash.Info("Sorry!", "Slot is full, please try another");
}
catch (Exception ex) {
stop = true;

Lee

unread,
Apr 18, 2014, 10:40:29 AM4/18/14
to rav...@googlegroups.com
Unless something has changed if you end with a slash then raven assigns consecutive integers. Hilo doesn't come into play.

Khalid Abuhakmeh

unread,
Apr 18, 2014, 10:43:15 AM4/18/14
to rav...@googlegroups.com
How would RavenDB know what the previous integer was? Is it keeping track of it or is it doing a LoadStartWith() first and doing a count? What does it do if you delete a document in the sequence?

Oren Eini (Ayende Rahien)

unread,
Apr 18, 2014, 10:53:01 AM4/18/14
to ravendb



Oren Eini

CEO

Mobile: + 972-52-548-6969

Office:  + 972-4-674-7811

Fax:      + 972-153-4622-7811





--

Lee

unread,
Apr 18, 2014, 10:53:56 AM4/18/14
to rav...@googlegroups.com
I don't know how raven does it but it is in the documentation. With deletion you would need to get the id after saving and then do a starts with. If the id generated is one of the lowest 6 then they got in.

Khalid Abuhakmeh

unread,
Apr 18, 2014, 10:55:16 AM4/18/14
to rav...@googlegroups.com
ah so you create a document key. That makes sense. I stand corrected. Thanks Lee and Oren :)

The final option for key generation in Raven is to ask the database to define REST like keys, such as "posts/2392", Raven supports such keys natively. If you save a document whose key ends with '/', Raven will automatically start tracking identity numbers for the prefix if it doesn't exist and append the current identity value to the key. This approach is recommended for most scenarios, since it produces keys that are human readable.

Kijana Woodard

unread,
Apr 18, 2014, 12:10:54 PM4/18/14
to rav...@googlegroups.com

Yeah. I've used identity before to great effect. You get a sequence per sub collection which can be nice. The only downside is you don't know the identity until after save changes.

--

Khalid Abuhakmeh

unread,
Apr 18, 2014, 1:55:04 PM4/18/14
to rav...@googlegroups.com
So does the code I provided look ok or would you guys do anything different?

Lee

unread,
Apr 18, 2014, 2:16:38 PM4/18/14
to rav...@googlegroups.com
Well as there is a solution where there is no concurrency issue, my preference is always that :)

But given your code, your load in the catch won't go back to the server as you already loaded it into the session object.

Lee

unread,
Apr 18, 2014, 2:21:13 PM4/18/14
to rav...@googlegroups.com
Also you say the slot is full in the catch without checking if it is actually full.

Khalid Abuhakmeh

unread,
Apr 18, 2014, 2:26:13 PM4/18/14
to rav...@googlegroups.com
IsFull is a helper property 

public bool IsFull { get { return Slots.Count > 6 } );

so if I call call session.Load after a concurrency exception that item is not evicted from the session?

Did I also say that people reserving can unreserve and delete their reservations? Sorry if I missed that part :)

Lee

unread,
Apr 18, 2014, 2:28:28 PM4/18/14
to rav...@googlegroups.com
I don't actually know this without checking but if you call store and you get a concurrency exception when saving, does that stored document stay in the session? I.e if you then go and store another reservation will the second save changes try and commit both reservations.

Khalid Abuhakmeh

unread,
Apr 18, 2014, 2:33:17 PM4/18/14
to rav...@googlegroups.com
yeah very good questions, I'll have to do some tests :) 

Lee

unread,
Apr 18, 2014, 2:34:19 PM4/18/14
to rav...@googlegroups.com
No, I mean you output the message saying it is full without checking if it is full. You just assign is full to stop.

I didn't think a concurrency exception auto evicted all the documents. Could be wrong though.

Khalid Abuhakmeh

unread,
Apr 18, 2014, 3:17:36 PM4/18/14
to rav...@googlegroups.com

I keep trying to install RavenDB Test Helper project from NuGet and I keep getting this. Anybody else experiencing this issue or is it just me?


Khalid Abuhakmeh

unread,
Apr 18, 2014, 3:22:49 PM4/18/14
to rav...@googlegroups.com
I think I see the issue, it seems you have System.Spatial defined twice. >= 5.2 and = 5.0.2. I could be wrong though. 

Khalid Abuhakmeh

unread,
Apr 18, 2014, 3:43:50 PM4/18/14
to rav...@googlegroups.com
@Lee It does look like that doing another Load will give you the latest value in the database and will "evict" any changes you made in the failed attempt to modify the object. Here is a test (uses remote server).

public class Test
{
    public string Id { get; set; }
    public string Name { get; set; }
}

[Fact]
public void concurrency_test_to_see_what_happens()
{
    using (var store = new DocumentStore {
         Url = "http://localhost:8080", 
         DefaultDatabase = "concurrency_test" 
       }.Initialize())
    {
        using (var session = store.OpenSession())
        {
            var seed = session.Load<Test>("test/1");
            if (seed == null) {
                seed = new Test {Id = "test/1", Name = "hello, world!"};
                session.Store(seed);
            }

            seed.Name = "hello, world!";
            session.SaveChanges();
        }

        using (var outerSession = store.OpenSession())
        {
            try
            {
                outerSession.Advanced.UseOptimisticConcurrency = true;
                var test = outerSession.Load<Test>("test/1");

                Assert.Equal(test.Name, "hello, world!");
            
                using (var innerSession = store.OpenSession())
                {
                    var again = innerSession.Load<Test>("test/1");
                    again.Name = "changed";
                    innerSession.SaveChanges();
                }

                test.Name = "will fail";

                outerSession.SaveChanges();
            }
            catch (ConcurrencyException cex)
            {
                Console.WriteLine("concurrency fail!");
                
                var loaded = outerSession.Load<Test>("test/1");
                outerSession.Advanced.Refresh(loaded);
                // will give me the correct result
                Assert.Equal(loaded.Name, "changed");

Khalid Abuhakmeh

unread,
Apr 18, 2014, 3:45:25 PM4/18/14
to rav...@googlegroups.com
Doh! you don't need that outerSession.Advanced.Refresh(loaded); that is a mistake on my part, but my point is still valid :)

Kijana Woodard

unread,
Apr 18, 2014, 5:01:43 PM4/18/14
to rav...@googlegroups.com
"Did I also say that people reserving can unreserve and delete their reservations? Sorry if I missed that part :)"

Forget identity then. :-)


--

Kijana Woodard

unread,
Apr 18, 2014, 5:02:44 PM4/18/14
to rav...@googlegroups.com
"I'm gonna try to make it next year promise :)"

Was looking for you. Mentioned you in one talk and Chris in the other. No shows. Sheesh. ;-]

Kijana Woodard

unread,
Apr 18, 2014, 5:06:05 PM4/18/14
to rav...@googlegroups.com
"No, I mean you output the message saying it is full without checking if it is full."
The flash message won't go to the user unless the loop stops....i think. Unless the Flash messages accumulate instead of overwrite.

However, in either case, if you get a ConcurrencyException, then an Exception, I think the user will see a "Slot is full" message.


On Fri, Apr 18, 2014 at 1:34 PM, Lee <safe...@hotmail.com> wrote:
No, I mean you output the message saying it is full without checking if it is full. You just assign is full to stop.

I didn't think a concurrency exception auto evicted all the documents. Could be wrong though.

Khalid Abuhakmeh

unread,
Apr 19, 2014, 8:12:50 AM4/19/14
to rav...@googlegroups.com
Yeah I was moving this month, so I was more into box storage than document storage :P

Thanks for all your help guys :)
Reply all
Reply to author
Forward
0 new messages