[Из песочницы] Долой оковы MongoDB
Многие из нас в свое время бросились с энтузиазмом осваивать MongoDB, действительно красота — удобный JSON формат, гибкая схема (точнее полное ее отсутствие), от установки системы до первого использования проходят буквально минуты. Но через некоторое время, уже когда Mongo надежно «зашита» в наш проект наступает разочарование. Простейшие запросы требуют постоянного тыкания в документацию, чуть более сложные способны убить почти целый день рабочего времени, а уж если понадобится join разных коллекций — то увы…И вот уже кто-то возвращается к Постгресу с его частичной поддержкой JSON…
Но, к счастью, уже куется, уже спешит к нам полноценная замена Mongo, полноценная полу-структурированная Big Data СУБД AsterixDB. Этот проект возглавляет профессор UCI Michael Carey, ученик легендарного пионера СУБД Майкла Стоунбрейкера.
Проект стартовал просто как исследовательское начинание в области Big Data и изначально ориентировался на создание общего стэка для MapReduce и SQL. Но, буквально несколько лет назад, было принято решение построить Big Data JSON СУБД. По словам Майкла Кери, «AsterixDB is Mongo done right.» В чем же основные фишки AsterixDB?1. Схема. Да-да, схема штука все-таки полезная, и полностью избавляться от нее не надо. Уверен, в любом хранилище JSON часть полей заранее известна, фиксирована и изменению не подлежит. Но, естественно, Asterix не заставляет вас полностью проектировать всю схему данных. Можно и дальше жить без схемы. Но, если хочется привнести немного порядка — те поля, которые фиксированы, заносим в схему данных, остальные оставляем «открытыми». Что это дает? Проверку данных во время insert, более компактное хранение, наглядное представление чего у вас где лежит.
Примерчик:
create type TwitterUserType as open { screen-name: string, lang: string, friends_count: int32, statuses_count: int32, name: string, followers_count: int32 } Создали тип данных TwitterUserType, все перечисленные поля в нем фиксированные, но, так как тип открыт, можно добавлять произвольные другие поля. Теперь на основе этого типа, можем создать «родительский» тип:
create type TweetMessageType as closed { tweetid: string, user: TwitterUserType, sender-location: point?, send-time: datetime, referred-topics: {{ string }}, message-text: string } Тут мы видим поле «user», тип которого соответствует TwitterUserType. Не лазил в код, но думаю что есть возможность описывать сразу и вложенные структуры, не присваивая их к каким-то именованным типам. Но, даже если нет, уверен, скоро такой функционал появится.
А-а, да, типы данных поддерживаемые AsterixDB:
Boolean Int8 / Int16 / Int32 / Int64 Float Double String Point — геометрия Line — геометрия Rectangle -геометрия Circle — геометрия Polygon — геометрия Date Time Datetime Duration/Year-month-duration/Day-time-duration Interval — временной интервал, в AsterixDB реализована логика Алена для временных интервалов, страшная штука, но может быть полезной Ну и вложенные типы:
Record OrderedList UnorderedList 2. Язык запросов! Когда-нибудь в Mongo приходит время, когда надо сгруппировать, сагрегировать данные, и выдать некое подмножество исходных полей, дополненных вычисленными. Вот тогда начинается жуткая головная боль. Так вот, в AsterixDB присутствует полноценный язык запросов, со всем функционалом SQL, только разработанный специально для слабо-структурированных данных. Это — упрощение функционального языка запросов XQuery, который используется в XML СУБД. Не буду отсылать читателей ботать W3C спецификацию XQuery, хотя, если есть желание — то велкам! Попробую написать мини-туториал по языку AQL (Asterix Query Language).
Основу языка запросов составляет контрукция FLOWR (почти цветочек): For-Let-OrderBy-Where-Return. Еще сюда вставим GroupBy, но немного позже. Транслируя это на SQL получаем:
For — это практически FROM clause в SQL, здесь выбираем коллекции, по которым пробегаем. После For получаем таблицу переменных с их значениями, сверху которой применяются остальные операции, практически как в SQL.
Например: после
for $x in users, $y in groups
получаем записи в форме:
($x: user1, $y: group1), ($x: user1, $y: group2), …
То есть обычный cross-product в SQL.
Let — это дополнительный clause, здесь можно вводить новые переменные. Здесь не добавляются новые кортежи к результату For, а просто добавляются новые переменные и их значения.
OrderBy — тут все просто, эквивалент SQL сортировки.
Where — опять же, обычный фильтр, полный аналог SQL Where.
Return — тут мы задаем, что именно мы хотим вернуть. В отличие от SQL, где мы всегда возвращаем список колонок, здесь можно городить любые JSON структуры. И в этом clause, на ура идут вложенные запросы, которые генерят разные кусочки результата.
Надеюсь вас все перечисленное не расстроило, давайте рассмотрим несколько примеров. Сначала самый примитивный:
for $user in dataset FacebookUsers where $user.id = 8 return $user Просто сделали выборку коллекции FacebookUsers, это эквивалентно Mongo: db.FacebookUsers.find ({«id»: 8 })
Писанины больше, но это для простых запросов. Когда начнется жесть, разобраться в запросе будет куда проще.
Теперь поинтереснее запрос, здесь мы сделаем join и создадим новый тип данных на выходе:
for $user in dataset FacebookUsers for $message in dataset FacebookMessages where $message.author-id = $user.id return { «uname»: $user.name, «message»: $message.message }; Вроде все очевидно, не так ли? For пробегают по парам users/messages, where задает условие джоина, return создает новый JSON объект с полями uname и message.
Теперь давайте сгруппируем все сообщения одного пользователя в одном JSON объекте, с полем uname:
for $user in dataset FacebookUsers let $messages:= for $message in dataset FacebookMessages where $message.author-id = $user.id return $message.message return { «uname»: $user.name, «messages»: $messages }; Здесь мы замутили вложенный запрос в let и к переменной $messages присвоили список всех сообщений пользователя. Другой вариант достичь того же:
for $user in dataset FacebookUsers return { «uname»: $user.name, «messages»: for $message in dataset FacebookMessages where $message.author-id = $user.id return $message.message }; Мне лично вторая форма запроса нравится больше, вместо того, чтобы готовить данные «заранее» и вставлять их в объект, сразу пишем вложенное выражение.
Так же в AQL есть конструкция GroupBy, но по сути она заменяется вложенными запросами и не обязательна (хотя Having штука полезная бывает). С другой стороны, скорее всего оптимизация запросов будет более качественная с GroupBy, но это уже к вопросу об эффективности.
По сути основы AQL мы покрыли, напишем последний запрос:
for $user in dataset TwitterUsers distinct by $user.name order by $user.followers_count desc limit 2 return $user Здесь мы заюзали сразу несколько стандартных фишек SQL — distinct, order by, limit. Вроде очевидно, что делает запрос: сначала убирает дупликаты по имени пользователя, сортирует, выдает первые 2 значения и формирует результат.
Что забыли? Агрегаты? Тут совсем все просто — агрегат, это просто обычная функция в AQL, которая берет на вход список значений. AsterixDB включает в себя все знакомые агрегаты, причем сразу в 2-х вариантах: полностью SQL совместимых и более человеческих (кто помнит как SQL обращается с NULL внутри агрегатов?). Ну и конечно можно понаписать своих.
Что еще хорошего в AsterixDB? Основа системы процессинга запросов — прослойка Algebrix — это гибкая алгебраическая система, которая позволит в будущем написать качественный cost-based оптимизатор запросов для распределенной СУБД. Пока оптимизация на начальном этапе развития, вроде бы хорошо подхватывает индексы, но еще нет статистики и настоящей оптимизации (хотя можно добиться приемлимых результатов хинтами в запросах). Индесков тоже несколько типов — B-Tree, keyword — инвертированные списки для полнотекстового поиска, R-Tree для геометрии, n-gram для поиска по подобию.
AsterixDB написана на Java, уже можно добавлять свои UDF (User Defined Function) или в Java или в Jython, для тех, кто любит Питон.
Consistency в Asterix довольно слабая, ACID на уровне индивидуальных документов, как и в MongoDB. Планов по поддержке транзакций, затрагивающих серию докуметов или коллекций нет. Да вроде это всех устраивает.
Ну вот, теперь надеюсь искра надежды, на то, что можно будет в принципе перебраться с MongoDB на более нормальную платформу посеяна, осталось ждать пока разработчики до боевого вида.
Чего осталось доделать?
Не доделана еще настоящая репликация с отказоустойчивостью. Нету стриминга результатов из базы по API (внутри с этим все нормально, но для внешних запросов система «готовит» весь результат у себя и отдает его потом по REST API). Нет еще клиентских библиотек. Много работы по отпимизации запросов осталось, начиная со сбора статистики. Ну и наверное море мелких задач.
Так что если не хочется дожидаться в сторонке, можно засучить рукава и помочь ребятам допилить такую классную штуку.
Asterix DBMS.