Инженерная практика Межотраслевые

Диагностика на два байта: баг семи месяцев за один вечер

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

1ч выполнено
Диагностика битых XLSX: 2 байта CRLF, 1 час работы

Есть класс багов, которые живут месяцами именно потому, что они ничьи. Разработчики смотрят в код: код чистый. Админы смотрят в сервер: сервер отдаёт файлы как положено. Баг сидит на стыке и пересиживает обоих. Здесь таким стыком оказались два байта.

Сводка

Отрасль конечного клиента детский отдых: сайт лагеря с онлайн-заказом путёвок
Конечный клиент клиент студии; админка отчётов по сменам
Формат сотрудничества абонентская DevOps-поддержка студии: трекер + общий чат
Тип проекта диагностика прикладного бага на стыке кода и сервера
Объём работ 1 сервис отчётов (Yii 2 / PHP 7.4), 1 сервер (nginx + Apache)
Дата проекта 5 сен 2024 – 11 сен 2024 (6 дней)
Трудозатраты 1 ч по трекеру
Команда 2 специалиста (инженер-сисадмин · руководитель проекта)
Технологический стек Yii 2 · PHP 7.4 · nginx · Apache · dd / xxd / file
Сдано причина найдена за вечер: 2 лишних байта CRLF из кода; обходная отдача файлов работает с 11.09.2024

Постановка задачи

В начале 2024 года студия перевезла сайты своего клиента, детского лагеря, с чужого хостинга на собственный сервер. После переезда в админке заказов сломалась одна функция: отчёты по сменам. Кнопка «сгенерировать документ» отдаёт Excel-файл, который не открывается. Тот же отчёт, сохранённый на диск сервера и забранный по FTP, открывается без единой ошибки.

Запрос пришёл словами клиента, без прикрас: «Мы в коде не нашли никаких проблем, и складывается впечатление, что он портится где-то в момент скачивания в браузер. То ли nginx его коверкает при отдаче, то ли ещё чего. Сможете посмотреть, проанализировать?» К этому моменту проблема тянулась с февраля — семь месяцев. Сотрудникам лагеря выдали FTP-доступ, и отчёты они забирали вручную с сервера. Жить можно, работать неудобно.

Что в этом сложного

Такой баг дорог не часами работы, а тем, сколько он висит. Он не роняет сайт, поэтому никогда не попадёт в очередь срочных аварий. Разработчики проверили свою зону и честно ничего не нашли; на старом хостинге тот же код работал, значит «виноват сервер»; админ смотрит на сервер: сервер штатный. Каждый прав, а файл битый. Из этой точки у студии обычно два пути: переписывать выгрузку наугад или возить отчёты по FTP вечно. Тому, кто платит за поддержку, нужен третий: локализовать виновный слой за конечное, заранее понятное время.

Как мы это сделали

1. Сначала факт, потом гипотезы. Взяли два экземпляра одного отчёта: скачанный браузером и его близнец с диска сервера. Скачанный оказался на 2 байта длиннее. Отрезали эти 2 байта через dd ... bs=1 skip=2 — и «битый» файл открылся. Утилита file подтвердила: усечённая копия — Microsoft Excel 2007+, оригинал — безликая data. Сами байты вытащили и посмотрели в xxd: 0d0a. Это CRLF, перевод строки. Формулировка задачи сжалась с «где-то что-то портится» до «кто-то дописывает rn перед телом файла».

2. Слой-виновник искали контрольными отдачами, а не спорами. Гипотезу про nginx (сжатие, правка ответа) инженер снял сразу, по опыту: ничего подобного nginx с телом ответа не делает, что и подтвердилось дальше. С Apache — та же история. Решающих тестов было два. Тот же код отдачи на соседнем проекте этого же сервера, но на PHP 8.2, вернул файл целым. А простой PHP-скрипт в три строки, который забирает готовый XLSX с диска и отдаёт его мимо фреймворка, вернул целый файл с той же самой площадки. Диск чист, веб-серверы чисты, PHP чист. Остался один подозреваемый — цепочка отдачи Yii.

3. Серия «не сработало» — тоже результат. Параллельно разработчики студии проверяли точечные гипотезы. MIME-тип application/zip вместо xlsx — мимо. Слэш в Content-Disposition (полный путь вместо имени файла; нашли попутно, поправили): имя при скачивании стало нормальным, файл всё ещё битый. sendFile() вообще без ручных заголовков: битый. auto_detect_line_endings в настройках PHP — мимо. Каждый промах сужал круг: дело не в заголовках и не в конфигурации, CRLF впрыскивается где-то между отправкой заголовков и телом файла.

4. Решение выбрали по цене вопроса. Можно было копать внутренности фреймворка на PHP 7.4 в поисках строчки, которая печатает лишний перевод строки. Вместо этого инженер предложил обход: отдельный скрипт-прокладка вне Yii, который по идентификатору забирает готовый файл с диска и отдаёт его браузеру, а интерфейс выдаёт ссылку на прокладку. И назвал вещи своими именами: «костыль, да. но работать будет». Через 5 дней разработчики подтвердили в чате: «Костылик рабочий))». Отчёты снова скачиваются из админки, FTP-карусель закончилась.

Инциденты и реакция

Простой — ноль. Диагностика шла на работающей админке: эксперименты гоняли на тестовой смене, не трогая боевые заказы, а выданный для проверки доступ инженер вернул в тот же день, сам напомнив: «доступ в админку можно убирать». Деструктивных действий на сервере не было вовсе: вся побайтовая хирургия делалась на копиях файлов.

Результаты

Метрика Значение
Возраст проблемы к моменту обращения ~7 месяцев (с февраля 2024)
Время до локализации слоя-виновника один вечер: взято в работу в 15:30, к 18:39 виновник определён
Трудозатраты по трекеру 1 ч
Физический размер дефекта 2 байта (0d0a, CRLF) перед телом XLSX
Исключено слоёв диск → nginx → Apache → версия PHP → фреймворк
Статус обходная отдача в работе с 11.09.2024, подтверждена разработчиками

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

Команда

  • инженер-сисадмин (бюро) — побайтовая диагностика, исключение слоёв, схема обходной отдачи
  • руководитель проекта (бюро) — параллельные гипотезы (сжатие nginx, auto_detect_line_endings), координация с разработчиками студии

Проверки внутри кода вели разработчики на стороне клиента: диагностика шла в четыре руки, в одном чате, в один вечер.

Антон Херсун, Xaver Pro — руководитель проекта.

Похожая история — файл «портится непонятно где», а сервис месяцами живёт на обходном пути? Пришлите описание симптомов. Посмотрим, назовём виновный слой и вернёмся с оценкой в часах. Разбор — бесплатно.

Запросить разбор →

Прокрутить вверх