Skip to content
Quest 06Intermediate15 min

Never Lose a Card

A PUT to an existing key silently replaces the object. When two researchers upload Mudkip's card at once, one of them loses. Versioning keeps every overwrite recoverable, and conditional writes stop the overwrite from happening at all.

What you'll learn

  • With versioning on, every write creates a new version and a delete adds a delete marker instead of removing data.
  • IfNoneMatch: "*" makes a PUT create-only; IfMatch: etag makes it update-only-if-unchanged.
  • A failed condition returns 412 PreconditionFailed — handle it as a normal outcome.

Section 01

Versioning

Turn on versioning with PutBucketVersioning. From then on each PUT gets a new VersionId, and older versions stay (and are billed) as noncurrent versions. A bucket can be unversioned, versioning-enabled, or versioning-suspended — once enabled, it can never go back to unversioned.

A DELETE without a version ID adds a delete marker: GET now returns 404, but every version is still there. Delete the marker and the object is back. Only a DELETE with a specific VersionId removes data permanently.

Section 02

Conditional writes

Versioning lets you recover from a lost update; a conditional write prevents it. S3 checks the condition atomically on the server, so no read-then-write race is possible.

HeaderMeaningUse it to
IfNoneMatch: "*"Write only if no object has this keyCreate a card exactly once
IfMatch: "<etag>"Write only if the current ETag matchesUpdate a card you just read, without clobbering someone else's edit

Both work on PutObject and CompleteMultipartUpload. If another write to the same key is in flight, S3 may answer 409 ConditionalRequestConflict — retry.

Optimistic update with IfMatchJavaScript
const current = await client.send(new GetObjectCommand({ Bucket, Key }));
const card = JSON.parse(await current.Body.transformToString());
card.types.push("ground"); // Marshtomp evolves!

try {
  await client.send(new PutObjectCommand({
    Bucket, Key, Body: JSON.stringify(card), ContentType: "application/json",
    IfMatch: current.ETag,
  }));
} catch (err) {
  if (err.name === "PreconditionFailed") { /* someone else changed it: re-read and retry */ }
  else throw err;
}

Practice

Your turn

Two researchers upload Mudkip's card at almost the same moment. Make your upload create-only, so it fails instead of replacing a card that already exists, and handle that failure.

Add IfNoneMatch: "*" and catch the PreconditionFailed error.
Tab inserts two spaces. Press Escape, then Tab, to leave the editor. Control or Command plus Enter runs the code.
Simulated — no AWS calls

Check

Check your understanding

Quick Quiz · one question, as many tries as you need.

Versioning is on. You run DeleteObject on cards/0258-mudkip.json without a version ID. What happens?

Choose one answer.