JournalindeksDockup / feltnote
Note / unexpected-egress-charges

Uventede egress-gebyrer: Hvor båndbreddeomkostningerne kommer fra

Uventede egress-gebyrer skyldes som regel fire vaner: at levere media fra appen, manglende cache headers, snakkende API'er og trafik på tværs af regioner. Find kilden, og skær regningen ned uden at skifte udbyder.

Regningen kommer, og den er fire gange højere end forventet. Compute-linjen svarer omtrent til det, du budgetterede med. Det gør den anden linje ikke: egress — de bytes, der forlader din infrastruktur — og der er en reel chance for, at du aldrig har tænkt over det.

Uventede egress-gebyrer er den mest almindelige overraskelse på regningen ved hosting af applikationer, og årsagen er strukturel: Intet i din udviklingsproces måler bytes. Din laptop tager ikke betaling for dem. Staging har ingen brugere. Første gang båndbredde bliver synlig, er på en faktura, og på det tidspunkt har du allerede indført den vane, der forårsagede det.

Her er, hvor det faktisk kommer fra, sorteret efter hvor ofte det er svaret.

1. Levering af media fra applikationen

Denne årsag overskygger de andre, og det er næsten altid utilsigtet.

Du lægger en video i /public for at demonstrere noget. Det virker, så den bliver liggende. Nu streamer hver sidevisning filen fra din container. Én video på 40 MB på en side med tusind besøgende om måneden svarer til 40 GB — fra en fil, du opfattede som en placeholder.

Det samme gælder produktbilleder, PDF-filer, brugeruploads og fontfiler. Ingen af dem føles som infrastrukturvalg, når du tilføjer dem.

Løsningen er ikke avanceret: Statisk media hører hjemme i object storage bag en CDN, ikke i din applikations filsystem. Din app bør levere HTML og JSON. I det øjeblik den leverer et hero-billede på 4 MB til hver besøgende, udfører den en CDN's arbejde uden nogen af en CDN's økonomiske fordele.

2. Ingen cache headers

Hvis dine svar ikke har cache headers, downloader hver besøgende alle assets ved hver sideindlæsning. Tilbagevendende besøgende downloader det hele igen. En crawler downloader det dusinvis af gange om dagen.

# Fingerprinted build assets never change — cache them for a year
Cache-Control: public, max-age=31536000, immutable

# HTML changes — revalidate but allow a short window
Cache-Control: public, max-age=0, must-revalidate

# Anything user-specific
Cache-Control: private, no-store

Det er en ændring på én linje pr. responseklasse, som ofte fjerner størstedelen af en båndbreddeomkostning, fordi den trafik, du eliminerer, udelukkende er gentagelser.

3. API'er, der returnerer mere, end klienten bruger

Et liste-endpoint, der returnerer komplette objekter, selvom brugergrænsefladen viser tre felter, sender forskellen med ved hver request, for altid. Det dukker aldrig op i profiling, fordi det er hurtigt — det er bare stort.

Der er to ting, du bør kontrollere på dine travleste endpoints:

  • Er compression slået til? Content-Encoding: gzip eller br på JSON reducerer typisk størrelsen med 70–80 %. Det kræver én middleware-linje, og den mangler ofte.
  • Sender du felter, som ingen læser? At serialisere hele rækken, fordi ORM'en gjorde det nemt, er standardadfærden i de fleste codebases.

En polling-klient gør begge dele værre med en faktor svarende til, hvor mange gange i minuttet den poller.

4. Trafik på tværs af regioner og services

Hvis din app ligger i én region, og din database ligger i en anden, krydser hvert query-resultat en betalingsgrænse. Det er nemt at gøre ved et uheld — du opretter databasen først, vælger en region uden at tænke over det og placerer appen et andet sted flere uger senere.

Trafik mellem dine egne services kan også være egress, hvis den forlader det private netværk og kommer tilbage via et offentligt hostname. To services i det samme workspace, der taler sammen via deres offentlige URL'er, betaler altså to gange for en samtale, der aldrig behøvede at forlade netværket.

På Dockup er det svært at begå netop denne fejl, fordi services adresserer hinanden via interne navne. En service forbinder til main-db.internal:5432, og en anden service forbindes via dens <slug>.internal-alias — trafik, der bliver på workspace-netværket, rammer aldrig en offentlig listener. Det er også grunden til, at databasen ikke har et offentligt hostname som standard: Der er ikke noget, der ved et uheld kan routes igennem.

Find kilden i stedet for at gætte

Det spilder en faktureringsperiode at gætte på, hvilken af de fire årsager det er. Mål i stedet:

Læs access-loggen efter bytes, ikke antal requests. Sortér dine vigtigste responses efter det samlede antal leverede bytes i stedet for efter antal requests. Det endpoint, der sender mest data, er sjældent det, der kaldes oftest.

Kontrollér dine største statiske assets. Hvis noget på over en megabyte leveres fra din app, er det det første, du bør flytte.

# What is the service actually doing right now
dockup metrics my-project/my-api --json

# And what is it logging
dockup logs my-project/my-api -n 1000

Hold øje med crawlere. En bot, der opfører sig uhensigtsmæssigt og gentagne gange henter store filer, kan stå for størstedelen af en måneds egress på et website med moderat menneskelig trafik. robots.txt og en noindex på preview-miljøer er billige løsninger.

De miljøer, ingen tæller med

To vaner genererer egress, som aldrig indgår i nogens mentale model:

Preview-miljøer. Et preview pr. pull request er virkelig nyttigt og fordobler ubemærket dit footprint. Fem åbne PR'er er fem kopier af din stack, hver med sin egen trafik — ofte fra de samme crawlere, fordi preview-URL'er bliver indekseret, medmindre du stopper dem.

Langtidsholdbart staging. Staging, der har kørt i otte måneder, og som ingen besøger, henter stadig images, bliver stadig crawlet og kører stadig sine cron-jobs.

Ingen af delene er en grund til at holde op med at bruge dem. Begge dele er en grund til at sætte grænser: Udløb previews, når PR'en lukkes, og markér dem med noindex, så søgemaskiner holder op med at downloade dem på dine vegne.

dockup noindex my-project/my-api --on

Sådan ser god fakturering ud

Du bør kunne svare på tre spørgsmål, før fakturaen kommer:

  1. Hvad kører der lige nu? Også det, du har glemt.
  2. Hvad har hver del brugt? Opdelt i compute og transfer, ikke som ét samlet tal.
  3. Hvad sker der, hvis forbruget stiger voldsomt? En grænse, der stopper services, er ubehagelig. En grænse, der ikke findes, er værre.

Dockup måler infrastruktur separat fra abonnementet, så abonnementsprisen ikke skjuler forbruget — og Pro-planens kredit på $20 modregnes i forbruget i stedet for at være en rabat på abonnementsprisen. Pointen er, at du kan se, hvad der er hvad.

Den korte version

Flyt media til object storage. Sæt cache headers. Slå compression til. Hold services og deres databaser i samme region, og lad dem kommunikere via det private netværk i stedet for det offentlige internet. Udløb preview-miljøer, og hold dem ude af søgeresultaterne.

De fem vaner håndterer næsten alle uventede båndbreddeomkostninger, og ingen af dem kræver, at du ændrer, hvor du hoster.

Ofte stillede spørgsmål

Hvad er egress? Data, der forlader din infrastruktur på vej mod internettet. Svar til brugere, downloadede filer og API-payloads. Indgående trafik er som regel gratis; udgående trafik er som regel ikke.

Hvorfor er min egress-regning højere end min compute-regning? Næsten altid fordi applikationen leverer filer, der hører hjemme på en CDN. Ét stort asset på en populær side kan koste mere end flere ugers request-håndtering.

Reducerer en CDN det faktisk? Ja, når den cacher. Origin leverer filen én gang pr. edge i stedet for én gang pr. besøgende, og CDN'ens pris pr. gigabyte er typisk en brøkdel af prisen på en application platform.

Koster preview-miljøer lige så meget som production? De koster det, de bruger, hvilket ofte er mere end forventet, fordi hvert miljø er en komplet kopi, og preview-URL'er bliver crawlet, medmindre du forhindrer det.