Быстрый ресайз и кеширование картинок при помощи "django-nginx-image"

Для KinsburgTV нужно было сделать ресайз картинок. Сначала я скопировал с одного из своих проектов шаблонный тег с ресайзингом (берущий корни от этого сниппета), но для продакшена столь медленное решение казалось неприемлемым. Поэтому я написал другой шаблонный тег, который строит нужный URL для Nginx, и далее его обрабатывает ngx_http_image_filter_module и proxy_cache.
У меня есть график для сравнения, который я сделал через JMeter, но он к сожелению оказался неинформативен. Если кому всётаки интересно, то вот этот график. Слева вариант на PIL, справа Nginx. Выборка на 1000 реквестов.
Получился быстрый, прозрачный для приложения ресайзер и кроппер с возможностью кешировать результаты, а также с командой для конвертирования картинок в подходящий формат. Итак, встречайте django-nginx-image!
Прозрачный он, потому что нет необходимости подготавливать заранее все необходимые варианты эскизов (они изготавливаются по запросу и их не видно в каталогах проекта), а при смене размеров нужно это делать снова и снова. Я сторонник того чтобы хранить только оригиналы, а всякие кеши, денормализованные данные и прочее - являются только мерами по повышению производительности, но не частью логики проекта.
Если вас волнует вопрос перебора ширины и высоты в URL, то можно указать нужные правила для ограничения размеров непосредственно в Nginx.
Установка и настройка
Ставим с PyPI:
pip install django-nginx-image
Далее необходимо настроить 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 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.
./manage.py nginx_image_converter -i /storage/project/media -o /storage/project/newmedia
Дополнительные опции этой команды смотреть тут.
Проблема с Nginx resolver
Если вам встретилась примерно такая ошибка в nginx/error.log:
... no resolver defined to resolve image.example.org ...
то укажите resolver в nginx/nginx.conf, например так:
resolver 127.0.0.1 8.8.8.8;
Поддержка image_filter в Nginx
Чтобы добавить поддержку этого модуля в ваш Nginx, необходимо пересобрать Nginx. В Debian надо просто доустановить nginx-extras:
sudo apt-get install nginx-extras
Во FreeBSD надо пересобрать порт:
cd /usr/ports/www/nginx make config
Выбираем опцию HTTP_IMAGE_FILTER и переустанавливаем:
make deinstall && make install clean
Всё.
In English
Комментарии
Очень полезная библиотека, спасибо! Единственное, вот здесь в статье ошибка, долго искал в чем проблема - 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.
Спасибо! Добавил
Оставьте свой комментарий