- PVSM.RU - https://www.pvsm.ru -
Исторически во многих уголках Яндекса разрабатывались свои системы хранения и обработки больших объемов данных — с учетом специфики конкретных проектов. При такой разработке в приоритете всегда была эффективность, масштабируемость и надежность, поэтому на удобные интерфейсы для использования подобных систем времени, как правило, не оставалось. Полтора года назад разработку крупных инфраструктурных компонентов выделили из продуктовых команд в отдельное направление. Цели были следующими: начать двигаться быстрее, уменьшить дублирование среди схожих систем и снизить порог входа новых внутренних пользователей.
Очень скоро мы поняли, что тут мог бы здорово помочь общий высокоуровневый язык запросов, который бы предоставлял единообразный доступ к уже имеющимся системам, а также избавлял от необходимости заново реализовывать типовые абстракции на низкоуровневых примитивах, принятых в этих системах. Так началась разработка Yandex Query Language (YQL) — универсального декларативного языка запросов к системам хранения и обработки данных. (Сразу скажу, что мы знаем, что это уже не первая штука в мире, которая называется YQL, но мы решили, что это делу не мешает, и оставили название.)
В преддверии нашей встречи [2], которая будет посвящена инфраструктуре Яндекса, мы решили рассказать о YQL читателям Хабрахабра.
Мы, конечно, могли бы взглянуть в сторону популярных в мире open source-экосистем — таких как Hadoop или Spark. Но всерьез они даже не рассматривались. Дело в том, что требовалась поддержка уже распространенных в Яндексе хранилищ данных и вычислительных систем. Во многом из-за этого YQL был спроектирован и реализован расширяемым на любом из уровней. Все уровни мы по очереди разберем ниже.
На диаграмме пользовательские запросы перемещаются сверху вниз, но обсуждать затрагиваемые элементы мы будем в обратном порядке, cнизу вверх, чтобы рассказ получился более связным. Для начала пару слов о поддерживаемых на данный момент бэкендах или, как мы их называем, провайдерах данных:
Технически YQL, хоть он и состоит из относительно изолированных компонентов и библиотек, внутренним пользователям предоставляется в первую очередь как сервис. Это позволяет выглядеть с их точки зрения как «служба одного окна» и минимизировать трудозатраты на организационные вопросы вроде выдачи доступов или настроек фаервола для каждого из бэкендов. Кроме того, обе реализации классического MapReduce в Яндексе требуют наличия синхронно ожидающего завершения транзакции клиентского процесса, а сервис YQL берет это на себя и позволяет пользователям работать в режиме «запустил и забыл пришел за результатами позже». Но если сравнить модель предоставления сервиса с распространением в виде библиотеки, минусы тоже найдутся. Например, следует гораздо более аккуратно относиться к несовместимым изменениям и релизам — иначе можно в самый неподходящий момент сломать пользовательские процессы.
Основной точкой входа в сервис YQL является HTTP REST API, который реализован как Java-приложение на Netty [6] и не только занимается запуском поступающих запросов на вычисление, но и имеет широкий спектр вспомогательных обязанностей:
Использование Java позволило достаточно быстро реализовать всю эту бизнес-логику благодаря наличию готовых асинхронных клиентов для всех нужных систем. Поскольку слишком строгих требований по latency пока нет, то проблем со сборкой мусора было мало, а после перехода на G1 [7] они практически исчезли. Ещё, помимо упомянутого выше, для синхронизации между узлами используется ZooKeeper [8], в том числе в паттерне publisher-subscriber при отправке уведомлений.
Само выполнение пользовательских запросов на вычисление оркестрируется отдельными процессами на С++ под названием yqlworker. Они могут быть запущены как на тех же машинах, что и REST API, так и удаленно. Дело в том, что между ними идет общение по сети с помощью разработанного и широко распространенного в Яндексе протокола MessageBus. Под каждый запрос с помощью системного вызова fork (без exec) создается копия yqlworker. Такая схема позволяет достичь достаточной изоляции между запросами разных пользователей и при этом — благодаря механизму copy-on-write [9] — не потратить время на инициализацию.
Как видно из диаграммы с высокоуровневой архитектурой, Yandex Query Language имеет два представления:
Из запроса, вне зависимости от выбранного синтаксиса, создается граф вычислений (Expression Graph), который логически описывает необходимую обработку данных с использованием примитивов, популярных в функциональном программировании. К таким примитивам относятся: λ-функции, отображение (Map и FlatMap), фильтрация (Filter), свёртка (Fold), сортировка (Sort), применение (Apply) и многие другие. Для SQL-синтаксиса лексер и парсер, основанные на ANTLR v3 [10] строят Abstract Syntax Tree, по которому затем строится граф вычислений. Для синтаксиса s-expression парсер практически тривиален, поскольку грамматика крайне проста, а программы и так оперируют этими абстракциями.
Далее для получения требуемого результата запрос проходит через несколько стадий, при необходимости возвращаясь к уже пройденным:
На любой из стадий жизни запроса он может быть сериализован обратно в синтаксисе s-expressions, что крайне удобно для диагностики и понимания происходящего.
Как упоминалось во введении, одним из ключевых требований к YQL являлось удобство использования. Поэтому публичным интерфейсам уделяется особое внимание и они крайне активно развиваются.
На картинке показан интерактивный режим с автодополнением, подсветкой синтаксиса, цветовыми темами, уведомлениями и прочими украшательствами. Но консольный клиент может запускаться и в режиме ввода-вывода из файлов или стандартных потоков, что позволяет интегрировать его в произвольные скрипты и регулярные процессы. Есть как синхронный, так и асинхронный запуск операций, просмотр плана запроса, прикрепление локальных файлов, навигация по кластерам и прочие основные возможности.
Такая богатая функциональность появилась по двум причинам. С одной стороны, в Яндексе есть заметный пласт людей, предпочитающих работать преимущественно в консоли. С другой, это было сделано, чтобы выиграть время на разработку полнофункционального веб-интерфейса, о котором мы ещё поговорим.
Любопытный технический нюанс: консольный клиент реализован на Python, но распространяется как статически слинкованное нативное приложение без зависимостей со встроенным интерпретатором, которое компилируется под Linux, OS X и Windows. Кроме того, он умеет автоматически самостоятельно обновляться — примерно как современные браузеры. Всё это было достаточно просто организовать благодаря внутренней инфраструктуре Яндекса для сборки кода и подготовки релизов.
Python является вторым по распространенности языком программирования в Яндексе после C++, поэтому клиентская библиотека YQL реализована именно на нем. На самом деле она изначально разрабатывалась как часть консольного клиента, а затем была выделена в независимый продукт, чтобы появилась возможность использовать её в других Python-окружениях, не изобретая аналогичный код заново.
Например, многие аналитики любят работать в среде Jupyter [11], для которой на основе данной клиентской библиотеки был создан так называемый %yql magic:
Вместе с консольным клиентом поставляются две специальные подпрограммы, которые запускают преднастроенный Jupyter или IPython [12] с уже доступной клиентской библиотекой. Именно онии показаны выше.
Основной инструмент изучения языка YQL, разработки запросов и аналитики мы оставили на закуску. Благодаря отсутствию технических ограничений консоли, в веб-интерфейсе все функции YQL доступны в более наглядной форме и всегда находятся под рукой. Часть возможностей интерфейса показана на примерах других экранов:
Логика автодополнения запросов у консольного клиента и веб-интерфейса общая. Она умеет достаточно точно учитывать контекст, в котором происходит ввод. Это позволяет ей подсказывать только релевантные ключевые слова или имена таблиц, колонок и функций, а не всё подряд.
При сохранении запроса под именем они попадают в мини-аналог репозитория кода с возможностью просмотра истории и возврата к предыдущим версиям.
Здесь показана наиболее простая и универсальная реализация JOIN в терминах MapReduce.
Все ручки в самом REST API аннотируются по коду и на основе этих аннотаций с помощью Swagger автоматически генерируется подробная онлайн-документация. Из неё можно попробовать позадавать запросы без единой строчки кода. Это позволяет легко использовать YQL, даже если перечисленные выше готовые варианты по каким-то причинам не подошли. Например — если вы любите Perl.
Настала пора поговорить о том, какого плана задачи можно решать с помощью Yandex Query Language и какие возможности предоставляются пользователям. Эта часть будет скорее тизисной, чтобы не удлинять и без того длинный пост.
SELECT
произвольного уровня сложности и опционально содержат INSERT INTO
.CREATE TABLE
) и CRUD (плюс UPDATE
, REPLACE
, UPSERT
и DELETE
).
Позволяют при большом количестве уровней вложенности подзапросов писать их по очереди, а не в друг в друге по стандарту. Также они дают возможность не копипастить часто используемые выражения.
Доступен как синтаксис для получения элементов по ключу или индексу, так и набор специализированных встроенных функций.
FLATTEN BY
За этим ключевым словом закреплена возможность размножать строки исходной таблицы с вертикальным разворачиванием контейнеров (списков или словарей) переменной длины из колонки с соответствующим типом данных.
Звучит немного запутанно — проще показать на примере. Возьмём таблицу следующего вида:
[a, b, c] | 1 |
[d] | 2 |
[] | 3 |
Применив FLATTEN BY
к левой колонке, получим такую таблицу:
a | 1 |
b | 1 |
c | 1 |
d | 2 |
Подобное преобразование может быть удобным, когда по ячейкам из колонки-контейнера нужно посчитать какую-либо статистику (скажем, через GROUP BY
) или когда в ячейках — идентификаторы из другой таблицы, с которой нужно сделать JOIN
.
Самое забавное во FLATTEN BY
вот что: оно называется по-разному во всех системах, которые умеют так делать. Из того, что мы нашли, нет ни одного повтора:
ARRAY JOIN
— ClickHouse,unnest
— PostgreSQL,$unwind
— MongoDB,LATERAL VIEW
— Hive,FLATTEN
— Google BigQuery,
PROCESS
(Map) и REDUCE
(Reduce).
Позволяют встраивать в запросы на YQL cуществующий код, написанный в парадигме MapReduce, в сочетании с механизмом пользовательских функций, о котором пойдет речь ниже.
Не все виды преобразований данных удобно выражать декларативно. Иногда проще написать цикл или воспользоваться какой-нибудь готовой библиотекой. Для таких ситуаций YQL предоставляет механизм пользовательских функций, они же User Defined Functions, они же UDF:
Внутри у агрегационных функций используется общий framework с поддержкой DISTINCT
, выполнения как на верхнем уровне, так и в GROUP BY
(в том числе и с ROLLUP/CUBE/GROUPING SETS
из стандарта SQL:1999). А отличаются эти функции лишь бизнес-логикой. Вот некоторые примеры:
COUNT
, SUM
, MIN
, MAX
, AVG
, STDDEV
, VARIANCE
;COUNT_IF
, SOME
, LIST
, MIN_BY/MAX_BY
, BIT_AND/OR/XOR
, BOOL_AND/OR
;MEDIAN
и PERCENTILE
(по алгоритму TDigest [14]);HISTOGRAM
— адаптивные гистограммы по числовым значениям, не требующие никакого знания их распределения, (по алгоритму на основе Streaming Parallel Decision Tree [15]).Из соображений производительности, в терминах MapReduce для агрегационных функций автоматически создается Map-side Combiner с объединением промежуточных результатов агрегации в Reduce. DISTINCT
сейчас всегда работает точно (без приближенных вычислений), поэтому требует дополнительного Reduce для разметки уникальных значений.
Слияние таблиц по ключам — одна из самых популярных операций, которая часто нужна для решения задач, но правильно реализовать которую в терминах MapReduce — почти целая наука. Логически в Yandex Query Language доступны все стандартные режимы плюс несколько дополнительных:
Чтобы скрыть детали от пользователей, для основанных на MapReduce бэкендов стратегия выполнения JOIN выбирается на лету в зависимости от требуемого логического типа и физических свойств участвующих таблиц (это так называемая cost based optimization):
Стратегия | Краткое описание | Доступна для логических типов |
Common Join | 1-2 Map + Reduce | Все |
Map-side Join | 1 Map | Inner, Left, Left only, Left semi, Cross |
Sharded Map-side Join | k паралелльных Map (k <= 4 по-умолчанию) | Inner, Left semi с уникальной правой, Cross |
Reduce Without Sort | 1 Reduce, но требует заранее правильно отсортированного входа | в разработке |
Среди наших ближайших и среднесрочных планов по Yandex Query Language:
SQL over ***
: в первую очередь нас интересовала именно поддержка активно используемых в Яндекс систем. Хотелось упростить миграции проектов, то есть все компоненты из диаграммы в начале поста всё равно пришлось бы разрабатывать и/или дорабатывать. При этом пришлось бы подстраиваться под устои open source-сообщества. Кроме того, были бы трудности с тем, что Java-разработчиков в Яндексе примерно на порядок меньше, чем C++-разработчиков, а люди с опытом разработки ядра этих open source-проектов — дефицит даже в США. И в результате совершенно не факт, что получилось бы лучше или быстрее. YQL создан с нуля где-то за год командой примерно из 10 человек, большинство из которых участвовали не full time.Напоследок — ещё раз приглашаем на встречу в нашем офисе [2] в ближайшую субботу, 15 октября, где мы подробнее расскажем о разных аспектах инфраструктуры в Яндексе.
Автор: Яндекс
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/algoritmy/198485
Ссылки в тексте:
[1] Image: https://habrahabr.ru/company/yandex/blog/312430/
[2] встречи: https://events.yandex.ru/events/meetings/15-oct-2016/?utm_source=habr&utm_medium=articles&utm_campaign=Infr
[3] недавнем посте: https://habrahabr.ru/company/yandex/blog/311104/
[4] был отдельный пост: https://habrahabr.ru/company/yandex/blog/189362/
[5] ClickHouse: https://habrahabr.ru/company/yandex/blog/303282/
[6] Netty: http://netty.io/
[7] G1: https://docs.oracle.com/javase/7/docs/technotes/guides/vm/G1.html
[8] ZooKeeper: http://zookeeper.apache.org/
[9] copy-on-write: https://en.wikipedia.org/wiki/Copy-on-write#Copy-on-write_in_virtual_memory_management
[10] ANTLR v3: http://www.antlr3.org/
[11] Jupyter: http://jupyter.org/
[12] IPython: http://ipython.org/
[13] Python 3 type hints: https://docs.python.org/3/library/typing.html
[14] TDigest: https://github.com/tdunning/t-digest
[15] Streaming Parallel Decision Tree: http://jmlr.org/papers/volume11/ben-haim10a/ben-haim10a.pdf
[16] V8: https://developers.google.com/v8/
[17] LuaJIT: https://luajit.org
[18] Яндекс.Статистики: https://stat.yandex.ru
[19] Hive: http://hive.apache.org
[20] Spark SQL: http://spark.apache.org/sql
[21] Источник: https://habrahabr.ru/post/312430/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.