KoderKoder.ai
PrezziEnterpriseIstruzionePer gli investitori
AccediInizia ora

Prodotto

PrezziEnterprisePer gli investitori

Risorse

ContattaciAssistenzaIstruzioneBlog

Note legali

Informativa sulla privacyTermini di utilizzoSicurezzaNorme di utilizzoSegnala un abuso

Social

LinkedInTwitter
Koder.ai
Lingua

© 2026 Koder.ai. Tutti i diritti riservati.

Home›Blog›Object storage vs blob nel database per upload veloci ed economici
28 dic 2025·7 min

Object storage vs blob nel database per upload veloci ed economici

Object storage vs blob nel database: conserva i metadati file in Postgres, i byte in object storage e mantieni i download veloci con costi prevedibili.

Object storage vs blob nel database per upload veloci ed economici

Il vero problema con gli upload degli utenti

Gli upload degli utenti sembrano semplici: accetta un file, salvalo, mostrane il contenuto dopo. Funziona con pochi utenti e file piccoli. Poi il volume cresce, i file diventano più grandi e il dolore emerge in posti che non hanno nulla a che fare con il pulsante di upload.

I download rallentano perché il tuo server applicativo o il database stanno facendo il lavoro pesante. I backup diventano enormi e lenti, quindi i restore richiedono più tempo proprio quando ne hai più bisogno. Le bollette di storage e banda (egress) possono salire perché i file vengono serviti in modo inefficiente, duplicati o mai puliti.

Quello che in genere vuoi è noioso e affidabile: trasferimenti veloci sotto carico, regole d'accesso chiare, operazioni semplici (backup, restore, cleanup) e costi che restano prevedibili con la crescita dell'uso.

Per arrivarci, separa due cose che spesso vengono confuse:

I metadati sono piccole informazioni su un file: chi ne è il proprietario, come si chiama, dimensione, tipo, quando è stato caricato e dove si trova. Questo appartiene al database (come Postgres) perché devi poterlo interrogare, filtrare e joinare con utenti, progetti e permessi.

I byte del file sono il contenuto effettivo del file (la foto, il PDF, il video). Conservare i byte dentro i blob del database può funzionare, ma appesantisce il database, rende i backup più grandi e le prestazioni più difficili da prevedere. Mettere i byte in object storage mantiene il database concentrato su quello che sa fare meglio, mentre i file vengono serviti in modo rapido ed economico da sistemi pensati per questo lavoro.

Object storage vs blob in un database, in parole semplici

Quando si dice "memorizzare gli upload nel database", di solito si intende i blob del database: o una colonna BYTEA (byte raw in una riga) o i "large objects" di Postgres (una feature che conserva valori grandi separatamente). Entrambe le soluzioni possono funzionare, ma fanno ricadere sul database la responsabilità di servire i byte dei file.

L'object storage è un'idea diversa: il file vive in un bucket come oggetto, indirizzato da una chiave (tipo uploads/2026/01/file.pdf). È progettato per file grandi, storage economico e download in streaming. Gestisce anche molte letture concorrenti senza occupare le connessioni del database.

Postgres eccelle nelle query, nei vincoli e nelle transazioni. È ideale per i metadati come chi possiede il file, cosa è, quando è stato caricato e se può essere scaricato. Questi metadati sono piccoli, facili da indicizzare e facili da mantenere consistenti.

Una regola pratica:

  • Usa Postgres per i metadati dei file, i permessi e le relazioni.
  • Usa object storage per i byte quando i file possono superare pochi MB, o quando i download sono frequenti.
  • Considera i blob nel DB solo per asset minuscoli che devono essere legati transazionalmente a un record (come un'icona piccola) e sei sicuro che la crescita del database resterà modesta.

Un rapido controllo di buon senso: se backup, repliche e migrazioni diventerebbero dolorose con i byte inclusi, tieni i byte fuori da Postgres.

Un'architettura semplice che resta gestibile

La configurazione con cui la maggior parte dei team finisce è semplice: conserva i byte in object storage e il record del file (chi lo possiede, cosa è, dove si trova) in Postgres. La tua API coordina e autorizza, ma non fa da proxy per grandi upload e download.

Questo ti dà tre responsabilità chiare:

  • Postgres contiene una riga piccola per file: un file_id stabile, proprietario, dimensione, content type e il puntatore all'oggetto.
  • Object storage conserva i byte effettivi, ottimizzato per file grandi e storage economico.
  • La tua API crea e autorizza i record dei file e fornisce permessi a breve durata per lo storage.

Quel file_id stabile diventa la chiave primaria per tutto: commenti che fanno riferimento a un attachment, fatture che puntano a un PDF, log di audit e strumenti di supporto. Gli utenti possono rinominare un file, puoi spostarlo tra bucket e il file_id resta lo stesso.

Quando possibile, tratta gli oggetti memorizzati come immutabili. Se un utente sostituisce un documento, crea un nuovo oggetto (e di solito una nuova riga o una riga di versione) invece di sovrascrivere i byte in loco. Semplifica la cache, evita sorprese del tipo "il link vecchio mostra il file nuovo" e ti dà una storia di rollback pulita.

Decidi la privacy in anticipo: privata per default, pubblica solo per eccezione. Una buona regola è: il database è la fonte di verità su chi può accedere a un file; l'object storage applica i permessi a breve durata che la tua API rilascia.

Come modellare i metadati dei file in Postgres

Con la separazione pulita, Postgres conserva i fatti sul file e l'object storage i byte. Questo mantiene il database più piccolo, i backup più veloci e le query semplici.

Una tabella uploads pratica ha bisogno di pochi campi per rispondere a domande reali come "chi possiede questo?", "dove è memorizzato?" e "è sicuro scaricarlo?"

CREATE TABLE uploads (
  id               uuid PRIMARY KEY,
  owner_id         uuid NOT NULL,
  bucket           text NOT NULL,
  object_key       text NOT NULL,
  size_bytes       bigint NOT NULL,
  content_type     text,
  original_filename text,
  checksum         text,
  state            text NOT NULL CHECK (state IN ('pending','uploaded','failed','deleted')),
  created_at       timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX uploads_owner_created_idx ON uploads (owner_id, created_at DESC);
CREATE INDEX uploads_checksum_idx ON uploads (checksum);

Alcune decisioni che evitano problemi in seguito:

  • Usa bucket + object_key come puntatore di storage. Lascialo immutabile dopo l'upload.
  • Tieni traccia dello stato. Quando un utente inizia un upload, inserisci una riga pending. Passa a uploaded solo dopo che il sistema conferma che l'oggetto esiste e la dimensione (e idealmente il checksum) corrispondono.
  • Salva original_filename solo per la visualizzazione. Non fidarti di esso per decisioni di tipo o sicurezza.

Se supporti le sostituzioni (come un utente che ricarica una fattura), aggiungi una tabella upload_versions separata con upload_id, version, object_key e created_at. In questo modo puoi mantenere la cronologia, fare rollback degli errori ed evitare di rompere riferimenti vecchi.

Flusso di upload passo passo (senza bloccare la tua API)

Mantieni gli upload veloci facendo gestire alla tua API la sola coordinazione, non i byte del file. Il tuo database resta reattivo, mentre l'object storage prende il colpo di banda.

Inizia creando il record di upload prima che qualcosa venga inviato. La tua API restituisce un upload_id, dove risiederà il file (un object_key) e un permesso di upload a breve durata.

Un flusso comune:

  1. Il client chiede di caricare: la tua API crea una riga con pending, più la dimensione prevista e il content type intenzionato.
  2. L'API restituisce un presigned URL: per file grandi, genera un URL di upload presigned. Per file piccolissimi (come avatar) puoi ancora fare proxy tramite il backend se vuoi codice client più semplice.
  3. Il client carica direttamente nello storage: il browser o l'app mobile inviano i byte allo storage, non alla tua API.
  4. Finalizza: il client chiama la tua API con upload_id e eventuali campi di risposta dello storage (come ETag). Il server verifica dimensione, checksum (se lo usi) e content type, poi marca la riga uploaded.
  5. Fallire in modo sicuro: se la verifica fallisce, marca failed ed eventualmente elimina l'oggetto.

Retry e duplicati sono normali. Rendi la chiamata di finalize idempotente: se lo stesso upload_id viene finalizzato due volte, restituisci successo senza cambiare nulla.

Per ridurre duplicati tra retry e re-upload, conserva un checksum e tratta "stesso proprietario + stesso checksum + stessa dimensione" come lo stesso file.

Flusso di download passo passo (veloce e cache-friendly)

Add file versioning safely
Draft versioned objects and rollback-friendly keys so replacements do not break links.
Prototype

Un buon flusso di download inizia con un URL stabile nell'app, anche se i byte vivono altrove. Pensa: /files/{file_id}. La tua API usa file_id per cercare i metadati in Postgres, verifica il permesso e poi decide come consegnare il file.

  1. Il client richiede il tuo URL stabile con file_id.
  2. L'API verifica che l'utente possa accedervi e che il file sia uploaded.
  3. L'API restituisce o un redirect verso l'object storage (spesso la scelta migliore), o un URL GET presigned a breve durata per file privati.
  4. Il client scarica direttamente dall'object storage, tenendo la tua API e i server app fuori dal percorso critico.

I redirect sono semplici e veloci per file pubblici o semi-pubblici. Per file privati, gli URL GET presigned mantengono lo storage privato permettendo comunque il download diretto dal browser.

Per video e download grandi, assicurati che l'object storage (e qualsiasi layer proxy) supporti le richieste range (Range headers). Questo permette seeking e download riprendibili. Se fai passare i byte attraverso la tua API, il supporto per range spesso si rompe o diventa costoso.

La cache è dove arriva la velocità. Il tuo endpoint stabile /files/{file_id} dovrebbe di solito non essere cacheabile (è una porta d'accesso con auth), mentre la risposta dell'object storage può spesso essere cacheata in base al contenuto. Se i file sono immutabili (nuovo upload = nuova key), puoi impostare una lunga durata di cache. Se sovrascrivi file, tieni i tempi di cache brevi o usa chiavi versionate.

Un CDN aiuta quando hai molti utenti globali o file grandi. Se il tuo pubblico è piccolo o per lo più in una regione, l'object storage da solo spesso è sufficiente e più economico per iniziare.

Mantenere i costi prevedibili nel tempo

Le bollette a sorpresa di solito arrivano da download e churn, non dai byte grezzi sul disco.

Valuta i quattro driver che muovono la spesa: quanto memorizzi, quanto leggi e scrivi (requests), quanta dati escono dal provider (egress) e se usi un CDN per ridurre i download ripetuti dall'origin. Un file piccolo scaricato 10.000 volte può costare più di un file grande che nessuno tocca.

Controlli che mantengono la spesa sotto controllo:

  • Limita la dimensione per upload e imposta quote per utente in base al piano.
  • Rate limit per upload e download per evitare abusi e loop accidentali.
  • Usa regole di lifecycle così i file vecchi si spostano in tier più economici o scadono quando non servono più.
  • Deduplica tramite checksum così retry o re-upload non creano copie extra.
  • Conserva contatori d'uso in Postgres così fatturazione e alert si basano su fatti, non su stime.

Le regole di lifecycle sono spesso la vittoria più semplice. Per esempio: conserva le foto originali "hot" per 30 giorni, poi spostale in una classe di storage più economica; conserva le fatture per 7 anni, ma elimina parti di upload falliti dopo 7 giorni. Anche politiche di retention di base fermano la crescita incontrollata dello storage.

La deduplica può essere semplice: memorizza un hash di contenuto (tipo SHA-256) nella tabella dei metadati e applica unicità per proprietario. Quando un utente carica lo stesso PDF due volte, puoi riutilizzare l'oggetto esistente e creare solo una nuova riga di metadati.

Infine, traccia l'uso dove già fai contabilità utente: Postgres. Memorizza bytes_uploaded, bytes_downloaded, object_count e last_activity_at per utente o workspace. Questo rende semplice mostrare limiti nell'UI e triggerare avvisi prima di ricevere la fattura.

Sicurezza e compliance di base per gli upload

Get rewarded for building
Share what you build with Koder.ai and earn credits to keep iterating faster.
Earn Credits

La sicurezza per gli upload si riduce a due cose: chi può accedere a un file e cosa puoi dimostrare dopo se qualcosa va storto.

Controllo accessi che rispecchi l'uso reale

Inizia con un modello d'accesso chiaro e codificalo nei metadati di Postgres, non in regole una tantum sparse tra i servizi.

Un modello semplice che copre la maggior parte delle app:

  • Owner-only: solo chi ha caricato (e gli admin) possono accedere.
  • Shared: accessibile a utenti specifici o a un team/workspace.
  • Public: accessibile senza login (usalo con parsimonia e tienilo comunque tracciato).

Per file privati, evita di esporre le object key raw. Emissiona URL di upload e download firmati a tempo limitato e con ambito limitato, e ruotali spesso.

Controlli di compliance che ti salvano dopo

Verifica la cifratura in transito e a riposo. In transito significa HTTPS end-to-end, inclusi gli upload diretti allo storage. A riposo significa cifratura lato server nel provider di storage e che backup e repliche siano anch'essi cifrati.

Aggiungi checkpoint per sicurezza e qualità dei dati: valida content type e dimensione prima di emettere un upload URL, poi valida di nuovo dopo l'upload (basandoti sui byte effettivamente memorizzati, non solo sul nome del file). Se il tuo profilo di rischio lo richiede, esegui scansione malware in modo asincrono e metti il file in quarantena fino al superamento.

Conserva campi di audit così puoi indagare incidenti e soddisfare esigenze di compliance di base: uploaded_by, ip, user_agent e last_accessed_at sono una baseline pratica.

Se hai requisiti di residenza dei dati, scegli consapevolmente la regione di storage e mantienila coerente con dove esegui il compute.

Errori comuni che causano rallentamenti e incidenti

La maggior parte dei problemi di upload non riguarda la velocità pura. Nascono da scelte di design che sembrano comode all'inizio e poi diventano dolorose con traffico reale, dati reali e ticket di supporto reali.

  • Memorizzare i byte dei file dentro Postgres: funziona per app piccole, poi i backup esplodono, i restore diventano lunghi e la manutenzione ordinaria diventa rischiosa. Una singola tabella grande può rallentare vacuum, replica e anche query semplici.
  • Usare il filename fornito dall'utente come object key: avvengono collisioni (due utenti caricano "invoice.pdf") e caratteri strani crean edge case. Mantieni i filename solo per la visualizzazione e genera una chiave unica (tipo UUID) per lo storage.
  • Saltare la validazione al finalize: anche se validi sul client, hai bisogno di controlli server-side per dimensione, content type e proprietà quando marchi un upload come completo.
  • Rendere gli oggetti pubblici per errore e non ruotare mai l'accesso: una policy di bucket "temporanea" pubblica o URL a lunga durata spesso diventano permanenti. Preferisci link di download a breve durata e un modo per revocare accesso rapidamente.
  • Eliminare solo un lato (metadati o byte): cancellare la riga in Postgres ma lasciare l'oggetto crea perdite di costo silenziose. Eliminare l'oggetto ma mantenere i metadati crea download rotti e carico di supporto.

Un esempio concreto: se un utente sostituisce una foto profilo tre volte, puoi finire per pagare tre oggetti vecchi per sempre a meno che tu non pianifichi la pulizia. Un pattern sicuro è soft delete in Postgres, poi un job in background che rimuove l'oggetto e registra il risultato.

Checklist rapida pre-lancio

La maggior parte dei problemi appare quando arriva il primo file grande, un utente ricarica la pagina a metà upload o qualcuno cancella un account e i byte restano indietro.

Assicurati che la tabella Postgres registri la dimensione del file, il checksum (per verificare l'integrità) e un percorso di stato chiaro (per esempio: pending, uploaded, failed, deleted).

Una checklist finale:

  • Conferma che i retry siano sicuri: tentativi ripetuti non devono creare oggetti extra o righe "uploaded" con byte mancanti.
  • Rendi gli upload riprendibili o almeno riavviabili senza ticket di supporto (timeout e reti mobili succedono).
  • Verifica che i download gestiscano le range request così i file grandi partono velocemente e riprendono dopo una pausa.
  • Definisci la cancellazione end-to-end: tombstone dei metadati, cancellazione dei byte oggetto e gestione del cleanup ritardato se un job fallisce.
  • Aggiungi monitoraggio di base: tasso di errore upload/download, crescita dello storage e picchi improvvisi di egress.

Un test concreto: carica un file da 2 GB, aggiorna la pagina al 30%, poi riprendi. Poi scaricalo con una connessione lenta e vai a metà file. Se uno dei due flussi è instabile, risolvilo ora, non dopo il lancio.

Scenario esemplare: foto e fatture nella stessa app

Automate cleanup jobs
Create background jobs for orphan cleanup, soft deletes, and lifecycle handling.
Generate

Una SaaS semplice spesso ha due tipi di upload molto diversi: foto profilo (frequenti, piccole, sicure da cacheare) e PDF fatture (sensibili, devono restare privati). Qui la separazione tra metadati in Postgres e byte in object storage paga davvero.

Ecco come i metadati possono apparire in una tabella files, con un paio di campi che influenzano il comportamento:

fieldesempio foto profiloesempio PDF fattura
kindavatarinvoice_pdf
visibilityprivate (servito via signed URL)private
cache_controlpublic, max-age=31536000, immutableno-store
object_keyusers/42/avatars/2026-01-17T120102Z.webporgs/7/invoices/INV-1049.pdf
statusuploadeduploaded
size_bytes184233982341

Quando un utente sostituisce una foto, trattala come un file nuovo, non come una sovrascrittura. Crea una nuova riga e un nuovo object_key, poi aggiorna il profilo utente per puntare al nuovo file_id. Marca la riga vecchia come replaced_by=<new_id> (o con deleted_at) e cancella l'oggetto vecchio successivamente con un job in background. Questo mantiene la cronologia, facilita i rollback ed evita condizioni di race.

Supporto e debug diventano più facili perché i metadati raccontano una storia. Quando qualcuno dice "il mio upload è fallito", il supporto può controllare status, un last_error leggibile, un storage_request_id o etag (per tracciare i log dello storage), timestamp (si è bloccato?) e owner_id e kind (la policy d'accesso è corretta?).

Prossimi passi per implementare senza sovraingegnerizzare

Inizia in piccolo e rendi il percorso felice noioso: i file si caricano, i metadati si salvano, i download sono veloci e niente si perde.

Un buon primo traguardo è una tabella minima in Postgres per i metadati file più un singolo flusso di upload diretto allo storage e un singolo flusso di download che puoi spiegare su una lavagna. Quando quello funziona end-to-end, aggiungi versioning, quote e regole di lifecycle.

Scegli una policy di storage chiara per tipo di file e mettila per iscritto. Per esempio, le foto profilo possono essere cacheable, mentre le fatture devono essere private e accessibili solo tramite URL di download a breve durata. Mescolare policy all'interno di un prefisso di bucket senza un piano è come causare esposizione accidentale.

Aggiungi instrumentazione presto. I numeri che vuoi dal giorno uno sono: tasso di fallimento finalize upload, tasso di orfani (oggetti senza riga DB corrispondente e viceversa), volume di egress per tipo di file, latenza download P95 e dimensione media oggetto.

Se vuoi un modo più veloce per prototipare questo pattern, Koder.ai (koder.ai) è costruito attorno alla generazione di app complete da chat e rispecchia lo stack comune usato qui (React, Go, Postgres). Può essere un modo utile per iterare sullo schema, gli endpoint e i job di cleanup senza riscrivere sempre gli stessi scaffolding.

Domande frequenti

Devo memorizzare i file caricati in Postgres o in object storage?

Usa Postgres per i metadati che devi interrogare e proteggere (proprietario, permessi, stato, checksum, puntatore). Metti i byte in object storage così i download e i trasferimenti grandi non consumano connessioni al database né gonfiano i backup.

Qual è lo svantaggio principale di memorizzare i byte dei file in blob di Postgres?

Fa fare al database anche il lavoro del file server. Aumenta la dimensione delle tabelle, rallenta backup e restore, aumenta il carico di replica e può rendere le prestazioni meno prevedibili quando molti utenti scaricano contemporaneamente.

Qual è l'architettura di upload più semplice che scala senza complicarsi?

Sì. Mantieni un file_id stabile nella tua app, conserva i metadati in Postgres e i byte in object storage indirizzati tramite bucket e object_key. La tua API dovrebbe autorizzare l'accesso e fornire permessi brevi per upload/download invece di fare da proxy per i byte.

Come dovrebbe funzionare il flusso di upload se non voglio che la mia API gestisca i byte dei file?

Crea prima una riga pending, genera un object_key unico, poi lascia che il client carichi direttamente nello storage usando un permesso a breve durata. Dopo l'upload, fai chiamare il client a un endpoint di finalize così il server può verificare dimensione e checksum (se lo usi) prima di segnare la riga come uploaded.

Perché ho bisogno di uno stato di upload come pending/uploaded/failed/deleted?

Perché gli upload reali falliscono e vengono ritentati. Un campo di stato ti permette di distinguere file previsti ma non presenti (pending), completati (uploaded), corrotti (failed) e rimossi (deleted) così la UI, i job di cleanup e gli strumenti di supporto si comportano correttamente.

Posso usare il filename fornito dall'utente come object key nello storage?

Tratta original_filename solo per la visualizzazione. Genera una chiave di storage unica (spesso un percorso basato su UUID) per evitare collisioni, caratteri strani e problemi di sicurezza. Puoi comunque mostrare il nome originale nell'interfaccia mantenendo i percorsi di storage puliti e prevedibili.

Qual è il pattern raccomandato per il download di file privati?

Usa un URL stabile dell'app come /files/{file_id} come porta d'accesso per le autorizzazioni. Dopo aver verificato l'accesso in Postgres, restituisci un reindirizzamento oppure un URL di download firmato a breve scadenza così il client scarica direttamente dall'object storage, tenendo la tua API fuori dal percorso critico.

Cosa causa di solito costi imprevisti con gli upload degli utenti?

L'egress e i download ripetuti di solito dominano i costi, non lo storage grezzo. Imposta limiti di dimensione e quote, usa regole di retention/lifecycle, deduplica tramite checksum dove ha senso e registra i contatori d'uso così puoi avvisare prima che le bollette crescano.

Quali controlli di sicurezza vale la pena implementare per gli upload fin dal giorno uno?

Conserva permessi e visibilità in Postgres come fonte di verità, e tieni lo storage privato per impostazione predefinita. Valida tipo e dimensione prima e dopo l'upload, usa HTTPS end-to-end, cripta i dati a riposo e aggiungi campi di audit così puoi indagare problemi successivamente.

Come posso implementare questo rapidamente senza sovraccostruire?

Inizia con una tabella di metadati, un flusso di upload diretto allo storage e un endpoint di download che fa da gate; poi aggiungi job di cleanup per oggetti orfani e righe soft-deleted. Se vuoi prototipare rapidamente su uno stack React/Go/Postgres, Koder.ai (koder.ai) può generare endpoint, schema e task di background da una chat e lasciarti iterare senza riscrivere boilerplate.

Indice
Il vero problema con gli upload degli utentiObject storage vs blob in un database, in parole sempliciUn'architettura semplice che resta gestibileCome modellare i metadati dei file in PostgresFlusso di upload passo passo (senza bloccare la tua API)Flusso di download passo passo (veloce e cache-friendly)Mantenere i costi prevedibili nel tempoSicurezza e compliance di base per gli uploadErrori comuni che causano rallentamenti e incidentiChecklist rapida pre-lancioScenario esemplare: foto e fatture nella stessa appProssimi passi per implementare senza sovraingegnerizzareDomande frequenti
Condividi
Koder.ai
Build your own app with Koder today!

The best way to understand the power of Koder is to see it for yourself.

Start FreeBook a Demo