...

среда, 7 мая 2014 г.

[Из песочницы] Долой оковы 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.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий