Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Atomic increment of a counter in django

I'm trying to atomically increment a simple counter in Django. My code looks like this:

from models import Counter from django.db import transaction  @transaction.commit_on_success def increment_counter(name):     counter = Counter.objects.get_or_create(name = name)[0]     counter.count += 1     counter.save() 

If I understand Django correctly, this should wrap the function in a transaction and make the increment atomic. But it doesn't work and there is a race condition in the counter update. How can this code be made thread-safe?

like image 865
Björn Lindqvist Avatar asked Oct 21 '09 05:10

Björn Lindqvist


2 Answers

In Django 1.4 there is support for SELECT ... FOR UPDATE clauses, using database locks to make sure no data is accesses concurrently by mistake.

like image 41
Emil Stenström Avatar answered Sep 25 '22 10:09

Emil Stenström


Use an F expression:

from django.db.models import F 

either in update():

Counter.objects.get_or_create(name=name) Counter.objects.filter(name=name).update(count=F("count") + 1) 

or on the object instance:

counter, _ = Counter.objects.get_or_create(name=name) counter.count = F("count") + 1 counter.save(update_fields=["count"]) 

Remember to specify update_fields, or you might encounter race conditions on other fields of the model.

A note on the race condition avoided by using F expressions has been added to the official documentation.

like image 147
Oduvan Avatar answered Sep 25 '22 10:09

Oduvan