Pessimitic locking and Optimistic locking in Django: Managing concurrent transactions

Pessimitic locking and Optimistic locking in Django: Managing concurrent transactions

In previous article, i mentioned using select_for_update() to solve concurrent transactions. select_for_update() can be used to implement a form of pessimistic locking. So, in this article i will implement Optimistic locking in Django.

What is Optimistic locking?

Optimisitic locking is a strategy to handle concurrent transactions by using version number or timestamp. If two transactions attempt to update the same row has been modified since it was last read, ensuring no conflicting updates are commited.

Implementing Optimistic locking.

The steps to implement:

  1. Versioning the resource: Assign a version or timestamp to each resource being modified. This version is updated whenever a write operation occurs.
  2. Checking for conflicts: During a write operation, compare the version of the resource being modified with the version stored in db. If the versions is different, it indicates that another transaction has modified the resource concurrently
  3. Resolving conflicts: In case of a conflict, rollback the transaction and notify the user of the conflict

import time
from django.db import models
from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist

class OptimisticLockError(Exception):
    pass

class BankAccount(models.Model):
    balance = models.DecimalField(max_digits=10, decimal_places=2)
    version = models.IntegerField(default=0)

    def save(self, *args, **kwargs):
        if self.pk is None:
            super().save(*args, **kwargs)
        else:
            with transaction.atomic():
                # Check if the version has changed in the database
                current = BankAccount.objects.select_for_update().get(pk=self.pk)
                time.sleep(3)
                if current.version != self.version:
                    raise OptimisticLockError("The record has been modified by another transaction.")

                # Increment the version and save
                self.version += 1
                super().save(*args, **kwargs        

First, create a model name BankAccount with 2 fields: balance (current balance of the account), version (default = 0) in models.py

Override the save method to include logic for checking the version field and handling conflict.

Note: I add time.sleep to simulate 2 transactions occuring a few second apart.

In view.py, i add logic to handle OptimisticLockError to inform the user or retry the transaction.

from rest_framework import renderers, viewsets, status, pagination
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.response import Response

from .serializers import *
from .models import BankAccount, OptimisticLockError

from django.shortcuts import get_object_or_404, HttpResponse

class LockingViewSet(viewsets.ModelViewSet):
    parser_classes = (FormParser, MultiPartParser)
    serializer_class = BankAccountSerializer

    def update(self, request, *args, **kwargs):
        try:
            amount = request.data.get('amount')
            account = get_object_or_404(BankAccount, pk=int(kwargs.get('pk')))
            account.balance += int(amount)
            account.save()
            return Response({"message":"Balance updated successfully."}, status=status.HTTP_200_OK)
        except OptimisticLockError:
            return Response({"message":"Conflict detected. Please try again."}, status=status.HTTP_409_CONFLICT)        

If no conflict, return status code 200 else return conflict message.

To test it, i open 2 tab in postman, using the same url and update the same account. The second transaction will throw "Conflict detected. Please try again".

Choosing Pessimistic locking and Optimistic locking

The choice between pessimistic locking and optimistic locking depends on the characteristics of your application and the level of conflict expected.

Use pessimistic locking when:

  • Conflicts are expected to occur frequently
  • Data inregrity is of utmost importance

Use pessimistic locking when:

  • Conflicts are rare or infrequent
  • Concurrent access is necessary for performance reasons

What's next

In the next article, I will cover the topic of optimizing Django ORM.

Nguy?n Th? Tùng

? Backend Developer at CGT Media | Let's Connect! | #WeCommit100xHabit ?

10 个月

Very informative

回复
Tùng Lam Nguy?n

Astrophysicist | Python Developer

10 个月

Useful tips!

回复
Nam Nguyen

? Chia s? v? nh?ng ?i?u mình bi?t, nh?ng sai l?m ?? g?p ph?i và gi?i pháp lu?n :D Coaching 1:1 thu?t toán và mindset t? duy Whatever you are not changing, you are choosing Let's connect!

10 个月

I still see select_for_update. So it makes me confuse

要查看或添加评论,请登录

Bùi Minh Hoàng的更多文章

  • Rank in MySQL

    Rank in MySQL

    Introduction to MySQL RANK() function The RANK() fucntion assigns a rank to each row within the partition of a result…

  • Fuzzy Search in Django with PostgreSQL

    Fuzzy Search in Django with PostgreSQL

    In real life, sometimes we want to search football but have the wrong typo, like footbal or fotball, but Google or…

    2 条评论
  • Why does offset make your query slow?

    Why does offset make your query slow?

    When working with Pagination, whenever i receive an API request with a URL like: /employee?page=10&count=10, my sql…

    4 条评论
  • Setup Redis Replication

    Setup Redis Replication

    In this article, i will setup Redis replication with 1 master and 3 replicas. First, create the project folder: RedisM…

    6 条评论
  • Composite index and include columns in PostgreSQL

    Composite index and include columns in PostgreSQL

    In PostgresSQL, both composite indexes and indexes with included columns are used to enhance query performance, but…

    7 条评论
  • Yields and List in Python

    Yields and List in Python

    In this article, i will compare two common ways to return a sequence of values from a function is using yield to create…

    1 条评论
  • Django ORM: prefetch_related

    Django ORM: prefetch_related

    In previous article, i mentioned select_related to minimize time hit database in Django, so in this article, i addition…

    1 条评论
  • Django ORM select_related.

    Django ORM select_related.

    In Django, select_related are designed to stop the deluge of database queries that are caused by accessing related…

  • Using select_for_update() to solve concurrent transactions

    Using select_for_update() to solve concurrent transactions

    The Bank Account Transfer Problem A classic example of concurrent transaction issues is the bank account transfer…

    5 条评论
  • Transaction in Django ORM

    Transaction in Django ORM

    Django’s default behavior is to run in autocommit mode. Each query is immediately committed to the database, unless a…

    1 条评论

社区洞察

其他会员也浏览了