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.
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: