Предыстория

Жил был один новостной проект. Время шло, одни фичи добавлялись, вторые удалялись... Одной из важнейших фишек была генерация превьюшек к картинкам (thumbnails), а именно - быстрая генерация (до 5 минут) всех thumbnails. Все было хорошо, пока не начали поступать жалобы, что, иногда, генерация не успевает за 5 минут все сделать. Начали "копать" и обнаружили интересную вещь: мы генерим 112 превьюшек к одной картинке. Нашей "радости" не было предела. После небольших дискуссий было решено увеличить maxReplicas до 60 в HPA (проблема возникала когда загружалось больше 80 картинок), так как это самое быстрое и дешевое решение.

Наши дни

Пришёл новый проект и ему потребовались thumbnails. Сервер немного опережал фронт, поэтому у нас было время на R&D.

Первым был найден imgproxy, а потом уже все остальные (см заголовок). Так же нам на глаза попалась следующая статья. Она, конечно, очень смахивала на заказную (что в результатах и будет видно), но все равно очень интересно.

Перед тем как приступить к тестированию, нужно было определиться, что мы хотим в итоге:

  • приемлемый RPS (от 10)

  • возможность работы с S3 (Minio)

  • кеширование сгенерированных превьюшек

  • защита от URL tampering

Краткий обзор каждого инструмента

Imgproxy

Github (5k starts, latest release - 2021-09-07)

Плюсы:

  • самая "живая" из всех (на момент написания статьи - последний релиз 8 дней назад)

  • подробная документация

  • большой список возможностей (имеется PRO версия со своими фичами)

  • есть защита от URL tampering (есть примеры генерации сигнатур для разных ЯП - https://github.com/imgproxy/imgproxy/tree/master/examples)

  • может "читать" оригиналы картинок не только по http, а и по S3, Google Cloud Storage и Azure Blob Storage

  • есть готовый helm chart

Минусы:

  • нет готового механизма кеширования (это принципиальная позиция: imgproxy does one thing — resizing remote images — and does it well. It works great when you need to resize multiple images on the fly to make them match your application design without preparing a ton of cached resized images or re-doing it every time the design changes.)

thumbor

Github (8.6k starts, latest release - 2020-02-27)

Плюсы:

  • больше всего звезд на гитхабе

  • есть разные плагины

  • есть защита от URL tampering

  • есть встроенный механизм кеширования

Минусы:

  • может "читать" с S3, но как выяснилось - плагин для S3 написан на Python 2.7, а мы выбрали Release 7.0.0a5 который использует Python 3. В итоге эти штуки не взлетели вместе

  • единственное кеширование, которое работает - file_storage, что не есть хорошо, если говорить в контексте k8s

imaginary

Github (3.8k starts, latest release - 2020-08-12)

Плюсы:

  • есть защита от URL tampering

  • очень прост в настройке

  • есть интересная фишка: запросы GET | POST на /info - "Returns the image metadata as JSON"

Минусы:

  • нет S3

  • нет кеширования

Отдельное слово хочу добавить про генерацию сигнатуры (защита от URL tampering). Все что есть в документации: Enable URL signature (URL-safe Base64-encoded HMAC digest). Хорошо если ты пишешь на Go, можно зайти в исходники и найти это дело: https://github.com/h2non/imaginary/blob/master/middleware.go#L173

У нас же был PHP...

Пришлось потратить много времени, что бы отыскать это.

Если вкратце, то параметры в URL должны быть в определенном порядке (отсортированы по abc): height=240&url=XXX&width=320&sign=YYY. Если вы написали следующее: width=320?height=240&url=XXX&sign=YYY, то сигнатура в этом случае не совпадет и вместо картинки вы увидите текст об ошибке.

picfit

Github (1.5k starts, latest release - 2020-09-11)

Плюсы:

  • есть защита от URL tampering

  • прост в настройке (все настройки в json файле)

  • есть кеширование в Amazon S3, DigitalOcean S3, Google Cloud Storage.

Минусы:

  • нет готового докер образа в docker hub (но в репе есть Dockerfile)

  • как оказалось позже, кеширование работает напрямую с Amazon (те. нельзя сделать s3_endpoint=http://minio:9000). Кеширование на файлах есть, но нам не подходит

imageproxy

Github (2.6k starts, latest release - 2020-04-03)

Плюсы:

  • есть защита от URL tampering

  • есть кеширование в S3 и много куда еще (и даже in-memory LRU cache)

Минусы:

  • нет механизма upscale (если у вас картинка 1920x1080, то вы никак не сможете получить 3840x2160). Upd: @garfik подсказал параметр scaleUp, после его добавления - все взлетело.

weserv/images

Github (908 starts, latest release - 2021-09-07)

Плюсы:

  • простой и быстрый

  • есть встроенное кеширование на уровне nginx proxy

Минусы:

  • нет защиты от URL tampering

  • нет S3

Тестирование

Был собран docker-compose стенд (там можно найти все исходники), все что могло работать с кешом - было запущено в отдельных контейнерах:

  • thumbor настроен с файловым кешом

  • picfit настроен с файловым кешом

  • imageproxy настроен с in-memory кешом

Полный docker-compose.yaml:

version: '3.8'

services:
  imgproxy:
    image: darthsim/imgproxy:v2.17
    ports:
      - 8080:8080
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1
    environment:
      - IMGPROXY_CONCURRENCY=1
      - IMGPROXY_USE_ETAG=true

  thumbor:
    image: minimalcompact/thumbor:7.0.0a5
    ports:
      - 8081:80
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1

  thumbor-cache:
    image: minimalcompact/thumbor:7.0.0a5
    ports:
      - 8082:80
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1
    environment:
      - RESULT_STORAGE_EXPIRATION_SECONDS=3600
      - RESULT_STORAGE_FILE_STORAGE_ROOT_PATH=/tmp/thumbor/result_storage
      - RESULT_STORAGE_STORES_UNSAFE=True
      - RESULT_STORAGE=thumbor.result_storages.file_storage

  imaginary:
    image: h2non/imaginary:1.2.4
    command: -enable-url-source -cpus 1
    ports:
      - 8083:80
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1
    environment:
      PORT: 80

  picfit:
    build:
      context: .
      dockerfile: ./.docker/picfit/Dockerfile
    ports:
      - 8084:3001
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1
    environment:
      - PICFIT_CONFIG_PATH=/config.json

  picfit-cache:
    build:
      context: .
      dockerfile: ./.docker/picfit/Dockerfile
    ports:
      - 8085:3001
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1
    environment:
      - PICFIT_CONFIG_PATH=/config_cache.json

  imageproxy:
    image: willnorris/imageproxy:latest
    ports:
      - 8086:8080
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1
    environment:
      - IMAGEPROXY_SCALEUP=true

  imageproxy-cache:
    image: willnorris/imageproxy:latest
    ports:
      - 8087:8080
    environment:
      - IMAGEPROXY_CACHE=memory:100:24h
      - IMAGEPROXY_SCALEUP=true

  weserv-images:
    image: ghcr.io/weserv/images:5.x
    ports:
      - 8088:80
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1

  weserv-images-without-cache:
    build:
      context: .
      dockerfile: ./.docker/weserv-images/Dockerfile
    ports:
      - 8089:80
    restart: always
    deploy:
      resources:
        limits:
          cpus: 1

networks:
  default:
    driver: bridge
    ipam:
      config:
        - subnet: 172.10.10.0/24

Результаты

Выводы

Серебряной пули нет. Кто-то хорош в одном, кто-то в другом. Где-то есть кеширование, где-то его нет и тд.

На данный момент мы остановились на Imgproxy. Да, в ней нет кеширования готовых превьюшек, но там есть ETag/Cache-Control/Expires HTTP headers. Плюс есть возможность добавить watermarks и много чего еще. Да и обновляется регулярно.

P.S. Несколько слов про чтение оригиналов с S3 и по http: так как мы разворачивали Minio S3 рядом с генераторами превьюшек - разницы в производительности не было, от слова совсем.

PPS. Добавил weserv/images по просьбе @mazaxakon.

Комментарии (5)


  1. BasilioCat
    16.09.2021 13:40
    +1

    Если вы планируете использовать сервис "из коробки", без модификации кода или написания плагинов, то какая в сущности разница, на чем он написан. Да, python 2.7 для thumbor кажется устаревшим, но это самый фичастый инструмент из все перечисленных, с несколькими активными форками и сотнями плагинов
    Imgproxy всем хорош, кроме наличия Pro версии, в которой в частности декларируется возможность использования кастомных вотермарков и более оптимальный алгоритм сжатия jpeg'ов.
    Порядок следования фильтров в урле важен для подписи и в thumbor (подписывается uri), однако никто не мешает использовать nginx secure link для подписи любых сегментов урлов. Кэширование тоже вполне себе задача для nginx, а не для тумбора


    1. mamchyts Автор
      16.09.2021 14:56

      Из всех фичей, нам пока что нужен только resize. Все эти кеши, s3, URL tampering - можно разрулить на уровне nginx.

      Основным критерием нашего выбора Imgproxy стали регулярные комиты в репозиторий. https://github.com/orgs/thumbor-community/repositories большинство плагинов к thumbor остановились в 2016-2018 годах.

      А вообще, если imageproxy прикрутит upscale, то мы быстро "переобуемся". In-memory есть только у них и это просто огонь.


      1. garfik
        16.09.2021 18:55
        +1

        А вообще, если imageproxy прикрутит upscale, то мы быстро "переобуемся". In-memory есть только у них и это просто огонь.

        а разве параметр scaleUp в imageproxy не включит режим upscale?


        1. mamchyts Автор
          16.09.2021 19:08

          Оппа, и как это я пропустил, спасибо, затестим и отпишусь


        1. mamchyts Автор
          17.09.2021 12:15
          +1

          IMAGEPROXY_SCALEUP=true - помог, переедем на imageproxy при первом удобном случае. Еще раз спасибо.