{ "index": 117, "slug": "editorial-2024-10-practice-capacity-cost", "title": "Ёмкость и стоимость: почему зелёный SLO не означает дешёвую систему", "excerpt": "Как связать нагрузку, резерв ресурсов и единицу тарификации, чтобы рост расхода не маскировался выполненным SLO.", "contentHtml": "
Сервис держит p95 на уровне 235 мс при целевом SLO 300 мс, но резерв CPU и памяти растёт каждую неделю. На графике задержка зелёная. В счёте появляется лишняя базовая ёмкость. Если принять зелёный SLO за доказательство эффективности, команда платит за резерв, которого не связывает ни с нагрузкой, ни с единицей тарификации.
\nОшибка возникает в месте, где смешивают четыре разные величины: поток запросов, выделенный ресурс, фактическое использование и цену. SLO отвечает за задержку или долю успешных запросов. Он не объясняет, сколько CPU зарезервировано и как провайдер выставляет счёт. Тезис статьи простой: стоимость ёмкости проверяют цепочкой workload → resource → billing unit → stop condition. Если звено пропущено, число на дашборде остаётся симптомом.
Workload описывает поток работы: requests per second, размер сообщения, число фоновых задач и период измерения. Resource описывает выделение: replicas, CPU request, memory request, limit и квоту. Usage показывает фактическое потребление за интервал. Billing unit говорит, за что считают деньги: час инстанса, CPU-hour, GiB-hour, запрос, байт или составную единицу.
\nЭти поля связаны, но не заменяют друг друга. Низкий usage не доказывает, что request можно уменьшить: запас может защищать от пика. Высокий usage не доказывает рост цены: тариф может быть фиксированным. Quota ограничивает суммарное потребление пространства имён, но сама по себе не является счётом. Сначала нужно назвать роль каждого значения.
\nПредставьте один сервис с 120 запросами в секунду в обычный час и 180 в пиковый. Его p95 равен 235 мс. На каждый экземпляр задано 1,2 CPU и 2 ГиБ памяти, а предел равен 1,6 CPU и 3 ГиБ. Для учебного расчёта возьмём фиксированную часть 0,36 условной единицы в час, 0,08 за CPU-hour и 0,01 за GiB-hour.
\nЕсли модель считает зарезервированный request, стоимость часа равна 0.36 + 1.2 * 0.08 + 2 * 0.01 = 0.476. За 24 часа это 11,424 условной единицы. Это не валюта и не счёт провайдера. Числа нужны, чтобы показать границу: формула использует allocation rule, а не процент CPU на графике. При двух репликах результат удваивается. При изменении тарифа меняется billing unit, а не SLO.
Теперь увеличим request до 1,4 CPU и 2,2 ГиБ. p95 в учебном сценарии остаётся ниже 300 мс, но дневная стоимость становится (0.36 + 1.4 * 0.08 + 2.2 * 0.01) * 24 = 11.856. Разница равна 0,432 условной единицы за сутки на одну реплику. Нельзя назвать её экономией или потерей в реальной среде: для этого нужны настоящий тариф, число реплик, период, скидки, shared overhead и подтверждённая нагрузка.
const card = {\n scope: 'checkout-api',\n period: '24h',\n workload: { baselineRps: 120, peakRps: 180, p95Ms: 235, sloMs: 300 },\n resource: { replicas: 2, requestCpu: 1.2, requestMemoryGiB: 2, limitCpu: 1.6 },\n billing: { fixedPerHour: 0.36, cpuHour: 0.08, memoryGiBHour: 0.01 },\n stop: 'p95 headroom < 10 ms or saturation is observed'\n};\n\nconst hourly = card.billing.fixedPerHour\n + card.resource.requestCpu * card.billing.cpuHour\n + card.resource.requestMemoryGiB * card.billing.memoryGiBHour;\nconst daily = hourly * 24 * card.resource.replicas;\nconsole.log({ hourly, daily, sloHeadroomMs: card.workload.sloMs - card.workload.p95Ms });\nКод — учебный пример. Он не читает Kubernetes API, облачный биллинг или метрики. В нём намеренно видны период, replicas и правило тарификации. В рабочем контуре эти значения нужно получить из разрешённых источников и сохранить вместе с timestamp. Если источник не различает request и usage, расчёт нельзя выдавать за стоимость резервирования.
\n| Симптом | Причина-кандидат | Проверка | Действие |
|---|---|---|---|
| SLO зелёный, расход растёт | Увеличили request или replicas | Сравнить deployment, период и число реплик | Разделить изменение ресурса и изменение нагрузки |
| CPU usage низкий, уменьшение не проходит | Нужен запас на пик или действует quota | Сверить peak RPS, p95, eviction и admission policy | Проверить один меньший request в одинаковом интервале |
| Процент CPU вырос, цена не изменилась | Фиксированная тарификация | Прочитать billing unit и rate card | Не считать usage заменой счёта |
| Цена сравнивается у двух сервисов | Разные scope или периоды | Сверить регион, replicas, shared overhead и окно | Остановить сравнение до выравнивания входа |
| Новый limit отклонён | Quota или LimitRange запрещает значение | Проверить policy и сообщение admission | Сначала исправить контракт ресурса, затем считать стоимость |
Таблица задаёт порядок проверки, а не автоматическое решение. Один симптом может иметь несколько причин. Проверка должна исключить хотя бы очевидные альтернативы. Например, низкий CPU при большом p95 может указывать на ожидание сети или блокировку, а не на свободный запас вычислений. Уменьшение request в таком случае меняет риск, но не устраняет задержку.
\nВ Kubernetes scheduler учитывает requests при размещении Pod. Limits задают отдельные ограничения выполнения. Поэтому запись «сервис использует 60% CPU» не говорит, что он занимает 60% оплачиваемой ёмкости. Нужно знать, от какой базы рассчитан процент и какую величину использует финансовая модель.
\nПамять требует отдельной осторожности. Краткий средний usage может скрыть редкий пик. Если процесс получает OOMKilled, зелёный средний график не спасает запросы. Для памяти полезнее сверять peak, рабочий набор, ошибки и время окна. Для CPU важны throttling, очередь и p95. Один общий порог utilisation не подходит обоим ресурсам.
\nQuota и LimitRange задают допустимый диапазон на уровне политики. Они могут отклонить Pod с большим request или назначить значения по умолчанию. Но policy не знает цену часа инстанса. Она проверяет допустимость ресурса, а billing-система применяет свою формулу. Смешение этих слоёв рождает ложный вывод: «quota равна capacity» или «limit равен тарифу».
\nСравнение нельзя продолжать бесконечно. До расчёта назовите условие остановки. Для latency это может быть запас p95 до SLO. Для ресурса — сигнал saturation, throttling или нехватка памяти. Для стоимости — максимально допустимая дневная дельта. Для политики — отказ admission. Stop condition не говорит, какой вариант выбрать. Он говорит, когда следующий вариант нельзя считать продолжением того же эксперимента.
\nВ учебном примере зададим запас 10 мс. При p95 235 мс запас до SLO равен 65 мс, и сравнение двух соседних reservation допустимо как расчётная иллюстрация. Если p95 стал 295 мс, запас равен 5 мс. Следующий рост request уже требует отдельного решения о надёжности. Нельзя спрятать этот риск в таблице стоимости.
\nТакая карточка не заменяет capacity planning. Она не моделирует autoscaling, cold start, сеть, хранилище, резервирование, скидки, burst-кредиты, простои и общие узлы. Она не доказывает, что меньший request безопасен. Она только не даёт связать цену с SLO напрямую.
\nОтрицательный путь важнее удачного расчёта. Если метрики собраны за разные окна, остановитесь. Если тариф относится к узлу, а request — к Pod, не складывайте их без правила распределения. Если неизвестно число реплик в пике, не называйте дневную стоимость точной. Если policy изменилась после замера, пересчитайте вход. Не подставляйте ноль вместо неизвестного поля: это превращает отсутствие данных в ложную экономию.
\nНе стоит запускать уменьшение ресурса только потому, что модель дала меньшую цифру. Сначала проверьте peak и p95 на том же окне, затем выполните изменение по обычному безопасному процессу, а после него сравните ошибки, задержку, throttling и billing. В этой статье нет production-результата и нет обещания экономии. Есть только критерии, по которым такой результат можно будет подтвердить.
\nПроверка готова, если второй инженер может по одной карточке ответить на пять вопросов: какой workload измеряли; какой resource зарезервирован; что означает usage; какая billing unit применена; при каком сигнале сравнение останавливается. Формула должна воспроизводиться на том же входе. Ссылки на тариф и политику должны быть доступны владельцу. При отсутствии любого ответа статус должен быть «данных недостаточно», а не «дешевле».
\n