A multipart upload is not an object under construction. It is a durable upload session containing separately stored parts until the client explicitly completes or aborts it.
If the client disappears after sending parts, those bytes remain chargeable. Ordinary object expiration rules do not remove incomplete multipart uploads. The storage system needs an explicit garbage-collection policy.
Model the upload as a state machine #
initiated -> uploading -> completing -> complete
| |
+-----------> aborting -> abortedPersist the provider upload ID, destination key, expected content identity, initiator, creation time, and application status. The upload ID is authority to add parts to one session; it should not be reconstructed from an object key.
A minimal control table might look like:
CREATE TABLE object_upload (
operation_id uuid PRIMARY KEY,
bucket text NOT NULL,
object_key text NOT NULL,
provider_upload_id text UNIQUE NOT NULL,
expected_size bigint,
content_sha256 text,
status text NOT NULL,
created_at timestamptz NOT NULL,
completed_at timestamptz
);operation_id is the business idempotency key. Retrying initiation with the same operation should return the same active session or the already completed outcome, rather than create another set of orphaned parts.
Completion is the commit boundary #
Uploading the final part does not publish the object. The client completes the upload by supplying the part numbers and returned identifiers in order. A timeout during completion creates an ambiguous outcome: the server may have committed even though the response did not arrive.
Resolve ambiguity by reading authoritative object or upload state. Do not blindly initiate a replacement upload. Completion retries must use the stored session and exact part manifest.
Checksums prove transfer integrity; they do not prove that the object corresponds to the intended business operation. Bind bucket, key, expected content hash, tenant, and operation identity at the application boundary.
Add a lifecycle safety net #
AWS provides AbortIncompleteMultipartUpload as a lifecycle action. A rule can make sessions older than a chosen number of days eligible for abort and deletion of their stored parts.
<Rule>
<ID>abort-incomplete-uploads</ID>
<Status>Enabled</Status>
<Filter><Prefix>uploads/</Prefix></Filter>
<AbortIncompleteMultipartUpload>
<DaysAfterInitiation>7</DaysAfterInitiation>
</AbortIncompleteMultipartUpload>
</Rule>The lifecycle rule is a backstop, not the normal control path. Applications should abort known cancellations promptly. The lifecycle window must exceed legitimate slow uploads, paused mobile transfers, and recovery time for downstream incidents.
Operational contract #
| Failure | Safe response |
|---|---|
| one part fails | retry that numbered part |
| client loses local state | recover manifest from durable control state |
| completion times out | inspect object/session before retrying |
| user cancels | stop active part work, then abort session |
| worker crashes | lease expires; another worker reconciles |
| session exceeds maximum age | lifecycle policy aborts it |
| checksum mismatches | do not publish business reference |
Monitor incomplete multipart bytes, upload count, oldest active session, completion latency, abort failures, and sessions per tenant. Alert on rate of growth, not only a large absolute total.
Security boundaries #
Presigned part URLs should be short-lived and scoped to one upload. Enforce allowed object prefixes and content limits before initiation. Do not let a client choose arbitrary buckets or overwrite another tenant’s keys. Treat object metadata as untrusted input when it later becomes an HTTP header or download filename.
Trade-offs #
Larger parts reduce request count but increase the cost of retrying one failed part. More parallelism shortens ideal upload time while increasing client memory, network bursts, and server request rate. Longer lifecycle windows protect slow uploads but retain garbage longer. Shorter windows save cost but can terminate valid resumable work.
Multipart upload is a distributed transaction without an automatic rollback. Give it durable state, a reconciliation loop, and garbage collection.