Search code examples
djangopostgresqlconcurrencydatabase-concurrency

Django database concurrency problems


I´ve got a "single" method within a web api that makes two database calls

  1. Ensure with count() an specific objects does not exists
  2. Save the object if it doesn´t exists already

The logic here is obviously not saving an object if it exists already. Pretty simple. Problem comes when duplicated objects begins to appear in the db table. Then I realized I can´t control bad client calls (duplicated calls at the same time).

So the problem occurs when a second call to this method happen exactly between code blocks 1) and 2) with the same parameters (same object):

  1. Call 1 => count() == 0
  2. Call 2 => count() == 0 (it´s 0 because the first call didn´t finish processing and saving the object at this moment)
  3. Call 1 => saves the object
  4. Call 2 => saves the object because count() == 0

Wrong result => my table has two rows with the same values

So I need to not process "Call 2" until "Call 1" code block finishes.

I´ve been researching about this a lot. An option would be blocking the table I´m querying until the method finished execution (but this can bring new problems to the equation).

Other people is talking about using atomic transactions but I don´t see how that can help if another call runs at the same time.

Another possible solution would be using unique_together so it´s impossible to create two rows with same values (I´m starting to think this will be the best option)

I´m lost in the middle of nowhere trying to figure out how to fix this. Given that this issue is hard to test I thought about asking here first. Maybe I´m just missing something obviously simple...

Just to clarify, this is how I tell if an object exists already:

    # return error if the follow is already done
    current_follow_query = request.user.follows.filter(influencer=influencer)
    if current_follow_query.count() > 0:
        return Response(status=status.HTTP_204_NO_CONTENT)

    # save the actual follow object
    obj = Follow(follower=request.user, influencer=influencer)
    obj.save()

Any ideas?


Solution

  • So, as you define identity by the two fields influencer and follower, then those must be unique. Just tell that to the database, using a unique_together = [('influencer', 'follower')] constraint.

    Now for saving, you will want to rely on the database to perform this test: simply save all the time and handle the errors when they occur.

    from django.db import IntegrityError
    
    try:
        obj = Follow.objects.create(follower=request.user, influencer=influencer)
    except IntegrityError:
        # the object already existed!
    else:
        # the object was successfully created!