Skip to content
qibdo qibdo
Theme
Book a demo

Object lifecycle

Deleting an object and freeing the space it occupies are two different things. In a bucket without versioning they happen together, so the difference never comes up. Turn versioning on and they come apart permanently — which is why this page exists, and why every object store asks you to free space deliberately rather than as a side effect.

Every operation on this page answers one of two questions, and keeping them apart is the whole idea:

  • Visibility — does this object appear in listings, and can it be read?
  • Storage — are you still being charged for its bytes?
flowchart LR
  classDef primary fill:#e8e8ff,stroke:#1100ff,stroke-width:2px,color:#0e0d1a
  classDef danger fill:#ffe3e8,stroke:#ff002f,stroke-width:2px,color:#0e0d1a

  D["You delete<br/>an object"] --> V{"Bucket<br/>versioning?"}
  V -- "disabled" --> H["The object and<br/>its bytes are removed"]
  H --> F(["Space freed"])
  V -- "enabled or<br/>suspended" --> M["A delete marker is written.<br/>Earlier versions are kept"]
  M --> B(["Still stored,<br/>still billed"])
  B -- "you remove the versions" --> F

  class F primary
  class B danger
  class M danger

There is one copy of an object, so deleting it is unambiguous: the object goes and its storage is released.

stateDiagram-v2
  direction LR
  classDef primary fill:#e8e8ff,stroke:#1100ff,stroke-width:2px,color:#0e0d1a

  [*] --> Stored : upload
  Stored --> Stored : upload again, replacing it
  Stored --> [*] : delete, space freed

  class Stored primary

Overwriting replaces the object in place — the previous content is gone and is not recoverable. This is the default for new buckets.

versioning is not a boolean, and the third position is the one that surprises people on an invoice:

StateNew writesWhat is already stored
BUCKET_VERSIONING_DISABLEDAn overwrite replaces the object; no earlier copy is keptNothing to keep
BUCKET_VERSIONING_ENABLEDEvery overwrite keeps the earlier copy as a versionRetained, and billed
BUCKET_VERSIONING_SUSPENDEDNo new versions are createdEvery version already written is retained, and still billed

Suspended is not off. It stops history accumulating; it does not discard the history you have, and those bytes keep costing storage until you remove them yourself. Collapsing the three into an on/off switch is what makes a bill impossible to explain.

Enabling is a one-way door. A bucket that has been versioned can be suspended, but never returned to disabled: the versions already written have to stay addressable. Decide before you turn it on, not after.

Each write keeps the previous content as a distinct version, addressable by its own version identifier. Deleting behaves differently, and it is the part that surprises people: a delete does not remove anything. It adds a delete marker that becomes the object’s current version.

stateDiagram-v2
  direction LR
  classDef primary fill:#e8e8ff,stroke:#1100ff,stroke-width:2px,color:#0e0d1a
  classDef danger fill:#ffe3e8,stroke:#ff002f,stroke-width:2px,color:#0e0d1a

  [*] --> Current : upload
  [*] --> Marker : delete

  Current : Current version
  Current : returned by a read
  Previous : Previous version
  Previous : kept, addressable by version id
  Marker : Delete marker
  Marker : the object reads as absent

  Current --> Previous : a newer version is written
  Marker --> Current : the marker is removed
  Previous --> [*] : you delete this version

  class Current primary
  class Marker danger

Walk through it with a file uploaded twice and then deleted:

StepVersions of images/logo.pngStored
uploadv1 (2.1 MB) — current2.1 MB
upload againv1 (2.1 MB) — previous
v2 (2.4 MB) — current
4.5 MB
deletev1 (2.1 MB) — previous
v2 (2.4 MB) — previous
v3 — delete marker
still 4.5 MB

After the delete the object does not appear in a listing and reading it returns a not-found error. Nothing was freed. v1 and v2 are exactly where they were.

Because the delete only added a marker, undoing it means removing that marker. The previous version becomes current again, unchanged — it never moved. This recoverability is the reason to turn versioning on, and the retained bytes are what it costs.

Pick the operation that matches what you actually want:

flowchart TD
  classDef primary fill:#e8e8ff,stroke:#1100ff,stroke-width:2px,color:#0e0d1a
  classDef danger fill:#ffe3e8,stroke:#ff002f,stroke-width:2px,color:#0e0d1a

  Q(["Remove an object"]) --> A{"Bucket versioning?"}
  A -- "disabled" --> N["Delete the object"]
  N --> NF(["Bytes released"])

  A -- "enabled or suspended" --> B{"What do you want?"}
  B -- "hide it, keep it recoverable" --> H["Delete the object"]
  H --> HF(["Marker written.<br/>Nothing released"])
  B -- "remove one specific version" --> V["Delete that version"]
  V --> VF(["That version released"])
  B -- "remove it and all its history" --> P["Purge the object"]
  P --> PF(["Every version released"])

  class NF primary
  class VF primary
  class PF primary
  class HF danger

Only the first row of this table is built today. The rest is designed and recorded, and the Built? column says so on every line rather than leaving you to discover it from a 501:

You want toDo thisFrees spaceBuilt?
Take an object out of your wayDelete the objectOnly when versioning is offUnversioned buckets only
Remove one specific version for goodDelete that version by its version identifierThat versionNot yet
Remove an object and its whole historyPurge the objectAll of itNot yet
Keep a bucket tidy without askingA bucket lifecycle ruleOn a scheduleNot yet

Purging is worth knowing about, once it lands. In other object stores, removing an object together with its history means listing every version and deleting them one batch at a time, and remembering that delete markers are versions too. Miss some and you keep paying for data you believe you deleted. Purge does that enumeration for you, in one request.

Retention is the gap that matters most. Under versioning, nothing expires an old version by itself, and no retention policy exists on this platform yet. Until one does, a versioned bucket only ever grows, and the sole remedy is removing versions by hand. That is the reason the rows above are worth reading before you ask for versioning, rather than after.

Versioning cannot be switched on today, anywhere. This is not a rollout working its way through regions: it is one platform-wide restriction, and asking for it is refused rather than silently accepted.

The reason is worth stating, because it is not caution. On the storage backend in use today, each upload overwrites the object under its key, so versioning would be a fiction: the index could record five versions while the store held one set of bytes. Recording history the store did not keep is worse than not offering the feature, so the door stays shut until per-version storage lands.

Two refusals enforce it, both UNIMPLEMENTED:

You ask toYou get
Create or update a bucket with versioning enabled or suspended8300020 VERSIONED_BUCKET_NOT_SUPPORTED
Delete an object in a bucket that is already versioned8302022 OBJECT_DELETION_IN_VERSIONED_BUCKET_NOT_SUPPORTED

Both doors are guarded, not just the entry. Refusing only the exit produced a state a workspace could enter and never leave: versioning would be set on the bucket, and because it can only ever be suspended and never returned to disabled, the bucket could then be filled and neither emptied nor reverted. The create path is refused before anything is persisted for exactly that reason.

Practically, that means today: deleting an object frees its space immediately, and is not recoverable. Everything above describes how the service behaves once versioning is available. Check a bucket’s versioning field if you need to know which applies to it.