1

I have a Django web app that registers competitors for events. When someone register for an event they are logged in and complete a registration form, then in the view I check the form, do save(commit=False) and get the highest competitor-number from existing instances of competitors and for this new competitor I set the number to highest+1, then I do save() on the

On the model I have a unique-together on 'competitor' model for event and number, this ensures that the number is unique within the event.

Now, for large matches I get an issue when many registers concurrently as they end up in the save(commit=false) -- set number -- save() in the same time, and attempt to save() with same number - and this raises an IntegrityError, duplicate key as I attempt to save a new instance with same event and number as an instance that has been created during this interval.

:-(

Now, any ideas or logic for how to avoid this issue?

/ Jens

kmp
  • 10,535
  • 11
  • 75
  • 125
Jens Lundstrom
  • 702
  • 8
  • 15
  • Please explain why you need this versus using the primary key? At any time you could just filter on the event to get the count and you can order by the id. This should be enough to alleviate the need for a separate field to store the competitor number per event. – Furbeenator Sep 09 '13 at 22:07
  • Prefer to have the number fixed for each competitor, e.g. when showing a single competitor detail I would like to avoid the need to calculate the 'number' by having to sort something like 500+ competitors and find the position of this competitor relative to PK and state this as a number. Interesting idea anyway... – Jens Lundstrom Sep 10 '13 at 05:19
  • Gotcha, well there is the `{{ forloop.counter }}` template tag that would show the current competitor number, and you wouldn't need to "calculate" it. As long as you are doing a loop in a django template, this would be available for any listing page. – Furbeenator Sep 10 '13 at 21:41
  • Yep, but forloop.counter is e.g. not available when only showing a singe competitor (like competitor_details page or all competitors for a single users). – Jens Lundstrom Sep 11 '13 at 05:14
  • Ah-hah. Makes sense. Alternately, you could calculate the index at `save()` using `.order_by()` and store it in a `competitor_number` field, then it would be calculated once at save and stored indefinitely with the object for quick lookup later. Your solution would definitely be more scalable, depending on whether you can have a big-O of x footprint for detection. – Furbeenator Sep 11 '13 at 16:33

1 Answers1

0

Here is my idea on how to do this now (inspired from Handling race condition in model.save());

while True:
    try:
        competitor.save(commit=False)
        # do stuff where there could be RACE that breaks unique-togher requirement on model
        competitor.save()
        break
    except IntegrityError:
        sleep(random.uniform(0.001, 0.5)) # chill out, try again
        continue

Will try this out now...

Community
  • 1
  • 1
Jens Lundstrom
  • 702
  • 8
  • 15
  • One other thing to keep in mind here is this gets done every time you save the competitor. I would suggest checking if it is a new one. If not, just save() and if so, do the 'stuff where there could be RACE...' You can add `if competitor.pk:` or `if not competitor.pk` to verify if it is an INSERT competitor or an UPDATE. – Furbeenator Sep 10 '13 at 21:44
  • Good point, the case above is only when creating the competitor! – Jens Lundstrom Sep 11 '13 at 05:59