Optimistic concurrency control (OCC) is a strategy for managing shared resources and preventing lost updates or race conditions when multiple users or processes modify the same resource at the same time.
For example, when working with Identity and Access Management, an IAM policy is a shared resource attached to a resource such as a project, bucket, or service. Without concurrency control, simultaneous modifications can overwrite recent policy changes, inadvertently revoking legitimate access or granting unauthorized permissions.
To implement OCC, systems typically use a version number or an etag (entity
tag) field on the resource struct.
How OCC works
Suppose two processes, A and B, try to update a shared resource at the same time:
- Process A reads the current state of the resource.
- Process B reads the same current state.
- Process A modifies its copy and writes it back to the server.
- Process B modifies its copy and writes it back to the server.
Because Process B overwrites the resource without knowing that Process A already changed it, Process A's updates are lost.
OCC solves this problem by introducing a unique fingerprint that changes every
time an entity is modified. In many systems, such as IAM, this
fingerprint is an etag. The server checks this tag on every write:
- When you read the resource, the server returns an
etag(a unique fingerprint). - When you send the modified resource back, you must include the original
etag. - If the stored
etagdoesn't match theetagthat you sent—meaning another process modified the resource after you read it—the write operation fails with anABORTEDorFAILED_PRECONDITIONerror.
This failure signals the client to retry the entire process: re-read the
updated state, re-apply the changes, and try the write again with the new
etag.
Implement the OCC loop
The core of an OCC implementation is a retry loop that reads, modifies, and attempts to save the resource, exiting after a successful write. To prevent infinite loops during high contention, configure a reasonable maximum number of retry attempts.
Steps of the loop
The following table describes each step in an OCC retry loop:
| Step | Action | Implementation example |
|---|---|---|
| Read | Fetch the current resource state, including the etag. |
let mut policy = client .get_iam_policy() .set_resource(&secret_name) .send() .await?; |
| Modify | Apply the changes to the local struct. | policy.bindings.push(new_binding); |
| Write and check | Attempt to save the modified resource using the existing etag, and check for concurrency error codes. |
match client .set_iam_policy() .set_resource(&secret_name) .set_policy(policy) .send() .await { Ok(p) => return Ok(p), Err(e) => { /* ... */ } } |
| Success or retry | If the write succeeds, exit the loop. If it fails with a concurrency error, increment the retry counter and continue the loop from the Read step. | - |
Example
The following example demonstrates how to implement an OCC loop that updates an
IAM policy on a Secret Manager secret resource by
granting a member the roles/secretmanager.secretAccessor role.
To run this example, first declare the dependency in your Cargo.toml file:
cargo add google-cloud-secretmanager-v1
Then implement the OCC retry loop:
Complete code
The following code shows the complete function with all required imports: