Onverwachte egresskosten: waar bandwidthfacturen vandaan komen
Onverwachte egresskosten zijn meestal terug te voeren op vier gewoonten: media vanuit de app serveren, geen cacheheaders gebruiken, chatty API's en verkeer tussen regio's. Vind de oorzaak en verlaag de factuur zonder van provider te wisselen.
De factuur komt binnen en is vier keer zo hoog als verwacht. De regel voor compute komt ongeveer overeen met je begroting. De regel die daarvan afwijkt, is egress — de bytes die je infrastructuur verlaten — en de kans is groot dat je hier nog nooit bij hebt stilgestaan.
Onverwachte egresskosten zijn de meest voorkomende verrassing op facturen voor application hosting, en de reden is structureel: niets in je developmentloop meet bytes. Je laptop brengt ze niet in rekening. Staging heeft geen gebruikers. De eerste keer dat bandwidth zichtbaar wordt, is op een factuur — en tegen die tijd heb je de gewoonte die de kosten veroorzaakt al uitgerold.
Dit is waar het daadwerkelijk vandaan komt, in volgorde van hoe vaak het de oorzaak is.
1. Media vanuit de applicatie serveren
Deze oorzaak is veel groter dan de andere en vrijwel altijd onbedoeld.
Je zet een video in /public om iets te demonstreren. Het werkt, dus je laat het staan. Elke pageview streamt dat bestand nu vanuit je container. Eén video van 40 MB op een pagina met duizend bezoekers per maand is 40 GB — afkomstig van een bestand dat je als placeholder beschouwde.
Hetzelfde geldt voor productafbeeldingen, PDF's, user uploads en fontbestanden. Wanneer je ze toevoegt, voelen geen van deze zaken als infrastructuurbeslissingen.
De oplossing is niet ingewikkeld: statische media hoort in object storage achter een CDN, niet in het filesystem van je applicatie. Je app hoort HTML en JSON te serveren. Zodra de app aan elke bezoeker een hero-afbeelding van 4 MB serveert, doet hij het werk van een CDN, maar zonder de economische voordelen van een CDN.
2. Geen cacheheaders
Als je responses geen cacheheaders bevatten, downloadt elke bezoeker elk asset bij elke page load. Terugkerende bezoekers downloaden alles opnieuw. Een crawler downloadt dezelfde bestanden tientallen keren per dag.
# 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
Dit is een wijziging van één regel per responseklasse die doorgaans het grootste deel van een bandwidthfactuur wegneemt, omdat het verkeer dat je elimineert pure herhaling is.
3. API's die meer retourneren dan de client gebruikt
Een list endpoint dat volledige objecten retourneert terwijl de UI drie velden rendert, verstuurt het verschil bij elke request, voor altijd. Het komt nooit naar voren in profiling omdat het snel is — het is alleen groot.
Controleer twee dingen op je drukst gebruikte endpoints:
- Staat compressie aan?
Content-Encoding: gzipofbrop JSON verkleint de response doorgaans met 70–80%. Het is één regel middleware en ontbreekt vaak. - Verstuur je velden die niemand uitleest? De volledige row serialiseren omdat de ORM dat eenvoudig maakt, is het standaardgedrag van de meeste codebases.
Een pollingclient maakt beide problemen erger met een factor die gelijk is aan het aantal keer per minuut dat de client pollt.
4. Verkeer tussen regio's en services
Als je app in de ene regio staat en je database in een andere, doorkruist elk queryresultaat een betaalde grens. Dat gebeurt gemakkelijk per ongeluk — je maakt eerst de database aan, kiest zonder erbij na te denken een regio en plaatst de app weken later ergens anders.
Verkeer tussen je eigen services kan ook als egress worden aangerekend als het het private network verlaat en via een public hostname terugkomt. Twee services in dezelfde workspace die via hun public URLs met elkaar praten, betalen twee keer voor een gesprek dat nooit naar buiten had hoeven gaan.
Op Dockup is deze specifieke fout moeilijk te maken, omdat services elkaar via interne namen aanspreken. Een service bereikt main-db.internal:5432 en een andere service via de alias <slug>.internal — verkeer dat binnen het workspace-netwerk blijft, raakt nooit een public listener. Daarom heeft de database standaard ook geen public hostname: er is niets waarlangs je per ongeluk verkeer kunt routeren.
De oorzaak vinden in plaats van gokken
Gokken welke van de vier oorzaken speelt, kost je een factuurcyclus. Meet het in plaats daarvan:
Lees het accesslog op basis van bytes, niet op basis van aantallen. Sorteer je belangrijkste responses op het totale aantal verzonden bytes in plaats van op het aantal requests. Het endpoint dat de meeste data verstuurt, is zelden het endpoint dat het vaakst wordt aangeroepen.
Controleer je grootste statische assets. Als er iets van meer dan een megabyte vanuit je app wordt geserveerd, verplaats dat dan als eerste.
# 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
Let op crawlers. Een slecht functionerende bot die grote bestanden herhaaldelijk ophaalt, kan het grootste deel van de egress van een maand veroorzaken op een site met weinig menselijk verkeer. robots.txt en een noindex op previewomgevingen zijn goedkope maatregelen.
De omgevingen die niemand meetelt
Twee gewoonten genereren egress die in niemands mentale model voorkomt:
Previewomgevingen. Een preview per pull request is echt nuttig en vergroot je footprint stilletjes. Vijf open PR's betekent vijf kopieën van je stack, elk met eigen verkeer — vaak afkomstig van dezelfde crawlers, omdat preview-URL's worden geïndexeerd als je dat niet voorkomt.
Stagingomgevingen die lang blijven bestaan. Staging die al acht maanden draait, waar niemand komt, haalt nog steeds images op, wordt nog steeds gecrawld en voert nog steeds cronjobs uit.
Geen van beide is een reden om ze niet te gebruiken. Het is wel een reden om grenzen in te stellen: laat previews verlopen wanneer de PR wordt gesloten en markeer ze als noindex, zodat zoekmachines ze niet langer namens jou downloaden.
dockup noindex my-project/my-api --on
Hoe goede facturatie eruitziet
Je moet drie vragen kunnen beantwoorden voordat de factuur binnenkomt:
- Wat draait er momenteel? Ook de dingen die je vergeten bent.
- Wat heeft elk onderdeel verbruikt? Uitgesplitst in compute en transfer, niet als één getal.
- Wat gebeurt er als het verbruik piekt? Een limiet die services stopt is vervelend. Geen limiet is erger.
Dockup meet infrastructuur afzonderlijk van het plan, zodat de planaan niet het verbruik verbergt — en het tegoed van $20 van het Pro-plan wordt verrekend met dat verbruik in plaats van als korting op de prijs te worden toegepast. Het punt is dat je beide onderdelen afzonderlijk kunt zien.
De korte versie
Verplaats media naar object storage. Stel cacheheaders in. Schakel compressie in. Houd services en hun databases in één regio en laat ze via het private network communiceren in plaats van via het publieke internet. Laat previewomgevingen verlopen en houd ze uit zoekresultaten.
Deze vijf gewoonten lossen vrijwel elke onverwachte bandwidthfactuur op en vereisen geen wijziging van je hostinglocatie.
Veelgestelde vragen
Wat is egress? Data die je infrastructuur verlaat richting het internet. Responses aan gebruikers, gedownloade bestanden en API-payloads. Inkomend verkeer is meestal gratis; uitgaand verkeer meestal niet.
Waarom is mijn egressfactuur hoger dan mijn computefactuur? Vrijwel altijd omdat de applicatie bestanden serveert die op een CDN thuishoren. Eén groot asset op een populaire pagina kost meer dan weken aan requestafhandeling.
Verlaagt een CDN dit daadwerkelijk? Ja, wanneer het bestanden cached. De origin serveert het bestand één keer per edge in plaats van één keer per bezoeker, en de prijs per gigabyte van een CDN is doorgaans een fractie van die van een application platform.
Kost een previewomgeving evenveel als production? Ze kosten wat ze verbruiken, en dat is vaak meer dan verwacht omdat elke omgeving een volledige kopie is en preview-URL's worden gecrawld als je dat niet voorkomt.
