Pull to refresh

Долой оковы MongoDB

Reading time6 min
Views29K
Многие из нас в свое время бросились с энтузиазмом осваивать 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.
Tags:
Hubs:
+49
Comments87

Articles

Change theme settings