Moving forty terabytes of objects sounds like a bandwidth problem. It is not. The copy is the easy part — it runs unattended for a few days and finishes. The hard part is the writes that keep arriving while it runs, and proving at the end that both sides hold the same data.
Done in the right order, a migration of this size needs no maintenance window at all. The application keeps writing to the old bucket until the moment it does not.
Two numbers decide the shape of the migration: total bytes and total object count. Forty terabytes in forty thousand large media files is a bandwidth exercise. Forty terabytes in four hundred million small objects is a request-rate exercise, and it will take far longer than the bandwidth maths suggests, because per-object overhead dominates.
Then check the two things that cost money rather than time. Egress from the source, which for most hyperscalers is the single largest line in the migration budget, and per-request charges on the read side. List the bucket and price the copy before you start; it changes whether you move everything or only what is still live.
Which is the other question worth asking early: does all of it need to move? A bucket that has accumulated for eight years usually contains a large fraction that has not been read once in three. Copying it is paying egress to relocate data you could have expired. Run the access analysis, apply a lifecycle rule to the dead prefixes at the source, and move a smaller bucket.
The first pass copies everything as it currently stands, with the application still reading and writing the source normally. Nothing is at stake, so run it hard and let it take as long as it takes.
Practical notes. Parallelism is set by object size — many concurrent transfers for small objects, fewer with multipart uploads for large ones. Copy prefix by prefix rather than as one job, so a failure resumes without re-listing four hundred million keys. Preserve content types and any metadata the application relies on; a media library that loses its content-type headers serves video as a download. And keep the checksums the source reports, because pass three needs them.
Expect this pass to be incomplete the moment it finishes. That is fine — that is what it is for.
The second pass copies only what changed since the first. It is short, and the point is to run it more than once: each run is faster than the last, and the delta shrinks until the remaining gap is a few minutes of writes rather than a few days.
Two source patterns need different handling. If objects are only ever created — logs, backups, renditions — the delta is a listing comparison and trivially safe. If objects are overwritten in place, timestamp comparison is not enough on its own; verify with checksums, and be honest about the window in which a write can land on the old side after you have already copied that key.
Run the delta on a schedule and watch the trend. When a pass completes in minutes and moves gigabytes rather than terabytes, you are ready to cut over.
There are two honest ways to finish, and both take minutes rather than a window.
The safer one is dual-write. Deploy a build that writes every new object to both buckets and reads from the old one. Once that is live, no new write can be missed. Run a final delta, verify, then flip reads to the new bucket in configuration, and remove the dual-write a few days later once you are confident. No request fails at any point.
The simpler one is a DNS or configuration flip. Run the final delta, switch the endpoint the application uses, and accept a window of seconds to minutes in which a write could land on the old bucket. Then run one more delta afterwards to sweep it up. For a media library this is usually acceptable; for a system of record it is not.
Either way, keep the old bucket read-only rather than deleting it. It is the rollback, and it costs a month of storage to hold.
Object count and total bytes matching is a smoke test, not proof. Compare per-object checksums on both sides, or at minimum on a statistically meaningful random sample plus everything in the prefixes that matter most. Multipart uploads are the classic trap: the composite ETag depends on part size, so an object copied with a different part size can be byte-identical and have a completely different ETag. Compare a real content hash, not the ETag.
Then verify the application, not just the data. Signed URLs, lifecycle rules, CORS configuration, bucket policies and access keys do not travel with the objects. A migration that copies forty terabytes perfectly and forgets the CORS rule breaks the browser upload path on the first day.
Count objects as well as bytes, price egress before you start, and expire what does not need to move. Bulk copy with the application untouched, then run deltas until they are boring. Cut over with dual-write if the data is a system of record, or a flip and a sweep if it is not. Verify with content hashes rather than ETags, migrate the bucket configuration alongside the objects, and keep the old bucket read-only as your rollback.
Antyxsoft object storage is S3-compatible, so the tools you already use for the copy work unchanged, with flat per-gigabyte pricing and no egress surprises — see how object storage works.