Індекс журналуDockup / польова нотатка
Note / unexpected-egress-charges

Неочікувані витрати на egress: звідки беруться рахунки за bandwidth

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

Рахунок приходить у чотири рази більший, ніж ви очікували. Витрати на compute приблизно відповідають бюджету. Але є рядок, який не відповідає, — egress, тобто байти, що залишають вашу інфраструктуру. І цілком можливо, що ви досі жодного разу про це не замислювалися.

Неочікувані витрати на egress — найпоширеніший сюрприз у рахунках за хостинг застосунків, і причина має структурний характер: у вашому циклі розробки ніщо не вимірює кількість байтів. Ваш ноутбук не виставляє за них рахунок. На staging немає користувачів. Уперше bandwidth стає помітним у рахунку — а на той момент ви вже розгорнули звичку, яка спричинила ці витрати.

Ось звідки вони насправді беруться — у порядку від найпоширенішої причини.

1. Роздавання медіа із застосунку

Ця причина з великим відривом перевершує всі інші й майже завжди виникає випадково.

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

Те саме стосується зображень продукту, PDF-файлів, завантажень користувачів і файлів шрифтів. Додаючи їх, ви не сприймаєте це як інфраструктурні рішення.

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

2. Відсутність cache headers

Якщо у ваших відповідях немає cache headers, кожен відвідувач завантажує кожен asset під час кожного завантаження сторінки. Повторні відвідувачі завантажують усе знову. 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

Це зміна в один рядок для кожного класу відповідей, яка регулярно прибирає більшу частину рахунку за bandwidth, адже усунутий нею трафік — це чисте повторне передавання.

3. API, які повертають більше, ніж використовує клієнт

Endpoint списку, який повертає повні об’єкти, хоча UI відображає лише три поля, надсилає зайві дані з кожним запитом — постійно. У profiling це ніколи не помітно, бо все працює швидко: відповідь просто завелика.

Для найзавантаженіших endpoint перевірте дві речі:

  • Чи ввімкнено compression? Content-Encoding: gzip або br для JSON зазвичай зменшує його розмір на 70–80%. Це один рядок у middleware, якого часто бракує.
  • Чи надсилаєте ви поля, які ніхто не читає? Серіалізація всього рядка лише тому, що ORM спростив це завдання, — стандартна поведінка більшості codebase.

Polling-клієнт погіршує обидві проблеми в стільки разів, скільки разів на хвилину він виконує polling.

4. Трафік між регіонами та сервісами

Якщо застосунок розташований в одному регіоні, а база даних — в іншому, кожен результат запиту перетинає платну межу. Таке легко зробити випадково: спочатку створити базу даних, не замислюючись вибрати регіон, а через кілька тижнів розмістити застосунок в іншому місці.

Трафік між вашими власними сервісами також може рахуватися як egress, якщо він залишає private network, а потім повертається через public hostname. Два сервіси в одному workspace, які спілкуються через свої public URL, двічі платять за розмову, якій ніколи не потрібно було виходити назовні.

У Dockup цю конкретну помилку важко зробити, оскільки сервіси звертаються один до одного за внутрішніми іменами. Один сервіс доступний за адресою main-db.internal:5432, а інший — через його alias <slug>.internal; трафік, який залишається всередині мережі workspace, ніколи не звертається до 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 URL індексуються, якщо їх не заблокувати.

Довгоживучий staging. Staging, який працює вісім місяців і який ніхто не відвідує, усе одно завантажує images, потрапляє до crawler і запускає свої cron jobs.

Жодна з цих причин не означає, що потрібно відмовитися від таких середовищ. Але обидві означають, що їх потрібно обмежити: видаляти preview після закриття PR і позначати їх як noindex, щоб пошукові системи припинили завантажувати їх від вашого імені.

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

Як виглядає хороший білінг

Ви маєте вміти відповісти на три запитання ще до отримання рахунку:

  1. Що зараз працює? Зокрема те, про що ви забули.
  2. Скільки спожив кожен компонент? Окремо compute і transfer, а не одним числом.
  3. Що станеться, якщо навантаження різко зросте? Ліміт, який зупиняє сервіси, неприємний. Ліміт, якого не існує, — ще гірший.

Dockup обліковує інфраструктуру окремо від плану, тому вартість плану не приховує фактичне використання, а кредит $20 у плані Pro зараховується в рахунок цього використання, а не є знижкою на вартість плану. Важливо, що ви можете бачити, з чого складається кожна половина рахунку.

Коротко

Перенесіть медіа в object storage. Налаштуйте cache headers. Увімкніть compression. Розміщуйте сервіси та їхні бази даних в одному регіоні й дозвольте їм взаємодіяти через private network, а не через public internet. Видаляйте preview environments і не допускайте їх до результатів пошуку.

Ці п’ять звичок усувають майже всі сюрпризи з рахунками за bandwidth, і для цього не потрібно змінювати місце хостингу.

Поширені запитання

Що таке egress? Дані, які залишають вашу інфраструктуру та прямують до internet. Це відповіді користувачам, завантажені файли й API payloads. Вхідний трафік зазвичай безкоштовний, а вихідний — зазвичай ні.

Чому мій рахунок за egress вищий за рахунок за compute? Майже завжди тому, що застосунок роздає файли, які мають зберігатися на CDN. Один великий asset на популярній сторінці може коштувати більше, ніж тижні обробки запитів.

Чи справді CDN зменшує такі витрати? Так, якщо він кешує дані. Origin роздає файл один раз для кожного edge, а не один раз для кожного відвідувача, і ціна CDN за гігабайт зазвичай становить лише частину ціни application platform.

Чи коштують preview environments стільки ж, як production? Вони коштують відповідно до фактичного споживання, яке часто виявляється більшим, ніж очікується, оскільки кожне середовище є повною копією, а preview URL індексуються crawler, якщо цьому не перешкодити.