Найти любой ценой, или Как гарантированно сформировать поисковый ответ

Краткая версия доклада:

Ответы для всех пользователей Яндекса формирует программа AppHost. После ввода запроса в поисковой строке он начинает уточняться данными («источниками» - геолокацией, логином, скоростью), которые позволят дать более точный ответ, после чего начиняется сам процесс поиска – страниц, картинок, маркет-информации и т.п. Затем, после компоновки и верстки, страница передается пользователю.

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

В этих условиях логичным выглядит шаг по реализации логики обхода источников «прямо в коде», т.е. в одном большом приложении. На первый взгляд здесь можно было бы применить стандарт GraphQL, однако он работает на модели REST, что не всегда возможно для источников в Яндексе, которые используют бинарные протоколы, потому что отправляют довольно много данных.

В итоге нами было принято решение заняться разработкой решения своими силами, описав обход источников в виде графа. Преимущество данной схемы состоит в том, что можно статистически проверить граф на ошибки, также для его описания не нужно уметь программировать. Программу, которая осуществляет такой обход источников по описанию графа, назвали AppHost, язык ее реализации – C++. Проблема, решением которой занимается программа, - организация общения источников между собой, в ходе которого каждый из них что-то добавляет в общий контекст. Удаление какого-то фрагмента из этой платформы невозможно. Кроме того, специальная клиентская библиотека может встраиваться в источник и абстрагировать его от деталей протокола, которым мы пользуемся, что позволяет обмениваться данными в унифицированном виде. Результирующий контекст попадает в вёрстку, которая сама пор себе становится источником.

Для формирования ответа AppHost обходит граф источников, в каждый посылает текущий контекст, в который источники добавляют свои данные. Результирующий контекст отправляется в вёрстку и оттуда – пользователю.

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

Далее, для решения типовых задач их выносят в синтетические источники. Например, язык JSON будет преобразовываться по специальной схеме из входного в выходной, сюда же относятся и статические источники, которые всегда возвращают одно и то же. Был разработан специальный адаптер, который преобразует HTTP-запрос в контекст и обратно, таким образом, появляется возможность подключать источники, которые находятся в режиме поддержки. Таким образом, предложенная архитектура позволила сделать множество полезных оптимизаций и запустить программу в production. Говоря о полученном опыте разработки и применения, важно отметить, что мы отделили бизнес-логику от логики обхода, что помогло реализовать схему статических проверок без циклов и с достижимостью выхода, получать некоторые характеристики, например, самые длинные пути в графе, с учётом статистики ответов, а также описать все сетевые взаимодействия в одном месте.

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

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

Благодаря архитектуре программы авторы графов получили возможность тестировать свои изменения, в том числе производить нагрузочное тестирование, подключать AppHost и без клиентской библиотеки (и обращаться к AppHost-источникам напрямую), проводить эксперименты путем изменения части готового графа и выкатки её на процент пользователей.

Дальнейшее развитие архитектуры AppHost предполагает максимизацию локального трафика, например, не использовать сеть в случаях, когда два источника оказались на одной машине. Еще одно направление для оптимизации – IDE для графов, т.е. более выразительный и краткий DSL для описания графов, их отладка и профилирование, схематизация API источников и саджест по нему, и все это в удобном GUI.