1

I have models like:

class Deck(models.Model):
    some_hash = models.CharField(max_length=64)
    store_id = models.CharField(max_length=256)
    tier = models.IntegerField()

class Card(models.Model):
    deck = models.ForeignKey(Deck, related_name='cards')

And a view that should do something like this:

class GetCardView(ApiView):  # ApiView from Django REST Framework
    response_json = get_some_data_from_external_service(
        request.GET['store_id'], request.GET['tier']
    )  # if this data for a store_id and tier changes a new Deck is needed

    deck, created = Deck.objects.get_or_create(
        some_hash=hashlib.sha256(response).hexdigest(), 
        store_id=request.GET['store_id'], tier=request.GET['tier']
    )
    if created:
        deck.add_cards(response_json)  # adds for e.g. 5 cards

    card = deck.pick_a_card()
    if not card: #  deck depleted
        Deck.objects.create(
            some_hash=hashlib.sha256(response).hexdigest(), 
            store_id=request.GET['store_id'], tier=request.GET['tier']
        )
        deck.add_cards(response_json)  # adds for e.g. 5 cards
        card = deck.pick_a_card()
    return Response(card)

I know that this view is ugly but shows what I want to do.
And now the question is how to protect my self from a race condition when concurrent request appear? Is locking the table the only way? Django 1.7.7, MySQL (transaction set to: READ COMMITED)

McAbra
  • 2,382
  • 2
  • 21
  • 29
  • How does Django let you specify `BEGIN` and `COMMIT` of "transactions"? I don't see any hint of it in your code. – Rick James Apr 08 '15 at 00:22
  • You can simply do a `with transaction.atomic():` - `BEGIN` on `__enter__`, `COMMIT` on `__exit__`, `ROLLBACK` on exceptions. – McAbra Apr 08 '15 at 06:54
  • 1
    Many flavors of "race condition" can be handled by wrapping in a transaction and using `FOR UPDATE` on `SELECTs` that lead to doing an `UPDATE`. – Rick James Apr 08 '15 at 12:22
  • You can find the answer here: http://stackoverflow.com/questions/2235318/how-do-i-deal-with-this-race-condition-in-django – Felippe Raposo May 25 '15 at 18:29

1 Answers1

0

So I did not really fount the answer as now I think that the idea was bad from the beginning.
I ended up with not storing the Card model and I'm picking those with LFSR and only storing seeds in the Deck model. This is enough for my case.

For future reference. You need something that fails on the DB level (like the unique constraint) that will raise some Exception that can be recognized by your logic in the transaction.atomic() context manager.

MySQL with READ-COMMITTED isolation level is suitable for get_or_create.

Thanks all for given info.

McAbra
  • 2,382
  • 2
  • 21
  • 29