Note that there are some explanatory texts on larger screens.

plurals
  1. POIncrement a counter and trigger an action when a threshold is exceeded
    primarykey
    data
    text
    <p>I have a model like this</p> <pre class="lang-py prettyprint-override"><code>class Thingy(models.Model): # ... failures_count = models.IntegerField() </code></pre> <p>I have concurrent processes (Celery tasks) that need to do this:</p> <ol> <li>do some kind of processing</li> <li>if the processing fails increment <code>failures_counter</code> of the respective <code>Thingy</code></li> <li>if <code>failures_counter</code> exceeds a threshold for some <code>Thingy</code>, issue a warning, but ONLY ONE warning.</li> </ol> <p>I have some ideas about how to do this without a race condition, for example using explicit locks (via <code>select_for_update</code>):</p> <pre class="lang-py prettyprint-override"><code>@transaction.commit_on_success def report_failure(thingy_id): current, = (Thingy.objects .select_for_update() .filter(id=thingy_id) .values_list('failures_count'))[0] if current == THRESHOLD: issue_warning_for(thingy_id) Thingy.objects.filter(id=thingy_id).update( failures_count=F('failures_count') + 1 ) </code></pre> <p>Or by using Redis (it's already there) for synchronization:</p> <pre class="lang-py prettyprint-override"><code>@transaction.commit_on_success def report_failure(thingy_id): Thingy.objects.filter(id=thingy_id).update( failures_count=F('failures_count') + 1 ) value = Thingy.objects.get(id=thingy_id).only('failures_count').failures_count if value &gt;= THRESHOLD: if redis.incr('issued_warning_%s' % thingy_id) == 1: issue_warning_for(thingy_id) </code></pre> <p>Both solutions use locks. As I'm using PostgreSQL, is there a way to achieve this without locking?</p> <hr> <p>I'm editing the question to include the <strong>answer</strong> (thanks to Sean Vieira, see answer below). The question asked about a way to avoid locking and this answer is optimal in that it leverages <a href="http://www.postgresql.org/docs/9.2/static/mvcc-intro.html" rel="nofollow"><em>multi-version concurrency control</em> (MVCC) as implemented by PostgreSQL</a>.</p> <p>This specific question explicitly allowed using PostgreSQL features, and though many RDBMSs implement <code>UPDATE ... RETURNING</code>, it is not standard SQL and is not supported by Django's ORM out of the box, so it requires using raw SQL via <code>raw()</code>. The same SQL statement will work in other RDBMSs but every engine requires its own discussion regarding synchronization, transactions isolation and concurrency models (e.g. MySQL with MyISAM would still use locks).</p> <pre class="lang-py prettyprint-override"><code>def report_failure(thingy_id): with transaction.commit_on_success(): failure_count = Thingy.objects.raw(""" UPDATE Thingy SET failure_count = failure_count + 1 WHERE id = %s RETURNING failure_count; """, [thingy_id])[0].failure_count if failure_count == THRESHOLD: issue_warning_for(thingy_id) </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload