Save your sanity with optimistic concurrency control

 

 

 

 

 

Paris.py, 16/05/2018

Pb

Web interface

Rest Resource

HTTP PUT

Pb

Web interface

Rest Resource

Web interface

Rest Resource

Guido

Ada

Guido and Ada see the same resource...

  HTTP GET

  HTTP GET

Pb

Web interface

Rest Resource

Web interface

Rest Resource

Guido

Ada

Guido modifies the resource...

HTTP PUT

Pb

Web interface

Rest Resource

Web interface

Rest Resource

Guido

Ada

Ada modifies the resource...

HTTP PUT

HTTP PUT

Pb

Web interface

Rest Resource

Web interface

Rest Resource

Guido

Ada

Guido's changes are lost

Possible solutions

  • Real time (polling, websockets...). Examples: Facebook, Slack, Google Drive...

 

Possible solutions

  • Real time (polling, websockets...). Examples: Facebook, Slack, Google Drive...
  • Optimistic concurrency control

 

Optimistic concurrency control

Main idea: use of a token for current state of the resource. Usually done by a hash function

Optimistic conccurency control

 

 

Web Client

Rest Resource

  HTTP GET

Token

Token retrieved via ETag header

Optimistic conccurency control

 

 

Web Client

Rest Resource

  HTTP PUT

Token

Token sent via a If-Match header

Optimistic concurrency control

 

 

Web Client

Rest Resource

  HTTP PUT

Case 1: the token sent matches the current resource token

=> the resource is updated and the new token is sent

 

New Token

Optimistic concurrency control

 

 

Web Client

Rest Resource

  HTTP PUT

Case 2: the token sent  does not match the current resource token (it is outdated)

=> the resource is not updated and a 412 response is sent.

 

Error 412

Implementation in Django + DRF

 

 

from rest_framework.generics import RetrieveAPIView, UpdateAPIView

from .serializers import DocumentSerializer
from ..models import Document


class DocumentView(RetrieveAPIView, UpdateAPIView):
    queryset = Document.objects.all()
    serializer_class = DocumentSerializer

Django rest framework view: reminder

Implementation in Django + DRF

 

 

def optimistic_concurrency_control(compute_etag_func):
    # [NOTE] - we only consider GET and PUT methods in this example
    def cls_decorator(cls):
        get_meth = cls.get
        cls.get = get_decorator(get_meth)

        put_meth = cls.put
        cls.put = put_decorator(put_meth)
        return cls

...
    return cls_decorator

Occ implemented via a class decorator

Implementation in Django + DRF

 

 

def optimistic_concurrency_control(compute_etag_func):

    def cls_decorator(cls):
        ...

    def get_decorator(meth):
        def g(self, request, *args, **kwargs):
            resp = meth(self, request, *args, **kwargs)
            resp['etag'] = compute_etag_func(self)
            return resp
        return g


    return cls_decorator

GET method decoration

Implementation in Django + DRF

 

 

def optimistic_concurrency_control(compute_etag_func):

    def cls_decorator(cls):
        ...

    def put_decorator(meth):
        def g(self, request, *args, **kwargs):            
            etag = request.META.get('HTTP_IF_MATCH')
            if etag != compute_etag_func(self):
                return Response(status=status.HTTP_412_PRECONDITION_FAILED)
            resp = meth(self, request, *args, **kwargs)
            resp['etag'] = compute_etag_func(self)
            return resp
        return g



    return cls_decorator

PUT method decoration

Implementation in Django + DRF

 

 

from hashlib import md5
from rest_framework.generics import RetrieveAPIView, UpdateAPIView

from .serializers import DocumentSerializer
from .optimistic_concurrency import optimistic_concurrency_control
from ..models import Document


def _compute_etag(self):
    return md5(
        self.get_object().text.encode('utf-8')
    ).hexdigest()

@optimistic_concurrency_control(_compute_etag)
class DocumentView(RetrieveAPIView, UpdateAPIView):
    queryset = Document.objects.all()
    serializer_class = DocumentSerializer

Example of usage

Demo

Demo with small Django web app

(code here)

Final notes

Contact: vperez@legalstart.fr

  • Simple to setup in the backend
  • On the other hand it can require some tradeoff for your frontend (stateful client, synchronization for requests which take a long time)

On recrute!

Made with Slides.com