Сохранение изображений и эскизов на s3 в django
Я пытаюсь получить мои изображения с миниатюрами и сохранить их на s3, используя django-storageages, boto и sorl-thumbnail. У меня он работает, но он очень медленный, даже с небольшими изображениями. Я не возражаю, что это медленно, когда я сохраняю форму и загружаю изображения в s3, но мне хотелось бы, чтобы изображение было быстро после этого.
Ответ на этот вопрос SO объясняет, что миниатюра не будет создана до первого доступа, но вы можете использовать get_thumbnail() для ее создания заранее.
Django + S3 (boto) + Sorl Thumbnail: рекомендации по оптимизации
Я делаю это, и теперь кажется, что все записи в таблицу thumbnail_kvstore создаются при загрузке изображения, а не при его отображении.
Проблема в том, что страница, отображающая изображение, все еще очень медленная. Посмотрев панель регистрации на панели инструментов отладки, похоже, что с s3 все еще существует большая связь. Похоже, что после того, как изображение и миниатюры будут загружены и сохранены в кэше, страница должна быстро отобразиться без связи с s3.
Что я делаю неправильно? Спасибо!
Обновить: слабый хак, похоже, заработал, но я хотел бы знать, как это сделать правильно:
https://github.com/asciitaxi/sorl-thumbnail/commit/545cce3f5e719a91dd9cc21d78bb973b2211bbbf
Обновить: дополнительная информация для @sorl
Я работаю с 2 видами:
ADD VIEW: в этом представлении я представляю форму для создания модели с изображением в ней. Изображение загружается на s3. В сигнале post_save я вызываю get_thumbnail() для создания эскиза до его необходимости:
im = get_thumbnail(instance.image, '360x360')
DISPLAY VIEW: В этом представлении отображается эскиз, сгенерированный в окне добавления:
{% thumbnail object.image "360x360" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
Без патча:
ADD VIEW: создает 3 записи в таблице kvstore, обращается к кешу 10 раз (6 наборов, 4 получает), вкладка ведения журнала панели инструментов отладки говорит "установить HTTP-соединение" 12 раз
DISPLAY VIEW: все еще всего 3 записи в таблице kvstore, только 1 из кеша, но панель инструментов отладки говорит "установить HTTP-соединение" еще 3 раза
Только с изменением в строке 122:
ДОБАВИТЬ ПРОСМОТР: то же самое, что и выше, кроме журнала только говорит "установление HTTP-соединения" 2 раза
ПРОСМОТР ДИСПЛЕЯ: то же, что и выше, за исключением того, что в протоколе указано только "установление HTTP-соединения" 1 раз
Также добавление изменения в строке 118:
ДОБАВИТЬ ПРОСМОТР: то же, что и выше, но теперь мы доходим до 2 сообщений об установлении HTTP-соединения
ПРОСМОТР ДИСПЛЕЯ: то же, что и выше, без каких-либо сообщений о регистрации
UPDATE: похоже, что storage._setup() вызывается дважды, а storage.url() вызывается один раз. Основываясь на времени, я бы сказал, что каждый из них устанавливает соединения с s3:
1304711315.4
_setup
1304711317.84
1304711317.84
_setup
1304711320.3
1304711320.39
_url
1304711323.66
Это, по-видимому, отражается в протоколе boto, в котором говорится о "установлении HTTP-соединения" 3 раза.
Ответы
Ответ 1
Как автор миниатюры sorl, я действительно заинтересован в решении этого вопроса, если он не работает так, как я предполагал. Если значение ключа sotre заполнено, оно будет хранить: имя, хранилище и размер. Я сделал предположение, что URL-адрес основан на имени и, следовательно, не должен вызывать вызовы памяти. Глядя на хранилища django, https://github.com/e-loue/django-storages/blob/master/storages/backends/s3boto.py#L214, кажется, что это безопасное предположение. По какой-то причине в вашем патче вы исправили метод чтения. При создании эскиза экземпляр ImageFile извлекается из кеша (если его не создавать), вы можете, конечно, вызывать чтение, которое будет читать файл, но предполагаемое использование -.url, которое вызывает URL-адрес на хранилище с кешированным именем, которое должно быть оператором доступа без хранения. Не могли бы вы попытаться изолировать свою проблему до места, где в вашем коде происходит доступ к хранилищу?
Также убедитесь, что у вас включен THUMBNAIL_DEBUG, и что у вас правильно сохранено хранилище ключей.
Ответ 2
Я не уверен, что у вас проблема такая же, как у меня, но я обнаружил, что доступ к свойству width или height обычного Django ImageField будет считывать файл с базы данных хранилища, загружать его в PIL и возвращать размеры оттуда. Это особенно дорого стоит с удаленным бэкэнд, как мы используем, и у нас есть очень тяжелые страницы.
https://code.djangoproject.com/ticket/8307 был открыт для решения этой проблемы, но разработчики Django закрыты как wontfix, потому что они хотят, чтобы свойства width и height всегда возвращали true значения. Поэтому я просто monkeypatch _get_image_dimensions(), чтобы использовать эти поля, что предотвращает большое количество сообщений boto и улучшает время загрузки страницы.
Ниже мой код изменен из патча, прикрепленного к этому билету. Я застрял в этом месте, которое выполняется раньше, например models.py.
from django.core.files.images import ImageFile, get_image_dimensions
def _get_image_dimensions(self):
from numbers import Number
if not hasattr(self, '_dimensions_cache'):
close = self.closed
if self.field.width_field and self.field.height_field:
width = getattr(self.instance, self.field.width_field)
height = getattr(self.instance, self.field.height_field)
#check if the fields have proper values
if isinstance(width, Number) and isinstance(height, Number):
self._dimensions_cache = (width, height)
else:
self.open()
self._dimensions_cache = get_image_dimensions(self, close=close)
else:
self.open()
self._dimensions_cache = get_image_dimensions(self, close=close)
return self._dimensions_cache
ImageFile._get_image_dimensions = _get_image_dimensions
Ответ 3
Посмотрев на билет @shadfc django, я повторно выполнил команду monkeypatch следующим образом:
from django.core.files.images import ImageFile
def _get_image_dimensions(self):
if not hasattr(self, '_dimensions_cache'):
if getattr(self.storage, 'IGNORE_IMAGE_DIMENSIONS', False):
self._dimensions_cache = (0, 0)
else:
close = self.closed
self.open()
self._dimensions_cache = get_image_dimensions(self, close=close)
return self._dimensions_cache
ImageFile._get_image_dimensions = _get_image_dimensions
Чтобы использовать его, просто добавьте IGNORE_IMAGE_DIMENSIONS = True
в свой класс хранения, и он не будет затронут, чтобы получить размеры изображения. Скорее всего:
from storages.backends.s3boto import S3BotoStorage
S3BotoStorage.IGNORE_IMAGE_DIMENSIONS = True
Мне все еще нужно исследовать, где используются числа, чтобы знать, может ли простое возвращение (0, 0)
привести к какой-либо проблеме, но пока что ошибка не возникла.