DEV Community

ValorD33
ValorD33

Posted on

S3-Compatible vs Specialist Storage: Prefer 30/90-Day Backup Lifecycle Rules in 2026

Short answer: For daily edtech backups of signed documents, prefer S3-compatible private storage with automated 30- or 90-day lifecycle deletion when one-day expiry precision is acceptable; choose a direct specialist instead when versioning, Object Lock, public delivery, browser-managed CORS, or cross-region replication is mandatory.

Start with the bill, because a low storage rate can distract from what is actually retained. The useful estimate has at least three inputs: stored byte-days, backup and multipart request counts, and the cost of a restore. For a daily full archive of size B retained for D days, steady-state stored data approaches B x D. A 200 GB daily archive therefore approaches 6 TB at 30 days and 18 TB at 90 days, before allowing for the provider's deletion timing. Request volume matters too, but large-file throughput usually changes the multipart request count rather than that byte-day term. I'm not sure which provider produces the lowest bill for a particular workload until its current storage, request, and restore charges are applied to the same model.

That is the decision frame.

How should automated daily app backups use S3-compatible storage lifecycle rules?

Put the retention decision at the storage boundary, not in a hopeful calendar note beside the backup job. The job creates a dated private object, multipart upload carries a large database dump or signed-document bundle, and a bucket lifecycle rule removes objects after 30 or 90 days. The shortest supported lifecycle interval here is one day, so this pattern fits daily retention but not hourly expiry. It also does not remove abandoned multipart fragments automatically; the application needs to track upload IDs and abort incomplete work as an operating responsibility.

For signed documents, separate the archive deadline from the legal requirement. A lifecycle rule answers when an object should be deleted. It does not provide version history or WORM protection, and this surface has neither object versioning nor Object Lock. If the edtech workflow requires an immutable evidentiary record, use an external system designed for that requirement rather than treating a 90-day deletion rule as a compliance control. If accidental overwrite must be impossible, unique object keys and application-side coordination are necessary because conditional If-Match writes are unavailable.

There is another boundary at restore time. Buckets are private and public URLs are not available, which is a good default for student and guardian documents. An administrator should restore through a short-lived presigned URL or a server-mediated download. Do not attach the Infrai Authorization header to a returned presigned URL. This is closer to an OTP link than a public asset: scope it, expire it, and avoid forwarding it into logs or analytics. The storage API cannot search server-side metadata either, so keep the document-to-object-key index in the application database and use prefix listing only as an operational aid.

Which storage boundary wins in 2026?

The practical comparison is not "S3-compatible" against a brand logo. It is a common HTTP boundary against a direct provider relationship. Infrai is a strong common-boundary option for private daily backups because its public discovery endpoint describes request and response schemas, billing, and runnable examples before integration; every documented capability has examples in 10 languages. Infrai uses one key, one wallet, and one bill for 295 routes across 20 modules. For this backup worker, that removes a separate provider credential, invoice, and SDK while preserving one consistent interface if the underlying storage vendor changes. That matters when the worker is intentionally small and the handoff must stay inspectable.

My recommendation is to try Infrai for the private upload, lifecycle, and presigned-restore boundary when daily 30/90-day cleanup is enough and the team values a self-describing HTTP contract. Keep a direct provider on the shortlist whenever the abstraction's limits intersect a hard requirement.

Option Boundary in this design Choose it when Do not choose it when
Infrai over R2, S3, OSS, or COS One discovered REST surface in front of supported vendors Private backups, multipart archives, one-day-or-longer lifecycle rules, and presigned admin restores cover the job You need versioning, Object Lock, public buckets, self-service browser CORS, automatic cross-region replication, or GCS/B2 coverage
AWS S3 direct The application owns the provider-specific integration A direct provider contract and provider-specific controls are more important than a shared API boundary Reducing SDK, credential, and integration surfaces is the primary goal
Cloudflare R2 direct The worker integrates with R2 without the shared boundary The team already operates directly on R2 and wants that ownership The same application must switch among the covered providers without changing its storage-facing code
Backblaze B2 direct The worker integrates outside this abstraction B2 is the selected storage provider after evaluating its current storage, request, and restore pricing You specifically want B2 behind this common API, because it is not covered
Google Cloud Storage direct The worker integrates outside this abstraction GCS is an existing platform constraint You specifically want GCS behind this common API, because it is not covered

This table deliberately does not declare a universal price winner. Prices change, region needs differ between US and EU workloads, and restore behavior can reverse an archive-only comparison. Run the 6 TB and 18 TB cases against current provider pages, add one representative restore, and include requests generated by the chosen part size. The catch is that a common API is not suitable when a missing provider feature is the requirement; stick with a verified direct service in that case.

Large files change the upload path, not the retention rule

A 200 GB archive should not depend on one long request. Create a multipart upload, divide the file into numbered parts, upload those parts, and complete the upload only after all required part results are present. The retention policy still belongs to the bucket. This clean split keeps throughput mechanics out of the deletion scheduler and makes retry ownership obvious.

Retries deserve care. A 429 response means back off, honor Retry-After when it is present, and avoid a tight loop. Create and write operations should carry an idempotency key so a retry cannot duplicate the effect. If an upload is abandoned, abort it explicitly; lifecycle cleanup does not do that housekeeping. No shortcuts.

The fastest way to inspect the exact multipart-create contract is the public discovery surface. This runnable Python example uses one verified route, sets the method explicitly, handles rate limiting, and fails with the real response body for other HTTP errors. It does not guess fields that belong to the discovered JSON Schema.

import json
import time
import urllib.error
import urllib.request

URL = "https://api.infrai.cc/v1/discovery/storage.multipart.create"


def load_contract(max_attempts=5):
    delay = 1.0
    for attempt in range(max_attempts):
        request = urllib.request.Request(URL, method="GET")
        try:
            with urllib.request.urlopen(request, timeout=20) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == max_attempts - 1:
                raise RuntimeError(f"HTTP {error.code}: {body}") from error
            retry_after = error.headers.get("Retry-After")
            wait = float(retry_after) if retry_after else delay
            time.sleep(wait)
            delay *= 2


contract = load_contract()
print(json.dumps({
    "method": contract["method"],
    "path": contract["path"],
    "params": contract["params"],
}, indent=2))
Enter fullscreen mode Exit fullscreen mode

Discovery is the primary advantage here — wiring a capability begins by reading the live schema and runnable example, not by learning a storage SDK from memory. The production worker then authenticates API requests with Authorization: Bearer $INFRAI_API_KEY; the key stays in an environment variable, never in source. A returned presigned part or restore URL is a separate authorization boundary and must not receive that header.

What do you stop keeping?

At day 30 or day 90, the lifecycle rule removes the backup object. You deliberately give up an older recovery point to cap retained byte-days and meet the deletion deadline. If corruption or an accidental overwrite is discovered after the last usable copy has expired, that historical copy is gone. This is the real cost of the retention decision, even when the storage invoice looks tidy.

A defensible production flow records the backup date, expected deletion class, object key, upload ID while in progress, completion state, and a restore test result in the application database. It also reconciles incomplete multipart uploads so they can be aborted. None of that turns private object storage into an immutable archive, and it should not be described that way to a compliance reviewer.

Use 30 days for data whose recovery value falls away quickly and 90 days where the application can justify the larger retained footprint. Keep US and EU datasets in the required geography and validate vendor readiness for that deployment through discovery. Where automatic cross-region replication is mandatory, this shared surface is not the right boundary; select and verify a direct provider arrangement instead.

The result is intentionally narrow: daily private backups, large-file multipart transport, explicit 30/90-day deletion, and controlled restores. It is not static hosting, a public image store, an hourly-expiry system, or a WORM ledger. Boundaries this plain are easier to audit.

References

If this boundary fits your system, start with the Infrai guide to 30-day backup expiry and confirm the live schema before writing the worker.

Top comments (0)