File Storage

Object storage, presigned URL, chunked upload, CDN.

Skoro každá appka dřív nebo později potřebuje ukládat soubory — profilové fotky, přílohy, videa, PDF. Vypadá to jednoduše, ale je v tom víc, než se zdá: kam soubory ukládat, jak zvládnout velké soubory, a jak je nezahltit svým serverem. Tahle kapitola tě tím provede od nuly.


Pár pojmů na úvod

  • Soubor / blob = binární data (obrázek, video, PDF). „Blob" = Binary Large OBject.
  • Object storage = úložiště navržené přímo pro soubory (ne pro řádky jako databáze). Typicky cloudová služba (Amazon S3, Cloudflare R2, Google Cloud Storage).
  • Metadata = informace o souboru (jméno, velikost, kdo ho nahrál, kdy) — ty patří do databáze.
  • Upload = nahrání souboru, download = stažení.

Kam soubory ukládat (a kam ne)

  • Ne do databáze. Databáze je na malé strukturované řádky, ne na megabajtové soubory. Nafoukneš ji, zpomalíš zálohy a dotazy. (Drobné výjimky existují, ale jako pravidlo: soubory do databáze ne.)
  • Ne na disk serveru. Na jednom serveru to „jde", ale jakmile máš serverů víc, soubor nahraný na jeden není vidět z druhého — a při výpadku serveru o soubory přijdeš.
  • Do object storage (S3/R2…). Je to navržené přesně na tohle: levné, prakticky neomezené, dostupné ze všech serverů. A do databáze ulož jen metadata + odkaz (cestu) na soubor v úložišti.

Presigned URL — nahrávej přímo do úložiště, mimo svůj server

Naivně by soubor tekl přes tvůj server: klient → tvůj server → úložiště. U velkých souborů tím ale zbytečně zatěžuješ a zdržuješ server. Lepší trik je presigned URL (předpodepsaná adresa):

Tvůj server vygeneruje dočasnou podepsanou adresu, na kterou smí klient nahrát přímo do úložiště, a pošle ji klientovi. Klient soubor pošle rovnou do úložiště (tvůj server se vyhne přenosu) a pak tvému serveru jen oznámí „hotovo", aby si uložil metadata. Stejný princip funguje i pro download.


Velké soubory: chunked a resumable upload

Nahrát 2GB video jedním požadavkem je křehké — když spojení v 90 % spadne, začínáš od nuly. Chunked upload soubor rozdělí na malé kousky (chunky) a nahrává je po jednom. Resumable (obnovitelný) upload navíc umí po výpadku pokračovat od posledního úspěšného kousku, ne od začátku. U velkých souborů to chceš.


Servírování souborů přes CDN

Když si soubor (obrázek, video) stahuje hodně lidí z celého světa, nepouštěj to z jednoho úložiště — dej před něj CDN (Caching), která drží kopie blízko uživatelům. Rychlejší a levnější. Pro neveřejné soubory se i ke stažení používá presigned URL s krátkou platností (jen kdo má odkaz a včas, soubor dostane).


Bezpečnost uploadu — tady se to nejčastěji láme

Uživatelský upload je vstup od cizího člověka, takže platí „nevěř ničemu" (Security):

  • „Ověř typ" znamená ověřit obsah, ne hlavičku. Content-Type ani příponu si určuje klient a triviálně je podvrhne. Kontroluj skutečný obsah (tzv. magic bytes — prvních pár bajtů prozradí reálný formát), a u obrázků je nejbezpečnější je znovu zakódovat (re-encode), čímž zahodíš schovaný škodlivý obsah.
  • Neservíruj user uploady z vlastní domény. Když pustíš HTML nebo SVG (umí v sobě JavaScript) na své doméně, je to XSS v cizím prohlížeči pod tvou doménou. Servíruj uploady z oddělené domény / s Content-Disposition: attachment.
  • U presigned uploadu zapeč limity do podpisu. Když klient nahrává přímo do úložiště (mimo server), kontroly velikosti a typu musí být součástí podepsané policy (např. content-length-range, povolený content-type) — jinak nahraje cokoli a ty se o tom dozvíš až po faktu.

Failure modes — jak to v praxi praská

  • Soubory v databázi nebo na disku serveru → nafouklá databáze, nebo soubory zmizí/nejsou vidět z jiných serverů.
  • Vše teče přes server → velké soubory zbytečně zatěžují a zdržují server (řeší presigned URL).
  • Upload bez limitů a kontrol → někdo nahraje 10GB soubor nebo spustitelný škodlivý soubor. Vždy omez velikost a ověř typ (Security).
  • Veřejné neveřejné soubory → citlivé soubory dostupné komukoli s odkazem; používej presigned URL s expirací.
  • Osiřelé soubory → smažeš řádek v databázi, ale soubor v úložišti zůstane (nebo naopak).

🛠️ Cvičení

  1. Kam to uložit? Pro profilovou fotku, fakturu v PDF a 1GB video rozhodni, kam soubor i jeho metadata uložíš, a zdůvodni.
  2. Presigned upload. Nakresli kroky, jak klient nahraje fotku přes presigned URL, aniž by soubor tekl přes tvůj server.
  3. Velký soubor. Spojení při nahrávání 1GB videa spadne v 80 %. Co udělá obyčejný upload a co resumable upload?
  4. Bezpečnost uploadu. Vyjmenuj tři kontroly, které u nahrávaného souboru uděláš (velikost, typ, …) a proč.
  5. Neveřejný soubor. Jak zařídíš, aby si fakturu mohl stáhnout jen její vlastník, a ne kdokoli s odkazem?
Náčrt řešení — rozbal, až si cvičení zkusíš sám
  1. Kam to uložit — pointa: profilovku, PDF i 1GB video ulož do object storage (S3/R2) a do databáze jen metadata + odkaz; je to levné, neomezené a dostupné ze všech serverů. Pozor: soubory nedávej do databáze (nafoukneš ji, zpomalíš zálohy) ani na disk serveru (z jiného serveru nejsou vidět a při výpadku zmizí).
  2. Presigned upload — pointa: klient si u serveru řekne o nahrání, dostane dočasnou podepsanou URL, soubor pošle rovnou do úložiště a serveru pak jen oznámí „hotovo" pro uložení metadat. Pozor: soubor neteče přes tvůj server, takže ho velké uploady nezatěžují ani nezdržují.
  3. Velký soubor — pointa: obyčejný upload po pádu v 80 % začíná od nuly, resumable upload soubor rozseká na chunky a po výpadku pokračuje od posledního úspěšného kousku. Pozor: u velkých souborů (videa) tím výrazně zvýšíš spolehlivost.
  4. Bezpečnost uploadu — pointa: omez velikost (proti 10GB souborům), ověř skutečný obsah přes magic bytes (ne Content-Type ani příponu, ty si klient určuje sám) a obrázky raději re-encode; uploady servíruj z oddělené domény / s Content-Disposition: attachment. Pozor: u presigned uploadu zapeč limity (velikost, typ) do podepsané policy, jinak klient nahraje cokoli.
  5. Neveřejný soubor — pointa: soubor nedávej veřejně přístupný — při stažení na serveru ověř vlastnictví a teprve pak vydej presigned URL s krátkou platností. Pozor: pouhá znalost adresy nesmí stačit (IDOR), proto je nutná kontrola práv plus expirace odkazu.

🧠 Otázky & odpovědi

Proč neukládat soubory do databáze?

Databáze je stavěná na malé strukturované řádky, ne na megabajtové binární soubory. Když do ní nacpeš soubory, nafoukneš ji, zpomalíš zálohy i dotazy. Soubory patří do object storage (S3/R2), které je na to navržené, a do databáze ulož jen metadata (jméno, velikost, vlastník) a odkaz na soubor v úložišti.

Proč neukládat soubory na disk serveru?

Na jednom serveru to funguje, ale jakmile máš serverů víc, soubor nahraný na jeden není vidět z ostatních — a když ten server spadne, o soubory přijdeš. Object storage je dostupné ze všech serverů a odolné. Disk serveru ber jako dočasný, ne jako trvalé úložiště.

Jak funguje presigned URL a proč ho použít?

Tvůj server vygeneruje dočasnou podepsanou adresu, na kterou smí klient nahrát soubor přímo do object storage, a pošle ji klientovi. Klient pak soubor pošle rovnou do úložiště, takže přenos neteče přes tvůj server (méně zátěže a zpoždění). Server si po dokončení jen uloží metadata. Stejně to jde použít i pro bezpečné stahování s krátkou platností odkazu.

K čemu je chunked a resumable upload?

Nahrát velký soubor jedním požadavkem je křehké — když spojení spadne, začínáš od nuly. Chunked upload soubor rozdělí na malé kousky a posílá je po jednom. Resumable navíc umí po výpadku pokračovat od posledního úspěšného kousku místo od začátku. U velkých souborů (videa) to výrazně zvýší spolehlivost.

Jak zařídíš, aby si neveřejný soubor stáhl jen oprávněný uživatel?

Soubor nedávej veřejně přístupný. Při stažení na serveru ověř, že na něj má uživatel právo (vlastnictví, viz Security a IDOR), a pak mu vydej presigned URL s krátkou platností. Jen kdo má oprávnění a stihne to včas, soubor dostane; pouhá znalost adresy nestačí.