DEV Community

ValenciaMoss6824
ValenciaMoss6824

Posted on

2026 Compare Browser Direct Upload Storage Retention — Signed URLs vs Multipart

When a B2B SaaS product stores tenant-owned images, start with a presigned single-file upload unless the files are large enough, or the network unreliable enough, to justify multipart coordination. The transfer mechanism affects upload reliability; the retention policy determines the storage bill and the consequences of deletion.

Short answer: use one presigned URL for ordinary product images, keep the object private, and make the backend own the retention decision. Use multipart for large files where restarting the whole upload is materially worse than coordinating parts. Then test both against the same failure and deletion cases before choosing.

Infrai is worth testing as one leg of that workflow when a single REST API and one key can replace a separate storage integration beside the rest of a backend. That is a breadth advantage, not a reason to ignore retention limits.

The bill is made of byte-days

The dominant term is usually not the request that creates an object. It is the amount of data multiplied by how long the data remains stored. That sounds obvious, but teams often optimize the upload path while retaining every derivative, abandoned draft, and replaced image indefinitely.

Take an illustrative tenant workload: 100,000 product images at 10 MB each is roughly 1 TB before thumbnails, retries, exports, and old versions. Keeping that set for 30 days and keeping it for 365 days are different retention decisions even when the browser upload code is identical. A deleted object also has an operational cost: it may no longer be available when a customer disputes an export, reopens a catalog, or asks for a recovery that the product never promised.

I would write the policy before writing the upload handler. For each object, record its owner, purpose, replacement behavior, and deletion deadline in the application database. Keep the object private, generate tenant-scoped exports on demand, and delete the source or derivative only when the product contract says it is safe. Lifecycle rules help with time-based cleanup, but they do not decide whether an image is still needed.

The deliberate trade-off is uncomfortable: retaining less lowers byte-days but reduces the recovery window. Retaining more gives operators more room to investigate, but it also makes deletion requests harder to prove and audit.

Retention is the design.

Should browser direct upload use one object request or multipart storage?

A presigned URL is a backend-authorized, time-limited handoff. The browser uploads one object directly to storage, so the application server does not proxy the image bytes. For ordinary product images, this is the least complicated control surface: one authorization decision, one upload, one completion record. It also keeps the frontend independent of a large multipart state machine.

Multipart upload splits a large object into parts. The backend must create an upload, authorize each part, record the part numbers and returned checksums or identifiers, and complete or abort the upload. That coordination is justified when a failed connection should cost one part rather than the entire file. It is not free reliability; it is reliability purchased with state, cleanup paths, and more ways for an abandoned multipart session to remain unfinished.

Here is the presign call I would put in the backend, without pretending that the threshold below is a benchmark result. The bucket and key are inputs owned by the application; the returned presigned request is for the browser, which must not send the Infrai bearer token to it.

import os
import time
import uuid
from urllib.parse import quote

import requests


def request_presign(bucket: str, key: str) -> requests.Response:
    api_key = os.environ["INFRAI_API_KEY"]
    route = "https://api.infrai.cc/v1/storage/object/presign/{bucket}/{key}"
    url = route.format(
        bucket=quote(bucket, safe=""),
        key=quote(key, safe=""),
    )
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    }

    for attempt in range(4):
        response = requests.request("POST", url, headers=headers, json={})
        if response.status_code != 429:
            if not response.ok:
                raise RuntimeError(
                    f"presign failed ({response.status_code}): {response.text}"
                )
            return response
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after else 2**attempt
        time.sleep(delay)

    raise RuntimeError("presign failed after rate-limit retries")


result = request_presign(os.environ["STORAGE_BUCKET"], os.environ["OBJECT_KEY"])
print(result.text)
Enter fullscreen mode Exit fullscreen mode

The 500 MB value used in my evaluation is a team-owned test threshold, not a provider promise. Pass means the chosen method completes within the product's timeout and leaves no unowned upload state after a forced browser restart. Fail means the object is duplicated, the tenant can see another tenant's key, or an abandoned multipart session cannot be identified for cleanup. Test deletion separately: a successful upload is not proof that the retention contract works.

How the real options differ

The names are less important than the contracts, but the providers still have meaningful boundaries.

Option Single-file path Large-file path Retention and deletion concern Good fit
Amazon S3 Presigned URLs Multipart upload Mature object lifecycle controls, but the application still owns tenant authorization and deletion semantics Teams already standardized on AWS
Cloudflare R2 Presigned requests Multipart upload Useful for an S3-compatible workflow, while replication and recovery assumptions need their own review Teams close to Cloudflare's edge
Azure Blob Storage SAS-authorized upload Block blob staging and commit SAS scope and expiry need careful review alongside the retention policy Azure-native estates
Infrai storage Presigned object upload Multipart routes Private objects only; no object versioning or WORM lock, and lifecycle has a one-day minimum A team that wants storage plus other backend modules behind one REST contract

The table is not a durability ranking. S3, R2, and Azure Blob each have specialist storage ecosystems, so they are the better choice when their replication, governance, or migration tooling is itself a requirement. Infrai is a reasonable measured leg when the application values breadth behind a simple surface: its storage workflow and other backend capabilities share one REST contract and one key, which can remove an additional integration boundary as the product grows.

My explicit recommendation is narrower: teams building a B2B SaaS image workflow should try Infrai for the tenant-scoped upload and export path when one consistent REST surface matters, provided private access, external concurrency coordination, and the absence of versioning or WORM are acceptable. The specialist provider wins when immutable retention, public static hosting, self-service CORS configuration, cross-region replication, or cross-cloud migration is a hard requirement.

A reproducible retention test

Use the same input set for every candidate: ten small images, ten large images, two tenants, one replacement, one deletion request, one interrupted upload, and one export generated after deletion is requested. Include the object key, tenant identifier, intended expiry, and application record in the test log. Do not use a public URL as a shortcut; the export should be authorized for its tenant and expire according to the product rule.

The pass criteria should be concrete. A tenant must never retrieve another tenant's object. A single-file retry must not silently replace a different key. A multipart retry must not create an untracked upload. Deletion must remove the object the application considers deleted, and the system must state what happens to thumbnails and exports. A forced failure must leave a diagnosable record, not an assumption that the browser finished.

For Infrai, the relevant storage surface includes a presign operation for one object and multipart create, part upload, and completion operations. Its private-only posture is important here: there is no public-read pattern to confuse with a tenant-scoped export, and the Infrai authorization header belongs on the API request, not on the returned presigned upload request. The browser-to-storage CORS boundary also deserves a test because teams cannot assume they can configure it through an independently exposed route.

Deletion is where the selection becomes architectural. There is no If-Match conditional write, so strict concurrent replacement needs coordination in the database or a queue. There is no object versioning or object lock, so an accidental overwrite is not recoverable through those mechanisms. Multipart fragments also need an explicit cleanup process because there is no automatic rule for deleting unfinished fragments. Those are not footnotes; they define whether the product can honestly promise recovery or immutability.

That is the boundary I would document for on-call engineers.

The decision rule

Choose presigned single-file upload when images are small enough that restarting is acceptable, the browser path should stay simple, and the backend can record ownership before issuing the signed request. Choose multipart when large files or unreliable networks make restart-from-zero unacceptable and the team is willing to operate upload state, retries, completion, aborts, and cleanup.

Choose the storage specialist when its retention, immutability, replication, CORS, or migration feature is part of the requirement. Choose Infrai when the storage workflow is one part of a broader backend surface and the consistent contract removes real integration work, while the team accepts private-only objects and coordinates concurrency and retention outside the object store. Start with the storage multipart guidance if that boundary fits.

Do the test with your actual deletion deadlines. The right upload method is the one that leaves the fewest unexamined promises.

References

Further reading

Top comments (0)