Search code examples
reactjsamazon-dynamodblockingaws-amplify

Pessimistic locking with DynamoDB in Amplify / React


Right now im developing an Amplify-Application in React, which displays data fetched from a DynamoDB. Each row has a unique id. A user is able to modify rows and commit them to the database.

I would like to implement a pessimistic lock, when a row is edited.

As statet in other questions, dynamodb does not support pessimistic locking per default, please correct me if i am wrong.

What is the best practice for this use case?

My thoughts, how this could be done:

  1. Second DynamoDB-table, which holds the locks (id) of the rows
  2. Is it possible, to have a global variable accross all amplify-sessions? You could store the lock(id) of the row in this global variable
  3. The fallback is optimistic locking.

I would love to hear your opinion on this topic! :)


Solution

  • Welcome to Stack Overflow :)

    Typically, no databases really "support" pessimistic locking out of the box. Both optimistic and pessimistic locking for SQL databases is typically done in an ORM or some application side library. The database only provides the primitives required to implement them.

    And this is the same with DynamoDB!

    To be honest, I've only implemented optimistic locking myself. For pessimistic locking, there's probably some things to consider that I don't have any top-of-mind ideas about how to solve, such as timing out locks in case something gets locked, and is never unlocked again?

    Anyway, generally, these kinds of things can be achieved using condition expressions.

    To create a lock on an item, use a condition expression to ensure that there is no lock already set:

    const itemLockId = uuidv4()
    ddb.updateItem({
      TableName: "...",
      Key: {
        Id: "my-id-thingie-here"
      },
      UpdateExpression: "SET ItemLockId= :itemLockId",
      ConditionExpression: "attribute_not_exists(ItemLockId)",
      ExpressionAttributeValues: {
        ":itemLockId": itemLockId
      }
    })
    

    Later, you can put the item, and check that it's not locked and remove the lock (by not specifying any ItemLockId in the attributes):

    ddb.putItem({
      TableName: "...",
      Item: {
        Id: "my-id-thingie-here",
        Attr1: "my value",
        Attr2: 123
        // ItemLockId not specified, so it will be deleted
      },
      ConditionExpression: "ItemLockId = :itemLockId",
      ExpressionAttributeValues: {
        ":itemLockId": itemLockId
      }
    })
    

    This put will fail if the lock has a different value in the database than the one you got when you requested the lock.

    Hopefully this was helpful, good luck :)