Home › Cloud services › Data services
guide

Object storage

Problem Backups, media, logs, data lakes and model weights need cheap, durable storage for large files. This guide answers when object storage is the right home and when a database, block storage or cache is needed instead.

S3-compatible blob storage at cents per GB-month with 11 nines durability; pick by egress price, request price and latency, not by storage price alone.

s3r2gcsazure-blobbackblazestorage-classesegress
Overview

Object storage keeps immutable blobs under keys in a flat namespace, replicated or erasure-coded across disks and zones, accessed over HTTP. It is the cheapest durable storage you can rent and the default home for backups, media, data lakes, model weights and the cold tier of databases. The S3 API is the de facto standard: R2, GCS (XML API), MinIO, Ceph, Backblaze B2 and Hetzner all speak it.

Key facts

Use when

  • Large or many immutable files, backups, logs, data lakes (Parquet), static assets behind a CDN, database tiering.

Avoid when

  • Small random updates or sub-ms reads; use a database, block storage or a cache.

Deep dive

Storage classes (AWS names; others similar)

ClassStorage $/GB-month (order of)RetrievalUse
S3 Standard0.023freeHot data
S3 Express One Zone~0.11 (prices cut 2025)free, single-digit msHot small-object, ML training reads
Standard-IA0.0125$0.01/GB, 30-day minBackups read rarely
Glacier Instant0.004$0.03/GBArchive with ms access
Glacier Deep Archive~0.001hours, $/GBCompliance archive
Intelligent-Tieringautofree + monitoring feeUnknown access patterns, objects > 128 KB

Decision table

SituationChoiceWhy
Public assets, downloads, video served to usersR2 (or B2 behind Cloudflare/Bunny)Zero or near-zero egress
Data processed by compute in AWS/GCP/AzureThat cloud's native store, same regionIn-region transfer is free; cross-cloud is egress
Backups of servers on Hetzner/OVHAnother provider's S3 (R2, B2, Hetzner in a different location)Off-site, cheap
Data lake for analyticsS3/GCS + Parquet + Iceberg/DeltaEvery engine reads it
Millions of tiny objects (< 64 KB)Pack into larger files, or use a databasePer-request and per-object overhead dominate
Self-hosted, on-premMinIO, Ceph RGW, Garage, SeaweedFSS3 API without a cloud
Compliance write-onceS3 Object Lock (compliance mode)WORM retention enforced by the provider

Code

Fast parallel upload with the AWS CLI (works against R2/B2 with --endpoint-url):

aws configure set default.s3.max_concurrent_requests 64
aws configure set default.s3.multipart_chunksize 64MB
aws s3 cp ./dump.tar.zst s3://backups/db/2026-10-20.tar.zst \
  --endpoint-url https://<account>.r2.cloudflarestorage.com

Lifecycle rules: expire old versions and incomplete multipart uploads (the forgotten cost).

{ "Rules": [
  { "ID": "abort-mpu", "Status": "Enabled", "Filter": {},
    "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 3 } },
  { "ID": "tier-logs", "Status": "Enabled", "Filter": { "Prefix": "logs/" },
    "Transitions": [ { "Days": 30, "StorageClass": "STANDARD_IA" },
                     { "Days": 180, "StorageClass": "GLACIER_IR" } ],
    "Expiration": { "Days": 730 } },
  { "ID": "old-versions", "Status": "Enabled", "Filter": {},
    "NoncurrentVersionExpiration": { "NoncurrentDays": 30 } } ] }

Presigned URL so clients upload directly, bypassing your servers (Rust, aws-sdk-s3):

use aws_sdk_s3::presigning::PresigningConfig;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conf = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
    let s3 = aws_sdk_s3::Client::new(&conf);
    let req = s3.put_object()
        .bucket("uploads").key("u/123/avatar.webp").content_type("image/webp")
        .presigned(PresigningConfig::expires_in(Duration::from_secs(300))?)
        .await?;
    println!("{}", req.uri());                       // client PUTs to this URL within 5 minutes
    Ok(())
}

Efficiency

  • Cost: S3 sustains at least 3,500 PUT/COPY/POST/DELETE or 5,500 GET/HEAD requests per second per partitioned prefix, with no limit on prefixes (S3 performance docs, as of 2026-10). Small objects are bound by request count and per-request latency (order of 10-100 ms first byte); large objects by connection bandwidth. R2 has no egress bandwidth charge (R2 pricing, as of 2026-10). Bound: requests for small objects, egress price for large ones.
  • Levers:
    • parallel connections and ranged GETs — throughput scales with parallelization (S3 performance docs) — large objects
    • retry the slowest 5 % of large requests (over 128 MB) — cuts tail latency (S3 design patterns doc) — big transfers
    • CloudFront or ElastiCache in front — fewer direct requests, "orders of magnitude" lower GET latency with ElastiCache (S3 design patterns doc) — hot objects
    • spread keys over several prefixes — request limits apply per prefix (S3 performance docs) — hot key spaces
    • pack tiny objects into larger files — fewer billed requests (this page) — logs, events
  • Naive → standard → state of the art:
    • one sequential GET per small object, LIST to find keys · one request and 10-100 ms per object (order of)
    • parallel ranged GETs or multipart, manifest instead of LIST, lifecycle rules · connection count times per-connection rate
    • cache tier, packed columnar files such as Parquet, same-region or zero-egress reads · requests per byte and egress both fall

Implementation notes

  • Throughput: one connection gives order of 50-100 MB/s; use 16-64 parallel ranged GETs or multipart parts (8-64 MB) to saturate 10-100 Gbit/s.
  • S3 scales per prefix to order of 3,500 writes and 5,500 reads per second; spread hot keys over prefixes or let it auto-partition and back off on 503 SlowDown.
  • S3 is strongly read-after-write consistent since 2020; other S3-compatible stores may differ, check before relying on list-after-write.
  • Use conditional writes (If-None-Match: *, If-Match on ETag, supported on S3 since 2024 and on R2) to build atomic manifests for logs and table formats.
  • Enable versioning on anything you can't regenerate; it protects against deletes and ransomware.

Pitfalls

  • Egress from a hyperscaler bucket to users or another cloud: 100 TB/month ≈ $5-9k.
  • Minimum storage durations and minimum object sizes in IA/Glacier classes bill tiny objects as 128 KB and early deletes as full months.
  • LIST is slow and paid; keep your own index (a manifest or DB) instead of listing millions of keys.
  • Public buckets leak data; block public access account-wide and serve via CDN with signed URLs or origin access control.
  • Incomplete multipart uploads are invisible in the console and billed forever without a lifecycle rule.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
object_storeRustapache/arrow-rs-object-storecargo add object_store————one API for S3, GCS, Azure and local files
aws-sdk-s3Rustawslabs/aws-sdk-rustcargo add aws-sdk-s3————official S3 client, presigned URLs
GarageRustgit.deuxfleurs.fr/Deuxfleurs/garagen/a (server binary, see quick start)————self-hosted S3-compatible store for small clusters
SeaweedFSGoseaweedfs/seaweedfsn/a (release binary, see repo)————S3 API on a blob store, small files
rcloneGorclone/rclonebrew install rclone————copy and sync between providers

Sources

  • AWS, Amazon S3 pricing pricing and Amazon S3 User Guide: performance guidelines docs
  • Cloudflare, R2 pricing pricing; Backblaze, B2 Cloud Storage pricing pricing
  • Andy Warfield et al., Building and operating a pretty big storage system called S3, All Things Distributed blog, 2023. post

Used by

chandy lamport snapshots · plannedConsistent distributed snapshots via markers/barriers (Flink-style checkpoints)
Chunked array storageTeams hold arrays too big for memory (grids over time, tensors, image volumes, rasters) and must read a small region, or write different regions from many workers, without loading or rewriting the whole array.Cloud providers comparedChoosing where to host a new system, or checking whether the current cloud bill is out of line. This guide answers which kind of provider (hyperscaler, commodity, specialist) fits which workload.Data loading pipelinesModel training leaves the GPU idle between steps because data arrives too slowly. This guide helps engineers get shuffled, distributed and crash-resumable batches to the accelerator fast enough to keep it busy.
distributed checkpointing · plannedSharded, asynchronous checkpoint save/load of model and optimizer state, with resharding to new world sizes
experiment tracking · plannedLogging params, metrics, artifacts and code versions per run with an append-only metric store and comparison
mapreduce · plannedMap, partition, sort/shuffle, reduce with combiners and re-execution of failed tasks
model registry · plannedVersioned model artifacts with metadata, stages, lineage and immutable content hashes
Point-in-time recoveryAfter a bad deploy, an exploit or an accidental delete, an operator must bring a database or service back to its exact state of a moment before the damage, without restoring only to the last nightly copy.
table snapshot manifests · plannedTable state as an immutable tree of snapshot -> manifest lists -> data files, committed by pointer swap

Systems (50):

Alternatives and related