Files

8 lines
23 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 116,
"slug": "editorial-2024-10-mechanism-capacity-cost",
"title": "Ёмкость и стоимость: как связать нагрузку, ресурс и счёт",
"excerpt": "Низкая загрузка и зелёный SLO не показывают цену сами по себе. Разбираем цепочку от workload до billing unit и даём воспроизводимый способ проверить следующий предел ёмкости.",
"contentHtml": "<p>Симптом виден сразу: сервис держит p95 ниже целевого значения, а счёт за инфраструктуру растёт. На графике CPU занято 35%, поэтому первая гипотеза звучит логично: ресурсов слишком много. Но процент загрузки описывает только наблюдаемое использование. Он не говорит, сколько ресурса зарезервировано, за какую единицу считает провайдер и какая часть стоимости фиксирована. Если перепутать эти слои, команда уменьшит резерв без доказательства безопасности или, наоборот, купит большой инстанс без объяснимого эффекта.</p>\n<p>Главный вопрос этой статьи — почему низкая загрузка не означает низкую стоимость. Ответ можно проверить цепочкой <code>workload → resource → billing unit → decision</code>. Сначала фиксируем поток работы и его качество, затем отделяем выделение ресурса от фактического использования, после этого читаем модель тарификации и только в конце выбираем изменение. Числа ниже учебные: они показывают метод расчёта, а не счёт конкретного облака.</p>\n<h2>Один график скрывает четыре разные величины</h2>\n<p><strong>Workload</strong> — поток работы: запросы в секунду, размер сообщения, фоновые задания и период наблюдения. <strong>Resource allocation</strong> — то, что система выделила или зарезервировала: число реплик, CPU и память в request, limit, размер виртуальной машины или другой контролируемый параметр. <strong>Usage</strong> — фактическое потребление в конкретном окне. <strong>Billing unit</strong> — единица, к которой привязан тариф: час инстанса, CPU-hour, GiB-hour, запрос, байт или составная модель.</p>\n<p>Эти величины связаны, но не заменяют друг друга. Низкий usage может быть полезным запасом для пика. Высокий usage может не менять счёт при фиксированной оплате инстанса. SLO отвечает за доступность или задержку, а не за цену. Quota отвечает за допустимый агрегат в политике платформы, а не за свободную ёмкость и не за финансовый результат.</p>\n<table><caption>Что измеряет показатель и какой вывод из него допустим</caption><thead><tr><th scope=\"col\">Слой</th><th scope=\"col\">Пример значения</th><th scope=\"col\">Что он показывает</th><th scope=\"col\">Чего он не доказывает</th></tr></thead><tbody><tr><td>Workload</td><td>120 RPS, пик 180 RPS</td><td>Какой поток нужно обслужить и в каком окне</td><td>Сколько стоит ресурс без правила тарификации</td></tr><tr><td>Allocation</td><td>2 реплики, request 1,2 CPU</td><td>Какой резерв заявлен системе</td><td>Фактическое потребление и цену без billing unit</td></tr><tr><td>Usage</td><td>CPU 35%, память 61%</td><td>Наблюдаемое использование за период</td><td>Что можно безопасно убрать на пике</td></tr><tr><td>SLO</td><td>p95 &lt; 300 мс</td><td>Качество обслуживания в заданной границе</td><td>Экономичность решения</td></tr><tr><td>Billing unit</td><td>CPU-hour и GiB-hour</td><td>К чему применяются ставки и ступени</td><td>Реальную сумму без тарифа, региона и счётных данных</td></tr></tbody></table>\n<p>Практическая ошибка начинается с подмены: «CPU 35%, значит оплачиваем 35%». Это может быть верно только при конкретном контракте тарификации. Если провайдер продаёт целый инстанс, график использования не уменьшит его часовую цену. Если тариф зависит от резервирования, процент usage также не станет входом формулы автоматически.</p>\n<h2>Механизм: request, limit и quota живут в разных границах</h2>\n<p>В Kubernetes эти границы хорошо видны. Scheduler использует resource request при выборе узла: сумма requests должна помещаться в доступную ёмкость узла, даже если фактическая загрузка сейчас мала. Limit задаёт другую границу исполнения; для CPU он связан с throttling, а превышение memory limit может привести к OOM-убийству при давлении на память. Следовательно, request, limit и usage нельзя складывать в одну метрику «занято».</p>\n<p>ResourceQuota ограничивает агрегатное потребление в namespace. Если создание или обновление объекта нарушает квоту, control plane может отклонить запрос с HTTP 403. Это проверка политики, а не доказательство того, что в кластере есть свободный узел или что вариант дешевле. Наличие места до quota не отменяет проверки scheduler, runtime-сигналов и биллинга.</p>\n<p>На другой платформе названия будут иными, но вопрос остаётся тем же: кто принимает решение о размещении, кто ограничивает исполнение, кто измеряет usage и кто выставляет счёт. Нельзя переносить Kubernetes-семантику на managed database или serverless-функцию без документации конкретного сервиса.</p>\n<figure><img src=\"/assets/editorial/2024/capacity-cost-2024-scenario-matrix.svg\" alt=\"Причинная схема: наблюдаемая загрузка и выделенный ресурс проходят через billing unit к расчётной стоимости, а saturation связывается с риском и задержкой\" loading=\"lazy\" /><figcaption>Схема разделяет usage, allocation, policy и billing. Стрелки показывают учебную модель связи, а не инвойс конкретного провайдера и не рекомендацию менять ресурс без измерений.</figcaption></figure>\n<h2>Учебный расчёт: цена начинается с единицы тарификации</h2>\n<p>Возьмём один сервис с двумя репликами. На реплику заявлены request 1,2 CPU и 2 GiB памяти. Для учебной модели считаем, что фиксированная часть равна 0,36 условной единицы за час, CPU стоит 0,08 за CPU-hour, а память — 0,01 за GiB-hour. Эти ставки специально обозначены как условные: в реальном проекте их нужно заменить на тариф, регион, скидку, commitment и правило распределения общих затрат.</p>\n<pre><code>const model = {\n replicas: 2,\n requestCpu: 1.2,\n requestMemoryGiB: 2,\n fixedPerHour: 0.36,\n cpuHour: 0.08,\n memoryGiBHour: 0.01,\n};\n\nconst perReplicaHour =\n model.fixedPerHour\n + model.requestCpu * model.cpuHour\n + model.requestMemoryGiB * model.memoryGiBHour;\nconst daily = perReplicaHour * 24 * model.replicas;\n\nconsole.log({\n perReplicaHour,\n daily,\n deltaAfterResize: (0.36 + 1.4 * 0.08 + 2.2 * 0.01)\n * 24 * model.replicas - daily,\n});\n// { perReplicaHour: 0.476, daily: 22.848, deltaAfterResize: 0.864 }</code></pre>\n<p>Расчёт даёт 0,476 условной единицы за реплику-час и 22,848 за сутки для двух реплик. Если request изменить на 1,4 CPU и 2,2 GiB, модель даст 23,712 за сутки; разница составит 0,864 условной единицы. Это воспроизводимая арифметика, но не обещание экономии или роста счёта. Она станет рабочим расчётом только после проверки, что тариф действительно считает reservation, а не usage, узел, инстанс или другой объект.</p>\n<p>Google Cloud Billing Catalog, например, описывает для SKU usage unit, base unit и tiered rates. Само наличие этих полей ещё не выбирает нужный SKU. Нужно сопоставить ресурс, регион, период, уровень тарифа и способ экспорта usage с конкретным счётом. При неизвестной ставке правильный результат — «стоимость не установлена», а не ноль.</p>\n<h2>Почему зелёный SLO не закрывает вопрос о стоимости</h2>\n<p>Представим учебное наблюдение: p95 равен 235 мс при SLO 300 мс, CPU usage — 35%, память — 61%. Эти данные говорят, что в выбранном окне запас по задержке равен 65 мс и среднее потребление CPU невелико. Они не говорят, что request можно уменьшить: пик мог быть выше, память могла быть близка к пределу, а тариф мог быть фиксированным.</p>\n<p>Для причинного сравнения меняйте один основной вход за раз и сохраняйте одинаковые условия. Если одновременно уменьшить request, число реплик, класс машины и лимит concurrency, после результата нельзя будет понять, какой рычаг помог. Если окно содержит только спокойный час, вывод не покрывает ежедневный пик. Если billing период и окно метрик различаются, это разные наблюдения, а не одна дельта.</p>\n<table><caption>Симптом → гипотеза → проверка → безопасный вывод</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Гипотеза</th><th scope=\"col\">Проверка</th><th scope=\"col\">Вывод до изменения</th></tr></thead><tbody><tr><td>CPU usage ниже 40%, счёт не меняется</td><td>Оплата фиксирована за инстанс</td><td>Найти billing unit и строку SKU в счёте</td><td>Низкий usage не даёт основания уменьшать ресурс</td></tr><tr><td>p95 зелёный, replicas растут</td><td>Запас куплен масштабированием</td><td>Сравнить peak RPS, replicas, allocation и период</td><td>Отделить надёжность от стоимости резерва</td></tr><tr><td>Pod Pending, quota ещё не исчерпана</td><td>Не хватает ёмкости узлов или сработала другая политика</td><td>Проверить Events, requests, allocatable и LimitRange</td><td>Quota не равна свободной ёмкости</td></tr><tr><td>CPU низкий, p95 высокий</td><td>Ожидание сети, блокировка или throttling другого слоя</td><td>Сопоставить latency, очередь, ошибки и trace</td><td>Не уменьшать CPU по одному графику</td></tr><tr><td>После resize цена не совпала с моделью</td><td>Изменён не тот объект тарификации</td><td>Сверить SKU, регион, tier, скидку и количество часов</td><td>Расчёт вернуть в статус гипотезы</td></tr></tbody></table>\n<h2>Метрики должны иметь окно и контекст</h2>\n<p>Метрика без периода — число без проверяемого смысла. OpenTelemetry описывает metric event как измерение, время его фиксации и связанные метаданные. Поэтому рядом с p95 или usage нужно хранить хотя бы сервис, окружение, регион, версию, workload, окно и единицу. Иначе сравнение «до/после» может оказаться сравнением разных маршрутов или разных пиков.</p>\n<p>Для CPU полезно видеть usage вместе с throttling и очередью. Для памяти — пик, OOM-события и рабочий набор. Для сервиса — p95/p99, ошибки и поток запросов. Набор зависит от системы: эти поля не являются универсальным дашбордом. Смысл проверки в том, чтобы связывать качество с нагрузкой и выделением ресурса, а не объявлять причиной первую зелёную или красную линию.</p>\n<p>Есть и стоимость самой наблюдаемости. В OpenTelemetry число уникальных комбинаций атрибутов определяет cardinality; атрибуты вроде user ID или необработанного URL могут раздувать состояние метрик. Поэтому при добавлении labels нужно одновременно проверить объём хранения, лимиты backend и полезность разреза. «Добавим больше измерений» тоже имеет ресурсную и финансовую цену.</p>\n<h2>Порядок проверки перед изменением ёмкости</h2>\n<ol><li><strong>Определите scope.</strong> Назовите сервис, окружение, регион, версию, число реплик и период. Не смешивайте production и нагрузочный стенд.</li><li><strong>Опишите workload.</strong> Запишите baseline и peak, RPS, размер сообщения, фоновые задачи и источник каждого значения.</li><li><strong>Разделите allocation и usage.</strong> Выпишите request, limit, quota и фактическое потребление по CPU и памяти. Не подставляйте процент usage вместо request.</li><li><strong>Зафиксируйте качество.</strong> Сохраните p95 или p99, error rate, очередь, throttling и OOM за то же окно. Назовите SLO и допустимый запас.</li><li><strong>Проверьте policy.</strong> Для Kubernetes посмотрите ResourceQuota, LimitRange, Events и allocatable узлов; для другой платформы найдите эквивалентные ограничения в официальной документации.</li><li><strong>Найдите billing unit.</strong> Сопоставьте SKU или тариф с ресурсом, регионом, периодом, ступенью, скидкой и общими затратами. Не ставьте неизвестные входы равными нулю.</li><li><strong>Сформулируйте один малый шаг.</strong> Измените один управляемый параметр, заранее запишите stop condition и обратную границу.</li><li><strong>Сравните одинаковые окна.</strong> После изменения проверьте тот же workload, качество, usage, allocation и счётные данные. Если условия разошлись, отметьте результат как несопоставимый.</li><li><strong>Примите ограниченный результат.</strong> Выберите следующий шаг только при сохранённом SLO и понятной цене. Иначе остановите сравнение, запишите пробел и назначьте владельца проверки.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Эта модель не заменяет capacity planning, нагрузочное тестирование, договор с облачным провайдером или финансовый разбор. Она не учитывает автоматически autoscaling, cold start, сетевой egress, хранилище, резервирование, налоги, скидки, простой, стоимость лицензий и сопровождения. Каждый фактор может изменить итоговую цену или безопасный ресурсный предел.</p>\n<p>Учебные значения 120 RPS, 180 RPS, 235 мс, 1,2 CPU, 2 GiB, 0,36 и остальные числа не описывают реальный сервис. Их нельзя переносить в бюджет, SLO или заявку на изменение. Иллюстрация также не является инвойсом. Для реального решения нужны собственные метрики, разрешённый тариф и подтверждённое правило распределения общих расходов.</p>\n<p>Нельзя делать вывод о свободной ёмкости из одной квоты, о цене — из одного usage-графика, а о безопасности уменьшения — из среднего значения. Если период, единица или владелец неизвестны, остановка — полноценный результат диагностики. Следующий шаг должен вернуть недостающий факт, а не маскировать его приблизительным числом.</p>\n<h2>Проверяемый результат</h2>\n<p>Разбор готов к решению, когда в одной записи видны workload, окно, allocation, usage, SLO, billing unit, формула, stop condition и обратная граница. Другой инженер должен воспроизвести арифметику, понять, какой параметр меняется, и назвать сигнал, который остановит эксперимент. Если он видит только «CPU 35%» и «p95 зелёный», стоимость и безопасность изменения ещё не доказаны.</p>\n<p>Такой порядок не обещает минимальный счёт. Он делает причину изменения проверяемой: можно увидеть, был ли куплен резерв, что именно считается провайдером и какой риск принимает команда. Низкая загрузка после этой проверки может стать аргументом для уменьшения ресурса, но только вместе с пиком, политикой, качеством и подтверждённой моделью тарификации.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Resource Management for Pods and Containers</a> — официальная документация о requests, limits, размещении Pod, CPU throttling и memory limits.</li><li><a href=\"https://kubernetes.io/docs/concepts/policy/resource-quotas/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Resource Quotas</a> — официальное описание агрегатных ограничений namespace и отказа при нарушении quota; quota не является счётом.</li><li><a href=\"https://docs.cloud.google.com/billing/docs/reference/rest/v1/services.skus/list\" target=\"_blank\" rel=\"noopener noreferrer\">Google Cloud Billing: services.skus.list</a> — официальное описание pricing expression, usage unit, base unit и tiered rates.</li><li><a href=\"https://opentelemetry.io/docs/concepts/signals/metrics/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: Metrics</a> — официальное описание времени и метаданных metric event, агрегирования и cardinality.</li></ul>"
}