10 ноября 2012 г. Django Nginx Python EN In English

Быстрый ресайз и кеширование картинок при помощи "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 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.

./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

Всё.

Комментарии

Очень полезная библиотека, спасибо! Единственное, вот здесь в статье ошибка, долго искал в чем проблема - 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'ом через подзапрос на конвертер, конвертер выплевывает картинки нужных размеров из сорцов.

во первых - где уведомлялки на email? сам вспомнил зайти глянуть коменты)

Мне приходят от тебя письма, только так я и узнаю о том что ты пишешь сейчас )

Другое дело я не настраивал DKIM, а SPF нормально установлен. Я переехал в понедельник на DigitalOcean, может с этим все как-то связано, как минимум PTR пока тупит.

Времячасы выгоднее свой бекенд сделать под конвертер, что я и запилил в общем то. Ща у меня красивые картиночки выплевываются без потери качества, еще и тремя утилитами по ним прохожусь чищу мету + пережимаю новыми алгоритмами сжатия (аля pngcrush).
Картинки весят мало, грузятся, прогрессивные жпжки и поддерживают профили ICC.
Работает по той же схеме - кеширование nginx'ом через подзапрос на конвертер, конвертер выплевывает картинки нужных размеров из сорцов.

Когда выложишь на github?)

PTR не обязательно для уверенной оставки. SPF + Dkim достаточно в принципе.

На гитхаб пока не могу, NDA.

Потом свое на основе когда нить потребуется, оформлю какнить более юзабельно да выложу. не жалко)

добавил тебя в контакты в гмайле, ща должно в инбокс сыпаться

Denis Golomazov 3 июня 2014 г. 7:49

Спасибо!
Обновите, пожалуйста, в статье инструкцию по установке (на github правильная), здесь не хватает INSTALLED_APPS.

Спасибо! Добавил

Оставьте свой комментарий

Markdown