Александр Кошелев
Александр Кошелев Python-разработчик

Виджет на морде

Виджет на морде

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

Django-Yandex

Обновленная Яндекс.Афиша работает на Джанге. Вот уже третий месяц как. Запустили мы её прямо в канун пятницы тринадцатого в марте!

Проект получился большой, со своими особенностями. Расскажу вам про процесс разработки с допустимой детальностью.

Глобальная цель была - обновить движок Яндекс.Афиши, переписав его на Django.

Что да как

Распил кода КВИ

Ни для кого не секрет, что сервис “Куда все идут” один из сервисов Яндекса написанных на Джанге. Долгое время он был дополнением к Афише и добавлял социальный фан для пользователей. Ему и было суждено дать начало новой Афише.

Мы резонно решили, что, переписывая Афишу на Джанге, нужно опираться уже на имеющийся code base КВИ.

Но при разработке КВИ никто и не думал, что в последствие этот код может быть использован в другом проекте, поэтому процесс отделения и обобщения имеющихся наработок занял много времени и сил.

Тогда мы впервые начали использовать наследование моделей в Джанге, т.к. многие наши сущности можно было строго разделить на общие части и какие-то сервисо-зависимые надстройки. Это кстати одно из преимуществ использования trunk версии - все плюшки получаются с пылу жару и не надо ждать релизов. В тот период я написал несколько постов про механизм наследования и его особенности. К счастью большинство проблем со временем решилось и мы не пожалели, что стали его активно применять.

Конечно, применение наследования таких комплексных сущностей как джанговские модели не могло оказаться бесплатным. Пришлось очень строго следить за тем чтобы родители и наследники были в строго консистентном состоянии - синхронно создавались и умирали. Но зато мы сократили до минимума необходимость переписывать уже имеющийся код КВИ в местах манипуляции с моделями - всё благодаря абсолютно прозрачно работы с атрибутами наследников и родителей, и ORM. Well done Django team!

Рефакторинг кода это штука хорошая, но как весь это ворох изменений влить в активно развивающийся апстрим? Вот тут тоже пришлось поломать голову и поработать чуть-чуть живым merge инструментом:-) Но в итоге всё удачно слилось и мы пошли дальше к нашей цели.

Могучий импорт

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

Кстати, системы импорта заработала раньше всех, уже в начале осени. И в канун Нового года показала свою работоспособность - когда за очень ограниченное время требовалось подключить несколько новый источников дынных о новогодних событиях. Всё удалось.

Удачно интегрировать её в проект помогла возможность писать свои команды для manage.py. Рекомендуем.

Первый камень

В начале сентября была написана первая строчка кода непосредственно в самом проекте Афиша. На базе выделенного обобщенного кода КВИ я начал разработку сервиса, постепенно шаг за шагом копируя имеющийся функционал. Не сказал бы, что сразу пришло понимание как должна выглядеть система. Причем что я приметил - в начале придумывал какие-то варианты архитектуры, потом с удивлением замечал как эти варианты хорошо и логично реализуется Джангой. То ли я уже настолько привык к Джанге, что мыслю уже её паттернами, то ли она сама настолько гибкая, что позволяет реализовать практически неограниченные варианты построения сервиса. Загадка:-)

Состояния

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

Каждое состояние описывается фильтрами которое оно может поддерживать, правилами перехода в другие состояния и некими вспомогательными функциями, которые нужны уже для отображения конкретных данных. Поскольку максимально логичным для меня было применение объектной модели, то получилось что все вьюхи Афиши это объекты, классов-состояний, которые инстанцируются в момент поступления запроса, делают свои дела и умирают.

В последствие я уже понял что по сути это другая реинкарнация шаблонного контекста. Поскольку почти весь шаблонный код изобилует конструкциями вида {{state.get_foo}} или {% if state.has_objects %}, то по сути state выполняет роль объекта-контекста на базе которого строиться страница.

Система живет и пока удовлетворяет потребностям. И из-за своей объектной сути позволяет легко наследовать функциональность и избежать дублирования кода.

Вот, кстати, объектное построение вызвало не мало споров у нас. Ваня отстаивал позицию, что так или иначе “наследование” можно использовать и в процедурном стиле, тем самым не нарушая привычной для Джанги архитектуры. Но мне первой на ум пришла именно такая, объектная, концепция, поэтому я за неё зацепился. Да и легла она мне на голову сразу, не вызвав каких-то дополнительных затруднений в проектировании.

Кеш

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

Причем особенность кеширования тут в том, что кеш протухает в определенные время. Т.е. есть определенные моменты времени дня, когда кеш должен обновиться. Из-за этого пришлось придумать механизм, который хранил бы в себе эти отсечки и позволял бы отсчитывать таймауты относительно них. Также для оптимизации работы кеша, был использован схема избежания dogpile эффекта с кастомным кеш-бекэндом Джанги. Вот тоже очередной пример её гибкости.

Ничего сверхъестественного, но благодаря единой системе взаимодействия компонентов(в виде состояний) интегрировать и настроить кеши получилось достаточно быстро и просто.

Кластер

Весь проект живет на большом кластере по соседству с КВИ и другими питон-сервисами. Поскольку процесс выкладки был уже отлажен, то мне оставалось только его скопировать и чуть-чуть адаптировать под свои нужны.

Как я сам убедился - дебиановские пакеты вполне себе решения для выкладок. Конечно у нас достаточно тепличные условия - однородные системы в кластере, поэтому выкладки (кроме самой первой тестовой:-)) проходили и проходят гладко и без проблем.

Media

Если масштабировать производительность приложения и базы данных вполне легко и очевидно как, то что делать с медиа файлами?

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

С медиа файлами, а в частности с картинками событий, связана ещё одна интересная особенность Афиши - тамбнейлы. Они генерируются по требованию, достаточно в шаблоне правильно позвать image.url.... По поводу этого интерфейса вызова правильной картинки было много споров, но мой вариант победил и прижился. Ваня предлагал сделать шаблонный тег с параметрами нужного изображения, а мне хотелось попробовать передавать эти параметры как часть имени атрибута, т.е. примерно так {{event.image.thumbnail_100x100}}. Благодаря гибкой подсистеме файлов в Джанге это получилось сделать достаточно легко.

pda

Когда уже проект был почти готов, мы поняли, что младшая сестра большой Афиши - pda’шная Афиша, тоже должна быть нами поддержана и реализована на новом движке. Старую оставлять было невозможно из-за слишком больших архитектурных изменений. Да и замкнуть весь проект на себя было в перспективе удобней.

И тут я ощутил всё прелесть MVC фреймворков и Джанги в частности. Вся задача свелась к тому, чтобы написать одну middleware, в которой определять какую афишу мы смотрим, и подсовывать правильный джанговский шаблон. Заняло это, если я не ошибаюсь, всего 2 рабочих дня у верстальщика. И не пришлось менять не строчек кода в других местах. Отлично!

Взаимодействие.

Так сложилось, что от Афиши в Яндексе зависят ещё достаточно много сервисов, в том числе большая и мобильная Мора, страница результатов поиска и другие даже ещё не выпущенные. Для налаживания взаимодействия нового сервиса с ними потребовалось очень много времени.

В основном это взаимодействие заключалось в предоставлении нами неких данных на экспорт, который все заинтересованные сервисы могли бы читать и обрабатывать. Для этого был написан очередной мини-фреймворк, который в полу-декларативном стиле позволяет указывать что мы хотим отдавать и как. Почему “полу”? Да просто по тому, что невозможно за какое-то разумное время придумать и реализовать полностью декларативный DSL и чтобы он мог “подходить под все размеры”. Вот поэтому императивные части неизбежны и со временем их становится всё больше и больше, если конечно не подтягивать DSL под новые требования. Я пытался сохранить баланс и вовремя вносить какие-то изменения. но при этом не тратя много времени.

Слава питону и Джанге, что они мне ни разу не помешали реализовать какую-то идею - правильные инструменты:-)

Ссылки

Поскольку предполагалось наиболее плавное и безболезненное переключение со “старой” Афиши на “новую” и чтобы вся информация за время жизни сервиса разлетевшаяся по просторам Интернета не потеряла свою актуальность, то требовалось иметь возможность поддерживать старые ссылки. Причем пришлось это делать именно на уровне приложения. Не долго думая, прикинув требования, я написал ещё один мини-фреймворк для умных редиректов:-) Это совпало с постом Малькольма о продвинутом redirect_to. Пост я сразу не прочитал, а лишь на следующий день, когда мне Ваня сказал, что мы с Малькольмом удивительно в одну сторону мыслим. Конечно у нас всё было гораздо серьезней, и айдишники старых сущностей надо конвертировать и сами урлы по схеме очень сильно отличались (это, кстати, и не позволило разруливать редиректы на уровне веб-сервера- пришлось опускаться до приложения).

Но в итоге всё решение - один view и urls.py с мапингом старых ссылок на новые. Получилось очень просто и в тоже время покрывало требования с лихвой.

Производительность

Последний этап разработки это оптимизации и тестирование производительности. Ваня, как-то уже упоминал наших танкистов, так вот тут они оттянулись по полной. Поскольку мы получаем трафик с большой морды, то должны выдерживать более солидную нагрузку, чем КВИ. Отсюда и более высокие требования к производительности. Проанализировав логи старого сервиса, и поняв предполагаемую нагрузку, мы стали стрелять. Как оказалось, мы по сути сходу перекрыли требования раза этак в два. Что очень всех порадовало.

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

Жизнь

Немного сумбурное описание получилось. Ещё много чего осталось за кадром и по возможности когда-нибудь я ещё расскажу о каких-то интересных деталях разработки и развития Яндекс Афиши.

А пока, спасибо всем кто работал над проектом - вы отличная команда! Вперед к новым целям!

comments powered by Disqus