Лаборатория диагностики: симптом, гипотеза, проверка
Оглавление · Наблюдаемость · Типичные проблемы
Задача: выбрать проверку, которая различает причины, и оценить результат без лишних изменений среды. Примеры используют вымышленные данные.
Начните с наблюдаемого симптома
«CMS сломалась» — гипотеза. «В 10:00 content-health вернул 503, а health — 200» — наблюдение. Зафиксируйте время, URL/операцию, среду, версию и ожидаемое поведение. Отдельно укажите масштаб: один запрос, один route, все посетители или только одна сеть. Не помещайте реальные tokens и приватные payload в публичный отчёт.
Исходник схемы
flowchart TD
Symptom["Точный симптом и границы"] --> Hypotheses["Несколько возможных причин"]
Hypotheses --> Prediction["Различимые предсказания"]
Prediction --> Probe["Одна целевая проверка"]
Probe --> Evidence["Наблюдение и его ограничения"]
Evidence --> Next{"Причина достаточно подтверждена?"}
Next -->|нет| Hypotheses
Next -->|да| Fix["Исправить и проверить исходный сценарий"]
Проверка должна менять уверенность в гипотезе. Если все причины предсказывают один и тот же ответ команды, нужен другой сигнал. Сначала соберите доступные свидетельства: перезапуск может стереть память и полезное состояние процесса, а смена сразу нескольких параметров затрудняет установление причины.
Опыт 1: health 200, content-health 503
| Гипотеза | Различающее наблюдение | Ограничение |
|---|---|---|
| CMS недоступна по сети | Чтение CMS по тому же внутреннему пути не устанавливает соединение | Внешний admin URL проверяет другой путь |
| Token не подходит | CMS отвечает отказом авторизации на чтение нужного API | Успех server health не проверяет token |
| Ответ CMS не проходит контракт | Доступ есть, выбранный ответ не соответствует схеме адаптера | Валидный JSON ещё не подтверждает контракт |
| Ошибка одной коллекции | Остальные чтения успешны, конкретное чтение падает | Общий content-health не сообщает это отдельно |
Таблица задаёт предсказания, а не обещает четыре разных HTTP-статуса наружу. Сверьте код endpoint: Promise.all объединяет чтения, catch возвращает общий 503. Отсюда нельзя определить, какая коллекция дала ошибку. Выберите одну проверку пути/операции; не включайте debug-печать полного окружения ради поиска token.
Если кешированная страница продолжает открываться, это согласуется с отказом CMS. Она могла не запрашивать зависимость. Чтобы проверить именно свежий путь чтения, используйте соответствующий контракт; не удаляйте все кеши и volumes по умолчанию.
Опыт 2: среднее, хвост и неверное объединение процентов
Этот блок выполняется Node 24, не открывает сервер и не подключает мониторинг. Используем nearest-rank: для отсортированных n>0 значений quantile q — элемент с номером ceil(q × n), начиная с 1. Это определение опыта, не алгоритм всех dashboards.
node --input-type=module <<'JS'
import assert from "node:assert/strict";
const durations = [...Array(95).fill(10), ...Array(5).fill(1000)];
const sorted = [...durations].sort((a, b) => a - b);
const nearestRank = (q) => sorted[Math.ceil(q * sorted.length) - 1];
const mean = durations.reduce((sum, value) => sum + value, 0) / durations.length;
assert.equal(mean, 59.5);
assert.equal(nearestRank(0.95), 10);
assert.equal(nearestRank(0.99), 1000);
const windows = [{ errors: 10, requests: 100 }, { errors: 1, requests: 1 }];
const unweighted = windows.reduce((sum, w) => sum + w.errors / w.requests, 0) / windows.length;
const totals = windows.reduce((a, w) => ({
errors: a.errors + w.errors, requests: a.requests + w.requests,
}), { errors: 0, requests: 0 });
const combined = totals.errors / totals.requests;
assert.equal(unweighted, 0.55);
assert.equal(combined, 11 / 101);
console.log(`mean=${mean}ms p95=${nearestRank(0.95)}ms p99=${nearestRank(0.99)}ms`);
console.log(`без весов=${(unweighted * 100).toFixed(2)}%; вместе=${(combined * 100).toFixed(2)}%`);
JS
Ожидается mean=59.5 ms, p95=10 ms, p99=1000 ms; 55% без весов против 10.89% по суммарным запросам. Пять медленных запросов не исчезают от хорошего p95. Агрегаты теряют часть информации: одинаковый p95 может соответствовать разным хвостам.
У функции quantile здесь намеренно узкая область: непустой заданный массив, q из (0,1]. Для общего входа добавьте валидацию; для пустого окна не делите на ноль. Измените опыт: 94 быстрых и 6 медленных; предскажите p95 и подтвердите результат. Средние p95 двух экземпляров не заменяют p95 объединённой выборки.
Опыт 3: нет записи в логах
Предположим, Caddy отдал ответ, но в Docker logs нет строки о запросе. До вывода «запрос не дошёл» проверьте, включён ли access log, где его output, какое окно просматривается и есть ли sampling/retention. Текущий Caddyfile.production не включает access log. Отсутствие записи при отсутствии инструмента не является свидетельством отсутствия события.
Во втором варианте trace существует только для части запросов. Sampling и неполная propagation могут объяснять пробелы; сравните trace с логами и общими счётчиками. Не экстраполируйте частоту сбоев по выборке traces без понимания отбора. Это концептуальный опыт: tracing в проект не добавлен.
Опыт 4: после релиза стало медленнее
Вымышленные сведения: общий p95 вырос, но релиз одновременно совпал с притоком новых посетителей и большим числом cache MISS. Сначала разделите route, HIT/MISS, объём запросов и время окна. Затем проверяйте выбранную гипотезу: количество CMS чтений, задержку зависимости, CPU или ожидание соединения. Совпадение по времени не позволяет выбрать причину автоматически.
Снятый CPU-профиль показывает исполняемые участки процесса; сетевое ожидание требует других измерений. Полное упражнение по profiling остаётся отдельной темой. Не запускайте нагрузочный тест на VPS без отдельной задачи и плана нагрузки.
Результат расследования
Запишите исходный симптом, проверенные гипотезы, различающее свидетельство, изменение и результат того же пользовательского сценария. Если причина не найдена, назовите текущую границу и следующий сигнал. «После restart всё работает» подтверждает восстановление доступности, но не установленную первопричину и не отсутствие повтора.
Существующие команды по конкретным симптомам — разбор проблем. Production Docker-команды выполняются через оболочку эксплуатации; в рамках этой лаборатории их не запускаем.