default_storage.exists очень медленно и часто тайм-аут

Мы только что перенесли проект Django в Heroku и поместили все наши мультимедийные и статические файлы в Amazon S3 (с использованием django-хранилищ и s3-boto).

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

@property
def photo(self):
    """Transparently serve the best available image for templates"""
    if self.model_shot.storage.exists(self.model_shot.name):
        return self.model_shot
    elif self.image.storage.exists(self.image.name):
        return self.image
    else:
        return None

Когда я тестировал модель, которая вызывала проблемы, я попробовал это:

$ heroku run python manage.py shell
...
>>> design = Design.objects.get(pk=10210)
>>> design.photo

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

Мой вопрос, каков наилучший способ справиться с этим? Я много слышал об использовании CloudFront в подобных ситуациях, но это определенно не связано с большим трафиком (на нашем сайте пока не должно быть трафика). Какой-то другой механизм кэширования? Что-то еще целиком?

Большинство изображений, о которых идет речь, имеют размер не менее 1000x1000 пикселей.

3 ответа

Решение

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

tl; dr Не обслуживать файлы напрямую из S3; настроить CloudFront.


Обслуживание S3 Bucket через CloudFront

Шаг 0: Если вы еще этого не сделали, убедитесь, что ваше имя сегмента соответствует "рекомендациям" для именования сегментов. Они не обязательно делают это очевидным во всех местах, где должны, но плохое имя корзины может полностью нарушить его совместимость с другими веб-сервисами Amazon. Лучшее, что можно сделать, это назвать ваше ведро как-нибудь строчными буквами, которые не слишком длинные (<= 60 символов или около того).

Шаг 1: Чтобы CloudFront обслуживал файлы из вашего сегмента, вам нужно настроить его так, чтобы он обслуживал статический веб-сайт. Вы можете сделать это на консоли Amazon AWS из своего ведра. Permissions Вкладка. У Amazon есть несколько мест, где есть инструкции / документация для этого; ИМО самые ясные из них. ВАЖНО! Убедитесь, что для корневого объекта по умолчанию установлено значение index.html - этот файл даже не должен существовать, но этот параметр существует.

Шаг 1.5 [возможно, необязательный]: убедитесь, что разрешения на вашем ведре правильные. Хотя я обслуживал файлы с S3 без проблем, переход на CloudFront для их обслуживания превратил все в ошибку 403: Access Forbidden. Если вы сомневаетесь, и ваши файлы не являются конфиденциальными, вы можете щелкнуть правой кнопкой мыши папки вашего сегмента в консоли AWS и выбрать Make Public, ВНИМАНИЕ: Это может быть очень трудоемким процессом, и по какой-то глупой причине (даже если это на стороне сервера) сеанс браузера должен оставаться открытым. Сделайте это первым и не закрывайте сеанс. Для нашего ведра это заняло около 16 часов.:/

Шаг 2: Перейти к Amazon CloudFront в консоли AWS и щелкните Create Distribution кнопка. Сделайте это веб-распространением (по умолчанию) и используйте созданный вами домен, настроив группу для статического веб-распространения на предыдущем шаге в качестве источника. И снова, IMO, это самые ясные и простые инструкции в документах AWS. Вы можете оставить здесь все по умолчанию. После того, как он создан, просто подождите, пока он не появится на консоли как "Развернутый".

Шаг 3. Настройте свое приложение для работы с CloudFront, а не с S3. Это самая простая часть, потому что URL-адреса прозрачно перемещаются из https://bucketname.s3.amazonaws.com/path в https://somerandomstring.cloudfront.net/path (бонус: вы можете настроить последний как CNAME запись, чтобы указать на что-то вроде media.yourdomain.tld; мы этого не делали, поэтому я не буду вдаваться в подробности). Так как я использую Django с комбинацией django-storages а также s3-boto это оказалось простым вопросом настройки домена Cloudfront в settings.py:

AWS_S3_CUSTOM_DOMAIN = 'd2ynhpzeiwwiom.cloudfront.net'

И это все! С этими изменениями все наши проблемы со скоростью исчезли, и наши мультимедийные страницы (изображения по 6-20 Мп на страницу) неожиданно загружаются быстрее, чем когда-либо!

Я столкнулся с похожими проблемами на производственной площадке, поскольку она увеличилась. Я бы порекомендовал использовать серверную часть хранилища, которая может хранить свою собственную мета-информацию о ваших файлах S3. Лучшим подобным проектом, вероятно, является MimicDB, хотя вы также можете проверить, что я сделал с модифицированными хранилищами django. Таким образом, запросы метаданных, такие как .exists(), .urlи т. д. мгновенно отвечают из локального кэша.

Кроме того, убедитесь, что вы, как правило, просто получаете URL или другие метаданные image объекты, и не использовать какой-либо код, который заставил бы ваш сервер без необходимости извлекать фактические данные изображения. Что мне нравится делать при настройке такого рода вещей, так это изменять оболочку S3 (например, boto), чтобы она регистрировала каждый необработанный запрос S3 REST, а затем проверяла сайт и следила за тем, чтобы простой просмотр веб-страниц на сайте не вызвать любые запросы S3 от веб-сервера.

Установите AWS_PRELOAD_METADATA = False (по умолчанию установлено значение False), чтобы избежать загрузки всех метаданных файлов из корзины в память. Это не только увеличит время ожидания запроса, но также уменьшит использование памяти. В моем случае использование памяти сократилось с ~1,5 ГБ до 5-6 МБ, а время сократилось только с 60 с до 4-5 с.

Оригинальный ответ: почему default_storate.exists() с django-хранилищами с бэкэндом S3Boto вызывает ошибку памяти с большим объемом памяти S3?

Другие вопросы по тегам