Добрый день. Я разработчик с более чем 10-летним стажем. Для того, чтобы оценить качество исходных кодов сайта, не без доли самоиронии, я создал небольшой чек-лист. Сегодня я поговорю о важном для меня пункте — изображения на сайтах. Я умышленно опустил конкретную технологию, потому что эта проблема встречалась и встречается повсеместно, я буду очень признателен, если в комментариях вы раскроете свои подходы с использованием вашего стека технологий, в конечном итоге мы все очень похожи.



Опытным путем, я установил, что способ работы с изображением на сайте – лакмусовая бумажка качества. Посмотри как человек работает с изображениями в пяти размерах и можно сделать вывод обо всем проекте. Причина вполне понятна, я не встречал книги или статьи, где описана логика хранения, обновления и работы с изображением, если кто-то укажет её в комментариях – буду признателен. Каждый придумывал свой велосипед сервисов, который умел сохранять по определённой структуре каталогов или в базе. Шло время, сайты на поддержке развивались, где-то менялись размеры изображений, где-то добавлялся новый функционал, который требовал нового размера ранее загруженных изображений. Каждый шаг добавлял новый виток уже отлаженной процедуры: походим по всем изображениям – оригиналам, меняем размер и сохраняем в нужных параметрах. Долго, дорого.

Ручное управление рано или поздно приведет к сбою, потребует лишних денег для проверки в тестовой среде. Я приступил к поиску подобной проблемы у других, ведь давно известно, что если у тебя какая-то проблема, то ты не первый, кто с ней столкнулся. Достаточно быстро я вышел на облачные сервисы изменения изображений, кэширования и размещения. Я был восхищен простотой решения, используя ссылку на оригинал изображения и управляющие параметры в ней можно получать обрезанные, сжатые, перевернутые, сдвинутые, прозрачные, изображения с нанесенным текстом и эффектами и многое, многое другое. Один из таких сервисов, показавшийся мне наиболее простым и удобным, я решил опробовать. Для этого я составил список необходимого мне функционала и решил снять временные характеристики, ведь ни для кого не секрет, что скорость работы сайта является очень важным параметром с точки зрения пользователя и поисковых систем.

Примерный список по памяти:

  • Изменение ширины, пропорциональное изменение высоты.
  • Изменение высоты, пропорциональное изменение ширины.
  • Обрезка изображения в заданных границах.
  • Вписывание изображения в границы прямоугольника.
  • Кэширование и хранение результатов на стороне сервиса, при этом его регулярное обновление в зависимости от ETag и MaxAge.
  • Время получения измененного изображения новым пользователем из России.
  • Доступность https.
  • Приемлемая цена сервиса и прозрачность его оплаты.

Я прошелся по функциональным требованиям, каждое из них было отражено с примером использования и полностью устраивало, была краткая инструкция по использованию и небольшая панель управления для наблюдения за статистикой и уменьшением баланса.

Параллельно я смотрел скорость и, сказать честно, я не ожидал хороших результатов по скорости, так как на карте расположения серверов обработки и хранения России не оказалось. Но раньше замечал, что скорость в Европу и США бывает значительно лучше, чем до соседнего города, потому решил проверить. Результаты оказались приемлемыми, около 35-60 ms на изображение при изменении ширины изображения с 3000 до 500 и 30-150KB результате.

Дальше кэширование было основано на MaxAge и Last-modified, ETag отсутствовал и никак не транслировался с исходного сервера хранилища, но я не смог придумать ситуации, когда он может понадобится, если есть Last-modified. MaxAge, при правильной настройке, спокойно перетекал на сервис. Мне стало интересно, каким образом сервис отрабатывает изменение оригинального изображения на сервере хранилище. В голове было три алгоритма, по первому он должен был перед тем, как вернуть изображение – обратиться к исходному хранилищу и посмотреть у него Last-modified, по второму он должен был в фоне сравнивать информацию о том, что хранится на сервисе и у хранилища раз в какое-то время, например, полученное через MaxAge и третий, когда следующее обновление назначалось через MaxAge, но происходило только если клиент делает запрос. Решил установить простым экспериментом, изменив оригинал и запросив от сервиса изображение. Тогда я обнаружил, что изображение не изменилось, значит сервис не обращается к хранилищу до определенного момента. При этом если изменить изображение снова, то оригинальное изображение будет взято из кэша сервиса, а не с хранилища. Дальше, меняя MaxAge, я добился обновления оригинального изображения в кэше сервиса.

В моем списке цена стояла на последнем месте по порядку, но не по приоритету. Я был неприятно расстроен ценой (цены привожу на сегодняшний день) от 500$ за план с SLA, при этом план по количеству доступов к оригинальному изображению тоже устраивал плохо, каждая 1000 изображений оплачивалась отдельно. При этом было непонятно, как считаются доступы к оригинальному изображению. Считается ли доступом повторный запрос ранее измененного изображения из кэша сервиса, без использования кэша браузера, т.е. новым пользователем, но раньше кто-то его уже вызывал и изменил. Вопрос этот возник потому, что у нас на одном из проектов было больше 150 новых оригинальных изображений каждый день, которые просматривали не менее 5 тысяч человек ежедневно. На запросы из кэша приходились только ~50% запросов. Ответ был простым, после загрузки в кэш сервиса изображения, все операции с ним безлимитные и ограничение только по расходуемому трафику. Прикинув количество изображений выходило порядка 150 долларов ежемесячно без учета трафика.



Все бы хорошо, но настал 2014 год и доллар стал уверенно преодолевать границы разумного, доброго, вечного. Бабло начало побеждать зло и было принято решение, создать свой сервис по изменению изображений налету. Так появился внутренний проект, а недавно (около года назад) я и команда опубликовали бета версию для публичного тестирования. Сервис построен с использованием технологий Microsoft .NET, и NoSQL Redis для кэширования оригинальных изображений. С нами работают разные страны, в совокупности около 15 сайтов. Один из наших клиентов, сайт из Вьетнама популярной вьетнамской музыки, насколько мы можем судить, откуда он узнал о нашем сервисе до сих пор остаётся загадкой. Посещаемость каждого из сайтов от 50 до 30 000 человек в день. Наша целевая аудитория это относительно небольшие (до 50 000 – 100 000 человек в день. Размер оригиналов изображений до 10MB каждого, ограничен внутри системы) информационный сайты, интернет-магазины, каталоги продукций и просто сайты с отзывчивым дизайном и желанием уменьшить трафик или восполнить пробелы с пережатием изображений в старом IE.

Архитектура


Первой версией проекта была прямолинейная обработка, никакой авторизации и полная свобода действий для пользователей сервиса. Скажу сразу – это плохая идея, по всем параметрам кроме удобства пользователя, пока сервис не упадёт под нагрузкой недобросовестных пользователей или напрямую нарушающих законодательство. Лучше всего это показать на схеме и обозначить основные компоненты.



Всю статистику запросов, все параметры кэширования мы хранили на Redis. Там же хранили оригиналы и обработанные файлы, потому что хотели добиться максимальной скорости отдачи контента пользователю, минимизировав участия дисков. Запустив наш прототип, поняли, что он вполне жизнеспособен и покрывает требования по одному из сайтов с лихвой (5 000 — 10 000 пользователей в сутки). Разместили на собственном железе, приступили к использованию. Проблемы начались, когда мы захотели масштабироваться. Не потому, что мы получили какой-то предел решения, а потому, что к нам начали проявлять интерес другие клиенты и мы стали опасаться, что не справимся с нарастающей нагрузкой.

Так родилась вторая версия сервиса. Версия, построенная на очереди поверх Redis и микросервисах, слушающих события этой очереди. Фасад остался за IIS, но сильно похудел и стал проксировать запросы к нужным сервисам. Появились ограничения как на количество файлов, так и на их размер, привязка к доменному имени, чтобы избежать недобросовестных пользователей и указать владельца соответствующим структурам, в случае их интереса. Появился DNS с Round Robin и возможностью выдавать адреса относительно предполагаемого адреса клиента (CDN).



Схема позволила нам разнести сервисы обработки, дублировать их с точки зрения отказоустойчивости и производительности, независимо отправлять в обслуживание конечные точки без остановки всей системы. Более того, сервисы перестали быть географически привязанными.

Вторую версию сервиса мы продолжаем развивать. Мы очень хотим получить от пользователей обратную связь. У нас есть список возможного развития, с новыми функциональными возможностями:

  • Нанесение текста на изображение — уже реализовано
  • Установка качества в контексте домена, правил и/или операции (сейчас это жесткое значение OptimalCompression)
  • Добавление выбора системы — плана кэширования оригинальных изображений
  • Добавление источников, отличных от сайта (например Amazon или новоиспеченный Mail с его холодным хранилищем)
  • Размещение дополнительных сервисов за пределами центрального региона

Нужно – не нужно, важно – не важно, поэтому при регистрации выдается план Early Adopter, с 500MB хранилища, до 5 доменов, 2000 оригинальных изображений. Он останется с вами, только если вы не удалите аккаунт (это можно сделать самостоятельно из панели управления в любой момент времени). Все предложения можно и нужно отправлять по контактам поддержки или в комментариях тут. Учитывайте, что план Early Adopter гибкий и, если вам необходимы большие масштабы, просто напишите в поддержку. Высока вероятность, что вам помогут. Также если интересно, как какая-то из частей устроена внутри – пишите. Я постараюсь ответить на все вопросы, где-то просто привести примеры кода.

Спасибо!


Контакты. Я старался подойти к статье объективно, не нарушать правила топиков и рекламы, чтобы поделиться с вами своими мыслями о простоте подхода. Больше вам спасибо за внимание и время. Контакты сообщу в личном сообщении или комментарии, при наличии интереса.

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


  1. ADSoft
    08.04.2018 21:29

    Это все хорошо и замечательно — но при использовании сторонних сервисов основные проблемы:

    • Любое изменение АПИ внешнего поставщика — выводи из строя работу на сайте
    • Все-же платно..


    1. vladimirkolyada Автор
      08.04.2018 21:34

      • Всегда есть версионность апи, но мы поступили по другому, один раз мы сменили API, но не могли никак сообщить об этом нескольким клиентам, кроме как подменять им картинки с сообщениями на английском языке. Но, мы пошли иным путем — редиректом. Т.е. старое апи осталось рабочим, а они сами зашли на сайт (обычно проверяешь изредка сервисы, с которыми сотрудничаешь, я например не могу налюбоваться на zilore) и сменили редирект на активную версию. В ASP.NET мы это вынесли в хелперы, сменить одно на другое, в случае необходимости — никаких проблем. Мы ведь пользуемся и сторонними сервисами тоже.
      • Не могу ничего сказать, пока сервис абсолютно бесплатный, но рано или поздно мы будем вынуждены или ограничить поступление клиентов или вводить какие-то рамки. Но доступные аккаунты, полученные сейчас, будут работать по тем же правилам — бесплатно. Надо просто не злоупотреблять сервисом, не пытаться на нем выехать с посещаемостью уровня Ленты или Медузы какой, условно, но у них свои подрядчики и вряд-ли они посмотрят в нашу сторону:)

      Меня лично, как пользователя, больше заботит доступность сервиса 24/7, а не работа раз от раза. Если происходят какие-то проблемы — знать о них. Если я хочу что-то сказать — чтобы меня услышали.


  1. technik
    08.04.2018 22:10

    Скажите, а на вашем сервисе можно решить не менее актуальную задачу по сжатию изображений?
    В частности имеется допустим много папок на нескольких доменах, с кучей изображений размером примерно под 10 Гб на каждом и нужно их сжать максимально, чтобы оптимизировались под проверку по Google Page Speed и GTmetirx.
    Также следить за актуальностью и допустим раз в сутки увидеть новые файлы и аналогично сжать их.
    Я знаю что есть подобные сервисы, но при текущих объёмах (и курсе доллара) суммы получаются какие-то очень внушительные.


    1. vladimirkolyada Автор
      08.04.2018 22:17

      Нет, к сожалению пока нет такого параметра, как quality, но вопрос интересный. У вас они в разных форматов? У нас основной кейс это кэширование пережатых изображений, а вот просто компрессии нет. Плюс сами мы не можем узнать, появились у вас файлы или нет, т.е. запрос всегда идёт от клиентов, по сути — их браузеров. Первый запрос строит изображение, остальные его из кэша подтягивают, который с использованием ETag и Last-modified обновляется. Те сервисы, что я встречал и искал, все равно работали по запросу клиента. Но каждый из них имел тот или иной API, можно соответственно вызывать его скриптом, а не браузером. В любом случае писать что-то. Я не подскажу универсального рабочего решения к сожалению.


  1. mgremlin
    09.04.2018 18:54

    Интересуюсь ссылкой.
    В принципе, у меня реализован собственный велосипед, между делом кроющий cloudinary :-) но всегда интересно посмотреть на чужое решение.
    Тем более — на .NET и IIS (?!). Мы-то на go морочились, чтоб это пошустрее работало…

    Насчет серверов — интересная тема: наш милтер картинок живет во франкфуртском ДЦ DO, оригиналы хранятся на AWS в германии же, и сколько мы ни пытались обработку/кэш перенести в РФ — ни черта не получилось. Тупо о-о-чень ме-е-едленно… В смысле, после прогрева кэша — все чудесно за счет меньшего пинга, но когда картинку надо выдрать из S3 для обработки — очень все плохо. Возникает ощущение, что наши компании, предоставляющие сервис VPS, экономят на аплинках в европу(?).

    Ждем AWS или DO в России.


    1. vladimirkolyada Автор
      09.04.2018 19:30

      Да, на .NET и IIS. Нас не очень волнует первичная обработка, основной кейс работы заключается в том, что картинка создана, она лежит на Redis и отдается исключительно от туда, там же в фоне мониторинг и обновляется по мере необходимости. Ну и по верх этого панель прикручена, с графиками и небольшой статистикой, для понимания. Ссылка imagecdn.ru статья с главной ушла, надеюсь не сочтут за рекламу, уже больше суток прошло. У вас именно проблема в загрузке с AWS, тут надо искать каналы, которые позволят получить достойную скорость, возможно в каких-то ДЦ он и будет в России.


      1. mgremlin
        09.04.2018 20:18

        Спасибо, посмотрю.


        1. vladimirkolyada Автор
          09.04.2018 20:38

          Можете попробовать каналы у мэйла (mail.ru), они тут свой ДЦ для других открыли, публичный доступ и дают 3000р на тестирование. Там и хранилища есть, виртуалки можно развернуть, все по взрослому, но сам не пробовал. Зарегистрировался, но пока не использовал, вот хочу инстансы пары сервисов там запустить ради эксперимента.


          1. mgremlin
            10.04.2018 10:10

            Дорого у мэйла, я видел релиз про облако.
            Я кучу разных пробовал, последний, например, vscale селектеловский.
            Всем хороши парни, можно бы их рекомендовать, но вот в данном аспекте полный швах.


      1. mgremlin
        10.04.2018 14:43

        Знач, так, отчитываюсь.
        попробовать не удалось: не работает, множественные проблемы.
        1. с главной ссылка на cp дает 404. при исправлении руками схемы на http прокатывает, можно регнуться. И даже добавить домен.
        2. Но даже при этом условии отдает XML

        <Error><Message>An error has occurred.</Message></Error>

        картинку тестил вот такую: image
        У меня ее вертит как хошь с каменным лицом.


        1. vladimirkolyada Автор
          11.04.2018 09:21

          Спасибо! Все просто, вы не подтвердили домен, в cp.imagecdn.ru в таблице доменов первая колонка, солнышко должно превратиться в галочку. Для этого нужно кликнуть на галочку, вернется файл, который нужно бросить в корень домена для подтверждения. Потому вам и возвращается ошибка. А вот с https спасибо, действительно, сертификата на панели нет, что более чем плохой тон, я этот вопрос решу на днях, а на корневом домене наверняка ссылка стоит вида \\:domainname, потому он и схватил https. Такая мелочь и так неприятно в результате.
          П.С. Когда мы делали сервис, нам показалось что самое простое верифицировать его через файл на хосте, заморочки с DNS как-то сложнее, а файл с хэшем бросил и бросил себе, тем более после подтверждения его сразу можно удалить.


          1. mgremlin
            11.04.2018 11:49

            Ну а я вот хотел попробовать картинку из инета. Это чужой домен, просто там картинка красивая и удобная. Но даже если бы я использовал большинство своих — у меня тупо нигде нет возможности быстро положить файл и отдать его из корня: везде сплошные прокси на апп сервера, nginx, в лучшем случае — статик генераторы с фиксированным деревом.
            Или вот я бы мог использовать какой-нибудь свой S3, так там тоже в корень ничего не добавишь.

            То есть, просто для того, чтобы потестить latency и качество обработки изображения мне надо:
            1. продраться сквозь дебри регистрации на сайте
            2. добавить домен, причем, несмотря на мой более чем 30летний опыт разработки, я не сумел сразу сделать этого правильно
            3. зайти на VPS sshем, поменять конфиги вебсервера, уложить в специально созданный корень этот файл.
            4. положить в корень этому домену картинку или настроить прокси
            Не крутовато ли? Просто чтоб попробовать?

            Ну уж сделали бы счетчик, что ли — 3 картинки (или 30) отдаем без регистрации… Да и все остальное, имхо, избыточно. Вот лично я даже по описаниям и прочему не увидел явного преимущества перед тем же Cloudinary, а если еще и просто попробовать настолько непросто… Или вы так, чисто теоретически хвастаетесь, а какое-либо использование не планируется?


        1. vladimirkolyada Автор
          11.04.2018 10:11

          Все, теперь сайт доступен по https с правильным сертификатом. Если активируете домен и попробуете — буду признателен.


    1. vladimirkolyada Автор
      09.04.2018 19:34

      А как вы обрабатываете? Первичная обработка, нет картинки в кэше. Прямо пришел запрос — обработали — сразу отдали? Или как-то интереснее? Волнует меня тема эта.


      1. mgremlin
        09.04.2018 20:17

        Ну а как еще можно?
        Пришел запрос на thumb, если в кэше нет — отдали на бэкенд. Он проверяет кэш оригиналов, если есть — обрабатывает оригинал из кэша. Если нет, вытягивает картинку из AWS, сделал с ней что надо, отдал. И оригинал, и обработанный экземпляр кэшатся.
        ну там много всякой мути по дороге…
        Да, ессно, кэши разные: оригиналы кэшатся ненадолго, в расчете на то. что если кто-то запросил один размер, то есть большой шанс, что ему и другой размер той же картинки понадобится. А вот уменьшенные варианты кэшатся всерьез.


        1. vladimirkolyada Автор
          09.04.2018 20:33

          Можно это делать синхронно, на общий бекенд, мы так делали изначально. А можно асинхронно разными способами, например у нас событие просто IIS примет, создаст задание и отправит его на выполнение и будет ждать события о том, что выполнилось все и тогда получит изображение из кэша. Т.е. не результат обработки от сервисов обработки, а именно от сервисов кэша, туда же перед тем как маякнуть событием об окончании той или ной обработки, положил его обработчик, допустим CropHandler. Вариантов по сути масса. Мы делали это для распределения нагрузки по нескольким серверам независимо, без балансировки нагрузки на базе бекенда, т.е. выбора при синхронном запросе одного из набора сервисов — обработчиков.


          1. vladimirkolyada Автор
            09.04.2018 20:36

            А еще мне нравится, когда разносортные сервисы через общую шину решат задачи независимо, при этом можно отключать то одни, то другие, а система устойчиво работает, забавно, как живой организм.


          1. mgremlin
            10.04.2018 10:08

            Допускаю, что это вопрос важный, хотя ни разу у нас не возникало какой-то очереди. Обработчик всегда совершенно пустой, хотя это самый дохлый и дешевый дроплет DO.