Предыстория
Жил был один новостной проект. Время шло, одни фичи добавлялись, вторые удалялись... Одной из важнейших фишек была генерация превьюшек к картинкам (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
Минусы:
нет готового механизма кеширования (это принципиальная позиция: 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...
![](https://habrastorage.org/getpro/habr/upload_files/889/3fc/999/8893fc999abf4801c4d02b410f69c401.png)
Пришлось потратить много времени, что бы отыскать это.
Если вкратце, то параметры в 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
Результаты
![](https://habrastorage.org/getpro/habr/upload_files/d88/0d1/d88/d880d1d8835cdf2042fdca2a3106fca5.png)
![](https://habrastorage.org/getpro/habr/upload_files/ba3/2b0/ff2/ba32b0ff20bcf51843d6c3e52b23af87.png)
![](https://habrastorage.org/getpro/habr/upload_files/8f1/727/0fb/8f17270fbf0753955bf90932f2e5e3ad.png)
Выводы
Серебряной пули нет. Кто-то хорош в одном, кто-то в другом. Где-то есть кеширование, где-то его нет и тд.
На данный момент мы остановились на Imgproxy. Да, в ней нет кеширования готовых превьюшек, но там есть ETag/Cache-Control/Expires HTTP headers. Плюс есть возможность добавить watermarks и много чего еще. Да и обновляется регулярно.
P.S. Несколько слов про чтение оригиналов с S3 и по http: так как мы разворачивали Minio S3 рядом с генераторами превьюшек - разницы в производительности не было, от слова совсем.
PPS. Добавил weserv/images по просьбе @mazaxakon.