# Какую задержку шлюза выполнения на рабочем компьютере следует требовать командам?

Шлюз выполнения на рабочем компьютере оправдан лишь тогда, когда его машинная задержка остаётся незаметной в интерактивной работе, а задержка одобрения измеряется отдельно. Я бы не одобрял внедрение по среднему значению, единственному прогретому запуску или графику, где время, за которое человек тянется к Touch ID, смешано со временем работы программы. Такие цифры могут выглядеть аккуратно, но не покажут команде инженеров, будет ли шлюз прерывать цикл «изменить код, проверить результат».

Для обычных HTTP-операций и SSH через уже подключённое соединение я использую предварительный порог внедрения: не более 25 мс добавленной задержки на p50 и 75 мс на p95 на целевых Mac разработчиков. Для новых SSH-соединений нужен отдельный, более крупный запас: не более 50 мс на p50 и 150 мс на p95. Считайте это стартовыми требованиями, а не универсальными константами. Команда должна ужесточать или смягчать их с учётом своего прямого базового уровня, частоты вызовов, сети и тестов восприятия. Описанный ниже тест даёт данные для такого решения и не прячет медленный хвост за достойной медианой.

## Процентили должны описывать одну и ту же операцию

Значения p50 и p95 имеют смысл, только если каждое измерение охватывает одну и ту же границу. p50 - это медиана: половина наблюдаемых вызовов завершилась за это время или быстрее. p95 - значение, за которое завершились 95 процентов вызовов. В серии из 200 отсортированных измерений p95 по методу ближайшего ранга будет 190-м значением. Так десять более медленных наблюдений остаются видимыми в хвосте, а не растворяются в среднем.

Определите границу как время от начала операции вызывающей стороной до получения полного результата. Именно такую задержку ощущает агент, который программирует. В неё входят работа локального процесса, передача к цели, удалённое выполнение, передача ответа и работа шлюза на этом пути. Она не включает подготовку теста и, в сценарии без наблюдения, не включает подтверждение человеком.

Команды нередко сравнивают разные границы. В прямом HTTP-графике может использоваться `time_starttransfer` curl, а в графике шлюза - полное время вокруг запуска процесса. Первое заканчивается на первом байте ответа, второе - после сериализации и возврата результата. Шлюз проигрывает ещё до начала теста. Для обоих путей выбирайте полное завершение, а время отдельных фаз оставляйте в диагностических колонках.

То же правило действует для SSH. Новое TCP-соединение, SSH-рукопожатие, аутентификация, удалённая команда и отключение образуют одну операцию. Команда по существующему мультиплексированному соединению - другая операция. Указывайте их в отдельных строках. Их смесь создаёт распределение, где p50 в основном описывает повторное использование, а p95 - подготовку соединения. Никакой порог не исправит запутанную совокупность.

Для каждого класса операций указывайте как минимум такие колонки:

- прямое p50 и p95 полного времени
- p50 и p95 полного времени через посредника
- добавленные p50 и p95, рассчитанные по парным измерениям
- число измерений, ошибок и тайм-аутов
- хост, состояние сети, версию шлюза и режим одобрения

Парная добавленная задержка важнее, чем вычитание двух главных процентилей. Для измерения `i` вычисляйте `delta_i = brokered_i - direct_i` в одинаковых условиях, затем берите p50 и p95 от разниц. `p95(brokered) - p95(direct)` полезно для контекста, но это не накладные расходы на p95 и такой расчёт может скрыть связь между сетевыми всплесками и работой шлюза.

## Контракт теста не даёт получить удобный результат

Запишите контракт теста до его запуска, потому что почти любая случайная оптимизация меняет сам вопрос. Зафиксируйте модель Mac и версию macOS, питание, тепловое состояние, сетевой путь, регион целевого хоста, HTTP-протокол, согласование SSH-шифров, размеры запроса и ответа, тайм-аут, параллелизм и повторное использование соединений. Фоновая нагрузка должна быть похожа на обычную машину разработчика. Нетронутый ноутбук с закрытыми редакторами и процессами сборки отвечает на лабораторный вопрос.

Используйте одну локальную цель и одну реалистичную удалённую. Локальная цель показывает машинные накладные расходы, потому что вариативность сети почти отсутствует. Удалённая цель показывает, остаются ли эти накладные расходы заметными при реальной задержке передачи. Шлюз, который добавляет 40 мс к loopback-запросу на 3 мс, ощущается совсем иначе, чем такой же прирост в удалённом вызове на 300 мс, но в отчёте нужны оба результата.

Создавайте классы операций, а не одну смешанную серию. Полезный минимум: небольшой HTTP-ответ по повторно используемому соединению, небольшой HTTP-ответ по новому соединению, новое SSH-соединение с командой `true` и SSH-команда по уже доступному соединению. Добавьте один типичный запрос и один типичный ответ из рабочего процесса команды по программированию. Не используйте разрушающий API или SSH-хост рабочей среды для проверки задержки.

Сначала выполните прогрев, который не попадёт в набор измерений. Затем соберите не менее 200 измеренных вызовов на класс, чередуя прямой путь и путь через посредника. Если все прямые вызовы идут первыми, последующее изменение температуры, сети, DNS-кэша или серверного кэша станет выглядеть как накладные расходы шлюза. Случайный порядок допустим, но строгое чередование проще проверять.

Для интерактивного теста сохраняйте параллелизм равным единице. Агент, ожидающий результат инструмента, работает последовательно. Если шлюзом будут пользоваться несколько агентов, проведите второй нагрузочный тест, но пометьте его как проверку ёмкости. Тест с 20 параллельными запросами может выявить пределы очередей, но не заменяет результат задержки одного вызова.

Ошибки должны остаться в отчёте. Записывайте тайм-аут как ошибку с его фактическим временем ожидания и публикуйте долю ошибок рядом с процентилями. Молчаливое исключение ошибок награждает путь, который быстро ломается или отбрасывает самые медленные вызовы. Если любой путь даёт столько ошибок, что его p95 нестабилен, перестаньте спорить о миллисекундах и сначала исправьте надёжность.

## Измеряйте HTTP, не меняя его смысл

Используйте одинаковые метод, URL, заголовки, тело, тайм-аут и получение ответа для прямого HTTP и HTTP через посредника. Источник учётных данных может отличаться, ведь их изоляция и есть цель шлюза, но сервер должен видеть запросы с эквивалентным смыслом. До сбора данных времени убедитесь, что оба варианта получают одинаковый статус, выбранные заголовки ответа и хеш тела.

curl предоставляет полезные часы фаз в документированных переменных write-out. `time_connect` заканчивается после установки сетевого соединения, `time_appconnect` включает TLS-рукопожатие, `time_starttransfer` достигает первого байта ответа, а `time_total` - полного завершения. Эти значения помогают найти регрессию, но границей сравнения остаётся полное время вызывающей стороны, потому что посредник может работать до запуска curl или после ответа от сервера.

Этот прямой запрос выводит четыре поля в секундах, не добавляя ссылку и не разбирая вывод о ходе работы:

```sh
curl -sS -o /dev/null -w '%{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}
' -H "$AUTH_HEADER" "$ENDPOINT"
```

Для парного измерения поместите полный прямой вызов в `DIRECT_CMD`, а полный вызов через шлюз - в `BROKER_CMD`. Следующий запускающий скрипт чередует их порядок, использует монотонные часы, сохраняет ошибки и выводит один JSON-объект на каждое наблюдение. Он намеренно обрабатывает каждую команду как вектор аргументов, чтобы операторы оболочки и подстановки не попадали в тест.

```python
import json
import os
import shlex
import subprocess
import time

samples = int(os.environ.get("SAMPLES", "200"))
timeout = float(os.environ.get("TIMEOUT", "10"))
commands = {
    "direct": shlex.split(os.environ["DIRECT_CMD"]),
    "brokered": shlex.split(os.environ["BROKER_CMD"]),
}

for n in range(10):
    label = "direct" if n % 2 == 0 else "brokered"
    subprocess.run(commands[label], stdout=subprocess.DEVNULL,
                   stderr=subprocess.DEVNULL, timeout=timeout)

for n in range(samples):
    order = ("direct", "brokered") if n % 2 == 0 else ("brokered", "direct")
    for label in order:
        started = time.monotonic_ns()
        try:
            result = subprocess.run(commands[label], capture_output=True,
                                    timeout=timeout)
            ok = result.returncode == 0
            code = result.returncode
        except subprocess.TimeoutExpired:
            ok = False
            code = None
        elapsed_ms = (time.monotonic_ns() - started) / 1_000_000
        print(json.dumps({"pair": n, "path": label, "ms": elapsed_ms,
                          "ok": ok, "code": code}, separators=(",", ":")))
```

Сохраняйте вывод в файл JSON Lines. До расчёта задержки сравните коды успеха и хеши ответов вне измеряемого цикла. Запрос через посредника, который возвращает меньше данных, будет казаться быстрее; прямой запрос, который следует перенаправлениям, когда посредник этого не делает, измеряет другую работу.

Повторное использование соединений требует явного контроля. Если протокол агента и шлюз сохраняют исходящее HTTP-соединение, сравнивайте его с прямым клиентом, который тоже сохраняет соединение. Новый процесс curl для каждого прямого измерения может лишить путь повторного использования и искусственно приукрасить путь через посредника. В тесте на основе процессов либо принудительно создавайте новые соединения с обеих сторон, либо напишите двух небольших постоянных клиентов. Опубликуйте выбранный вариант.

## Для SSH нужны измерения новых и повторно используемых соединений

Настройка SSH часто занимает больше времени, чем короткая удалённая команда, поэтому один процентиль SSH мало о чём говорит. Руководство клиента OpenSSH описывает совместное использование соединений через `ControlMaster` и `ControlPersist`. При включённом совместном использовании последующие сеансы могут повторно использовать существующее сетевое соединение, не проходя заново настройку транспорта и аутентификацию. Это допустимая оптимизация для рабочей среды, но только если оба пути получают равную возможность ею воспользоваться.

Для класса новых соединений отключите совместное использование, запустите безопасную команду вроде `true` и отключитесь. Прямая команда на выделенном тестовом хосте может выглядеть так:

```sh
ssh -F /dev/null -o BatchMode=yes -o ControlMaster=no -o ConnectTimeout=10 "$TEST_HOST" true
```

Для парного вызова через посредника используйте обычное действие SSH шлюза с тем же хостом, пользователем и удалённой командой. Не заставляйте шлюз проходить через искусственную настройку, которой агент никогда не будет пользоваться. Вместо этого приведите прямой путь в соответствие с документированным поведением подключения шлюза или укажите архитектурное различие в результате.

Для класса повторного использования установите соединение до прогрева и во время выборки запускайте только удалённую команду `true`. Проверяйте повторное использование, а не предполагаете его. Подробная диагностика OpenSSH может показать, обращается ли клиент к управляющему сокету; шлюз должен предоставлять достаточно диагностических временных данных или логов, чтобы установить, использовал ли он транспорт повторно. Если это нельзя доказать, пометьте класс как «неизвестное состояние соединения» и не сравнивайте его с путём, где повторное использование подтверждено.

Затем повторите тест с одной реальной неразрушающей задачей, например чтением небольшого фиксированного файла или запросом статуса у тестового сервиса. `true` изолирует соединение и механизм выполнения, но не покрывает передачу вывода, декодирование или ограничения результата. Содержимое файла должно быть фиксированным, а возвращённые байты нужно хешировать.

Аутентификация SSH тоже меняет сравнение. Агент не должен получать закрытый ключ только ради удобного прямого базового уровня. Запускайте прямой путь в изолированном стенде с эквивалентным доступом к сети и считайте доступ к его учётным данным частью тестовой конфигурации, а не одобренным проектным решением. Задержка показывает цену посредничества. Она не решает, допустимо ли давать автономному процессу ключ.

## Инструментируйте шлюз вокруг его собственной работы

Внешний секундомер подтверждает видимый пользователю результат, а внутренние метки времени объясняют его. Добавьте монотонные метки времени для получения запроса, завершения проверки запроса, завершения решения об авторизации, завершения операции с учётными данными, отправки запроса вверх по цепочке, первого байта от сервера, завершения ответа от сервера, записи аудита и выдачи результата. Записывайте длительности или общий идентификатор трассировки, но не секреты и не тела запросов.

Эти метки делят добавленную машинную задержку на части, с которыми команда может работать:

- разбор и сериализация локального протокола
- поиск авторизации и ожидание в очереди
- операция с хранилищем или учётными данными
- настройка исходящего клиента и получение соединения
- сохранение аудита и возврат результата

Не сравнивайте метки настенных часов отдельных процессов, если схема измерения не учитывает различия часов. На одном Mac монотонные часы в каждом процессе стабильны для измерения длительностей, но их точки отсчёта могут не совпадать в разных средах выполнения. Самый безопасный подход - пусть каждый компонент сообщает собственную длительность, а записи объединяются по непрозрачному идентификатору теста.

Работа с аудитом входит в измеряемую границу, если шлюз обещает записать действие до сообщения об успехе. Перенос записи после ответа может улучшить тест за счёт ослабления гарантии. Пакетная запись может быть оправдана, но опубликованная точка сохранности должна соответствовать рабочей среде. Тест должен измерять продукт, которым будут пользоваться люди, с обычным журналом и включёнными средствами безопасности.

Профилируйте только после того, как данные фаз укажут на подозрительный участок. Если p50 растёт с размером ответа, проверяйте копирование и сериализацию. Если p95 растёт, а p50 остаётся ровным, проверяйте блокировки, ожидание очереди, сброс аудита, промахи пула соединений и планирование операционной системы. Профиль CPU спокойного вызова около медианы редко объясняет задержку в хвосте.

Защитите сам тест от стоимости наблюдения. Подробное логирование в терминал может добавить блокирующий ввод-вывод. Пишите компактные записи в файл или буфер в памяти, один раз измерьте с включёнными диагностическими событиями и убедитесь, что разница незначительна. По возможности сохраняйте одинаковый диагностический режим для прямых запусков и запусков через посредника.

## Подтверждение человеком - отдельный уровень обслуживания

Время подтверждения начинается, когда карточка одобрения становится видимой, и заканчивается, когда решение достигает заблокированного вызова. Это не машинная задержка шлюза. Если смешать его с автоматическими вызовами, p95 будет зависеть от положения руки, внимания, состояния экрана и отвлечений. Это может быть важной метрикой рабочего процесса, но она отвечает на вопрос, вписывается ли одобрение в работу, а не эффективен ли путь выполнения.

Для защищённых действий публикуйте три распределения. Сначала фиксируйте машинное время шлюза до запроса. Затем время от видимого запроса до решения. Затем машинное время после решения до выдачи результата. Сквозное значение можно показать рядом с ними, если никто не использует его для диагностики накладных расходов передачи.

Не автоматизируйте клики и не называйте это человеческой задержкой. Автоматизация может измерить показ интерфейса и обработку решения, что относится к машинным категориям. Для исследования с людьми нужны согласившиеся участники, определённая задача и временные метки, не захватывающие чувствительный ввод. Указывайте число участников и знали ли они о предстоящем запросе. Ожидаемое подтверждение Touch ID во время теста по сценарию будет быстрее неожиданного одобрения при реальном программировании.

Режимы одобрения тоже требуют отдельных строк. Действие, разрешённое для уже авторизованного сеанса, первый вызов с запросом авторизации процесса и подтверждение каждого вызова по устройству проходят разными путями. Смешайте их, и результат будет зависеть от их случайной доли в выборке.

Полезный бюджет рабочего процесса описывает прерывание, а не делает вид, что каждое одобрение обязано уложиться в 100 мс. Например, потребуйте, чтобы карточка появлялась в течение 150 мс после поступления вызова в шлюз на p95, а затем измеряйте время решения человека без проходного порога, пока команда не увидит реальное использование. Позже задайте цель на основе отменённых действий, ошибочных одобрений и нарушения хода работы. Быстрое одобрение вредно, если карточка скрывает сведения о личности или действии, необходимые для обоснованного выбора.

## Считайте разницы до построения графика

Калькулятор результатов должен сопоставлять наблюдения по парам, исключать неполные пары из распределения задержки и сообщать о таких парах как об ошибках. Это не даёт успешному прямому вызову сравниваться со следующим вызовом через посредника, если его настоящий парный вызов завершился тайм-аутом. Используйте номер пары из запускающего скрипта как поле соединения.

Везде применяйте одно определение процентиля. Метод ближайшего ранга сортирует значения и выбирает ранг `ceil(p * n)`, начиная с единицы. Библиотеки предлагают интерполированные процентили, которые могут вернуть значение, которого никто не наблюдал. Интерполяция допустима в некоторых видах анализа, но смена метода между ноутбуком и панелью мониторинга создаёт ненужные споры у порога. Укажите метод в отчёте.

Этот небольшой калькулятор читает JSON Lines, выведенный предыдущим скриптом. Он печатает количество значений и p50, p95 по методу ближайшего ранга для прямого времени, времени через посредника и парной добавленной задержки:

```python
import json
import math
import sys

rows = [json.loads(line) for line in sys.stdin if line.strip()]
by_pair = {}
failures = {"direct": 0, "brokered": 0}

for row in rows:
    if not row["ok"]:
        failures[row["path"]] += 1
        continue
    by_pair.setdefault(row["pair"], {})[row["path"]] = row["ms"]

direct = []
brokered = []
deltas = []
for pair in sorted(by_pair):
    values = by_pair[pair]
    if "direct" not in values or "brokered" not in values:
        continue
    direct.append(values["direct"])
    brokered.append(values["brokered"])
    deltas.append(values["brokered"] - values["direct"])

def percentile(values, fraction):
    ordered = sorted(values)
    rank = max(1, math.ceil(fraction * len(ordered)))
    return ordered[rank - 1]

result = {"complete_pairs": len(deltas), "failures": failures}
for name, values in (("direct", direct), ("brokered", brokered),
                     ("added", deltas)):
    result[name] = {
        "p50_ms": percentile(values, 0.50),
        "p95_ms": percentile(values, 0.95),
        "max_ms": max(values),
    }
print(json.dumps(result, separators=(",", ":")))
```

Форма вывода намеренно проста: `complete_pairs`, число ошибок для каждого пути и блок для каждого распределения с `p50_ms`, `p95_ms` и `max_ms`. Добавьте доверительные интервалы, если команда уже ими пользуется, но не позволяйте интервалу заменить проверку исходного хвоста. Процентиль может измениться между запусками из-за изменений в системе или вариативности выборки. Исходные строки подскажут, какое объяснение подходит.

Отрицательные разницы допустимы. Они могут появиться, когда посредник повторно использует соединение, которое прямая команда открывает заново, или когда обычный сетевой шум благоприятствует члену пары через посредника. Не ограничивайте их нулём. Исправьте нечестную настройку соединения или оставьте вариативность в парном распределении. Ограничение смещает каждый процентиль вверх и затрудняет проверку расчёта.

Сохраняйте в данных полную точность, округляя только значения для показа, желательно до одной десятой миллисекунды. Проверка порога должна использовать неокруглённое число. Указанные 75,0 мс могут означать значение чуть выше порога в 75 мс, если форматирование произошло до сравнения.

## Хвост задержки показывает, где исчезает доверие

Интерактивное программирование сильнее страдает от нерегулярной задержки, чем от стабильной небольшой надбавки. Разработчик может привыкнуть к дополнительным 15 мс в каждом вызове инструмента. Случайная пауза в 800 мс создаёт ощущение, что агент сломан, особенно когда несколько вызовов идут подряд. Поэтому p95 - требование для внедрения, а не декоративный график.

Проверяйте исходные наблюдения хвоста. Отмечайте, совпало ли каждое из них с промахом HTTP-соединения, SSH-рукопожатием, разблокировкой хранилища, сбросом аудита, пробуждением приложения, нагрузкой CPU или сетевым всплеском. Не удаляйте выброс только потому, что можете его объяснить. Если такое состояние будет в рабочей среде, ему место в распределении. Исключайте лишь доказанную ошибку теста, сохраняйте исходные данные и публикуйте правило исключения.

p99 может помочь в инженерной работе, но на малой выборке он шумный. При 200 вызовах p99 опирается примерно на два самых медленных наблюдения. Для внедрения я требую p50 и p95, затем проверяю максимум и исходные строки хвоста на признаки ошибок. Более длинный ночной запуск может поддержать работу с p99 после прохождения интерактивного порога.

Прогретое и холодное состояние должны оставаться видимыми. Если пользователи с этим сталкиваются, измеряйте запуск приложения или разблокировку хранилища как отдельную операцию, но не смешивайте со стабильными вызовами. Так же отдельно сообщайте о восстановлении после сна и пробуждения Mac. Постоянно запущенное приложение может отлично выглядеть после прогрева и всё равно плохо обрабатывать первое действие после обеда.

Стоимость последовательности тоже входит в решение. Если задача по программированию делает двенадцать последовательных вызовов, добавленные задержки накапливаются. Воспроизведение записанной и очищенной от чувствительных данных последовательности действий может выявить расходы, которые не видны в отдельных запросах, например нестабильность пула или журнал, замедляющийся по мере роста сеанса. Сохраняйте распределения отдельных операций, а разницу времени выполнения задачи добавляйте как вторичную метрику.

## Задавайте порог по цене взаимодействия

Шлюз проходит проверку, когда добавленная машинная задержка мала и в абсолютном значении, и относительно прямого пути. Для современного рабочего компьютера при типичной нагрузке я начинаю с четырёх пределов:

- HTTP с повторным использованием: 25 мс на p50 и 75 мс на p95
- HTTP с новым соединением: 30 мс на p50 и 100 мс на p95
- SSH с повторным использованием соединения: 25 мс на p50 и 75 мс на p95
- SSH с новым соединением: 50 мс на p50 и 150 мс на p95

Применяйте относительную проверку, когда прямой p95 не меньше 100 мс. В loopback-вызове на 3 мс правило в 20 процентов допускает меньше миллисекунды и в основном измеряет шум. В медленном удалённом вызове одного абсолютного предела недостаточно: он может допустить реализацию, добавляющую большую долю прямого времени. Эти две проверки покрывают разные режимы.

Требуйте, чтобы доля ошибок была не хуже прямого пути плюс небольшой заранее объявленный допуск, а любой тайм-аут по вине шлюза должен автоматически становиться предметом расследования. Я бы не менял надёжность на выигрыш в 10 мс около медианы. Также требуйте эквивалентности результатов и ожидаемой сохранности аудита. Быстрый путь, который меняет ответ или записывает его позже, провалил другой, более серьёзный тест.

Проводите проверку на самом медленном поддерживаемом классе Mac, которым команда действительно пользуется, а не только на новейшей машине на стенде. Повторите её от сети и при типичной нагрузке: открытом редакторе и сборке. Для удалённых целей потребуйте три запуска в разные дни или периоды сети. Это не статистическая церемония, а защита от случая, когда одно удачное окно сети становится обещанием продукта.

Команды могут скорректировать предварительные числа с помощью слепого теста восприятия. Внесите фиксированные задержки в макет шлюза, воспроизведите типичную задачу агента и попросите разработчиков оценить прерывание, не сообщая им величину задержки. Выберите наименьшую задержку, которая стабильно вызывает жалобы, а затем установите предел p95 с запасом ниже неё. Оставляйте допуск 150 мс для нового SSH только если прямое рукопожатие уже занимает основное время и вся задача по-прежнему ощущается мгновенной.

Отклоняйте внедрение, если один распространённый класс операций не проходит предел p95, даже когда общий график проходит. Взвешенная агрегация позволяет частым быстрым HTTP-вызовам скрыть медленные действия SSH. Одобряйте каждый класс операций отдельно, затем проверяйте записанную последовательность задачи, чтобы убедиться: несколько приемлемых надбавок не складываются в неприемлемую паузу.

## Делайте внедрение обратимым и основанным на данных

Начинайте внедрение с канареечной группы и заранее записанного условия отката. Зафиксируйте в системе контроля версий скрипт теста, фикстуры, конфигурацию шлюза и калькулятор результатов. Сохраняйте исходные наблюдения, а не снимки экрана. Любой, кто проверяет решение, должен иметь возможность пересчитать процентили и увидеть каждую ошибку.

Для Sallyport запустите тот же стенд через его HTTP- и SSH-каналы с хранилищем, авторизацией сеанса, настройкой для каждого вызова и поведением аудита, точно соответствующими предполагаемому внедрению. Его зашифрованный аудит-лог с хеш-цепочкой можно проверить офлайн командой `sp audit verify`. Запускайте её после теста, чтобы отчёт о задержке и проверка целостности описывали одни и те же вызовы.

Расширяйте внедрение, только когда каждый класс операций проходит проверку на типичных Mac, хеши результатов совпадают, проверка аудита успешна, а разработчики принимают воспроизведение полной задачи. Во время канареечного этапа оставьте прямой путь для выхода, но фиксируйте каждое его использование и причину. Если люди обходят шлюз, потому что p95 кажется нестабильным, тест не охватил рабочий процесс. Исправьте измерение, прежде чем расширять внедрение.
