Часто хочется попробовать новое: архитектурное решение, рекламную партнерскую сеть или новый фреймворк. В боевых проектах новое применить сложно, не хочется нарушать принцип «работает — не трогай». Для этих целей подходят собственные небольшие проекты, которые «не жалко».
Представьте: огромный поток презентаций в PPT и PPTX, которые можно брать и использовать как угодно.
Реализация
Основную архитектуру заложил наш тимлид. Всё основано на очередях сообщений. Этапы обработки:
Ищем файлы PPT и PPTX и получаем ссылку для загрузки.
Загружаем презентации по полученной ссылке.
Извлекаем из презентации слайды, из слайдов — текст.
Фильтруем презентации. Не берем работы с матом или запрещенным контентом, с малым количеством текста или со слишком большим количеством слайдов.
Извлекаем из слайдов картинки.
Рендерим презентацию, чтоб каждый слайд представлялся одной картинкой.
Сохраняем обработанную презентацию в базу.
Каждый шаг — отдельный компонент. Компонент в любой момент можно остановить, поправить или заменить, запустить. При этом остальные компоненты продолжат выполнять работу.
После обработки для каждой презентации формируем страницу на сайте, где можно презентацию посмотреть и скачать.
Обработка PPTX
С обработкой PPTX никаких проблем не должно было быть — это открытый формат, для него есть OpenXML SDK. Начали мы с таких презентаций.
Самые «тяжелые» с вычислительной точки зрения компоненты — это извлекатель картинок и рендерер презентации. Картинки надо пережимать в JPEG, чтобы много места не занимали.
Казалось бы, проблем нет, но мы смогли их себе устроить.
Мы пишем под .NET, последнее время — только под .NET Core. Запускаться собирались на линуксовой виртуалке. Под .NET почти вся работа с графикой сделана через GDI/GDI+, реализация которого есть в рамках проекта Mono, но…
Короче, мы нарвались на сильно текущую память. Вроде и Dispose делали для всех Image/Bitmap, но память всё равно текла. Пришлось проводить исследование, как можно работать с картинками на .NET под линуксом. Возможные решения: упомянутая реализация из Mono (libgdiplus), биндинги для ImageMagick, биндинги для Skia. Выбор пал на ImageMagick: он не тёк и легко ставился в систему в отличие от Skia.
Рендеринг
Надо было делать свой рендерер или использовать готовое решение. Готовым решением оказался LibreOffice. У него есть headless режим: операции выполняются через командную строку.
Не нашли возможности, чтобы сразу резать картинки. Работу построили по схеме:
Преобразовывали PPTX в PDF с помощью LibreOffice.
Резали PDF на слайды с помощью ImageMagick. Не обошлось без приключений: люди любят шрифты. Хлебом не корми, дай использовать какой-нибудь красивый шрифт. Решилось прозаично: накатили шрифты рядом с LibreOffice.
Серверная часть
Мы не знали, выгорит ли затея, поэтому сразу брать в аренду выделенный сервер казалось нецелесообразным. Особенно когда есть BizSpark, с подачи которого можно развернуть на Azure тестовую виртуалку и не вылезти из лимита по деньгам.
Виртуалка была не самая мощная: два ядра, два гига, игровая видеокарта восемь гигабайт оперативки, SSD для системы и HDD для данных, объем которых ожидался большим.
Хозяйке на заметку 1: в Azure медленные диски, которые еще режутся по IOPS. Это было очень больно.
Хозяйке на заметку 2: текущая память в извлекателе картинок привела к активному своппингу. При условии медленных дисков это было ещё больнее. Своп в итоге отключили.
Каждый компонент обернут в Docker-контейнер. Помимо описанных компонентов для работы системы потребовались:
RabbitMQ в качестве очереди сообщений;
MySQL в качестве РСУБД;
фронт, написанный под ASP.NET Core, с которого отдавались страницы презентаций;
nginx в качестве web-сервера для отдачи статики и reverse proxy для фронта;
MongoDB в качестве хранилище для метаданных об обработанных презентациях;
Elasticsearch, Logstash и Kibana для работы с логами.
Довольно много. ELK-стек сразу вынесли на другой сервер, ибо жрал он знатно. Остальное крутилось рядышком, что привело к очередным трудностям.
Трудоемкие компоненты могли скушать целиком ядро, которых было всего два. В итоге, когда поток презентаций стабилизировался, получили неприятную картину: наглухо загруженный процессор.
Конвейер пришлось останавливать и думать (зачем думать сначала-то?). Docker умел ограничивать ресурсы для каждого контейнера, это стало решением. Рендерер, как компонент с самым большим аппетитом, получил выделенное ядро и не получил ограничений по процессорному времени. Остальные компоненты конвейера были раскиданы на второе ядро так, чтобы в сумме процент использования был меньше 100. У нас еще фронт, база и очередь.
С такой реализацией удалось добиться скорости около 1500 презентаций в сутки. Под конец 2018 года в базе было порядка 80000 презентаций. Поисковые системы индексировали контент. Мы готовились к прохождению модерации в рекламных сетях. Ничего не предвещало беды.
Неожиданные проблемы
После праздников нас ждала новость: на другой учетной записи закончился BizSpark. Это был первый звоночек. Сервер восстановили на ещё одной учетке, мы продолжили работать. Потом BizSpark закончился и на ней. Второй звоночек нельзя было игнорировать: 1.5 гигабайта базы и 500 гигабайт файлов очень не хотелось терять.
Конвейер остановили. Базу забэкапили и вытащили на локальный компьютер. Как быстро утащить 500 гигабайт — идей не было. Я решил заархивировать нужные файлы, а потом вытянуть архивы. Это должно было быть быстрее, чем вытягивать по одному файлу. Однако дали о себе знать ажурные диски. На упаковку всего требовалось примерно 10 часов. В тот момент я молился всем богам сразу, чтоб BizSpark не кончился внезапно, и я успел всё вытащить. Не повезло. Виртуалка кончилась.
Было грустно. Жалко контент, жалко потраченное время, жалко проиндексированные страницы. Всё, что осталось из контента — база.
Решение, что мы запустим всё ещё раз, приняли сразу. Оставили заказ на аренду сервера. Нормального, честного сервера с 8 ядрами и 32 гигабайтами памяти. С двумя нормальными дисками по 4 ТБ. Оставалось придумать, как восстановить всё с минимальными потерями.
Появилась идея. Я спросил нашего маркетолога, насколько критично, если выкатим всё без картинок? Оставим старые тексты и URL, вместо изображений пока поставим заглушки, а потом как-нибудь вытянем со старого сервера? Идея понравилась всем. Как только новый сервер заработал и мы получили доступы, приступил к минимальной настройке: Docker, база, nginx, фронт. Конвейер пока был не нужен, остальное бы запустить скорее.
DNS обновлялись долго, но оно хотя бы работало. Оплатили виртуалку Azure на один день. Я запустил rsync в три потока между серверами: один для презентаций, второй для картинок, третий для слайдов. Попутно перекинул данные RabbitMQ и MongoDB.
К утру все файлы перенесли. Сайт работал как обычно. Осталось восстановить конвейер и поток презентаций, что сделал в тот же день. Обновление сервера принесло плоды. Презентаций в сутки стало обрабатываться в 4 раза больше.
Подключаем обработку PPT
В какой-то момент поток презентаций сократился. Вместо старых 6000 в сутки получили жалкие 100–200, что сильно нас печалило. PPTX стало не хватать, надо было обрабатывать PPT.
PPT — формат закрытый, бинарный. Лучше всего с ним работает Microsoft Office, а у нас всё под линуксом крутится. Но бредовые идеи — лучшее, что периодически случается со мной. Почему бы не запустить на физическом сервере свою виртуалку с виндой, а в ней развернуть приложение для конвертации PPT в PPTX с помощью Office? А работать с PPTX мы уже умеем.
Запустили ещё один экземпляр поисковика презентаций. Его цель — PPT-файлы, ссылки на которые писались в отдельную очередь. Пока там плавно копились презентации, я собрал первый прототип, который работал по принципу:
Скачать PPT.
Открыть через Interop PPT в Office.
Сохранить PPT в PPTX.
Положить результат в очередь «загруженных».
Процесс пошёл. Поток восстанавливался. А потом приложение наткнулось на презентацию с паролем, и PowerPoint ждал, пока кто-нибудь его введёт. Проблему решили через передачу произвольного пароля. Это приводило либо к ошибке, если пароль не совпал, либо к открытию презентации, если пароля там не было.
Также добавили тайм-аут на обработку одной презентации. В случае слишком долгой обработки убивали процесс PowerPoint. Причина — большие презентации, которые не приносят пользу и требуют много ресурсов. Один из примеров — презентация по языкознанию из 812 слайдов, которая не сконвертировалась за 1,5 часа.
Что у нас получилось → https://slide-share.ru/
Таймскип на 3 года...
Спустя три года наш агрегатор презентаций все еще живёт. Не всегда здравствует, но живёт, что не может не радовать. В 2022 году получили более 6 млн посетителей на сайт:
На том моменте, как спасли данные и контент из лап Microsoft и взяли нормальный сервер, мы остановились и радовались. Радовались так сильно, что сервер стал выполнять дополнительные функции: на нём развернули тестовый полигон для других проектов. Иногда не самый лёгких.
В какой-то момент на одном сервере крутились: сам сайт, конвейер обработки презентаций, Jenkins для сборки проектов, пачка тестовых бэкендов и фронтендов. Постепенно сервер перестал вывозить. Посещаемость ресурса росла, количество контента — тоже, но мощности были конечными. Google и Яндекс отреагировали на это предупреждениями и ухудшением позиций.
Разделяем сервера
Можно было бы заморочиться с кэшем, попытаться что-то оптимизировать, но волей определенного (пусть и не самого приятного) случая мы приняли решение перевезти всю инфраструктуру из юрисдикции Российской Федерации.
Так у нас освободился еще один сервер на территории РФ, который решили использовать для организации «фронтовой» части проекта. Обработку запланировали вынести на отдельный сервер, чтоб она не нагружала контентные сервера. Таким образом, пришли к схеме с тремя серверами:
Сервер с «сайтом». На нем не предполагается хранить что-то тяжелое. База, кэш, Elasticsearch для полнотекстового поиска, какие-то другие не очень тяжелые проекты.
Сервер с конвейером для обработки. Всё, что требует постоянных нагрузок, уезжает туда и жрет все возможные мощности.
Сервер со статикой. Его задача — принимать в себя картинки и файлы презентаций, а потом отдавать посетителям и роботам. Именно этой задачей стал заниматься сервер, на котором изначально было все развернуто. В том числе потому, что у него самые большие диски, а поток контента и не думал уменьшаться.
Переезд прошёл гладко и без даун-тайма, так как продумали все заранее. Остановили обработку, чтоб ничего нового в базу не писалось. Сдампили базу и развернули её на новом сервере, запустили сайт. Старый не отключали сразу. Ждали, пока пройдёт TTL для обновления A-записей. Даже когда они обновились, оставили на какое-то время проксирование на новый сервер со старого через nginx.
В итоге страницы стали быстро загружаться. Так мы частично разнесли яйца по корзинам. К сожалению, одна из корзин содержала в себе вложенные корзины-яйца, из-за которых мы продолжили огребать с проблемами.
Оптимизируем работу с изображениями
Google любит оптимизированные форматы для изображений. Хлебом его кормить не стоит, а вот WebP — хорошее лакомство. Впрочем, как и для Яндекса сейчас.
Наш конвейер обработки был достаточно прямолинейным. Последним шагом после конвертации было сохранение исходного документа, слайдов, изображений со слайдов. Все раскладывалось в отдельные папки на диск. На один диск из двух. В одни и те же папки.
Вы когда-нибудь пытались выполнить `ls` в папке, где, скажем, десять миллионов мелких JPEG’ов? И у вас не шустрый SSD, а обычный HDD на 7200 RPM. Я — пробовал. И не дожидался результата, а только подвешивал дисковую подсистему попытками.
Итак, у нас есть несколько миллионов файлов на одном диске. Ext4, которую мы используем на серверах, работать с таким умеет, но это не очень приятно стреляет в производительность. Количество файлов продолжает расти, место постепенно заканчивается, статика отдается все медленнее. В iotop мы видели нагрузку в 99.99% на один диск. Все сжирал nginx, который героически пытался справиться с этим.
Нужно было придумать решение, которое бы позволило:
задействовать оба диска сервера со статикой для равномерной загрузки,
преобразовывать входящие jpeg в WebP,
не складывать все файлы в одну папку никогда,
раскидать весь текущий контент с учетом новой системы.
С разработкой решения для статики проблем не возникло (думала прошлая версия меня). Создал небольшое приложение на Python с Flask, которое умеет:
принимать в себя файл,
сохранять на диск,
если JPEG — преобразовывать в WebP,
вести статистику по использованному месту, чтобы было проще распределять нагрузку.
Идея, по которой файлы раскладывались на диск, показалась мне гениальной. В качестве имен для загружаемых файлов будем генерировать UUID’ы.
От UUID’ов посчитаем какой-нибудь хэш, но не очень длинный, например, MD5. Переводим MD5 в hex-строку, разбиваем по два символа и получаем путь из папок. Гениальное решение, да? Теперь в каждой папке у нас может быть не больше 256 папок, а файлов будет и того меньше.
Это было ужасное решение, которое выстрелило нам в ногу позже, но рассказ об этом будет в следующей статье.
Итак, мы сделали решение, которое позволило нам организовать статику. Поправили компонент в конвейере, который сохранял файлы. Теперь он раскидывал всё на два отдельных диска. Сделали небольшое приложение, которое должно было раскидать старую статику под новый, настроили редиректы для изображений. Запустили процесс, оценили, сколько потребуется времени на переносы и затаились в ожидании...
dprotopopov
Ищем файлы PPT и PPTX и получаем ссылку для загрузки.
А как с разрешением от автора решались проблемы? Или не решались? Честно украдено?
revoltlab Автор
Все презентации берем из открытого доступа. Авторские права мы уважаем, поэтому материалы удаляем по запросу.
dprotopopov
Понятно, как бомжики на чужой даче - поживем, но если приедут хозяева, то сьедем
dprotopopov
Надеюсь вы понимаете, что ваш бизнес до первого грамотного юриста, который стребует с вас компенсацию за незаконное использование материалов и просто удалением их со своего сайта вы не отделаетесь.
Из личного опыта сталкивался - в рекламной газете дизайнер поместил фото яйца фаберже тиснутое из интернета. Через некоторое время пришел иск от музея где это яйцо было.
При этом автор ppt мог сам тиснуть чужой контент, и ничего не отзывать у вас, но отвечать придеться вам. Украденое у вора не делает тыренный товар добросовесно приобретенным.
Дерзайте