Skip to content

Consistency model

Last updated View as MarkdownAgent setup

This page details R2's consistency model, including where R2 is strongly, globally consistent and which operations this applies to.

R2 can be described as "strongly consistent", especially in comparison to other distributed object storage systems. This strong consistency ensures that operations against R2 see the latest (accurate) state: clients should be able to observe the effects of any write, update and/or delete operation immediately, globally.

Terminology

In the context of R2, strong consistency and eventual consistency have the following meanings:

  • Strongly consistent - The effect of an operation will be observed globally, immediately, by all clients. Clients will not observe 'stale' (inconsistent) state.
  • Eventually consistent - Clients may not see the effect of an operation immediately. The state may take a some time (typically seconds to a minute) to propagate globally.

Operations and Consistency

Operations against R2 buckets and objects adhere to the following consistency guarantees:

Action Consistency
Read-after-write: Write (upload) an object, then read it Strongly consistent: readers will immediately see the latest object globally
Metadata: Update an object's metadata Strongly consistent: readers will immediately see the updated metadata globally
Deletion: Delete an object Strongly consistent: reads to that object will immediately return a "does not exist" error
Object listing: List the objects in a bucket Strongly consistent: the list operation will list all objects at that point in time
IAM: Adding/removing R2 Storage permissions Eventually consistent: A new or updated API key may take up to a minute to have permissions reflected globally

Additional notes:

  • In the event two clients are writing (PUT or DELETE) to the same key, the last writer to complete "wins".
  • When performing a multipart upload, read-after-write consistency continues to apply once all parts have been successfully uploaded. In the case the same part is uploaded (in error) from multiple writers, the last write will win.
  • Copying an object within the same bucket also follows the same read-after-write consistency that writing a new object would. The "copied" object is immediately readable by all clients once the copy operation completes.
  • To delete an R2 bucket, it must be completely empty before deletion is allowed. If you attempt to delete a bucket that still contains objects, you will receive an error such as: The bucket you tried to delete (X) is not empty (account Y) or Bucket X cannot be deleted because it isn’t empty. For instructions on emptying and deleting a bucket, refer to Delete buckets.

Conditional writes

R2 evaluates conditional write headers (for example If-Match and If-None-Match on PutObject) atomically, as part of the same write that commits the object:

  • The condition is always evaluated against the committed state of the bucket at the moment the write commits, never against an older snapshot.
  • Competing conditional writes to the same key are serialized. When two or more clients concurrently issue PutObject with If-None-Match: * for a previously absent key, at most one request succeeds and every other request fails with a 412 PreconditionFailed error. It is therefore safe to rely on If-None-Match: * for "create-once" semantics: two concurrent conditional writes can never both observe success.

For S3 API usage, refer to Conditional operations in PutObject.

Negative reads and in-flight writes

A strongly consistent read (HeadObject or GetObject) that returns NoSuchKey (404) tells you that the object did not exist at the moment the read was processed. This absence is not final with respect to an earlier PutObject whose outcome is still unknown: a request that was still in flight — for example, a client that lost its connection before receiving the response — may still commit after the negative read has returned. Reads are never stale, but they do not wait for or cancel earlier writes.

If you need to treat a negative read as proof that a write failed, first make sure the original write has definitively completed (either succeeded or returned a definitive error) before trusting the read. Alternatively, retry the write with If-None-Match: *: it fails with 412 PreconditionFailed if the ambiguous write did in fact commit.

Caching

When connecting a custom domain to an R2 bucket and enabling caching for objects served from that bucket, the consistency model is necessarily relaxed when accessing content via a domain with caching enabled.

Specifically, you should expect:

  • An object you delete from R2, but that is still cached, will still be available. You should purge the cache after deleting objects if you need that delete to be reflected.
  • By default, Cloudflare’s cache will cache HTTP 404 (Not Found) responses automatically. If you upload an object to that same path, the cache may continue to return HTTP 404s until the cache TTL (Time to Live) expires and the new object is fetched from R2 or the cache is purged.
  • An object for a given key is overwritten with a new object: the old (previous) object will continue to be served to clients until the cache TTL expires (or the object is evicted) or the cache is purged.

The cache does not affect access via Worker API bindings or the S3 API, as these operations are made directly against the bucket and do not transit through the cache.

Was this helpful?