Optimistic concurrency control (OCC)

Optimistic Concurrency Control (OCC) is a strategy used to manage shared resources and prevent "lost updates" or race conditions when multiple users or processes attempt to modify the same resource simultaneously.

As an example, consider systems like Google Cloud IAM, where the shared resource is an IAM Policyapplied to a resource (like a Project, Bucket, or Service). To implement OCC, systems typically use a version number or an etag (entity tag) field on the resource object.

Introduction to OCC

Imagine two processes, A and B, try to update a shared resource at the same time:

  1. Process Areads the current state of the resource.

  2. Process Breads the same current state.

  3. Process Amodifies its copy and writes it back to the server.

  4. Process Bmodifies its copy and writes it back to the server.

Because Process Boverwrites the resource without knowing that Process Aalready changed it, Process A's updates are lost.

OCC solves this by introducing a unique fingerprint which changes every time a entity is modified. In many systems (like IAM), this is done using an etag . The server checks this tag on every write:

  1. When you read the resource, the server returns an etag (a unique fingerprint).

  2. When you send the modified resource back, you must include the original etag .

  3. If the server finds that the stored etag does notmatch the etag you sent (meaning someone else modified the resource since you read it), the write operation fails with an ABORTED or FAILED_PRECONDITION error.

This failure forces the client to retrythe entire process—re-read the new state, re-apply the changes, and try the write again with the new etag .

Implement the OCC Loop

The core of the OCC implementation is a while loop that handles the retry logic. Set a reasonable maximum number of retries to prevent infinite loops in cases of high contention.

Steps of the Loop

Step Action Implementation Example
Read
Fetch the current resource state, including the etag . Policy policy = client.getIamPolicy(resourceName);
Modify
Apply the changes to the local object. policy = policy.toBuilder().addBinding(newBinding).build();
Write/Check
Attempt to save the modified resource using the old etag . This action must be inside a try block. try { client.setIamPolicy(resourceName, policy); return policy; } catch (AbortedException e) { // retry loop }
Success/Retry
If the write succeeds, exit the loop. If it fails with a concurrency error, increment the retry counter and continue the loop (go back to the Read step).

The following file provides a runnable example of how to implement the OCC loop using an IAM policy on a Project resource as the target.

Installation

To use this example, add the following dependency to your pom.xml :

 <dependency>  
<groupId>com.google.cloud</groupId>  
<artifactId>google-cloud-resourcemanager</artifactId>  
<version>1.45.0</version>
</dependency> 

Example

  import 
  
 com.google.api.gax.rpc. AbortedException 
 
 ; 
 import 
  
 com.google.api.gax.rpc. FailedPreconditionException 
 
 ; 
 import 
  
 com.google.cloud.resourcemanager.v3. ProjectName 
 
 ; 
 import 
  
 com.google.cloud.resourcemanager.v3. ProjectsClient 
 
 ; 
 import 
  
 com.google.iam.v1. Binding 
 
 ; 
 import 
  
 com.google.iam.v1. GetIamPolicyRequest 
 
 ; 
 import 
  
 com.google.iam.v1. Policy 
 
 ; 
 import 
  
 com.google.iam.v1. SetIamPolicyRequest 
 
 ; 
 import 
  
 java.util.ArrayList 
 ; 
 import 
  
 java.util.List 
 ; 
 public 
  
 class 
 IamOccExample 
  
 { 
  
 /** 
 * Executes an Optimistic Concurrency Control (OCC) loop to safely update a resource. 
 * 
 * This method demonstrates the core Read-Modify-Write-Retry pattern. 
 * 
 * @param projectId  The Google Cloud Project ID (e.g., "my-project-123"). 
 * @param role       The IAM role to grant (e.g., "roles/storage.objectAdmin"). 
 * @param member     The member to add (e.g., "user:user@example.com"). 
 * @param maxRetries The maximum number of times to retry the update. 
 * @return The successfully updated IAM policy (or null on failure). 
 */ 
  
 public 
  
 static 
  
  Policy 
 
  
 updateIamPolicyWithOcc 
 ( 
  
 String 
  
 projectId 
 , 
  
 String 
  
 role 
 , 
  
 String 
  
 member 
 , 
  
 int 
  
 maxRetries 
  
 ) 
  
 throws 
  
 Exception 
  
 { 
  
 // Setup Client 
  
 try 
  
 ( 
  ProjectsClient 
 
  
 projectsClient 
  
 = 
  
  ProjectsClient 
 
 . 
 create 
 ()) 
  
 { 
  
 String 
  
 projectName 
  
 = 
  
  ProjectName 
 
 . 
 of 
 ( 
 projectId 
 ). 
 toString 
 (); 
  
 int 
  
 retries 
  
 = 
  
 0 
 ; 
  
 // START OCC LOOP (Read-Modify-Write-Retry) 
  
 while 
  
 ( 
 retries 
 < 
 maxRetries 
 ) 
  
 { 
  
 try 
  
 { 
  
 // READ: Get the current policy. This includes the current etag. 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Attempt %d: Reading current IAM policy for %s...%n" 
 , 
  
 retries 
 , 
  
 projectName 
 ); 
  
  GetIamPolicyRequest 
 
  
 getIamPolicyRequest 
  
 = 
  
  GetIamPolicyRequest 
 
 . 
 newBuilder 
 () 
  
 . 
 setResource 
 ( 
 projectName 
 ) 
  
 . 
 build 
 (); 
  
  Policy 
 
  
 policy 
  
 = 
  
 projectsClient 
 . 
 getIamPolicy 
 ( 
 getIamPolicyRequest 
 ); 
  
 // MODIFY: Apply the desired changes to the local Policy object. 
  
 List<Binding> 
  
 bindings 
  
 = 
  
 new 
  
 ArrayList 
<> ( 
 policy 
 . 
  getBindingsList 
 
 ()); 
  
  Binding 
 
  
 targetBinding 
  
 = 
  
 null 
 ; 
  
 int 
  
 bindingIndex 
  
 = 
  
 - 
 1 
 ; 
  
 for 
  
 ( 
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 bindings 
 . 
 size 
 (); 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 bindings 
 . 
 get 
 ( 
 i 
 ). 
 getRole 
 (). 
 equals 
 ( 
 role 
 )) 
  
 { 
  
 targetBinding 
  
 = 
  
 bindings 
 . 
 get 
 ( 
 i 
 ); 
  
 bindingIndex 
  
 = 
  
 i 
 ; 
  
 break 
 ; 
  
 } 
  
 } 
  
 if 
  
 ( 
 targetBinding 
  
 != 
  
 null 
 ) 
  
 { 
  
 if 
  
 ( 
 targetBinding 
 . 
  getMembersList 
 
 (). 
 contains 
 ( 
 member 
 )) 
  
 { 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Policy for role %s and member %s exists already!%n" 
 , 
  
 role 
 , 
  
 member 
 ); 
  
 return 
  
 policy 
 ; 
  
 } 
  
 // Create a new binding based on existing one to add the member 
  
  Binding 
 
  
 updatedBinding 
  
 = 
  
 targetBinding 
 . 
  toBuilder 
 
 () 
  
 . 
  addMembers 
 
 ( 
 member 
 ) 
  
 . 
 build 
 (); 
  
 bindings 
 . 
  set 
 
 ( 
 bindingIndex 
 , 
  
 updatedBinding 
 ); 
  
 } 
  
 else 
  
 { 
  
 // Role not found, create a new binding 
  
  Binding 
 
  
 newBinding 
  
 = 
  
  Binding 
 
 . 
 newBuilder 
 () 
  
 . 
 setRole 
 ( 
 role 
 ) 
  
 . 
  addMembers 
 
 ( 
 member 
 ) 
  
 . 
 build 
 (); 
  
 bindings 
 . 
  add 
 
 ( 
 newBinding 
 ); 
  
 } 
  
 // The policy builder now contains the modified bindings AND the original etag. 
  
  Policy 
 
  
 updatedPolicy 
  
 = 
  
 policy 
 . 
  toBuilder 
 
 () 
  
 . 
  clearBindings 
 
 () 
  
 . 
  addAllBindings 
 
 ( 
 bindings 
 ) 
  
 . 
 build 
 (); 
  
 // WRITE/CHECK: Attempt to write the modified policy. 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Attempt %d: Setting modified IAM policy...%n" 
 , 
  
 retries 
 ); 
  
  SetIamPolicyRequest 
 
  
 setIamPolicyRequest 
  
 = 
  
  SetIamPolicyRequest 
 
 . 
 newBuilder 
 () 
  
 . 
 setResource 
 ( 
 projectName 
 ) 
  
 . 
  setPolicy 
 
 ( 
 updatedPolicy 
 ) 
  
 . 
 build 
 (); 
  
  Policy 
 
  
 resultPolicy 
  
 = 
  
 projectsClient 
 . 
 setIamPolicy 
 ( 
 setIamPolicyRequest 
 ); 
  
 // SUCCESS: If the call succeeds, return the new policy and exit the loop. 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Successfully updated IAM policy in attempt %d.%n" 
 , 
  
 retries 
 ); 
  
 return 
  
 resultPolicy 
 ; 
  
 } 
  
 catch 
  
 ( 
  AbortedException 
 
  
 | 
  
  FailedPreconditionException 
 
  
 e 
 ) 
  
 { 
  
 // If the etag is stale (concurrency conflict), this will throw a retryable exception. 
  
 retries 
 ++ 
 ; 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Concurrency conflict detected (etag mismatch). Retrying... (%d/%d)%n" 
 , 
  
 retries 
 , 
  
 maxRetries 
 ); 
  
 // Exponential backoff (100ms * retry count) 
  
 Thread 
 . 
 sleep 
 ( 
 100L 
  
 * 
  
 retries 
 ); 
  
 } 
  
 } 
  
 // END OCC LOOP 
  
 } 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Failed to update IAM policy after %d attempts due to persistent concurrency conflicts.%n" 
 , 
  
 maxRetries 
 ); 
  
 return 
  
 null 
 ; 
  
 } 
 } 
 
Design a Mobile Site
View Site in Mobile | Classic
Share by: