8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 221,
|
||
"slug": "editorial-2021-11-mechanism-load-testing",
|
||
"title": "Почему «много запросов» не является нагрузочной моделью",
|
||
"excerpt": "Нагрузка помогает найти предел системы только тогда, когда известны сценарий, среда, данные и критерий остановки. Разбираем, как отделить план входа от выполненной работы, проверить отрицательный путь и не выдать учебную модель за benchmark.",
|
||
"contentHtml": "<p>В отчёте после теста появляется одна цифра: «мы отправили 100 000 запросов». Через час команда видит рост времени ответа и начинает менять таймауты, пул соединений или SQL. Но из этой цифры не видно, какой endpoint вызывали, когда возникали запросы, какие данные читали, что считали завершённой работой и в какой среде шёл запуск. Ошибка стоит дорого: можно потратить релиз на исправление несуществующего узкого места и при этом оставить настоящий предел без проверки.</p>\n<p>Тезис простой: нагрузочная модель описывает не объём запросов, а договор между входом, системой и наблюдением. Минимальный договор содержит endpoint, данные, среду, порядок сегментов, критерий остановки и пакет свидетельств. Без него aggregate показывает только объём, но не объясняет причину и не подсказывает безопасное действие.</p>\n<h2>Что именно нужно разделить</h2>\n<p>Сначала отделите намерение создать вход от факта выполненной работы. В плане есть запланированный слот: например, одна операция чтения каталога в сегменте <code>steady</code>. В реальном запуске слот может стать HTTP-запросом, ошибкой клиента, таймаутом или вообще не стартовать. Эти события нельзя складывать в одну величину и называть её throughput.</p>\n<p>Вторая граница проходит между учебной моделью и настоящим тестом. В учебном примере можно материализовать слоты в принятые и отклонённые units, чтобы проверить порядок и остановку. Такой код не создаёт часы, сеть, сервер, очередь, базу или telemetry. Он проверяет форму рассуждения. Чтобы говорить о latency, error rate или capacity, нужен отдельный запуск с реальным источником времени, конфигурацией инструмента, данными и raw output.</p>\n<p>Третья граница — между профилем и результатом. Профиль задаёт переход <code>warm-up → steady → step → recovery</code>. Результат показывает, что произошло на каждом переходе. Один total скрывает порядок. Два запуска с одинаковым total могут иметь разные входы и разные причины ухудшения.</p>\n<h2>Минимальная модель workload</h2>\n<p>Запишите пять полей до запуска. <code>endpointIntent</code> ограничивает предмет и метод. <code>dataSetup</code> описывает набор данных и его identity. <code>environment</code> фиксирует build, конфигурацию и внешние зависимости. <code>segments</code> задают порядок и объём входа. <code>stopCriterion</code> объясняет, когда эксперимент заканчивается и какой результат считается достаточным.</p>\n<p>К этим полям добавьте <code>evidencePacket</code>. Он связывает план с наблюдением: хранит identity среды и данных, имена сегментов, число запланированных слотов, принятые и отклонённые units, состояние остановки и диагностические пометки. Пакет не заменяет результат инструмента. Он не делает fixture trace и не создаёт production evidence. Он не даёт потерять условия, пока команда читает результат.</p>\n<table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Есть только «много запросов» и один total</td><td>Aggregate выдали за workload</td><td>Назвать endpoint, данные, сегменты и критерий остановки</td><td>Отклонить сравнение и восстановить карточку сценария</td></tr><tr><td>На графике выросло время ответа</td><td>Неизвестно, измерялся ли тот же endpoint в той же среде</td><td>Сверить environment identity, build, конфигурацию и raw signal</td><td>Повторить один сценарий после фиксации среды</td></tr><tr><td>Часть входа исчезла из результата</td><td>Не сведены planned slots и completed units</td><td>Проверить равенство planned = accepted + rejected</td><td>Исправить reconciliation до анализа причин</td></tr><tr><td>Fixture показывает bottleneck</td><td>Учебный limit приняли за узкое место сервиса</td><td>Проверить наличие часов, сервера, сети и источника метрики</td><td>Назвать результат synthetic boundary и подготовить реальный tool-run</td></tr><tr><td>Threshold не пройден</td><td>Критерий не связан с конкретной метрикой и целью</td><td>Проверить имя метрики, единицу, окно и условие abort</td><td>Оставить один измеримый критерий и повторить тест</td></tr></tbody></table>\n<h2>Пример: отрицательный путь должен быть виден</h2>\n<p>Ниже учебная модель для одного нейтрального endpoint. Она намеренно не выполняет HTTP-запрос. Сегмент <code>step</code> планирует пять слотов, но принимает только три по заранее объявленному synthetic limit. Два слота остаются отклонёнными. Это позволяет проверить отрицательный путь: нагрузка не исчезла между планом и packet, а recovery идёт после границы.</p>\n<pre><code>const profile = {\n endpointIntent: { method: 'GET', path: '/catalog' },\n environment: 'isolated-training-envelope-v1',\n segments: [\n { name: 'warm-up', planned: 2, accepted: 2, rejected: 0 },\n { name: 'steady', planned: 4, accepted: 4, rejected: 0 },\n { name: 'step', planned: 5, accepted: 3, rejected: 2,\n syntheticLimit: 3 },\n { name: 'recovery', planned: 2, accepted: 2, rejected: 0 }\n ],\n stopCriterion: 'packet-reconciled-after-recovery'\n};\n\nfor (const segment of profile.segments) {\n if (segment.planned !== segment.accepted + segment.rejected) {\n throw new Error(`Unreconciled segment: ${segment.name}`);\n }\n}\n\nconst bareCount = { planned: 100000 };\nconst acceptedAsLoadModel =\n 'endpointIntent' in bareCount && 'stopCriterion' in bareCount;\nconsole.assert(acceptedAsLoadModel === false);</code></pre>\n<p>Assertion в конце не измеряет производительность. Она защищает термин. Объект с одним счётчиком не получает статус нагрузочной модели. В коде нет <code>fetch</code>, часов, процесса и внешнего состояния. Поэтому его результат нельзя читать как latency, throughput, error rate или capacity. Если добавить реальный вызов, это станет уже другой проверкой с другими условиями.</p>\n<figure><img src=\"/assets/editorial/2021/load-test-observation-2021.svg\" alt=\"Схема учебной модели нагрузки: endpoint, среда и данные проходят через сегменты в evidence packet, а bare aggregate отклоняется\"><figcaption>Схема показывает границу между планом входа и evidence packet. Красная ветка с одним aggregate отклоняется. Synthetic units не являются latency или throughput.</figcaption></figure>\n<h2>Как читать arrival</h2>\n<p>Arrival — это правило появления следующего входа. Оно не равно completed work. При модели с фиксированным числом виртуальных пользователей следующая итерация может зависеть от завершения предыдущей. При модели с открытым потоком инструмент старается поддержать заданный arrival rate и отдельно показывает, успевает ли система обрабатывать вход. Значение зависит от конкретного executor, версии и конфигурации. Переносить смысл одного параметра между инструментами нельзя.</p>\n<p>Поэтому сначала назовите вопрос. Проверяете поведение endpoint при росте входа? Проверяете сохранение времени ответа при фиксированном потоке? Проверяете прохождение порога ошибок? Для каждого вопроса нужны свои единицы и свой stop criterion. Слово «нагрузка» без этого выбора слишком широко.</p>\n<h2>Почему observability начинается с имён</h2>\n<p>Хорошая метрика имеет смысл до того, как попадёт на dashboard. Назовите endpoint, service, environment, segment и единицу. Не называйте synthetic rejection ошибкой HTTP. Не называйте число слотов RPS, если у него нет времени. Не смешивайте клиентский timeout с ответом сервера. Такие запреты короче, чем последующее расследование неверного графика.</p>\n<p>Смысл полей должен сохраняться при агрегации. Если два результата нельзя сопоставить по endpoint, данным, среде и профилю, их нельзя честно сравнить по одному p95 или total. Пакет свидетельств нужен именно для этого: он показывает, какие условия совпали, а какие изменились.</p>\n<h2>Порядок действий</h2>\n<ol><li>Сформулируйте вопрос теста одним предложением и назовите endpoint, метод и ожидаемый результат.</li><li>Зафиксируйте data setup: identity набора, объём, состояние и способ восстановления.</li><li>Опишите environment envelope: build, конфигурацию, зависимости, лимиты и место запуска.</li><li>Разложите вход на именованные сегменты <code>warm-up</code>, <code>steady</code>, <code>step</code> и <code>recovery</code> либо объясните другой порядок.</li><li>Для каждого сегмента укажите единицу входа и правило arrival. Отдельно запишите, что считается completed work.</li><li>Задайте один stop criterion, связанный с конкретной метрикой или с явной проверкой полноты packet.</li><li>Запустите инструмент отдельно от учебной модели и сохраните версию, конфигурацию и raw output.</li><li>Сведите planned slots с accepted и rejected units. При несовпадении остановите анализ и исправьте данные.</li><li>Сравните результат только с запуском, у которого совпадают endpoint, данные, среда и профиль. После изменения условий дайте запуску новую identity.</li></ol>\n<h2>Ограничения</h2>\n<p>Эта статья не сообщает, какую нагрузку выдержит конкретный сервис. Учебный пример не создаёт пользователей, соединения, DNS, TLS, сеть, очередь, CPU, память, базу, cache, browser или production incident. Synthetic limit показывает только заранее заданную границу модели. Rejected unit не означает HTTP error. Recovery в списке не доказывает восстановление реальной системы.</p>\n<p>Официальная документация инструмента всё равно нужна перед запуском. Сценарии k6 описывают разные профили workload, а thresholds задают pass/fail criteria для конкретных метрик. OpenTelemetry задаёт общие имена и смысл атрибутов, но не превращает любую локальную цифру в корректную телеметрию. Эти источники помогают выбрать термин и критерий. Они не заменяют проверку вашей среды и данных.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Материал и реальный запуск готовы к анализу, если независимый инженер может по packet ответить на шесть вопросов: какой endpoint проверяли; какие данные использовали; в какой среде; как появлялся вход; что считалось выполненной работой; почему тест остановился. Для каждого сегмента сходятся planned = accepted + rejected. Для каждой заявленной метрики указаны источник, единица и окно. Если хотя бы один ответ отсутствует, результат — не verdict о системе, а незавершённый эксперимент.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://grafana.com/docs/k6/latest/using-k6/scenarios/\" target=\"_blank\" rel=\"noopener noreferrer\">Grafana k6: Scenarios</a> — официальное описание сценариев и профилей нагрузки.</li><li><a href=\"https://grafana.com/docs/k6/latest/using-k6/thresholds/\" target=\"_blank\" rel=\"noopener noreferrer\">Grafana k6: Thresholds</a> — официальное описание pass/fail критериев для метрик и остановки теста.</li><li><a href=\"https://opentelemetry.io/docs/specs/semconv/general/metrics/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: General metrics semantic conventions</a> — официальные правила смысла, единиц и атрибутов метрик.</li></ul>"
|
||
}
|