Индекс журналаDockup / заметка с места
Note / unexpected-egress-charges

Неожиданные расходы на egress: откуда берутся счета за пропускную способность

Неожиданные расходы на egress обычно связаны с четырьмя привычками: раздачей медиафайлов из приложения, отсутствием cache headers, избыточно разговорчивыми API и межрегиональным трафиком. Найдите источник и сократите счёт без смены провайдера.

Счёт приходит, и он в четыре раза больше ожидаемого. Строка с compute примерно соответствует бюджету. А вот другая — нет: egress, то есть байты, покидающие вашу инфраструктуру. И вполне возможно, что раньше вы вообще об этом не задумывались.

Неожиданные расходы на egress — самый частый сюрприз в биллинге при hosting приложений. Причина системная: в вашем цикле разработки ничто не измеряет объём переданных данных. Ноутбук не выставляет за них счёт. В staging нет пользователей. Впервые пропускная способность становится видимой в инвойсе — но к этому моменту привычка, которая привела к расходам, уже закрепилась в продакшене.

Вот откуда они на самом деле берутся — в порядке частоты.

1. Раздача медиафайлов из приложения

Этот пункт намного важнее остальных, и почти всегда всё происходит случайно.

Вы кладёте видео в /public, чтобы что-то продемонстрировать. Оно работает, поэтому остаётся там. Теперь при каждом просмотре страницы этот файл передаётся из вашего контейнера. Одно видео размером 40 MB на странице с тысячей посетителей в месяц — это 40 GB трафика из-за файла, который вы считали временной заглушкой.

То же самое касается изображений товаров, PDF-файлов, пользовательских загрузок и файлов шрифтов. Когда вы добавляете их, ни один из этих элементов не воспринимается как инфраструктурное решение.

Решение не требует хитростей: статические медиафайлы должны храниться в object storage за CDN, а не в файловой системе приложения. Приложение должно раздавать HTML и JSON. Как только оно начинает отправлять каждому посетителю изображение для hero-блока размером 4 MB, оно выполняет работу CDN, но не получает ни одного из экономических преимуществ CDN.

2. Отсутствие cache headers

Если в ответах нет cache headers, каждый посетитель скачивает все assets при каждой загрузке страницы. Вернувшиеся пользователи скачивают их снова. Crawler загружает их десятки раз в день.

# 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

Это изменение в одну строку для каждого класса ответов, которое регулярно убирает большую часть счёта за пропускную способность: устранённый трафик — это чистое повторное скачивание.

3. API, возвращающие больше данных, чем использует клиент

Endpoint со списком, который возвращает полные объекты, хотя UI отображает три поля, отправляет разницу при каждом запросе — постоянно. В profiling это не проявляется, потому что endpoint работает быстро: он просто передаёт много данных.

На самых загруженных endpoints проверьте две вещи:

  • Включено ли сжатие? Content-Encoding: gzip или br для JSON обычно сокращает объём на 70–80%. Это одна строка в middleware, но её часто не хватает.
  • Отправляете ли вы поля, которые никто не читает? Сериализация всей строки только потому, что ORM упростил эту задачу, — стандартное поведение большинства codebase.

Polling-клиент усиливает обе проблемы во столько раз, сколько раз в минуту выполняется polling.

4. Межрегиональный трафик и трафик между сервисами

Если приложение находится в одном регионе, а база данных — в другом, результат каждого запроса пересекает платную границу. Так легко сделать случайно: сначала создать базу данных, выбрать регион не задумываясь, а через несколько недель разместить приложение в другом месте.

Трафик между вашими собственными сервисами тоже может считаться egress, если он покидает private network, а затем возвращается через public hostname. Если два сервиса в одном workspace общаются через public URLs, вы дважды платите за разговор, которому вообще не требовалось выходить наружу.

В Dockup такую конкретную ошибку допустить сложно, потому что сервисы обращаются друг к другу по internal name. Один сервис подключается к main-db.internal:5432, а к другому — по его alias <slug>.internal. Трафик, который остаётся внутри workspace network, никогда не обращается к public listener. Поэтому база данных по умолчанию не получает public hostname: случайно направить трафик через него невозможно.

Как найти источник, а не гадать

Попытка угадать, какой из четырёх пунктов стал причиной, отнимет целый биллинговый цикл. Вместо этого измерьте:

Изучайте access log по количеству байт, а не запросов. Отсортируйте самые частые ответы по общему объёму переданных данных, а не по числу запросов. Endpoint, который отправляет больше всего данных, редко оказывается самым часто вызываемым.

Проверьте самые крупные статические assets. Если приложение раздаёт что-либо размером больше мегабайта, это первое, что нужно перенести.

# 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

Следите за crawler. Плохо работающий bot, который снова и снова скачивает большие файлы, может составлять большую часть месячного egress на сайте с умеренным трафиком от людей. robots.txt и noindex для preview environments стоят дёшево.

Окружения, которые никто не учитывает

Две привычки создают egress, который не учитывается ни в чьей ментальной модели:

Preview environments. Preview для каждого pull request действительно полезен, но незаметно увеличивает ваш footprint. Пять открытых PR — это пять копий вашего stack, каждая со своим трафиком. Часто трафик создают одни и те же crawler, потому что preview URLs индексируются, если им это не запретить.

Долгоживущий staging. Staging, который работает уже восемь месяцев и которым никто не пользуется, всё ещё загружает images, всё ещё попадает в обход crawler и всё ещё запускает cron jobs.

Ни то ни другое не означает, что от них нужно отказаться. Но для обоих случаев нужны ограничения: удаляйте preview после закрытия PR и добавляйте noindex, чтобы search engines перестали скачивать их за вас.

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

Как выглядит хороший биллинг

До получения счёта вы должны уметь ответить на три вопроса:

  1. Что сейчас запущено? Включая то, о чём вы забыли.
  2. Сколько потребляет каждый компонент? Отдельно compute и transfer, а не одной общей цифрой.
  3. Что произойдёт при скачке нагрузки? Лимит, который останавливает сервисы, неприятен. Лимит, которого нет, ещё хуже.

Dockup учитывает инфраструктуру отдельно от стоимости плана, поэтому плата за план не скрывает фактическое использование. Кредит $20 в Pro plan списывается в счёт этого использования, а не предоставляется как скидка на стоимость плана. Главное — вы видите, какая часть расходов за что отвечает.

Кратко

Перенесите медиафайлы в object storage. Настройте cache headers. Включите compression. Держите сервисы и их базы данных в одном регионе и позволяйте им общаться через private network, а не через public internet. Удаляйте preview environments после завершения работы и исключайте их из search results.

Эти пять привычек устраняют почти все неожиданные счета за пропускную способность, и ни одна из них не требует менять место размещения приложения.

Часто задаваемые вопросы

Что такое egress? Данные, которые покидают вашу инфраструктуру и направляются в интернет: ответы пользователям, скачиваемые файлы и payloads API. Входящий трафик обычно бесплатен, а исходящий — нет.

Почему мой счёт за egress выше счёта за compute? Почти всегда потому, что приложение раздаёт файлы, которым место на CDN. Один большой asset на популярной странице может стоить больше, чем несколько недель обработки запросов.

Действительно ли CDN снижает расходы? Да, если он кэширует данные. Origin отдаёт файл один раз для каждого edge, а не один раз для каждого посетителя. Кроме того, цена CDN за гигабайт обычно составляет лишь малую долю стоимости application platform.

Стоят ли preview environments столько же, сколько production? Они стоят столько, сколько потребляют. Часто это больше ожидаемого, потому что каждое окружение представляет собой полную копию stack, а preview URLs индексируются crawler, если это не запретить.