Файл успешно появляется в Object Storage, но связанная облачная функция запускается не всегда. Одни изображения обрабатываются, другие остаются без превью; часть документов попадает в базу, а часть приходится запускать вручную. Снаружи это выглядит как случайный сбой Storage-триггера, хотя пропуск обычно возникает на одном из трех этапов: событие не было создано, триггер его отфильтровал или функция получила событие, но не завершила обработку.
Поэтому начинать нужно не с повторного создания триггера, а с восстановления пути конкретного объекта: загрузка, событие, доставка, запуск функции и запись результата. Такой подход одинаково полезен для AWS S3, Yandex Object Storage, Google Cloud Storage и совместимых S3-хранилищ, хотя названия журналов и типов событий у провайдеров различаются.
Как понять, на каком этапе потерялся файл
- объект есть в бакете, но в журнале событий нет его ключа — проверяйте тип загрузки и конфигурацию уведомлений;
- событие сформировано, но invocation функции отсутствует — проверяйте фильтры, права вызова, регион и состояние триггера;
- вызов функции есть, но результата нет — изучайте логи функции, таймауты, память, повторные попытки и внешние зависимости;
- результат появляется после повторной загрузки — ищите гонку, временную ошибку или неидемпотентную логику;
- пропускаются только файлы с определенными именами, каталогами или расширениями — проверяйте prefix, suffix и кодирование ключа.
Для расследования возьмите один обработанный и один пропущенный объект, загруженные одинаковым способом. Запишите bucket, полный object key, version id, размер, ETag, время завершения загрузки и идентификатор запроса. Сравнение двух конкретных случаев быстрее показывает различие, чем просмотр общего потока логов.
Почему Storage-триггер пропускает некоторые файлы
Фильтр не совпадает с реальным ключом объекта
Фильтр применяется к полному object key, а не к имени, которое видно пользователю. Префикс uploads/ не совпадет с /uploads/, а suffix .jpg может не принять .JPG. Пробелы, символы кириллицы, знак плюс и URL-кодирование также часто обрабатываются неверно уже внутри функции. Сначала сравните исходный ключ из события, а затем декодируйте его ровно один раз.
Отслеживается не тот тип события
Обычная загрузка, копирование внутри бакета, multipart upload, восстановление архивного объекта и создание новой версии могут завершаться разными событиями. Если триггер подписан только на один узкий тип, файл будет существовать, но ожидаемое уведомление не подойдет под правило. Отдельно проверьте, какое событие возникает после CompleteMultipartUpload, а не после загрузки каждой части.
Триггер привязан к другому бакету, региону или окружению
В проектах с dev, stage и production конфигурация часто выглядит одинаково. Функция обновлена в одном окружении, а триггер продолжает слушать старый bucket ARN, идентификатор каталога или регион. Проверяйте не отображаемое имя, а полный идентификатор ресурса и версию функции, которую вызывает триггер.
Недостаточно прав на вызов или чтение объекта
Сервис событий должен иметь право вызвать функцию, а сервисный аккаунт функции — прочитать объект и, при необходимости, расшифровать его KMS-ключом. При этом invocation может быть успешным, а чтение файла — завершаться 403. Пользователь видит необработанный файл и считает, что триггер не сработал, хотя ошибка находится внутри функции.
Функция упирается в лимиты
Пиковая пакетная загрузка способна исчерпать concurrency, квоту вызовов, память или соединения с базой. Большой файл увеличивает время скачивания и обработки, а холодный старт съедает часть таймаута. Без очереди и корректных retry временный отказ превращается в постоянный пропуск.
Обработчик считает повторное или старое событие новым результатом
Object Storage обычно дает доставку как минимум один раз, а строгий порядок событий не гарантируется. Событие удаления или перезаписи может прийти раньше события создания предыдущей версии. Если обработчик не учитывает version id, sequencer или generation, позднее событие способно затереть актуальный статус и создать впечатление, что файл не обрабатывался.
Пошаговая диагностика пропущенного события
- Убедитесь, что загрузка завершена: объект доступен, имеет ожидаемый размер, ETag и версию.
- Найдите операцию создания объекта в audit log хранилища и зафиксируйте точное серверное время.
- Проверьте конфигурацию триггера: bucket, регион, event type, prefix, suffix, сервисный аккаунт и целевую версию функции.
- Откройте метрики invocation за узкий интервал вокруг загрузки. Нулевой вызов и ошибочный вызов требуют разных исправлений.
- Найдите событие по object key или request id в логах функции, очереди и dead-letter queue.
- Сравните пропущенный файл с успешным: способ загрузки, регистр расширения, размер, шифрование, content type и путь.
- Отправьте в функцию сохраненный тестовый event. Если он обрабатывается, проблема до функции; если нет — внутри обработчика.
- Повторите тест серией файлов и проверьте не только итог, но и количество событий, попыток и уникальных object versions.
Не ограничивайтесь поиском строки ERROR. Таймаут или принудительное завершение контейнера может не попасть в прикладной лог. Сопоставляйте платформенную метрику вызова, длительность, статус завершения и последнюю запись обработчика.
Как исправить фильтры и контракт события
Временно уберите узкие prefix и suffix на тестовом триггере и журналируйте только безопасные метаданные события. Так можно увидеть фактические object keys и типы операций. После этого верните фильтры с учетом регистра, структуры каталогов и реальных способов загрузки.
Парсер события должен валидировать версию схемы, наличие bucket, object key и version id. Не предполагайте, что все провайдеры и все типы событий имеют одинаковую вложенность JSON. Неизвестную схему лучше отправить в quarantine или DLQ, чем подтвердить событие и потерять файл.
event received validate schema and event type normalize bucket and decode object key once build idempotency key from bucket + key + version reserve processing record download exact object version process and persist result mark processing record completedНадежная схема: событие, очередь и идемпотентный worker
Для важных документов лучше не выполнять всю работу непосредственно в Storage-триггере. Короткая функция принимает событие, валидирует его и кладет задание в надежную очередь. Отдельный worker скачивает объект и выполняет тяжелую обработку. Очередь дает управляемые повторы, контроль нагрузки и место для сообщений, которые не удалось обработать.
- идемпотентный ключ строится из bucket, object key и version id или generation;
- повторная доставка не создает второй результат и не списывает оплату повторно;
- retry применяется только к временным ошибкам с exponential backoff и jitter;
- неисправимые события попадают в DLQ вместе с причиной отказа;
- лимит concurrency защищает базу, API распознавания и другие внешние сервисы;
- статус processing позволяет отличить ожидающую, выполняемую и окончательно ошибочную задачу.
Если платформа уже умеет доставлять события через собственную очередь или EventBridge-подобную шину, используйте этот механизм вместо самодельного фонового запроса. Главное — не подтверждать событие до того, как задание надежно принято системой.
Как безопасно обработать уже пропущенные файлы
Не перезагружайте все объекты вручную: это изменит даты, версии и может породить дубли. Сделайте reconciliation-задачу, которая сравнивает список объектов с таблицей обработок. Для каждого объекта без завершенного результата создайте обычное задание с тем же идемпотентным ключом.
- Зафиксируйте временной диапазон инцидента и сделайте резервную копию таблицы статусов.
- Получайте список объектов постранично, учитывая версии и продолжение листинга.
- Не считайте наличие строки достаточным: проверяйте статус completed и актуальную версию объекта.
- Добавляйте задания небольшими пачками, контролируя очередь, ошибки и нагрузку.
- После backfill повторно сравните хранилище и реестр обработок и сохраните отчет о расхождениях.
Проверка результата после исправления
- маленький и большой файл обрабатываются одинаково надежно;
- обычная, multipart-загрузка и копирование создают ожидаемые события;
- пути с пробелами, кириллицей, плюсом и разным регистром расширения не теряются;
- повторная доставка одного события не создает дубль;
- серия параллельных загрузок не превышает безопасную concurrency;
- временная ошибка приводит к retry, а постоянная — к записи в DLQ;
- перезапись объекта не позволяет старому событию затереть новый результат;
- reconciliation находит искусственно пропущенный объект и ставит его в обработку.
Типичные ошибки при ремонте Storage-триггера
- пересоздать триггер и не выяснить, был ли invocation функции;
- считать наличие файла доказательством события нужного типа;
- декодировать object key дважды и ломать имена с символом плюс или процентом;
- ловить исключение, писать лог и завершать функцию успешным статусом;
- делать бесконечные retry для поврежденного файла без DLQ;
- обрабатывать object key без version id при включенном versioning;
- запускать массовый backfill без идемпотентности и ограничения нагрузки.
Как предотвратить новые пропуски
Добавьте технический реестр обработок и метрики по всей цепочке: количество созданных объектов, принятых событий, запусков, успешных результатов, retry и DLQ. Разница между числом новых объектов и завершенных обработок должна быть наблюдаемой, а не обнаруживаться по жалобе пользователя.
Периодическая reconciliation-задача остается полезной даже при исправном триггере. Событийная доставка может быть надежной, но ошибки конфигурации, исчерпание квот и сбои внешних сервисов полностью исключить нельзя. Плановая сверка делает систему восстанавливаемой.
Когда нужна помощь с облачной функцией
Если Storage-триггер пропускает часть файлов, я могу восстановить цепочку события по логам, проверить фильтры, IAM, лимиты и обработчик, настроить очередь, DLQ, идемпотентность и безопасный backfill. Для оценки пригодятся обезличенный пример успешного и пропущенного object key, конфигурация триггера, тип загрузки и фрагменты логов без секретных ключей.