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.
Two questions, not one
Section titled “Two questions, not one”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
Without versioning
Section titled “Without versioning”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.
The switch has three positions, not two
Section titled “The switch has three positions, not two”versioning is not a boolean, and the third position is the one that surprises
people on an invoice:
| State | New writes | What is already stored |
|---|---|---|
BUCKET_VERSIONING_DISABLED | An overwrite replaces the object; no earlier copy is kept | Nothing to keep |
BUCKET_VERSIONING_ENABLED | Every overwrite keeps the earlier copy as a version | Retained, and billed |
BUCKET_VERSIONING_SUSPENDED | No new versions are created | Every 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.
With versioning
Section titled “With versioning”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:
| Step | Versions of images/logo.png | Stored |
|---|---|---|
| upload | v1 (2.1 MB) — current | 2.1 MB |
| upload again | v1 (2.1 MB) — previous v2 (2.4 MB) — current | 4.5 MB |
| delete | v1 (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.
Recovering a deleted object
Section titled “Recovering a deleted object”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.
Freeing space
Section titled “Freeing space”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 to | Do this | Frees space | Built? |
|---|---|---|---|
| Take an object out of your way | Delete the object | Only when versioning is off | Unversioned buckets only |
| Remove one specific version for good | Delete that version by its version identifier | That version | Not yet |
| Remove an object and its whole history | Purge the object | All of it | Not yet |
| Keep a bucket tidy without asking | A bucket lifecycle rule | On a schedule | Not 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.
Availability
Section titled “Availability”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 to | You get |
|---|---|
| Create or update a bucket with versioning enabled or suspended | 8300020 VERSIONED_BUCKET_NOT_SUPPORTED |
| Delete an object in a bucket that is already versioned | 8302022 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.
Related concepts
Section titled “Related concepts”- Buckets and objects — what buckets and objects are, and how listing works
- Storage classes — where an object’s bytes are placed