Block storage exposes addressable blocks through a device interface. Object storage exposes named objects through an API. Neither label alone specifies durability, consistency, sharing, or price. Start with the application’s required operations, then check the particular service’s contract. A filesystem, database, or compatibility layer can add behavior, but it cannot make every underlying interface interchangeable.
Compare the actual operations
A block volume can be formatted with a filesystem or used directly by software that manages its own layout. The service does not necessarily supply a filesystem. Small overwrites are possible without replacing the whole volume, but safe shared access still needs coordination. Attaching one volume to multiple machines does not turn an ordinary single-host filesystem into a clustered filesystem. Database recovery also depends on write ordering and durability guarantees above the block interface.
An object is addressed within a bucket or another service-defined namespace. Ordinary S3 general-purpose bucket writes replace an entire object; reads can request byte ranges, so “object storage always reads everything” is wrong. Multipart upload assembles a completed object from uploaded parts; it is not a general filesystem’s random-write contract. Specialized products can offer additional append or update operations. Check the selected bucket type and API rather than treating one simple model as a universal rule.
Object storage commonly suits media, exports, and backup artifacts accessed by many clients. Block storage commonly suits database files and virtual-machine disks. These are workload patterns, not exclusive categories: databases can be designed around object storage, and shared filesystems can sit above block devices. Measure request sizes, concurrency, recovery behavior, and total request and transfer charges before choosing.
When it breaks
Do not assume object storage is eventually consistent. Amazon S3 documents strong read-after-write consistency for object PUTs and DELETEs and corresponding reads and listings. A client retaining an old downloaded value can still be stale, but that is not evidence that a fresh service read returns an obsolete value. CDN caches, cross-region replication, and multi-object application updates introduce separate boundaries that need their own guarantees.
Successful S3 PUT does not expose a partially uploaded object. A failed or ambiguous request requires checking the actual result, not inventing a truncated replacement. Atomic replacement of one object also does not prevent lost updates: two clients can read the same version and replace it independently. Conditional requests compare a validator before accepting an update. The model’s integer generation is synthetic; S3 If-Match uses an ETag, not that counter or a version ID. Use the service’s documented conditions; an ETag is not universally a content checksum or a transaction spanning several keys.
Lifecycle deletion and version retention need deliberate recovery tests. Enabling versioning is not the same as maintaining an independent backup, and retaining a local version number is not the same as preserving old payloads. Small-object workloads can be dominated by request overhead even when stored bytes are inexpensive. A mount adapter that accepts a database filename also needs the database’s locking, ordering, and failure semantics before it is a suitable home.
Check it quickly
Save this as storage_semantics.py and run python3 storage_semantics.py with Python 3.9 or newer. It is a standalone in-memory API model using synthetic bytes. No service, filesystem durability, network latency, or billing behavior is exercised.
"""In-memory API model, not an implementation of a storage service."""
class BlockVolume:
def __init__(self, data):
self.data = bytearray(data)
def write(self, offset, chunk):
if offset < 0 or offset + len(chunk) > len(self.data):
raise ValueError("write outside fixed volume")
self.data[offset:offset + len(chunk)] = chunk
class ObjectStore:
def __init__(self):
self.current = {}
def put(self, key, data, expected=None):
old = self.current.get(key)
version = old[1] if old else 0
if expected is not None and expected != version:
raise ValueError("version conflict")
self.current[key] = (bytes(data), version + 1)
return version + 1
def get(self, key):
return self.current[key]
block = BlockVolume(b"ABCDEF")
block.write(2, b"XX")
assert block.data == b"ABXXEF"
try:
block.write(5, b"too long")
except ValueError:
pass
else:
raise AssertionError("out-of-range write accepted")
assert block.data == b"ABXXEF"
store = ObjectStore()
assert store.put("report", b"ABCDEF", expected=0) == 1
cached = store.get("report")
assert store.put("report", b"XYZ", expected=1) == 2
assert store.get("report") == (b"XYZ", 2)
assert cached == (b"ABCDEF", 1)
try:
store.put("report", b"stale overwrite", expected=cached[1])
except ValueError:
pass
else:
raise AssertionError("stale conditional update accepted")
assert store.get("report") == (b"XYZ", 2)
assert store.get("report")[0][1:3] == b"YZ"
print("PASS: bounded overwrite; complete replacement; cached copy differs; stale condition rejected")
The block starts with six bytes. Writing two bytes preserves its neighbors, while an out-of-range write is rejected without changing the volume. The object starts at version one and is completely replaced by the three-byte value at version two. A fresh get immediately returns that replacement. The separately retained tuple still contains version one because the application kept a copy.
The conditional update using that old version fails, and the final assertion confirms that the current object remains unchanged. Slicing the retrieved bytes illustrates a selected range only; it is not a network Range-request test. The PASS line proves these explicit model rules. It does not prove a provider’s implementation, concurrency control, historical-version retention, crash atomicity, or recoverability. Before deployment, repeat the relevant operations against the chosen service with its real SDK and failure handling.
Sources
- Amazon S3 data model, object operations and strong consistency, checked 2026-10-15.
- S3 PutObject, complete-object writes and conditional requests, checked 2026-10-15.
- Amazon EBS, block volumes and application-managed filesystems, checked 2026-10-15.