Перенести uploads в облако имеет смысл, когда изображения, документы и пользовательские файлы занимают диск сервера, мешают масштабированию или теряются при развертывании новой версии сайта. Но простое копирование каталога в S3-совместимое хранилище не завершает миграцию: приложение должно правильно записывать новые объекты, формировать URL, проверять права и удалять файлы.

Безопасный перенос выполняется поэтапно: инвентаризация, пробная загрузка, копирование существующих файлов с проверкой, переключение новых uploads и только затем освобождение локального диска. На каждом шаге должен быть путь отката.

Сначала определите цель миграции

  • Освободить место на сервере.
  • Хранить файлы независимо от экземпляров приложения.
  • Подключить CDN и ускорить выдачу изображений.
  • Упростить горизонтальное масштабирование.
  • Разделить публичные и приватные документы.
  • Настроить версионирование, lifecycle и резервное восстановление.

Цель влияет на архитектуру. Для публичных картинок подходит постоянный CDN-адрес, а для договоров и пользовательских документов нужны закрытый bucket и временные подписанные ссылки.

Сделайте инвентаризацию uploads

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

  • Общий объем, количество объектов и скорость прироста.
  • Распределение файлов по типу и размеру.
  • Каталоги, которые являются оригиналами, кешем или временными данными.
  • Ссылки в базе: абсолютные URL, относительные пути или идентификаторы.
  • Файлы, которые должны быть закрыты от публичного доступа.
  • Имена с пробелами, Unicode, разным регистром и нестандартными расширениями.
  • Символические ссылки и файлы, находящиеся вне основного uploads.

Не считайте object storage обычной папкой

S3 хранит объекты по ключам, а не файлы в POSIX-файловой системе. У него нет привычного rename каталога, локальных блокировок и операций дописывания в середину файла. Некоторые функции приложения придется адаптировать.

  • Переименование обычно означает копирование объекта и удаление старого.
  • Папки являются частью ключа, а не самостоятельными каталогами.
  • Проверка существования и массовый list могут стоить времени и запросов.
  • Локальная обработка изображения требует временного файла или потока.
  • Прямой fopen по пути uploads может перестать работать.
  • Права Unix не переносятся: доступ регулируют IAM и policy bucket.

Выберите схему адресов

Лучше отделить логический идентификатор файла от физического URL. В базе можно хранить object key, а публичный адрес строить через конфигурацию. Тогда домен CDN или провайдер меняются без массовой правки записей.

  • Публичные изображения: стабильный домен вида media.example.ru.
  • Приватные документы: object key и подписанный URL с коротким сроком.
  • Оригиналы и производные размеры: предсказуемые разные префиксы.
  • Файлы разных окружений: отдельные bucket или четко изолированные prefix.
  • Имена объектов: безопасные ID вместо пользовательского имени как ключа.

Не записывайте временный signed URL в базу: он истечет. Храните ключ объекта и генерируйте ссылку при выдаче.

Спроектируйте публичный и приватный доступ

Bucket не следует открывать целиком только ради картинок. Разделите классы данных и выдавайте минимальные права. Приложению обычно нужны операции записи и чтения в конкретном prefix, а публичному посетителю — только чтение разрешенных объектов через CDN.

  • Запретите публичный list bucket.
  • Не храните секретные ключи в репозитории и веб-каталоге.
  • Создайте отдельную сервисную учетную запись с минимальными правами.
  • Регулярно меняйте ключи и поддерживайте безопасную ротацию.
  • Проверяйте тип, размер и содержимое пользовательской загрузки до публикации.
  • Для приватных файлов проверяйте право пользователя до создания signed URL.

Выберите способ загрузки новых файлов

Через сервер приложения

Браузер отправляет файл сайту, сервер проверяет его и загружает в object storage. Схема проще для существующего приложения, но трафик и временное хранение проходят через сервер.

Прямая загрузка по подписанному URL

Сервер выдает ограниченное разрешение, браузер загружает объект напрямую и затем подтверждает завершение. Это снижает нагрузку, но требует строгих ограничений ключа, размера, типа, срока действия и отдельной финализации записи.

  • Нельзя доверять только расширению файла.
  • Не подтвержденные загрузки нужно удалять по lifecycle.
  • Идентификатор объекта создается сервером.
  • CORS разрешает только нужные origin и методы.
  • После загрузки проверяется фактический размер и метаданные.

Подготовьте тестовую среду

Создайте отдельный bucket или prefix и перенесите небольшую репрезентативную выборку: изображения, документы, большие файлы, кириллические имена и несколько форматов миниатюр. Не начинайте с рабочего каталога целиком.

  • Загрузка нового файла.
  • Отображение и скачивание.
  • Создание миниатюр и оптимизированных версий.
  • Замена и удаление объекта.
  • Проверка приватного доступа.
  • Работа фоновых задач, импорта и редактора.
  • Поведение при временной недоступности хранилища.

Скопируйте существующие файлы с проверкой

Миграционный скрипт должен быть повторяемым. При перезапуске он пропускает уже подтвержденные объекты или проверяет их, а не создает новые ключи. Для каждого файла сохраняйте локальный путь, object key, размер, контрольную сумму и статус.

  1. Снять согласованный список локальных файлов.
  2. Исключить кеш и временные данные, которые можно пересоздать.
  3. Сформировать детерминированный object key.
  4. Загрузить файл с правильным Content-Type и cache metadata.
  5. Проверить размер и checksum доступным способом.
  6. Записать успешный результат в журнал миграции.
  7. Повторить только ошибочные объекты с ограниченным retry.
  8. Сформировать отчет по пропущенным и конфликтным файлам.

Не меняйте URL до завершения копирования

Если база начнет выдавать облачный адрес до загрузки всех объектов, часть изображений пропадет. Сначала скопируйте и проверьте файлы, затем переключайте чтение. Для большого каталога удобно временно использовать fallback: искать в облаке и при отсутствии читать локальную копию, одновременно фиксируя промах.

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

Организуйте переход без потери новых uploads

Пока идет копирование, пользователи продолжают загружать файлы. Нужна стратегия для изменяющегося набора данных.

  • Короткое окно обслуживания и финальная дельта после остановки записи.
  • Временная двойная запись в локальное и облачное хранилище с контролем ошибок.
  • Сначала переключить новые uploads в облако, затем переносить исторические файлы.
  • Использовать журнал изменений и повторно обработать файлы после начального snapshot.

Двойная запись сложнее, чем кажется: успех в одном хранилище и ошибка в другом создают рассинхронизацию. Если она нужна, храните статус каждой стороны и процедуру восстановления.

Проверьте обработку изображений

  • Где создаются миниатюры: до загрузки, в worker или облачной функцией.
  • Как связаны оригинал, WebP/AVIF и размеры для разных экранов.
  • Не запускается ли повторная оптимизация уже сжатого файла.
  • Сохраняется ли ориентация и корректно ли обрабатывается EXIF.
  • Какие Content-Type и Cache-Control получает каждый вариант.
  • Удаляются ли все производные объекты при удалении оригинала.

Подключите CDN без вечного старого кеша

Для файлов, которые могут заменяться, лучше использовать versioned URL или новый object key. Если адрес остается прежним, нужен управляемый purge и подходящий Cache-Control. Слишком короткий кеш увеличивает стоимость и задержки origin, слишком длинный показывает старые изображения.

  • Статические неизменяемые файлы получают долгий кеш и версию в имени.
  • Приватные signed URL не кешируются публично без отдельной архитектуры.
  • CDN origin закрыт от нежелательного обхода, если это требуется.
  • TLS и собственный media-домен настроены до переключения ссылок.
  • Ошибки 403, 404 и 5xx хранилища наблюдаются отдельно.

Не путайте object storage и резервную копию

Перенос в облако не гарантирует восстановление после ошибочного удаления, повреждения приложения или компрометации ключей. Настройте versioning, защиту от удаления и независимый план backup по требованиям проекта.

  • Проверяемый срок хранения версий.
  • Lifecycle для старых версий и незавершенных multipart upload.
  • Отдельные права на удаление и изменение политики.
  • Регулярная тестовая процедура восстановления.
  • Учет стоимости хранения, запросов и исходящего трафика.

Проверьте ссылки в базе и контенте

Абсолютные URL могут находиться в HTML статей, JSON, настройках темы, CSS и сериализованных полях. Не выполняйте слепую текстовую замену во всей базе. Используйте структуру данных и штатные инструменты CMS, сохраните резервную копию и отчет об изменениях.

  • Ссылки главной страницы и карточек.
  • srcset и lazy-load атрибуты.
  • Open Graph и изображения для соцсетей.
  • Файлы в письмах и PDF-шаблонах.
  • API-ответы мобильного приложения.
  • Кеш страниц и поисковый индекс сайта.

План переключения

  1. Проверить тестовый bucket и права.
  2. Развернуть код чтения и записи с feature flag.
  3. Переключить новые uploads или включить контролируемую синхронизацию.
  4. Перенести исторические файлы и проверить отчет.
  5. Выполнить финальную дельту.
  6. Переключить URL и очистить только нужные кеши.
  7. Проверить ключевые страницы, загрузку и удаление.
  8. Наблюдать ошибки и доступность в установленный период.
  9. Локальные файлы оставить как резерв на согласованный срок.
  10. Удалять локальную копию только после подтвержденного восстановления из облака.

Как проверить результат

  • Количество и суммарный размер объектов совпадают с ожидаемыми.
  • Выборочная checksum подтверждает содержимое файлов.
  • Публичные файлы открываются, приватные недоступны без авторизации.
  • Новые загрузки сразу появляются в облаке.
  • Миниатюры, письма, Open Graph и API используют правильные адреса.
  • Удаление и восстановление выполняются по правилам.
  • При временной ошибке хранилища заявка или запись не теряет связь с файлом.
  • Мониторинг видит ошибки SDK, CDN и фоновой обработки.

Типичные ошибки

  • Открыть весь bucket на публичное чтение.
  • Хранить секретный ключ в JavaScript или репозитории.
  • Скопировать файлы без проверки количества и checksum.
  • Переключить URL до завершения переноса.
  • Удалить локальный uploads сразу после первой успешной выборки.
  • Считать S3 обычной файловой системой.
  • Забыть о новых файлах, появившихся во время миграции.
  • Хранить истекающий signed URL вместо object key.
  • Не учитывать стоимость запросов, CDN и исходящего трафика.

Итог

Чтобы перенести uploads в облако без пропавших изображений, нужно адаптировать приложение к object storage, спроектировать ключи и доступ, проверить копирование, учесть новые загрузки и переключить URL только после успешной сверки. Локальную копию удаляют последней, когда откат и восстановление уже проверены.

Если нужно выполнить такую миграцию, я могу провести инвентаризацию файлов и ссылок, подключить S3-совместимое хранилище, настроить публичный и закрытый доступ, перенести объекты с проверкой, переключить uploads и CDN и подготовить контролируемый откат.