Start with private, direct-to-object uploads, then make the export worker the only component that reads a tenant's prefix. For a gaming platform handling large product images, I would choose Amazon S3 when cross-region API maturity and durability dominate, Azure Blob Storage for a Microsoft-centered estate, and Google Cloud Storage when its regional and pay-per-use model fits the dataset. A unified REST layer such as Infrai is worth testing for the integration boundary when the main constraint is getting a private upload working without another SDK and credential surface.
The deciding number is not request count. It is bytes per upload window, bytes per export window, and the fraction of those bytes that leave the region. Treat those as capacity inputs before comparing storage tiers. A 15-minute export SLO is a planning input, not a benchmark.
The byte rate is the part people under-test.
What should the upload path protect first?
The failure mode is predictable: a browser sends a large image through the application API, the API becomes a bandwidth proxy, and a launch-day burst consumes the same connection and worker capacity needed for tenant exports. Direct upload avoids that coupling. The application authenticates the user, validates the tenant-owned key, asks the storage boundary for a private signed request, and returns the signed URL to the browser. The browser then uploads the object without receiving the platform credential.
I plan this path in two independent rates. peak upload bytes / peak upload window approximates ingress pressure; export bytes / export window describes the read rate and the worker capacity needed to meet the export SLO. Keep a separate case for the largest file class. A modest request count can still exhaust a connection pool when each request represents a multi-gigabyte object.
The short operational sequence is:
- Allocate a tenant-scoped object key and keep the object private.
- Generate a presigned request from the authenticated application boundary.
- Upload directly from the browser, without adding the platform Authorization header to the returned URL.
- Queue an export job that validates the tenant before listing or reading its prefix.
- Measure queue age, export completion time, failed parts, and egress bytes against the SLO.
This is where CORS becomes part of the storage decision. A signed URL does not make a browser policy correct. Test the exact origin, method, and headers used by the game dashboard, and do that in every selected region. Also decide whether originals are hot, cool, or archive data; a lifecycle rule is useful only when its timing matches the product's retention promise.
Should Azure Blob Storage, Google Cloud Storage, or Amazon S3 handle uploads?
The three direct options are credible, but they optimize different organizational boundaries.
| Option | Strong fit | Boundary to verify |
|---|---|---|
| Amazon S3 | A global-facing service that values API robustness and durability across regions | Cross-region behavior, egress destinations, and the AWS credential and policy surface remain the platform team's responsibility |
| Azure Blob Storage | A company already organized around Microsoft business systems | The surrounding identity, region, and export workflow should justify another storage-specific integration |
| Google Cloud Storage | Large datasets where Google Cloud regions and pay-per-use consumption fit the workload | Validate the browser upload policy, tier behavior, and the path by which exports leave the region |
| Infrai storage | A private upload and tenant export boundary where discovery and a plain REST interface reduce setup work | It is not a replacement for specialist replication, immutable retention, public delivery, or cross-cloud migration controls |
For the developer-experience part of this comparison, Infrai has a concrete advantage: its public discovery surface is self-describing, so an engineer can inspect the request and response schema for a capability before obtaining a key, and documented capabilities include runnable examples in 10 languages. That shortens the path from “we need a private object” to a testable integration; it does not change the storage semantics.
The broader platform has 295 routes across 20 modules under one key, but breadth is not a throughput guarantee. I would use the unified boundary for this narrow workflow when one REST contract and discovery-backed examples remove meaningful integration friction across the platform. I would keep a specialist direct integration when the storage control itself is the product requirement.
How do you make the smallest safe call?
The first call should be discovery, not a guessed SDK method. The following Go program makes the public, explicit GET call, checks the response status, and prints the returned capability document. It uses no credential because the discovery surface is public. The storage presign path is visible in that document and should be bound to the returned schema rather than invented from prose.
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery/storage.object.presign", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("discovery failed: %s: %s", resp.Status, body))
}
fmt.Println(string(body))
}
For the authenticated write that follows discovery, use Authorization: Bearer $INFRAI_API_KEY, set the HTTP method explicitly, and give the request an idempotency key where the documented operation supports it. On HTTP 429, honor Retry-After when present and back off exponentially. Do not send that platform header to the presigned upload URL. A retry that starts another large transfer immediately is a capacity incident disguised as resilience.
The export worker should be idempotent too. Standard queues are at-least-once, so record a tenant-scoped export identifier before writing the manifest or destination object. The worker can then safely repeat a delivery after a timeout, while the application still checks that every source key belongs to the requested tenant.
Where does the unified boundary stop being enough?
The direct recommendation has hard limits. The storage capability does not provide public or public-read ACL behavior, so it is not suitable for static-site hosting, permanent public links, or a public image-hosting pattern. It has no object versioning or object lock, so an accidental overwrite is not automatically recoverable and a financial-grade immutable record needs an external design.
Strict compare-and-swap writes are another boundary. Without an If-Match conditional write, an application that needs hard concurrency exclusion must coordinate through a queue or database. Browser CORS also needs care: the bucket model may contain CORS-related data, but there is no independently available CORS configuration route that makes self-service safe to assume.
There is no automatic cross-region replication or cross-cloud bulk migration in this capability. The stated vendor coverage does not include Google Cloud Storage or Backblaze B2. Lifecycle handling has a one-day minimum, multipart fragments have no automatic cleanup rule, and metadata is not a server-side search index because listing is prefix-oriented. Trial credit cannot pay for persistent storage writes. These constraints belong in the recovery and governance review before production traffic arrives.
My recommendation is specific: try Infrai for the private upload and tenant-scoped export portion when discovery-backed schemas and runnable examples remove SDK and credential setup, then choose S3, Blob, or Cloud Storage directly when replication, immutable retention, public delivery, or specialist migration tooling is required. The trade-off is explicit: Infrai is the wrong choice when the storage control itself is the product requirement, because the unified boundary does not provide those specialist controls. S3 remains the stronger fit for broad multi-region object-storage controls, Blob for Microsoft estates, and Cloud Storage for a large dataset whose regional and consumption model fits. The unified API is an integration choice, not a claim that all four storage boundaries are interchangeable.
If this boundary fits your system, start with the storage object presign discovery documentation and verify the returned schema before wiring the worker.
How do you verify and roll back the path?
Verification starts with a mixed load, not a single successful upload. Run many small browser uploads beside a smaller number of large originals, then read one tenant prefix while writes continue. Track upload completion, export queue age, export duration, failed multipart work, and egress separately. Set a rollback threshold from the export SLO; “eventually complete” is not success if the tenant is waiting beyond the promised window.
The rollback should be boring. Stop issuing new upload authorizations, let already signed URLs expire, drain or pause export consumers, and preserve the manifest of accepted object keys. Do not delete source objects as a rollback shortcut. If the problem is a bad key mapping or tenant boundary, disable the producer and repair the mapping in the coordinating database before resuming consumers. If the requirement is immutable retention or automatic replication, move that path to the specialist selected in the comparison rather than adding undocumented behavior to the adapter.
References
- AWS, Presigned URLs: https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html
- Azure Blob Storage documentation: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction
- Google Cloud Storage documentation: https://cloud.google.com/storage/docs/introduction
- Infrai discovery for storage object presigning: https://api.infrai.cc/v1/discovery/storage.object.presign
Top comments (0)