python - Increment a counter and trigger an action when a threshold is exceeded -


i have model this

class thingy(models.model):     # ...     failures_count = models.integerfield() 

i have concurrent processes (celery tasks) need this:

  1. do kind of processing
  2. if processing fails increment failures_counter of respective thingy
  3. if failures_counter exceeds threshold thingy, issue warning, 1 warning.

i have ideas how without race condition, example using explicit locks (via select_for_update):

@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     ) 

or using redis (it's there) synchronization:

@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 >= threshold:         if redis.incr('issued_warning_%s' % thingy_id) == 1:             issue_warning_for(thingy_id) 

both solutions use locks. i'm using postgresql, there way achieve without locking?


i'm editing question include answer (thanks sean vieira, see answer below). question asked way avoid locking , answer optimal in leverages multi-version concurrency control (mvcc) implemented postgresql.

this specific question explicitly allowed using postgresql features, , though many rdbmss implement update ... returning, not standard sql , not supported django's orm out of box, requires using raw sql via raw(). same sql statement work in other rdbmss every engine requires own discussion regarding synchronization, transactions isolation , concurrency models (e.g. mysql myisam still use locks).

def report_failure(thingy_id):     transaction.commit_on_success():         failure_count = thingy.objects.raw("""             update thingy             set failure_count = failure_count + 1             id = %s             returning failure_count;         """, [thingy_id])[0].failure_count      if failure_count == threshold:         issue_warning_for(thingy_id) 

as far can tell django's orm doesn't support out of box - however, doesn't mean can't done, need dip down sql level (exposed, in django's orm via manager's raw method) make work.

if using postgressql >= 8.2 can use returning final value failure_count without additional locking (the db still lock, long enough set value, no additional time lost communicating you):

# assumptions: ids valid , ids unique # more defenses necessary if either of these assumptions # not true. failure_count = thingy.objects.raw("""     update thingy     set failure_count = failure_count + 1     id = %s     returning failure_count; """, [thingy_id])[0].failure_count  if failure_count == threshold:     issue_warning_for(thingy_id) 

Comments

Popular posts from this blog

linux - Does gcc have any options to add version info in ELF binary file? -

android - send complex objects as post php java -

charts - What graph/dashboard product is facebook using in Dashboard: PUE & WUE -