Самые опасные замеры — не те, что падают с ошибкой, а те, что уверенно возвращают красивое число. Ноль ошибок, всё холодное, ровно ноль совпадений. Ниже — шесть случаев из нашей практики, когда замер выглядел безупречно, а на деле не измерял ничего. Каждый раз мы чуть не сделали неверный вывод, а пару раз сделали.
Это шестая часть цикла «Мастерская разработчика».
1. Четыре прогона по 0,0 секунды
Гоняли воркер прогрева кэша и получили четыре прогона подряд по 0.0s и ноль предупреждений. Выглядело блестяще. На деле прошлый прогон мы сами прервали по таймауту, он оставил замок (транзиент на 3 минуты), и каждый следующий вызов упирался в замок и выходил, не сделав ни одного запроса. Ноль предупреждений — потому что нечего было предупреждать.
Прогон, который отработал подозрительно быстро, — не успех, а подозрение. Перед замером воркера с замком смотреть сам замок. После — побочный след: уменьшилась ли очередь, появилась ли запись в логе.
2. «679 из 679 холодных»
Спросили хранилище кэша, какие ячейки на месте, и получили «холодных 679 из 679» — сразу после выката. Чуть не подняли тревогу о развале кэша. Причина — тип аргумента: функции проверки передали задачи прогрева, а она ждала ключи ячеек (ключ из задачи строит отдельная функция). Ни один ключ не совпал. Правильный вызов — той функцией, которой пользуется сам продукт, — дал ноль холодных.
В другой раз — тот же класс ошибки: «очередь пуста при 1293 холодных ячейках — догрев после выката не работает». Смотрели не ту опцию: очередь называлась иначе, и в ней лежало 1094 задачи. Три замера подряд показали бы 1293 → 1152 → 999 за три минуты — и сняли бы вопрос без единой гипотезы.
Круглый экстремум — ровно ноль или ровно всё — почти всегда значит, что спрошено не то. Прежде чем объявлять поломку, позовите ту же функцию, что продукт, и посмотрите динамику, а не один снимок.
3. Ноль от собственного разбора вывода
Скрипт выката правила для fail2ban проверял правку утилитой fail2ban-regex и откатывал, если дата в журнале не распознаётся. Он откатил верную правку. Число совпадений мы вытаскивали шаблоном \d+(?= hit), а утилита печатает его в квадратных скобках:
Date template hits:
|- [# of hits] date format
| [400] "time":"Year-Month-DayT24hour:Minute:Second
Шаблон не совпал → «0» → «дата не распознана» → откат. Дата распознавалась во всех 400 строках.
Прежде чем поверить в ноль, убедитесь, что зонд способен вернуть не ноль. Один раз прогоните разбор на заведомо ненулевом случае — особенно если разбираете чужой вывод, формат которого меняется от версии к версии.
Рядом — ошибка подписи. Замер был подписан «идёт ли поток прямо сейчас, за последние 3 минуты», а считал по tail -300 — последним 300 строкам, что при редком трафике покрывает полчаса. Число 199 выглядело внушительно и не значило ничего. Подпись должна описывать то, что замер делает, а не то, что хотелось измерить: через десять минут сам поверишь подписи.
4. Таймаут — не «порт закрыт»
exec 3<>/dev/tcp/$HOST/$PORT && head -c 60 <&3 # пусто, код 124 — «порт не отвечает»?
Порт отвечал: честный ssh тут же получил Permission denied (publickey). head -c 60 ждёт ровно 60 байт, а приветствие SSH короче — около 40, и больше сервер не пришлёт, пока клиент не поздоровается сам. Зонд повис на добор и был убит таймаутом. Читать построчно:
exec 3<>/dev/tcp/$HOST/$PORT; IFS= read -r -t 6 line <&3; echo "$line"
Если зонд может «не дождаться», а не «получить отказ», таймаут ничего не говорит о доступности. Подтверждать инструментом, который знает протокол: ssh, curl, openssl s_client. Из той же серии: wc -l по /var/log/nginx/access.log внутри официального контейнера nginx висит вечно — это ссылка на /dev/stdout. Журнал читается через docker logs.
5. Символы против байтов
После вставки строки в 18 файлов скрипт отчитался: «20522 → 15045». Похоже на потерю 5 КБ. На деле «до» было в байтах (wc -c), а «после» — len() строки, то есть в символах; кириллица в UTF-8 занимает два байта. Каждый файл вырос ровно на 1039 байт. Единица измерения — часть результата: мерить тем же инструментом, что и «до», а при подозрении на потерю смотреть содержимое, а не только размер.
6. Ручные прогоны подряд — не работа по расписанию
Пять ручных прогонов обхода подряд — ноль предупреждений пула PHP-FPM. Первый же обход по крону — всплеск. Между тиками крона есть паузы, в ручном цикле их нет, и нагрузка ложится иначе. Финальную проверку надо гнать в том режиме, в каком механизм живёт на самом деле, и считать за окно времени, а не «есть/нет».
Вопросы к любому замеру
- может ли этот замер в принципе вернуть другой ответ?
- не упёрся ли он в замок, кэш или ранний выход?
- тот ли аргумент, та ли опция, та ли функция, что у продукта?
- не круглое ли число — ноль или всё?
- что значит подпись и что на самом деле посчитано?
- в той ли единице «до» и «после»?
- один снимок или динамика?
Мониторинг «зелёный», а пользователи жалуются?
Пришлите, как вы меряете, — найду, где замер отвечает не на тот вопрос.
// загружаю комментарии…