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.

Latest Articles

Implementing custom windowing and triggering mechanisms in Apache Flink for advanced event aggregation

Dive into advanced Apache Flink stream processing with this comprehensive guide to custom windowing and triggering mechanisms. Learn how to implement volume-based windows, pattern-based triggers, and dynamic session windows that adapt to user behavior. The article provides practical Java code examples, performance optimization tips, and real-world implementation strategies for complex event processing scenarios beyond Flink's built-in capabilities.

time
15
 min read

Implementing feature flags for controlled rollouts and experimentation in production

Discover how feature flags can revolutionize your software deployment strategy in this comprehensive guide. Learn to implement everything from basic toggles to sophisticated experimentation platforms with practical code examples in Java, JavaScript, and Node.js. The post covers essential implementation patterns, best practices for flag management, and real-world architectures that have helped companies like Spotify reduce deployment risks by 80%. Whether you're looking to enable controlled rollouts, A/B testing, or zero-downtime migrations, this guide provides the technical foundation you need to build robust feature flagging systems.

time
12
 min read

Implementing incremental data processing using Databricks Delta Lake's change data feed

Discover how to implement efficient incremental data processing with Databricks Delta Lake's Change Data Feed. This comprehensive guide walks through enabling CDF, reading change data, and building robust processing pipelines that only handle modified data. Learn advanced patterns for schema evolution, large data volumes, and exactly-once processing, plus real-world applications including real-time analytics dashboards and data quality monitoring. Perfect for data engineers looking to optimize resource usage and processing time.

time
12
 min read