DEV Community

frontendfacile.it
frontendfacile.it

Posted on Originally published at frontendfacile.it

S3 è “stupido” per design, ed è proprio per questo che regge mezzo Internet

Da quattro verbi a primitive distribuite: consistenza forte e write condizionali stanno trasformando l’object storage in un mattone su cui costruire log, code e persino database.

S3 è una di quelle tecnologie che sembrano quasi banali finché non ti rendi conto di quanta parte dell’ecosistema moderno ci sia costruita sopra. L’API, ridotta all’osso, è imbarazzante per semplicità: carichi un oggetto, lo leggi, lo elimini, ne fai la lista. Fine. Niente query, niente transazioni, niente indici, niente join.

Eppure basta toccare S3 nel punto sbagliato perché “mezza Internet” inizi a tremare. Questo paradosso è interessante: com’è possibile che un servizio che non fa query e non fa transazioni sia così centrale per così tanti sistemi complessi?

La risposta sta in due fattori:

  1. S3 è un primitivo volutamente “stupido”: pochi concetti, poco comportamento, molta stabilità nel tempo.
  2. Le garanzie sono fortissime (durabilità, scalabilità, disponibilità) e le complicazioni vengono spostate “sopra”, nei pattern architetturali.

Negli ultimi anni, però, non è cambiato solo “cosa ci costruiamo sopra”: sono cambiate anche alcune proprietà di base di S3 che lo rendono, oggi, una piattaforma di coordinamento sorprendentemente potente.


Perché l’API minimale di S3 è un vantaggio (non un limite)

Un’API piccola è una promessa: meno superficie significa meno breaking change, meno ambiguità, meno comportamenti “magici”. Non è raro trovare codice scritto molti anni fa che continua a funzionare senza modifiche, proprio perché i mattoni sono sempre gli stessi.

In cambio, S3 si prende in carico il lavoro più pesante:

  • replica interna su più macchine e data center
  • durabilità elevatissima
  • scala massiva sia per numero di oggetti che per throughput

Il trade-off storico è sempre stato chiaro: tu rinunci a semantiche ricche (file system, transazioni, lock) e in cambio ottieni un servizio “boringly reliable”.

Il punto, però, è che per molto tempo mancava un ingrediente fondamentale per usare S3 come base di sistemi “coordinati”.


Il vecchio problema: l’eventual consistency ti impediva di ragionare

S3, per anni, ha operato con eventual consistency in scenari cruciali come overwrite e delete.

In pratica:

  • scrivi una nuova versione di un oggetto
  • S3 ti dice “ok”
  • per un intervallo breve (di solito millisecondi, talvolta secondi) alcune letture potevano ancora vedere la versione precedente

Questo è fastidioso per un avatar che “non si aggiorna subito”. È catastrofico se stai simulando un database:

  • leggi saldo = 100
  • scrivi saldo = 150
  • rileggi e ottieni ancora 100

Non puoi costruire aggiornamenti corretti, né coordinarti tra writer multipli.

Il risultato: per usare S3 come storage… ti serviva un database

In molte pipeline big data e data lake, la soluzione è stata appoggiarsi a un sistema esterno (spesso DynamoDB) per:

  • tenere traccia di quali file “esistono davvero”
  • evitare conflitti tra writer
  • serializzare commit

In altre parole: S3 come layer dati + database come layer di coordinamento.


2020: strong consistency (finalmente)

A fine 2020 è arrivata una svolta fondamentale: strong consistency.

Da quel momento, una volta che S3 conferma una write:

  • ogni successiva GET vede la versione nuova
  • ogni successiva LIST è coerente
  • vale anche per overwrite e delete

Senza cambiare API, senza costi extra, senza migrazioni: semplicemente le stesse chiamate hanno iniziato a restituire “la risposta giusta”.

Ma attenzione: questa proprietà risolve una sola domanda.

“Leggo l’ultima versione?”

Non risolve invece la domanda che distrugge i sistemi concorrenti.

“Cosa succede se due writer aggiornano nello stesso momento?”

Strong consistency non ti salva dal classico lost update:

  • A legge 100
  • B legge 100
  • A scrive 150
  • B scrive 150

Tutto perfettamente consistente. Eppure è sbagliato: un deposito è sparito.

Per evitare questo, serve una semantica del tipo: scrivi solo se…


2024: write condizionali, ETag e il ritorno del Compare-and-Swap

Nel 2024 S3 ha aggiunto un’idea che, in pratica, porta dentro al bucket un mattoncino tipico dei database: la conditional write.

Una conditional write è una normale PUT, ma con una precondizione. Se la condizione fallisce, la write viene rifiutata con un errore (tipicamente 412 Precondition Failed). Importante: il controllo e la write avvengono in un singolo passo.

1) If-None-Match: crea solo se non esiste

Questa regola ti permette di dire:

  • “crea questo oggetto solo se non esiste già”

È un abilitatore enorme per pattern append-only:

  • log di commit numerati (commit-42 può esistere una sola volta)
  • code basate su oggetti immutabili
  • meccanismi di “claim” (un solo writer si prende un ID)

Dove prima serviva un coordinatore esterno, ora può bastare il bucket.

2) If-Match + ETag: aggiorna solo se nessuno ha cambiato

Ogni oggetto in S3 ha un ETag, un identificatore che cambia quando cambia il contenuto (pensalo come “impronta” della versione corrente).

Con If-Match puoi dire:

  • “scrivi questa nuova versione solo se l’ETag è ancora quello che ho letto”

Questo è, di fatto, un Compare-and-Swap (CAS):

  1. leggo valore + ETag = V1
  2. calcolo il nuovo valore
  3. provo a scrivere con If-Match: V1
  4. se qualcuno ha scritto prima, ricevo 412, rileggo e riprovo

È la base concettuale di:

  • update sicuri
  • lock leggeri
  • leader election / single-writer
  • coordinamento di metadati

E soprattutto: non richiede un database esterno per evitare lost update.


Pattern che funzionano bene su S3: log e manifest

Uno schema che sta prendendo piede è questo:

  • ogni write produce un nuovo oggetto immutabile (append)
  • esiste un piccolo file indice/manifest che “punta” agli oggetti (o ne elenca l’ordine)
  • l’aggiornamento del manifest avviene via CAS (ETag + If-Match)

Così ottieni:

  • immutabilità dei dati (più facile da rendere robusta)
  • rollback e audit naturali (le vecchie entry restano)
  • coordinamento ridotto a un singolo punto piccolo (il manifest)

È lo stesso modo di ragionare tipico di molti formati da data lake (log di commit + file dati), e oggi diventa più accessibile perché il “coordinamento minimo” può stare nello stesso object storage.


La portabilità come effetto collaterale: l’API S3 non è più “solo AWS”

Un altro aspetto pratico: S3 come API è diventata uno standard de facto.

Molti provider e prodotti implementano un’API compatibile S3 (object storage self-hosted e cloud). Questo sposta il valore dal servizio specifico al contratto:

  • scrivi contro “S3 API”
  • cambi backend con modifiche minime

Non è una garanzia assoluta di compatibilità al 100% (ci sono differenze), ma come direzione è potente: investire in questi pattern significa spesso investire in qualcosa di più portabile di quanto sembri.


Perché (ancora) non è la scelta giusta per tutto

L’entusiasmo va bilanciato con tre limiti molto concreti.

1) Latenza

Una richiesta a object storage tende a costare decine di millisecondi. Un disco locale lavora in microsecondi. Se il tuo dominio richiede single-digit milliseconds end-to-end, S3 come “database primario” ti mette immediatamente in salita.

2) Costi per request

Lo storage è economico. Le request no, soprattutto se fai tantissime piccole write. È uno scenario in cui il costo può diventare dominante rispetto ai GB archiviati.

3) Semantica non-POSIX

S3 non è un file system:

  • niente append “vero” a un file
  • rename non è un’operazione atomica economica come su FS tradizionali
  • devi progettare intorno al concetto di oggetti immutabili e “nuove versioni”

C’è anche la possibilità di usare tier più veloci, ma spesso con trade-off di resilienza/replica (ad esempio vincoli di zona).


Regola pratica: eccelle come stato durevole, soffre come stato caldo

Una buona euristica:

Ottimo per

  • log append-only
  • code e stream “a chunk”
  • data lake (Iceberg/Delta-style)
  • backup, snapshot, archivi
  • vettori/embedding quando lavori a segmenti grandi e con caching

Sconsigliato per

  • workload user-facing con latenza rigidissima
  • carichi “chatty” ad altissima frequenza di piccole write

Quando si adotta S3 come fonte di verità, spesso funziona bene una strategia ibrida:

  • S3 come source of truth
  • cache locale / storage veloce per hot path
  • CAS su manifest/metadati per decidere chi vince e mantenere coerenza

Sintesi: quattro verbi, garanzie forti, e un nuovo spazio di design

S3 resta un’API minimale — ed è proprio questa la sua forza. Per anni la consistenza eventuale lo ha tenuto fuori dal perimetro “database-like”. Con la strong consistency e le write condizionali basate su ETag, oggi S3 offre primitive che permettono:

  • letture coerenti
  • aggiornamenti sicuri senza lost update
  • coordinamento leggero tra writer

Il risultato non è “S3 rimpiazzerà tutti i database”, ma qualcosa di più interessante: un primitivo distribuito estremamente semplice su cui progettare sistemi che prima richiedevano più componenti, più operational burden e più punti di rottura.

La domanda utile, quindi, non è se S3 sia “troppo stupido” o “geniale”. È: quanto del tuo stato è davvero caldo e transazionale, e quanto invece può essere trattato come log durevole con caching intelligente? Quando la risposta pende verso la seconda opzione, S3 smette di essere un posto dove “buttare file” e diventa una base architetturale sorprendentemente moderna.


Articolo originale: https://frontendfacile.it/blog/s3-e-stupido-per-design-ed-e-proprio-per-questo-che-regge-mezzo-internet

Top comments (0)