{ "index": 116, "slug": "editorial-2024-10-mechanism-capacity-cost", "title": "Почему низкая загрузка не означает низкую стоимость", "excerpt": "Процент CPU показывает использование выбранного ресурса, но не цену. Разбираем fixed и variable части, quota и limit, saturation и способ проверить вывод до изменения capacity.", "contentHtml": "
График показывает CPU 31%. p95 равен 208 ms при SLO 300 ms. Команда делает вывод: инстанс слишком большой, его можно уменьшить. Через неделю тот же график используют как доказательство экономии. Но в расчёте нет базовой ставки, единицы тарификации, memory allocation и правила quota. Ошибка стоит дороже одного неверного числа: можно получить очередь, нарушить SLO или принять учебную арифметику за счёт провайдера.
\nТезис простой: utilisation, capacity и cost отвечают на разные вопросы. Низкий процент означает только, что наблюдаемая нагрузка мала относительно выбранной базы. Цена зависит от формулы, периода, fixed части и единицы расчёта. Quota ограничивает допустимый объём. Limit задаёт верхнюю границу ресурса. Saturation показывает, что система приближается к отказу. Эти величины связаны, но ни одна не заменяет другую.
\nСначала запишите факт без вывода. Например: «CPU 31%, p95 208 ms, model cost 17,76 u/24h». Это одна строка наблюдений, а не рекомендация. Затем разделите вопросы. Почему latency хорошая? Сколько ресурса зарезервировано? Какая часть формулы фиксирована? Какую единицу умножает тариф? Есть ли stop condition для следующего изменения?
\nТакой порядок защищает от короткой, но неверной стрелки «низкая загрузка → уменьшить инстанс». Usage измеряют относительно allocation. Allocation может влиять на модель стоимости. Saturation зависит от нагрузки и запаса latency. Между наблюдением и действием лежат ещё resource policy, billing expression и граница риска.
\nObserved utilisation — измерение использования относительно выбранного ресурса. CPU 31% не говорит, был ли выбранный request разумным и сколько стоит час. Fixed resource — постоянная часть учебной формулы, например base 0,36 u/h. Она остаётся в scope, пока существует сама модель.
\nVariable resource — часть, которая меняется с allocation. В примере это CPU request и memory request, умноженные на учебные ставки за core-hour и GiB-hour. Billing unit — единица, к которой относится формула: час, GiB-hour или другая unit из конкретного контракта. Без неё разность двух чисел не имеет смысла.
\nQuota и limit задают ограничения ресурса, а не цену. Quota может ограничивать суммарные requests и limits в namespace. Limit задаёт верхнюю границу для workload. Saturation — сигнал, что очередь, p95 или retry risk приближаются к принятой границе. Saturation может остановить выбор, но не пересчитывает billing formula.
\nНиже — ограниченный учебный пример. Он не читает облачный счёт и не предлагает менять рабочую систему. Формула нужна, чтобы сделать промежуточные величины видимыми:
\nconst card = {\n basePerHour: 0.36,\n cpuRequest: 4,\n memoryGiB: 6,\n cpuUnitPerCoreHour: 0.08,\n memoryUnitPerGiBHour: 0.01,\n hours: 24,\n};\n\nconst hourly =\n card.basePerHour +\n card.cpuRequest * card.cpuUnitPerCoreHour +\n card.memoryGiB * card.memoryUnitPerGiBHour;\n\nconst modelCost = hourly * card.hours;\n// 17.76 model units for this fixed educational card\nЗначение 17,76 — результат именно этой формулы. Оно не является тарифом, счётом или прогнозом. Если поменять CPU с 4 до 1,6, нужно сравнить не только cost. Нужны одинаковые period и unit, а также p95, workload, memory, quota и operational risk. В соседней учебной карточке 1,6 CPU и 2,4 GiB дают 0,512 u/h. При p95 296 ms и сигнале queue-growth меньшая цена не доказывает безопасное уменьшение.
Marginal cost — разность двух сопоставимых формул. Например, переход с 1,4 до 1,6 CPU при ставке 0,08 u/core-hour добавляет (1,6 - 1,4) × 0,08 × 24 = 0,384 u/24h. Это размер следующего вопроса, а не команда увеличить request. Если вместе с CPU меняются memory, region, commitment или shared base, одна разность уже не объясняет решение.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| CPU ниже 40% | Usage сравнили с allocation и сразу назвали ресурс лишним | Сверить request, workload, p95 и период | Не менять размер; сформулировать следующий контролируемый вопрос |
| Model cost выросла | Изменились fixed base, unit или allocation | Разложить формулу по полям и одинаковому периоду | Проверить billing contract; не называть число счётом |
| Quota ещё не исчерпана | Quota приняли за доступную безопасную capacity | Проверить aggregate rule, admission и saturation | Остановить изменение, если p95 или очередь уже у границы |
| Большой инстанс даёт лучший p95 | Одну метрику использовали вместо performance, cost и risk | Сравнить соседний limit и return boundary | Сохранить stop и запросить отдельное решение владельца |
| Два расчёта не совпадают | Смешаны scope, unit или interval | Сверить workload, resource, formula и часы | Не сравнивать карточки до выравнивания входов |
Quota отвечает на вопрос «какой aggregate объём policy допускает?». Она не отвечает на вопрос «выдержит ли сервис следующий request?». В Kubernetes quota может ограничивать суммарные requests и limits, но сама по себе не обещает свободную ёмкость кластера. LimitRange может задать default, minimum или maximum на admission. Это правила формы ресурса, а не доказательство latency.
\nВ учебной карточке quota равна 6 CPU, а request большого варианта равен 4 CPU. Из этого нельзя вывести, что сервис безопасно выдержит 4 CPU или что его следует уменьшить. Если p95 почти касается SLO и растёт очередь, saturation требует остановки даже при доступной quota. Если p95 стабилен, это всё равно не доказывает стоимость: нужна отдельная billing expression.
\nХорошая проверка должна уметь остановиться. Для large-instance-unjustified учебная ветка видит CPU 31%, p95 208 ms и большую reservation. Она возвращает stop-and-compare-smaller-limit. Ветка не уменьшает request и не создаёт rollback. Она запрещает два одинаково слабых вывода: «низкая загрузка означает лишний ресурс» и «лучший p95 оправдывает самый большой ресурс».
Остановка нужна и при подмене входа. Если отчёт содержит неизвестное поле вроде invoice, другой scope, sparse array или изменённую model cost, его нельзя молча принять. Сначала нужно вернуть форму к согласованному контракту. Если unit или период не подтверждены, вычисление marginal cost прекращается. Отрицательный путь защищает границу примера, а не реальный API и не рабочую систему.
Все числа в примере учебные. Модель не видит provider invoice, SKU, скидки, commitment, tax, network charge, storage, cluster state, runtime, trace или реальную нагрузку. Она не знает owner, полномочия, rollback policy и последствия изменения. Поэтому статья не обещает savings, не выбирает размер instance и не переносит unit из одного provider в другой.
\nПроверка готова, если читатель может ответить на четыре вопроса: что наблюдалось; какая формула и unit применены; какое правило остановит изменение; какой реальный источник подтвердит следующий шаг. Дополнительно должны быть видны request, limit, quota, p95 и период. Если ответ держится только на CPU percent или на красивом model cost, вывод не готов к operational решению.
\n