Object storage

Object storage integrity: How to verify migrated data

A copy tool reporting success is not verification. How to prove a migrated dataset is complete and identical.

7 min read
Two parallel streams of glowing data blocks being compared under violet light in a data center

A migration completes, the copy tool reports no errors, and the source is scheduled for decommissioning. That report describes what the tool attempted and what it believes succeeded. It is not evidence that the destination holds the same data, because the tool checked its own work against its own record of what it meant to transfer, and any object it never enumerated is absent from both the copy and the report.

Verification is usually treated as a step after the copy, using whatever summary the tool prints. That is the weakest available check, since a tool that skipped a prefix or truncated a paginated response reports success on the subset it saw. The gap is not corruption. It is omission, and omission leaves no error behind.

A serious verification answers three separate questions. Is every object present, is every object's content identical, and did everything attached to the object survive the trip. Each needs different evidence, and satisfying one says nothing about the others.

What object counts and byte totals actually prove

Comparing object counts between source and destination is cheap and worth doing first, since a mismatch is decisive. Equal counts only prove that the same number of keys exist. They do not prove the keys are the same keys, and on a migration that renames prefixes that distinction matters.

Summed byte totals catch truncated transfers that counts miss, and they introduce a trap. Totals reported by a management interface may include non-current versions, delete markers or protection overhead, while a total computed from a listing does not. Comparing a raw capacity figure on one system against a logical sum on the other produces a discrepancy that costs a day to explain and means nothing.

The reliable form of both checks is a full key listing from each side, written to a file and compared as sets. That yields three lists: keys only at the source, keys only at the destination, and keys whose reported size differs. Whatever a migration missed appears in the first, and whatever the destination acquired independently appears in the second.

Checksums and the limits of comparing ETags

Content identity requires a checksum over the bytes, and the temptation is to use the ETag already present in the listing, since it costs nothing to retrieve. For an object uploaded in a single PUT without server-side encryption, the ETag is the MD5 of the object and comparing them is valid.

For anything uploaded in parts, it is not. The ETag of a multipart object is a hash of the concatenated part hashes, followed by a hyphen and the part count, so it depends on how the object was split. An identical object copied with a different part size produces a different ETag. Since most tools switch to multipart uploads above a size threshold, a large share of any real dataset falls into this category.

The alternatives are the S3 additional checksum algorithms, where a checksum is stored as metadata and can be read without fetching the body, or a checksum computed by reading each object and hashing it locally. The first is available only if it was requested at upload time on both systems. The second works against any endpoint and costs a full read of the dataset, which is the real reason it is skipped.

What travels separately from the bytes

An object is a body plus a set of attributes, and the attributes are copied by different code paths that fail independently. Content type is the one applications notice fastest, since a body served with the wrong type breaks parsers while the bytes are intact. User-defined metadata, storage class, tags, retention settings and access control entries each have their own copy semantics, and many tools copy none of them by default.

Object Lock settings deserve specific attention. A copy creates a new object, and retention applied on the source does not follow the body unless the tool sets it explicitly. A migration can therefore produce a destination whose contents are correct and whose immutability is absent, discovered only when something deletes data that should not have been deletable.

AttributeHow it usually goes missingHow to verify it
User-defined metadataThe tool transfers bodies only, or drops headers it does not recognizeHEAD a sample on both sides and compare the full header set
Object tagsTags need a separate API call the tool never makesGetObjectTagging on a sample, and on any key a lifecycle rule depends on
Retention and legal holdCopy creates a new object with the destination bucket's defaultsGetObjectRetention on protected keys before the source is released
Non-current versionsOnly the current version is enumerated and copiedList versions on both sides where history is part of the requirement

Sampling, full verification and how to choose

Reading and hashing every object is the only check that proves content identity across the whole dataset, and it means a second full read at the point when the team wants the project closed. The question is not whether sampling is acceptable in principle. It is which parts of the dataset carry consequences severe enough to justify the cost.

A defensible split is a full listing comparison across everything, which is cheap and catches omission, plus full content verification on the subset that is legally retained, irreplaceable, or feeds a process that cannot detect bad input. Everything else gets a sample large enough to reveal a systematic fault. Systematic faults are what sampling finds well, because a tool that mishandles a class of objects mishandles all of them.

Sample selection matters more than sample size. A sample drawn from the head of an alphabetical listing over-represents whatever sorts early, so draw across prefixes and across the size distribution, and include zero-byte objects, keys with unusual characters, the largest objects and the oldest. The same reasoning applies to detecting missing or changed data in any large collection.

Where Scality RING fits

RING presents an S3-compatible API, so verifying a migration into or out of it uses the same calls as anywhere else: list keys and versions, HEAD for metadata, GET for content hashing, and the tagging and retention calls for what is attached to the object. No vendor tooling is needed to produce the evidence, which matters because whoever audits it later has to be able to reproduce it.

RING supports S3 Object Lock and versioning, so a destination can hold the retention state and version history the source had, provided the migration explicitly transfers them. It also self-heals, detecting and repairing lost or corrupted data as a background task. That addresses durability after the data lands, which is a separate question from whether the right data landed, so post-migration verification remains the operator's step.

Because RING runs in the operator's own facility on the operator's hardware, the verification read happens inside the environment and its output, including key lists and checksums, stays local. For a dataset subject to residency constraints, that keeps the evidence within the same boundary as the data it describes.

What to settle before releasing the source

Record the verification result as an artifact rather than a conversation. It should name the date, the object count and byte total on each side, the method used for content verification, the sampling rate and selection method where sampling was used, and every known difference with a reason. Objects deliberately excluded belong in that list too.

Keep the source read-only rather than deleted for a defined period after cutover, long enough for the slowest consumer to have exercised the new endpoint once. Quarterly reporting, annual jobs and disaster recovery tests read data nothing else touches, and they are what discovers a missing prefix months later. A documented retention period for the source, with an owner and an end date, is what turns what storage can prove into something an auditor accepts.

Then re-run the listing comparison immediately before the source is destroyed. The dataset at cutover is not the dataset at decommissioning, and the gap between those two moments is where late writes to the old endpoint hide.

See Scality in action

Exabyte-scale object storage for AI data and cyber resilience. Talk to our team about what it can do for yours.

Request a demo