Skip to content
>GLB_
Go back

S3 Eventual Consistency Edge Cases in Data Pipelines

>GLB_ // tools · Amazon S3

First, a correction to the title, because a lot of blog posts and Stack Overflow answers are still out of date: Amazon S3 is no longer eventually consistent for object operations. Since December 2020, S3 provides strong read-after-write consistency for PUT and DELETE of objects, and for LIST, in all regions, at no extra cost.

So S3Guard, emrfs consistent view and “sleep 30 then list” hacks exist to solve a problem that’s gone.

But pipelines on S3 still break in ways that look like eventual consistency. The cause is usually one of the semantics below. Strong consistency means a read sees the latest successful write. It doesn’t mean S3 is a filesystem, a transaction manager, or a queue.

1. There is no atomic rename

S3 has no rename. A “rename” in Hadoop/Spark is COPY + DELETE, per object, non-atomic, O(data size).

Why it matters: the classic Hadoop FileOutputCommitter writes to _temporary/ and renames on commit. On S3 that means:

Fixes:

2. Concurrent writers: last writer wins

Two writers PUT the same key at the same time → both succeed, the later one (by S3’s internal timestamp) wins. No error, no merge.

Classic victim: Delta Lake with multiple clusters writing to the same table on S3. Delta commits by creating _delta_log/00000000000000000042.json and requires that the create fails if the file already exists (“put-if-absent”). Historically S3 couldn’t guarantee that, so Delta’s default S3 LogStore is only safe for a single writer (single driver). Concurrent writers from different clusters can silently overwrite each other’s commit → lost data.

Options:

S3 now supports conditional writes: If-None-Match: * (create only if the key doesn’t exist, since August 2024) and If-Match: <etag> (compare-and-swap, since November 2024). This is the primitive table formats need. Check your table format/engine version for whether it uses it; don’t assume.

Your own code can use it directly:

aws s3api put-object --bucket lake --key locks/job-2026-10-08 \
  --body /dev/null --if-none-match '*'
# 412 Precondition Failed if another run already created it

3. LastModified is not “when the data became visible”

Incremental loaders often do: list objects with LastModified > last_watermark. Two problems:

Fixes:

4. Event notifications are at-least-once and unordered

S3 Event Notifications (to SQS, SNS, Lambda, EventBridge):

So: ObjectRemoved can arrive before the ObjectCreated for the same key, and a duplicate ObjectCreated can trigger a second load.

Fixes:

5. Bucket-level configuration is still eventually consistent

Strong consistency covers objects. Bucket configuration does not. AWS documents that:

Infra-as-code that creates a bucket, enables versioning and immediately starts writing in the same pipeline run can hit this.

6. Replication is asynchronous

Cross-Region (CRR) and Same-Region Replication (SRR) copy objects asynchronously. A reader in the replica region can see an older version or no object at all. S3 Replication Time Control (RTC) gives an SLA (99.99% of objects within 15 minutes), not immediacy.

If a downstream job in another region is triggered by the source write, it must handle “not replicated yet” — check x-amz-replication-status or retry with backoff.

7. Overwrites + caching layers

S3 itself is consistent, but things in front of it might not be:

The rule: don’t overwrite keys in place in a data pipeline. Write new keys (versioned paths or table-format commits) and switch pointers atomically.

Checklist

References


Share this post:

Related Posts


Previous Post
Comparing Parquet Compression Codecs: Snappy vs ZSTD Benchmark
Next Post
Managing OAuth2 Token Rotation in Postman Without Overwriting Environments