Быстрый ресайз и кеширование картинок при помощи "django-nginx-image"
Для KinsburgTV нужно было сделать ресайз картинок. Сначала я скопировал с одного из своих проектов шаблонный тег с ресайзингом (берущий корни от этого сниппета), но для продакшена столь медленное решение казалось неприемлемым. Поэтому я написал другой шаблонный тег, который строит нужный URL для Nginx, и далее его обрабатывает ngx_http_image_filter_module и proxy_cache.
У меня есть график для сравнения, который я сделал через JMeter, но он к сожелению оказался неинформативен. Если кому всётаки интересно, то вот этот график. Слева вариант на PIL, справа Nginx. Выборка на 1000 реквестов.
Получился быстрый, прозрачный для приложения ресайзер и кроппер с возможностью кешировать результаты, а также с командой для конвертирования картинок в подходящий формат. Итак, встречайте django-nginx-image!
Прозрачный он, потому что нет необходимости подготавливать заранее все необходимые варианты эскизов (они изготавливаются по запросу и их не видно в каталогах проекта), а при смене размеров нужно это делать снова и снова. Я сторонник того чтобы хранить только оригиналы, а всякие кеши, денормализованные данные и прочее - являются только мерами по повышению производительности, но не частью логики проекта.
Если вас волнует вопрос перебора ширины и высоты в URL, то можно указать нужные правила для ограничения размеров непосредственно в Nginx.
Далее необходимо настроить Nginx (приведены настройки, относящиеся только к теме обсуждения):
<STORAGE_ROOT> - путь до каталога с "media/statiс" (например, у меня это "/storage/kinsburg_tv");
<CACHE_NAME> - произвольное имя для кеша (например, "kinsburg_tv_thumbnails_cache").
http {
# Укажите необходимый путь до каталога кеша, имя кеша и максимальный размер кеша
proxy_cache_path <STORAGE_ROOT>/nginx/cache levels=1:2 keys_zone=<CACHE_NAME>:10m max_size=1G;
# Теперь настроим сервер, который будет кешировать результаты
server {
listen 80;
server_name www.example.org;
location ~* ^/(resize|crop)/ {
proxy_pass http://image.example.org$request_uri;
proxy_cache <CACHE_NAME>;
proxy_cache_key "$host$document_uri";
proxy_cache_valid 200 1d;
proxy_cache_valid any 1m;
proxy_cache_use_stale error timeout invalid_header updating;
}
}
# И сервер, который будет выполнять resize и crop
server {
listen 80;
server_name image.example.org;
location ~* ^/resize/([\d\-]+)/([\d\-]+)/(.+)$ {
alias <STORAGE_ROOT>/$3;
image_filter resize $1 $2;
image_filter_buffer 2M;
error_page 415 = /empty;
}
location ~* ^/crop/([\d\-]+)/([\d\-]+)/(.+)$ {
alias <STORAGE_ROOT>/$3;
image_filter crop $1 $2;
image_filter_buffer 2M;
error_page 415 = /empty;
}
location = /empty {
empty_gif;
}
}
}
Если вам не нужно кеширование, то просто удалите локейшен из первого "server" и перенесите локейшены из второго "server" в первый. Кеширование мне дало прирост производительности на 10-20%.
После чего в settings.py добавьте:
INSTALLED_APPS = (
'nginx_image',
)
Схема, иллюстрирующая работу:
Использование
В любом своём шаблоне подключите модуль nginx_image с шаблонным тегом thumbnail, передайте ему путь до картинки, ширину, высоту и опционально флаг crop, чтобы картинка обрезалась, а не ресайзилась:
{% load nginx_image %}
Пропорциональный ресайз картинки, основанный на её ширине и высоте (доминирует высота):
<img src="{% thumbnail user.profile.avatar 130 130 %}" />
Пропорциональный ресайз картинки, основанный на её ширине:
<img src="{% thumbnail user.profile.avatar 130 '-' %}" />
<img src="{% thumbnail user.profile.avatar 130 0 %}" />
<img src="{% thumbnail user.profile.avatar 130 %}" />
Пропорциональный ресайз картинки, основанный на её высоте:
<img src="{% thumbnail user.profile.avatar '-' 130 %}" />
<img src="{% thumbnail user.profile.avatar 0 130 %}" />
Обрезать картинку:
<img src="{% thumbnail user.profile.avatar 130 130 crop=1 %}" />
<img src="{% thumbnail user.profile.avatar 130 0 crop=1 %}" />
<img src="{% thumbnail user.profile.avatar 0 130 crop=1 %}" />
В общем этот тег только и делает, что формирует URL вида:
/resize/130/-/media/users/avatars/12345.jpg
/resize/-/130/media/users/avatars/12345.jpg
/crop/130/-/media/users/avatars/12345.jpg
Этот тег можно использовать вне шаблона, например:
class Film(models.Model):
thumbnail = models.ImageField(upload_to='films/thumbnails', null=True)
def thumbnail_preview_url(self):
from nginx_image.templatetags.nginx_image import thumbnail
return thumbnail(self.thumbnail, 160, 140, crop=True)
Команда nginx_image_converter
К сожалению (или к счастью), ngx_http_image_filter_module поддерживает только JPEG, GIF и PNG, поэтому пришлось писать конвертер, преобразующий форматы, отличные от вышеназванных, в JPEG.
Очень полезная библиотека, спасибо! Единственное, вот здесь в статье ошибка, долго искал в чем проблема - http://dl.getdropbox.com/u/930627/images/yvyfzpyksxuokxivixat.png - надо $uri вместо $request_uri
У меня есть пока не до конца ясная проблема, а именно почему при ресайзе несуществующего файла в браузер:
1. возвращается ошибка 415 вместо 404.
2. тогда по логике это должно было обработаться через error_page 415 = /empty; , но работает только если прописать error_page 404
Все хорошо, увы только nginx режет всю метаинфу, и ICC профили тоже.
Говна хапнул и выноса мозга недельного изза этой "фишки" nginx'а.
Если хочется сохранить ICC - ресайз бекенд на любом языке, который может работать с "умными утилитами", аля imagemagick. И создавать тумбки этим бекендом.
К слову, проверял долго и тщательно. Можно резать всю мета инфу с фоток(а ее там порядка 30% от размера файла в среднем на типичный jpg/png), но обязательно оставлять ICC профиль (он занимает 500-1500 байт).
во первых - где уведомлялки на email? сам вспомнил зайти глянуть коменты)
во вторых.
я шо ебнутый чтоли?) там сишка чистая + gd.
думаю это достаточно тяжело будет отсаппортить под себя.
Времячасы выгоднее свой бекенд сделать под конвертер, что я и запилил в общем то. Ща у меня красивые картиночки выплевываются без потери качества, еще и тремя утилитами по ним прохожусь чищу мету + пережимаю новыми алгоритмами сжатия (аля pngcrush).
Картинки весят мало, грузятся, прогрессивные жпжки и поддерживают профили ICC.
Работает по той же схеме - кеширование nginx'ом через подзапрос на конвертер, конвертер выплевывает картинки нужных размеров из сорцов.
во первых - где уведомлялки на email? сам вспомнил зайти глянуть коменты)
Мне приходят от тебя письма, только так я и узнаю о том что ты пишешь сейчас )
Другое дело я не настраивал DKIM, а SPF нормально установлен. Я переехал в понедельник на DigitalOcean, может с этим все как-то связано, как минимум PTR пока тупит.
Времячасы выгоднее свой бекенд сделать под конвертер, что я и запилил в общем то. Ща у меня красивые картиночки выплевываются без потери качества, еще и тремя утилитами по ним прохожусь чищу мету + пережимаю новыми алгоритмами сжатия (аля pngcrush).
Картинки весят мало, грузятся, прогрессивные жпжки и поддерживают профили ICC.
Работает по той же схеме - кеширование nginx'ом через подзапрос на конвертер, конвертер выплевывает картинки нужных размеров из сорцов.
Комментарии
Очень полезная библиотека, спасибо! Единственное, вот здесь в статье ошибка, долго искал в чем проблема - http://dl.getdropbox.com/u/930627/images/yvyfzpyksxuokxivixat.png - надо $uri вместо $request_uri
Странно, почему у вас "$request_uri" не работает, это корректная переменная http://nginx.org/ru/docs/http/ngx_http_core_module.html#variables
У меня есть пока не до конца ясная проблема, а именно почему при ресайзе несуществующего файла в браузер:
1. возвращается ошибка 415 вместо 404.
2. тогда по логике это должно было обработаться через error_page 415 = /empty; , но работает только если прописать error_page 404
С таким сталкивался?
Все хорошо, увы только nginx режет всю метаинфу, и ICC профили тоже.
Говна хапнул и выноса мозга недельного изза этой "фишки" nginx'а.
Если хочется сохранить ICC - ресайз бекенд на любом языке, который может работать с "умными утилитами", аля imagemagick. И создавать тумбки этим бекендом.
К слову, проверял долго и тщательно. Можно резать всю мета инфу с фоток(а ее там порядка 30% от размера файла в среднем на типичный jpg/png), но обязательно оставлять ICC профиль (он занимает 500-1500 байт).
Это сделает фотки красивыми, сочными и легкими)
А этот модуль nginx уже смотрел на предмет кастомизации при компиляции или может надо сорцы поправить?
во первых - где уведомлялки на email? сам вспомнил зайти глянуть коменты)
во вторых.
я шо ебнутый чтоли?) там сишка чистая + gd.
думаю это достаточно тяжело будет отсаппортить под себя.
Времячасы выгоднее свой бекенд сделать под конвертер, что я и запилил в общем то. Ща у меня красивые картиночки выплевываются без потери качества, еще и тремя утилитами по ним прохожусь чищу мету + пережимаю новыми алгоритмами сжатия (аля pngcrush).
Картинки весят мало, грузятся, прогрессивные жпжки и поддерживают профили ICC.
Работает по той же схеме - кеширование nginx'ом через подзапрос на конвертер, конвертер выплевывает картинки нужных размеров из сорцов.
Мне приходят от тебя письма, только так я и узнаю о том что ты пишешь сейчас )
Другое дело я не настраивал DKIM, а SPF нормально установлен. Я переехал в понедельник на DigitalOcean, может с этим все как-то связано, как минимум PTR пока тупит.
Когда выложишь на github?)
PTR не обязательно для уверенной оставки. SPF + Dkim достаточно в принципе.
На гитхаб пока не могу, NDA.
Потом свое на основе когда нить потребуется, оформлю какнить более юзабельно да выложу. не жалко)
добавил тебя в контакты в гмайле, ща должно в инбокс сыпаться
Спасибо!
Обновите, пожалуйста, в статье инструкцию по установке (на github правильная), здесь не хватает INSTALLED_APPS.
Спасибо! Добавил
Оставьте свой комментарий