DEV Community

KnutBerg8412
KnutBerg8412

Posted on

Private Healthtech App Backup Restore Costs: Retention and Deletion in EU/US Storage

Short answer: for private healthtech uploads and app backups, choose the storage design that can prove retention, deletion, and a complete restore in the required EU or US location; restore cost is a consequence of that tested path, not a useful starting point.

The dangerous assumption is that an upload that succeeded yesterday is a backup. It may be an object with no verified checksum, no deletion audit, and no recovery destination with enough capacity to meet the RTO. In a healthtech system, that gap also creates a privacy problem: keeping every copy forever is not automatically safer than deleting the right copy at the right time.

This is a runbook problem before it is a vendor comparison. Define what is retained, who may retrieve it, when it is deleted, and what evidence proves each state. Then measure a restore with the same object sizes, request shape, region, and network route that production will use. The storage bill can be modeled after those inputs exist.

Retention Is a Data Contract, Not a Bucket Setting

Start with a data inventory, not a price sheet. For each object class, record the healthtech purpose, sensitivity, source region, retention duration, expected growth, deletion trigger, and legal hold behavior. A patient-uploaded document, a derived thumbnail, and a database export may have different lifetimes even when they share a bucket.

The recovery worksheet should contain one full restore and one representative partial restore. Include the number of objects, total bytes, average and largest object, API requests, egress path, staging capacity, and the destination region. A million small objects can stress request limits and metadata handling differently from one large archive. I am not sure any published calculator captures your incident path until you run your own object shape through it; your mileage will vary with retries, concurrency, and the recovery destination.

Use an explicit SLO such as “the latest accepted backup can be retrieved and validated within 60 minutes.” The number is an example policy decision, not a fact about every service. Measure transfer time, time to first byte, retry count, request count, checksum result, and application validation separately. Capacity planning then becomes concrete: if the restore needs 4 TiB in 60 minutes, the destination and network must sustain roughly 1.14 GiB/s before protocol overhead, staging, and retry headroom. That requirement may eliminate an apparently cheap option before storage pricing enters the discussion.

Decision Buy a managed object store Build or operate a storage cluster
Retention and deletion Verify lifecycle, version, legal-hold, and deletion-audit semantics in the selected region Own policy enforcement, replication, disk replacement, and deletion evidence
Recovery path Test retrieval, request behavior, egress, and destination capacity against the RTO Test the same path plus cluster quorum, operators, upgrades, and spare capacity
Team load Pay for a service boundary and accept its interface and regional limits Keep more control, while adding an on-call surface and operational SLOs
Portability Keep an S3-compatible or other standard client where practical, but test the exact semantics Own migration tooling and the compatibility contract between clusters

The catch is straightforward: a managed service is not a retention policy. A self-hosted cluster is not a disaster recovery plan. Both need a deletion test and a restore drill.

How do EU/US app backups change private restore cost?

The first failure mode is a policy that names a duration but not an object identity. If an upload is replaced, does the old version remain recoverable? If a user requests deletion, does that request include replicas, derived files, backup exports, and queued work? If a legal hold applies, which rule wins and who can release it? “Delete after 30 days” is not an answer until those questions have owners.

The second failure mode is checking only the live bucket. A deletion worker may remove the current key while a backup manifest, version, cache, or exported archive still points to the content. Conversely, an aggressive lifecycle rule may erase the only copy before a scheduled restore test. Record deletion intent, object version or generation where the system exposes one, completion evidence, and the exceptions that prevented deletion. For example, imagine a patient-upload service that writes an encrypted PDF, a thumbnail, a database row, a search index entry, and a nightly backup manifest. A deletion request that removes only the PDF leaves five operational questions unanswered: whether the thumbnail is still downloadable, whether the database row can recreate the link, whether the index reveals metadata, whether the nightly copy is inside its retention exception, and whether a restore drill will reintroduce the deleted record. I would make the test data include all five artifacts, give them one correlation identifier, submit the deletion request, then inspect each store independently. A green response from the request endpoint is only an acknowledgement; the acceptance criterion is evidence that every permitted copy reached the intended state.

Measure it.

The third failure mode is treating region as a label. EU and US placement must be tested along the entire path: upload, replication, temporary download, restore staging, logs, metrics, and operator access. A private link is an access control mechanism, not proof of residency. The response should also make the downloaded filename safe and predictable; the HTTP Content-Disposition header is the standard control for presenting content as an attachment or inline response.

For private downloads, generate a short-lived URL only after authorization has checked the object and the requesting principal. Keep the object itself private, avoid putting sensitive identifiers in the key when a random identifier will do, and log an event that records the policy decision without logging the file contents or token. Expiry needs a test. So does revocation: if the system cannot revoke an already issued URL, the runbook should make its lifetime the bounded risk.

A safe restore path in Go

The application should separate backup selection from retrieval. Selection verifies that the object belongs to the intended tenant, date, region, and retention state; retrieval streams it into isolated staging; validation decides whether the result can be promoted. Do not let a download handler silently become a restore handler.

Here is the shape of a provider-neutral download wrapper. The storage client is deliberately an interface, so a drill can use a test implementation and the production adapter can be replaced without rewriting retention decisions.

package restore

import (
    "context"
    "crypto/sha256"
    "fmt"
    "io"
    "os"
)

type ObjectStore interface {
    Get(ctx context.Context, bucket, key string) (io.ReadCloser, error)
}

func DownloadAndHash(ctx context.Context, store ObjectStore, bucket, key, destination string) ([32]byte, error) {
    var zero [32]byte

    reader, err := store.Get(ctx, bucket, key)
    if err != nil {
        return zero, fmt.Errorf("get backup object: %w", err)
    }
    defer reader.Close()

    file, err := os.Create(destination)
    if err != nil {
        return zero, fmt.Errorf("create staging file: %w", err)
    }
    defer file.Close()

    hash := sha256.New()
    if _, err := io.Copy(io.MultiWriter(file, hash), reader); err != nil {
        return zero, fmt.Errorf("stream backup object: %w", err)
    }

    var result [32]byte
    copy(result[:], hash.Sum(nil))
    return result, nil
}
Enter fullscreen mode Exit fullscreen mode

The wrapper does not claim that a matching hash makes the application healthy. It proves only that the staged bytes match the bytes observed during the transfer. The next assertion should be domain-specific: restore a database into an isolated instance, verify a manifest of user uploads, check representative metadata, and confirm that no production credentials or public routes were used. Keep those checks in the runbook and in automation where possible.

How do you verify a deletion-safe backup design before rollout?

Run four tests against disposable data before changing the scheduled writer. First, upload a known manifest and record its expected size and digest. Second, create a private, expiring retrieval path and confirm that it expires at the promised boundary. Third, execute the deletion workflow, then search every index and derived copy that the policy covers. Fourth, restore an intentionally selected prior object and run the application validation.

The test should include a failed transfer and a cancelled deletion. A retry must not create an untracked second object; a cancelled deletion must leave a visible, reviewable state. These are boring cases, which is why they get omitted, and they are exactly the cases that turn an audit question into a late-night investigation.

For rollout, dual-write only long enough to compare manifests and complete one real restore drill. Keep the previously verified writer and retention path available until the new path has passed the retention window that matters to the service. Rollback means switching the scheduler to that verified writer, preserving the newer copy for investigation, and preventing a cleanup job from deleting either copy while ownership is being decided.

Use lifecycle rules carefully. They are useful for time-based expiration, but the rule must be reviewed against versioning, legal holds, replication, and the deletion evidence required by the service. The AWS object lifecycle documentation is a useful standards-adjacent reference for the concepts, while the exact behavior of the chosen implementation still belongs in your acceptance tests.

The decision rule I would put in the runbook is short: reject any option that cannot demonstrate the required deletion semantics and restore SLO in the target region. Among the remaining options, compare request volume, transfer path, operator burden, compatibility, and lock-in. Restore first.

Further reading

Top comments (0)