Как устроена система хранения изображений на S3
Что проверяют
Знаете ли вы, как хранить картинки так, чтобы бэкенд на Python не гонял через себя гигабайты и не раздавал приватные файлы всем подряд. Следом спрашивают, как вы делаете превью разных размеров и как закрываете доступ.
Как отвечать
S3 это объектное хранилище: бакет, в нём объекты по ключу, у объекта данные и метаданные (Content-Type, Cache-Control). Папок нет, есть префиксы вроде avatars/<uuid>.webp. С конца 2020 года S3 даёт строгую согласованность чтения после записи. Для S3-совместимых хранилищ в boto3 меняется только endpoint_url.
Схема по шагам:
- загрузка: бэкенд выдаёт presigned-ссылку (
generate_presigned_urlилиgenerate_presigned_postвboto3), клиент грузит файл прямо в бакет. В presigned POST размер ограничивается условиемcontent-length-range, для загрузки из браузера нужен CORS на бакете; - база: в PostgreSQL лежит ключ объекта, владелец, размеры, статус обработки. Не полная ссылка: домен и CDN могут смениться;
- обработка: событие о новом объекте запускает задачу (Celery или облачную функцию), которая через Pillow проверяет, что это картинка, вырезает EXIF с геолокацией, делает превью в WebP. Альтернатива: ресайз на лету через imgproxy с кешем в CDN;
- доступ: бакет закрыт, с апреля 2023 года новые бакеты AWS создаются с Block Public Access и выключенными ACL. Публичное отдаёт CDN (CloudFront с Origin Access Control), приватное: presigned GET с коротким сроком;
- lifecycle: правила удаляют брошенные multipart-загрузки и переносят старое в дешёвые классы хранения.
Где спотыкаются
Проксируют загрузку через свой сервер: FastAPI или Django держит соединение и память на каждый большой файл, хотя presigned-ссылка снимает эту нагрузку.
Строят ключ из имени файла. Получают коллизии, кириллицу и пробелы в URL, а иногда перезапись чужого объекта. Ключ лучше строить из UUID или хеша содержимого.
Верят Content-Type от клиента. Под видом картинки приходит HTML или SVG со скриптом, поэтому тип определяют по содержимому и отдают с правильным заголовком.