Preskoči na sadržaj
Blog

Vaši uploadi ne bi smjeli dodirivati vaš server

Provlačenje uploada fajlova kroz backend košta memoriju, propusni opseg i veću instancu — i to je pojedinačno najskuplja podrazumijevana odluka u većini Node aplikacija.

Infrastruktura5 min čitanja

Pridružili smo se državnom projektu identiteta i prvo što smo uradili bilo je da pročitamo račun za oblak. Bio je osam puta veći nego što je trebalo, iz tri odvojena razloga. Ovaj tekst je o trećem, jer je to onaj koji skoro svi pogriješe i onaj koji najgore skalira.

Svaki zahtjev u tom sistemu bio je upload. Fotografije lica, fotografije dokumenata, biometrija snimljena na terenu. I svaki od tih fajlova putovao je kroz backend na putu do skladišta.

Šta provlačenje kroz backend stvarno košta

Obrazac izgleda bezopasno. Fajl stiže na vaš server, middleware ga baferuje, vaš kod ga validira, i onda ga gurnete u objektno skladište:

app.post('/upload', upload.single('file'), async (req, res) => {
  await s3.putObject({
    Bucket: BUCKET,
    Key: key(req),
    Body: req.file.buffer,
  })
  res.json({ ok: true })
})

Dešavaju se tri stvari koje se na malom deploymentu ne vide.

Svaki bajt zauzima memoriju vaše instance. Fotografija od 4 MB je 4 MB RAM-a za trajanje zahtjeva. Pedeset istovremenih uploada je 200 MB koji ne rade ništa osim što čekaju. Instancu dimenzionirate prema vršnoj istovremenosti uploada, a ne prema poslu koji vaša aplikacija stvarno radi.

Svaki bajt prelazi mrežu dvaput. Od klijenta do servera, pa od servera do skladišta. Plaćate prenos koji nije imao razloga da postoji, i plaćate ga ponovo kroz latenciju, jer klijentov upload nije gotov dok upload vašeg servera nije gotov.

Vaš plafon propusnosti je vaša instanca, a ne vaše skladište. Objektno skladište će primiti daleko više istovremenih upisa nego ijedan pojedinačni aplikacioni server. Stavljanjem servera u sredinu zamijenili ste servis napravljen za ovo onim koji nije.

Ništa od ovoga se ne pojavljuje kao greška. Pojavljuje se kao veća instanca, sporiji upload, i račun za koji pretpostavljate da je jednostavno koliko stvari koštaju.

Alternativa

Klijent uploaduje direktno u skladište. Vaš server nikad ne vidi bajtove — samo odlučuje smije li se upload desiti, i potpisuje URL koji dopušta tačno tu jednu stvar.

// server: autorizuj, pa potpiši
app.post('/upload-url', requireAuth, async (req, res) => {
  const key = `applications/${req.user.id}/${randomUUID()}`

  const url = await getSignedUrl(
    s3,
    new PutObjectCommand({
      Bucket: BUCKET,
      Key: key,
      ContentType: req.body.contentType,
    }),
    { expiresIn: 300 },
  )

  res.json({ url, key })
})
// klijent: upload pravo u skladište
const { url, key } = await api.post('/upload-url', {
  contentType: file.type,
})

await fetch(url, {
  method: 'PUT',
  body: file,
  headers: { 'Content-Type': file.type },
})

await api.post('/applications', { photoKey: key })

Server je uradio tri male stvari: potvrdio ko traži, odlučio gdje fajl smije ići, i postavio kratak rok isteka. Premjestio je nula bajtova.

Dijelovi koje ljudi pogriješe

Validacija. Izgubili ste mogućnost da pregledate fajl prije nego slegne, pa validirajte šta možete prije potpisivanja — content type, prijavljenu veličinu i ko traži — a ostatak validirajte nakon što objekt postoji. U praksi to znači korak koji pročita pohranjeni objekt i odbije ga ako nije ono za šta se predstavio.

Niko serveru nije rekao da je upload gotov. Klijent vam kaže, a klijenti lažu ili nestanu. Ili potvrdite da objekt postoji prije nego zapis proglasite potpunim, ili se pretplatite na događaje skladišta i pustite zapis da se sam dovrši.

Siročad. Potpisani URL-ovi koji su iskorišteni ali nikad potvrđeni ostavljaju objekte koje niko ne referencira. Lifecycle pravilo koje nereferencirane objekte u prefiksu pušta da isteknu ne košta ništa i štedi vas od toga da za njih saznate godinu dana kasnije.

Rok isteka. Pet minuta je obično tačno. Dovoljno dugo za sporu vezu, dovoljno kratko da procureli URL ne vrijedi ništa do trenutka kad ga neko nađe.

Usko ograničite dozvolu. Potpišite za jedan ključ, jednu metodu, jedan content type. Potpisani URL je ovlaštenje — tako ga i tretirajte.

Šta je to promijenilo

Na tom projektu ta izmjena je bila jedna od tri, a tri zajedno su mjesečni račun spustile sa 24.000 na 2.000 dolara. Ova je bila najvažnija zbog toga šta je proizvod bio: u sistemu u kojem je svaki zahtjev upload, putanja uploada jeste zahtjev.

Instance su postale manje jer više nisu morale držati fajlove. Uploadi su postali brži jer su prestali praviti dva putovanja. A plafon se premjestio sa nečega što smo vodili mi na nešto što vodi Amazon.

Kad je provlačenje kroz server i dalje ispravno

Ne baš nikad. Ako fajl morate transformisati prije nego bude pohranjen — promijeniti veličinu, transkodirati, skinuti metapodatke, skenirati na malver prije nego igdje postoji — onda bajtovi moraju stići do nečega što taj posao može obaviti.

Ali to je argument za worker koji naknadno povlači iz skladišta, a ne za stavljanje vašeg API servera na putanju svakog bajta. I vrijedi provjeriti da li vam to stvarno treba, ili to radite zato što je tako radio tutorijal.

Ovo je proizašlo iz gradnje nacionalnog sistema za izdavanje ličnih karata i pasoša, gdje je isti pregled našao i prekomjerno dimenzioniranje i beskonačne petlje. Pročitajte studiju →

Srodno
Cijeli blog →

Plaćate putanju uploada koju niste birali?

Pregled infrastrukture sa tri do pet konkretnih nalaza, bez obaveze. Odgovaramo u roku od jednog radnog dana.

Kontaktirajte nas

Sistemi koji ne smiju stati — od arhitekture do produkcije.

© 2026 Micro Tech, Sarajevo