Files
progcode/editorial/agent-rewrites/115.json
T

8 lines
24 KiB
JSON
Raw 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": 115,
"slug": "editorial-2024-10-field-capacity-cost",
"title": "Ёмкость и стоимость: как выбрать следующий предел, а не самый большой инстанс",
"excerpt": "Большой инстанс может улучшить одну latency-метрику и одновременно увеличить расход и операционный риск. Разбираем причинную цепочку, проверку и безопасный критерий следующего шага.",
"contentHtml": "<p>На разборе ёмкости команда видит знакомую картину: после перехода на 4 CPU p95 снизился, а резерв и счёт выросли. Карточка с 1,6 CPU уже показывала очередь и почти касалась SLO, поэтому большой инстанс выглядит очевидным ответом. Цена ошибки — закрепить дорогую конфигурацию без доказанного эффекта, принять одну удачную latency-точку за решение и потерять понятную границу возврата.</p>\n<p><strong>Тезис:</strong> следующий размер выбирают не по лучшему p95, а по следующему проверяемому пределу. В одной карточке нужно связать workload, выделенный ресурс, сигнал производительности, единицу тарификации и условие остановки. Если хотя бы одно звено неизвестно, результатом должна быть остановка или отдельный разбор, а не заказ самого большого инстанса.</p>\n<h2>Начните с одного вопроса</h2>\n<p>Вопрос «сколько CPU нам нужно?» слишком короткий. Для проверки запишите: какой workload измеряем, в каком окружении, за какой период, какой SLO защищаем и что именно меняем. Для сервиса это может быть поток HTTP-запросов, число реплик, <code>requests</code> и <code>limits</code> контейнера, p95 задержки, ошибки и сигнал очереди. Для финансовой части добавьте billing unit: час виртуальной машины, CPU-hour, GiB-hour, запрос или трафик.</p>\n<p>Так появляются разные, но связанные факты. Workload описывает работу. Allocation описывает обещанный системе резерв. Usage показывает фактическое потребление. Performance показывает результат для пользователя. Billing expression объясняет, как провайдер переводит использование или резерв в деньги. Низкий usage не заменяет allocation и не доказывает экономию. Зелёный SLO не объясняет тариф.</p>\n<p>Ситуация считается описанной только после фиксации scope. Запись «p95 стал лучше» не воспроизводится без маршрута, нагрузки, числа реплик, периода, версии и способа измерения. Запись «стоимость выросла на 20%» не воспроизводится без счёта, валюты, региона, скидки и правила агрегации.</p>\n<figure><img src='/assets/editorial/2024/capacity-cost-2024-limit-curve.svg' alt='Диаграмма трёх вариантов ёмкости: текущий предел, следующий малый предел и большой инстанс с разными кривыми производительности, модельной стоимости и операционного риска' loading='lazy' /><figcaption>Кривая показывает, почему лучший p95 не является достаточным критерием. На каждой точке нужно отдельно проверить стоимость и риск, а пунктирная граница задаёт момент остановки сравнения.</figcaption></figure>\n<h2>Разделите request, limit, quota и цену</h2>\n<p>В Kubernetes эти слова отвечают на разные вопросы. Scheduler использует resource request при выборе узла. Limit задаёт ограничение выполнения контейнера: CPU может быть ограничен throttling, а превышение memory limit может привести к вмешательству OOM-механизма. ResourceQuota ограничивает суммарные ресурсы или объекты в namespace. LimitRange задаёт допустимые minimum, maximum и default для объектов на этапе admission.</p>\n<p>Ни один из этих механизмов не является тарифом. Если в namespace осталось 6 CPU quota, это говорит о политике допуска, но не о свободной ёмкости кластера, состоянии соседних workloads, задержке сервиса или цене часа. Если контейнер использует 31% CPU, это говорит об usage относительно выбранной базы, но не о том, сколько провайдер выставит в счёте. Сначала подтверждают policy и runtime, затем отдельно сверяют 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 31%</td><td>Наблюдаемое потребление в выбранном окне</td><td>Безопасный request и цену</td><td>Peak, throttling, p95 и billing unit</td></tr><tr><td>Quota ещё не исчерпана</td><td>Ограничение namespace не достигнуто</td><td>Доступный узел и запас по SLO</td><td>Admission, cluster capacity и saturation</td></tr><tr><td>p95 ниже SLO</td><td>Метрика укладывается в заданное окно</td><td>Экономичность и причинность улучшения</td><td>Одинаковый workload, период и allocation</td></tr><tr><td>Счёт вырос</td><td>Изменился итог billing-периода</td><td>Причину в одном ресурсе</td><td>SKU, rate, replicas, storage и traffic</td></tr></tbody></table>\n<p>Практический вывод отрицательный: нельзя уменьшать request только потому, что средний CPU низкий, и нельзя увеличивать limit только потому, что quota это допускает. Для памяти редкий пик важнее среднего значения. Для CPU полезно посмотреть throttling и очередь. Для latency — выяснить, не сидит ли время в сети, блокировке или внешнем сервисе.</p>\n<h2>Соберите прозрачную модель стоимости</h2>\n<p>Формула нужна не для имитации счёта, а для проверки причинной цепочки. В учебной модели отдельно обозначим фиксированную часть, резерв CPU, резерв памяти и ставки. Если провайдер считает инстанс целиком, а не request контейнера, формула должна отражать инстанс. Если billing unit неизвестна, арифметику нужно остановить, а не заменить процентом utilization.</p>\n<pre><code>node - &lt;&lt;'NODE'\nconst model = {\n fixedPerHour: 0.36,\n cpuHour: 0.08,\n memoryGiBHour: 0.01,\n hours: 24,\n replicas: 1\n};\n\nconst variants = [\n { name: 'current', cpu: 1.2, memoryGiB: 2, p95Ms: 248, queueGrowth: false },\n { name: 'next-limit', cpu: 1.4, memoryGiB: 2, p95Ms: 248, queueGrowth: false },\n { name: 'saturation-stop', cpu: 1.6, memoryGiB: 2, p95Ms: 296, queueGrowth: true },\n { name: 'large-instance', cpu: 4, memoryGiB: 6, p95Ms: 208, queueGrowth: false }\n];\n\nfor (const variant of variants) {\n const hourly = model.fixedPerHour\n + variant.cpu * model.cpuHour\n + variant.memoryGiB * model.memoryGiBHour;\n const daily = hourly * model.hours * model.replicas;\n const headroomMs = 300 - variant.p95Ms;\n console.log({ ...variant, hourly, daily, headroomMs });\n}\nNODE</code></pre>\n<p>Команда не обращается к кластеру, облаку или telemetry. Она воспроизводит только учебную арифметику на Node.js и печатает одинаковые результаты на машине с установленным Node. Для <code>current</code> получится 0,476 условной единицы в час и 11,424 за сутки; для <code>next-limit</code> — 0,492 в час и 11,808 за сутки. Это не валюта и не реальный invoice.</p>\n<p>В рабочей карточке рядом с формулой должны лежать источник значения, единица и период. Ставка может быть tiered, а расход — агрегироваться по проекту или аккаунту. Поэтому сравнение двух конфигураций по одной строке «CPU × ставка» безопасно только как черновая модель, пока billing export или rate card не подтверждают остальные компоненты.</p>\n<h2>Пример: три предела и отрицательная ветка</h2>\n<p>Рассмотрим фиксированный workload за 24 часа, одну реплику и SLO p95 не выше 300 мс. Текущая точка использует 1,2 CPU и 2 GiB памяти. Следующий малый предел меняет CPU до 1,4, сохраняя память и окно. В обоих учебных случаях p95 равен 248 мс, поэтому улучшение от изменения не доказано: новая точка всего лишь остаётся сравнимой.</p>\n<p>Вариант <code>saturation-stop</code> с 1,6 CPU показывает p95 296 мс и рост очереди. Headroom равен 4 мс. Даже если quota позволяет такой request, сравнение нужно остановить. Условная стоимость по скрипту равна 12,192 за сутки. Следующий рост ресурса не объясняет, почему растёт очередь.</p>\n<p>Большой вариант резервирует 4 CPU и 6 GiB. p95 равен 208 мс, CPU usage в карточке — 31%, а модельная стоимость — 17,76 за сутки. Это показывает, что latency можно получить за счёт большого резерва. Это не доказывает, что резерв нужен, что применена правильная ставка или что его можно без риска вернуть назад.</p>\n<table><caption>Учебное сравнение вариантов; числа не являются production-измерениями</caption><thead><tr><th scope='col'>Вариант</th><th scope='col'>Ресурс</th><th scope='col'>Performance</th><th scope='col'>Стоимость за 24 ч</th><th scope='col'>Решение</th></tr></thead><tbody><tr><td><code>current</code></td><td>1,2 CPU; 2 GiB</td><td>p95 248 мс; запас 52 мс</td><td>11,424 u</td><td>База сравнения</td></tr><tr><td><code>next-limit</code></td><td>1,4 CPU; 2 GiB</td><td>p95 248 мс; запас 52 мс</td><td>11,808 u</td><td>Проверить соседний шаг</td></tr><tr><td><code>saturation-stop</code></td><td>1,6 CPU; 2 GiB</td><td>p95 296 мс; запас 4 мс; растёт очередь</td><td>12,192 u</td><td>Остановить сравнение</td></tr><tr><td><code>large-instance</code></td><td>4 CPU; 6 GiB</td><td>p95 208 мс; CPU 31%</td><td>17,76 u</td><td>Не выбирать по одному p95</td></tr></tbody></table>\n<p>Таблица не превращает performance, cost и risk в общий score. Для такого score пришлось бы заранее определить веса, владельца и допустимые trade-off. Здесь важна сохранённая отрицательная ветка: хороший p95 при неизвестной цене не даёт согласия, а низкая цена при headroom 4 мс не даёт разрешения продолжать.</p>\n<h2>Где проходит граница Kubernetes</h2>\n<p>Request влияет на планирование, но не обещает, что приложение всегда получит ровно это количество CPU. Limit задаёт верхнюю границу исполнения и имеет собственные последствия. Quota и LimitRange могут не пропустить объект или назначить ему default, но не проверяют пользовательский SLO и не рассчитывают итоговый счёт.</p>\n<p>Из этого следует последовательность владельцев. Платформенная policy подтверждает, что объект допустим. Scheduler и runtime показывают, что workload размещён и исполняется. Сервисные метрики показывают latency, ошибки и saturation. Финансовый источник показывает SKU, usage unit, base unit, tier и период. Один владелец или один dashboard не заменяют четыре вида evidence.</p>\n<p>Особенно опасна фраза «в quota есть запас, значит можно уменьшить инстанс». Quota — это потолок политики, а не рекомендация по размеру и не прогноз поведения. И обратная фраза тоже неверна: превышение request не означает автоматически, что надо покупать большой инстанс. Сначала нужно установить bottleneck и проверить малый соседний шаг.</p>\n<h2>Определите stop condition заранее</h2>\n<p>Stop condition защищает решение от давления одного удачного графика. Для учебного примера зададим остановку при headroom меньше 15 мс, при росте очереди, при неизвестной billing unit или при одновременной смене CPU, памяти, сети и класса машины. Эти условия не выбирают победителя. Они говорят, когда текущая гипотеза больше не проверяется тем же экспериментом.</p>\n<p>Возврат тоже имеет границу. Запись «при ухудшении откатить» неполна, пока не названы владелец, изменение, разрешённый способ возврата и сигнал успешного восстановления. Эта статья не даёт команды rollback для чужого кластера: команда зависит от вашего deployment-процесса, прав и политики изменений. Если такой путь не описан, это причина остановить change, а не повод скрыть риск.</p>\n<h2>Порядок воспроизводимой проверки</h2>\n<ol><li><strong>Зафиксируйте scope.</strong> Назовите workload, окружение, маршрут, версию, число реплик, период и владельца.</li><li><strong>Запишите текущую точку.</strong> Сохраните request, limit, quota, p50/p95, ошибки, очередь, peak usage и источник каждого значения.</li><li><strong>Разделите policy и runtime.</strong> Отдельно подтвердите admission, LimitRange и ResourceQuota; отдельно проверьте placement, throttling, memory pressure и saturation.</li><li><strong>Назовите billing unit.</strong> Зафиксируйте SKU, usage unit, base unit, tier, валюту, регион, скидку и период. Неизвестное поле оставьте неизвестным.</li><li><strong>Выберите соседний шаг.</strong> Измените один основной управляемый параметр и сохраните workload, окно, версию и способ измерения.</li><li><strong>Запустите расчёт.</strong> Выполните учебную команду или свой эквивалент, проверьте единицы и пересчитайте marginal cost только для сопоставимых входов.</li><li><strong>Проверьте stop condition.</strong> Сверьте headroom, очередь, ошибки и согласованность billing. При срабатывании остановитесь и оформите следующий вопрос.</li><li><strong>Зафиксируйте outcome.</strong> Запишите compare, stop или human review, владельца и границу возврата. Не называйте учебный результат командой для production.</li><li><strong>Попросите повторить расчёт.</strong> Другой инженер должен восстановить входы, формулу, отрицательную ветку и причину выбора без устного контекста.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Все значения в примере — учебные: 0,36, 0,08, 0,01, p95 248/296/208 мс, 31%, 1,2/1,4/1,6/4 CPU и 2/6 GiB. Они не описывают конкретный provider, cluster, invoice, telemetry или production workload. Их можно использовать, чтобы проверить форму расчёта и смысл stop condition, но нельзя переносить их в бюджет, SLO или capacity plan.</p>\n<p>Модель не включает автоматически autoscaling, burst, cold start, noisy neighbor, storage, network egress, commitments, скидки, налоги, стоимость лицензий и сопровождения. Они могут изменить решение. Если хотя бы один фактор существенен, добавьте его в ту же billing-модель или остановите сравнение до получения данных.</p>\n<p>Источники Kubernetes подтверждают семантику ресурсов и policy, а источник Cloud Billing — форму описания SKU и тарифных ступеней. Ни один источник не знает вашу нагрузку, не обещает нужный p95 и не доказывает экономию. Доказательство для своего решения появляется только после выравнивания scope, измерения и финансового источника.</p>\n<h2>Критерий готовности</h2>\n<p>Решение о следующем пределе готово, когда одна карточка связывает workload, allocation, performance signal, billing unit, stop condition, owner и return boundary. В ней видно, какой параметр изменился, за какой период проведено сравнение и какой сигнал остановит эксперимент. Policy не выдана за runtime, usage не выдан за цену, а лучший p95 не выдан за универсальное решение.</p>\n<p>Если хотя бы одного поля нет, полезный результат — не самый большой инстанс, а список недостающих доказательств. Такой отказ от поспешного изменения сохраняет возможность сравнить соседний шаг и делает следующую итерацию проверяемой.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://github.com/kubernetes/website/blob/0666011822b3ebbec179d6b3a2e6832977daa3bc/content/en/docs/concepts/configuration/manage-resources-containers.md' target='_blank' rel='noopener noreferrer'>Kubernetes: Resource Management for Pods and Containers, snapshot 0666011</a> — подтверждает роль requests при планировании и отдельную семантику CPU и memory limits. Не подтверждает цену, invoice или безопасный размер инстанса.</li><li><a href='https://github.com/kubernetes/website/blob/c87b19193d8b1e96de03d76ec7eece02262580f0/content/en/docs/concepts/policy/resource-quotas.md' target='_blank' rel='noopener noreferrer'>Kubernetes: Resource Quotas, snapshot c87b191</a> — подтверждает ограничение суммарного потребления на уровне namespace. Не подтверждает свободную ёмкость кластера или стоимость.</li><li><a href='https://github.com/kubernetes/website/blob/c889d9b2510bba78dbb527a79b8e2099b16f3d67/content/en/docs/concepts/policy/limit-range.md' target='_blank' rel='noopener noreferrer'>Kubernetes: Limit Ranges, snapshot c889d9b2</a> — подтверждает defaults, minimum, maximum и ratio для admission. Не подтверждает latency, saturation или изменение уже работающего workload.</li><li><a href='https://github.com/googleapis/googleapis/blob/0fe653872eca3ece391a0b4ec2f8ace64634419f/google/cloud/billing/v1/cloud_catalog.proto' target='_blank' rel='noopener noreferrer'>Google Cloud Billing Catalog v1 proto, snapshot 0fe6538</a> — подтверждает поля pricing expression, usage unit, base unit и tiered rates. Не подтверждает конкретную цену, скидку или счёт читателя.</li></ul>"
}