Implementing Concurrent Data Updates with Optimistic Locking

Optimistic concurrency control (OCC), also known as optimistic locking, is a concurrency control method applied to transactional systems. In this blog, we talk about implementing concurrent data updates with Optimistic Locking.

GraphQL has a role beyond API Query Language- being the backbone of application Integration
background Coditation

Implementing Concurrent Data Updates with Optimistic Locking

Optimistic locking is a concurrency control technique that allows multiple users to access and modify a resource simultaneously, with the assumption that conflicts will be rare. It is called "optimistic" because it optimistically assumes that conflicts will not occur, and only handles them if they do.

It is often used in distributed systems, where multiple users may be accessing and modifying the same resource simultaneously. It is important to ensure that conflicts are properly handled in such systems to prevent data loss or corruption

In optimistic locking, each user reads the current version of the resource and makes changes to it. When the user attempts to save their changes, the system checks to see if the resource has been modified by another user since the user last read it. If the resource has not been modified, the changes are saved. If the resource has been modified, the system detects a conflict and the user must either resolve the conflict or discard their changes and start over.

Optimistic locking is useful in situations where conflicts are rare and the overhead of locking the resource for the duration of the update would be too high. It is also useful when the resource is large and it would be inefficient to lock the entire resource for the duration of the update.

Optimistic locking can be implemented in various ways, including using version numbers, timestamps, or checksums.

Using version numbers is a simple and effective way to implement optimistic locking. As mentioned earlier, each time the resource is modified, the version number is incremented. This allows the system to easily detect when a conflict has occurred by comparing the version number of the resource that the user has with the current version number of the resource.

Timestamps can also be used to implement optimistic locking. In this approach, the system records the time when the resource was last modified. When a user attempts to save their changes, the system checks the timestamp of the resource to see if it has been modified since the user last read it. If the timestamp has not changed, the changes are saved and the timestamp is updated to the current time. If the timestamp has changed, the system detects a conflict and the user must either resolve the conflict or discard their changes and start over.

Checksums can also be used to implement optimistic locking. In this approach, the system calculates a checksum for the resource when it is modified. When a user attempts to save their changes, the system recalculates the checksum and compares it to the original checksum. If the checksums match, the changes are saved and the original checksum is updated. If the checksums do not match, the system detects a conflict and the user must either resolve the conflict or discard their changes and start over.

Let’s take a look at an example

const { Client } = require('pg')

async function main() {
  const client = new Client()
  await client.connect()

  // Read the current version of the resource
  const res = await client.query('SELECT * FROM resource WHERE id=$1', [1])
  const resource = res.rows[0]

  // Make changes to the resource
  const resourceId = resource.id
  const resourceVersion = resource.version
  let resourceData = resource.data

  // Update the resource data
  resourceData = resourceData + ' updated'

  // Attempt to save the changes
  const updateRes = await client.query(
    'UPDATE resource SET data=$1, version=version+1 WHERE id=$2 AND version=$3',
    [resourceData, resourceId, resourceVersion]
  )

  // Check if the update was successful
  if (updateRes.rowCount === 0) {
    // The update was not successful, a conflict has occurred
    console.log('Conflict detected!')
  } else {
    // The update was successful, commit the changes
    await client.query('COMMIT')
  }
  await client.end()
}

main()

This example assumes that the resource is stored in a table in a PostgreSQL database with the following schema:

CREATE TABLE resource (
    id SERIAL PRIMARY KEY,
    version INTEGER,
    data TEXT
);

In this example, the version column is used to store the version number of the resource. Each time the resource is modified, the version column is incremented. When the user attempts to save their changes, the UPDATE statement includes a WHERE clause that checks if the version column has changed since the user last accessed the resource. If the version column has not changed, the UPDATE statement is successful, and the changes are saved. If the version column has changed, the UPDATE statement is not successful, and a conflict is detected.

Optimistic locking may not be appropriate in certain situations, such as:

  • Systems with very high write concurrency, as the overhead of checking version numbers or timestamps before every update may become a bottleneck.
  • Systems with very low contention (i.e. where multiple updates are unlikely to happen simultaneously), as the overhead of implementing optimistic locking may be unnecessary.
  • Systems that require strict consistency guarantees, as optimistic locking does not guarantee immediate consistency and can lead to conflicts that must be resolved manually.
  • Systems that require transactions, as Optimistic Locking is not transactional, meaning that if a transaction is rolled back, the lock will not be released.
  • Systems that require immediate consistency, as optimistic locking only checks for conflicts when the data is updated and does not provide a way to immediately detect conflicts when they occur.
  • It's worth noting that pessimistic locking is more appropriate for systems that need immediate consistency and transactional behaviour.

I am Saurabh Mahajan. a tech aficionado and passionate software engineer who loves making bad jokes. Always on the look out for the best coffee in town and the next big thing in software. A weekend gamer and an absolute nerd.

Want to receive update about our upcoming podcast?

Thanks for joining our newsletter.
Oops! Something went wrong.