Как Cloudflare расследовала «невидимый» баг в архитектуре

Инженерная команда Cloudflare столкнулась с необычной проблемой после оптимизации архитектуры сервиса Cloudflare Images. Переход с сетевых сокетов на локальные Unix-сокеты, призванный повысить производительность, привел к неожиданным последствиям: пользователи начали жаловаться на поврежденные или неполные изображения.

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

Поиск истины с помощью strace

Прорыв в расследовании произошел после использования strace — инструмента для отслеживания системных вызовов ядра. Выяснилось, что при передаче крупных файлов процесс выполнял системный вызов sendto для записи в буфер сокета, но сразу после этого инициировал закрытие соединения (shutdown), даже если часть данных оставалась во внутреннем буфере библиотеки.

Причиной оказалась ошибка в hyper — популярной HTTP-библиотеке на языке Rust. Баг, присутствовавший в коде годами, скрывался в цикле обработки соединений (dispatch.rs):

let _ = self.poll_flush(cx)?;

В этом фрагменте результат вызова функции poll_flush игнорировался. Если буфер сокета на стороне получателя временно переполнялся, функция возвращала статус Poll::Pending, сигнализируя, что данные не были переданы полностью. Однако из-за конструкции let _ библиотека ошибочно считала операцию завершенной и преждевременно закрывала сокет, что приводило к потере «хвоста» файла.

Уроки для высоконагруженных систем

Этот инцидент наглядно демонстрирует важность глубокого тестирования межсервисного взаимодействия. При работе с Kubernetes, sidecar-контейнерами или service mesh сетями критически важно:

  • Проводить стресс-тесты на переполнение буферов.
  • Активно симулировать поведение «медленных клиентов» (slow clients) в тестовой среде.
  • Не игнорировать возвращаемые статусы асинхронных операций в низкоуровневом коде.