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
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!
Optimistic concurrency control
By v-perez
Optimistic concurrency control
- 1,837