ArangoDB в реальном проекте

ArangoDB мультимодельная (документная и графовая) база данных. К ее положительным сторонам относятся:



Из менее существенных, но не менее удобных возможностей:



Справедливости ради отмечу и недостатки:



После анализа возможностей ArangoDB и, в особенности, после преодоления в последних версиях недостатков (таких как резкое падение производительности при превышении размера коллекции доступной оперативной памяти) и появлении новых возможностей (таких как нечеткий поиск) — пришло время испытаний в реальном приложении.


Возможности AQL (ArangoDB Query Language)


Один из главных вопросов, который меня волновал, будет ли выразительность AQL достаточной для выполнения всего спектра запросов в реальном приложении. И будет ли работа без ORM/ODM достаточно комфортной.


В ArangoDB есть несколько способов сделать запрос к данным. Есть привычный для тех, кто работает с MongoDB, объектно-ориентированный API, но такой способ в ArangoDB считается устаревшим и основной упор делается на запросы AQL.


Простейший запрос к одной коллекции выглядит так:


db.query({
  query: `for doc in managers
    filter doc.role == @role
    sort doc.@field @order
    limit @page * @perPage, @perPage
    return doc`,
  bindVars: { role, page, perPage, field, order },
});

Такой вот интересный язык запросов, построенный на ключевом слове FOR, которое в данном случае не означает перебор всех документов коллекции, если, конечно, по полю role создан индекс.


В большинстве случаев, для работы приложения нужно выбрать связанные объекты из нескольких коллекций. В библиотеке mongoose (MongoDB) для этого используют метод populate(). В ArangoDB это можно сделать одним запросом AQL:


db.query({
   query: `
      for mall in malls
        for city in cities
          filter mall.cityId == city._key
      return merge(mall, { city })
  `,
  bindVars: { },
});

Это типичный INNER JOIN. Только немного удобнее, так как объект city будет присутствовать в виде вложенного объекта, а не сольётся в список полей, как это происходит в стандартном SQL.


Что касается LEFT JOIN — для его реализации нужно использовать подзапросы и ключевое слово LET:


db.query({
  query: `
    for city in cities
      let malls=(
        for mall in malls
          filter mall.cityId==city._key
          return mall
      )
    return merge(city, {malls})`,
  bindVars: { },
});

Результирующий объект будет содержать поле malls типа array или значение null. Как Вы можете заметить, есть отличие от LEFT JOIN в стандартном SQL — это то, что количество объектов в результирующей коллекции будет равно количеству объектов в коллекции city, и не будет повторяться для каждого значение mall. Вместо этого mall представлено массивом. Я бы сказал, что такой вариант даже более удобен для работы. Получить же "классический" результат, как в SQL, также можно, но запрос будет более сложный.


Я привел самые простые запросы из тех, что есть в реальном приложении. Но, как выяснилось, на базе вышеперечисленных средств можно строить самые сложные запросы, которые не менее, а может быть и более выразительны, чем запросы SQL. При этом умолчу о других документо-ориентированных NoSQL базах данных, где аналогичные запросы просто невозможны.


Графы


Для реализации граф-ориентированных возможностей в ArangoDB применяются коллекции ребер графа. Документы в этой коллекции отличаются от документов в простых коллекциях наличием двух служебных полей: _from и _to. Работать с коллекциями ребер графом можно теми же средствами, что и с коллекциями документов. В дополнение существует несколько специальных средств для обходя графов.


Я планировал реализовать на граф-ориентированных возможностях дерево категорий товаров. Однако, реализация операций update оказалась неожиданно сложной. Поэтому я отказался от этой идеи. Возможно, просто не нашел еще ключ к этим возможностям.


Нечеткий поиск


Есть такая часто встречающаяся задача: искать текст, если в исходной строке есть опечатки или ошибки. Как правило, для этого используется база данных Elacticsearch. У такого решения есть два недостатка. Во-первых, нужно согласовывать в режиме реального времени значения в основной базе данных и в Elasticsearch. Это непросто, часто эти значения расходятся, и тогда приходится принудительно переиндексировать базу данных. И, во-вторых, Elasticsearch требовательна по ресурсам, что также не всегда приемлемо из соображений финансового порядка.


В последних версиях ArangoDB можно создать SEARCH VIEW в котором можно искать значения с неполным совпадением:


  await db.createAnalyzer('fuzzy_brand_search_bigram', {
    type: 'ngram',
    properties: { min: 2, max: 2, preserveOriginal: true },
    features: ['position', 'frequency', 'norm'],
  });
  await db.createView('brandSearch', {
    links: {
      brands: {
        includeAllFields: true,
        analyzers: ['fuzzy_brand_search_bigram'],
      },
    },
  });

Сам запрос выглядит так:


db.query({
    query: `
       for brand in brandSearch
          search NGRAM_MATCH(
              brand.name, 
              @brandName, 
              0.4, 
              'fuzzy_brand_search_bigram'
          )
          filter brand.mallId == @mallId
        return brand `,
    bindVars: { mallId, brandName },
});

Без ODM?


В своей статье я показал, что по статистике, MongoDB в половине случаев используется без ODM. То есть, это достаточно распространенная практика.


Действительно, сделать запрос, как это было показано выше, гораздо проще средствами AQL, чем определять схему с разными видами связей. Во всяком случае, не было еще ни одного проекта на Sequelize (ORM для реляционных баз данных), где не пришлось бы сделать один-два RAW запроса.


Однако, я, тем не менее, сторонник использования ODM. В своей статье я описал, что я хотел бы от ODM для ArangoDB. ODM не обязательно должна заниматься генерацией запросов в базу данных. Я бы хотел, чтобы ODM обеспечивала сохранение в базу данных только нужных полей, и следила за наличием обязательных полей. А при получении объекта из базы данных типизировала его, добавляла вычислимые поля, фильтровала набор полей для разных групп запросов, и обеспечивала локализацию значений полей.


В настоящее время я нашел всего один фреймвёрк, который очень близок к тому, что я хочу получить. Но мне в нем не хватает двух возможностей. Во-первых для методов типа PATCH входной объект, как правило, содержит не все, а только изменяемые поля. Для таких запросов нужно отключать полные правила валидации. И, во-вторых, там невозможно сделать локализацию значений. Я незамедлительно создал два issue в этом репозитарии. К чести автора, он ответил почти мгновенно, но ответ меня далеко не устроил. По первому вопросу он рекомендовал сначала забирать полный объект из базы данных, а затем мерджить его с объектом с неполным набором полей. По второму порекомендовал локализацию делать на фронтенде.


В своей статье я описал и реализовал свою библиотеку. Её и использовал в реальном проекте. Конечно, были моменты стресса, когда выходило, что возможностей этой библиотеки недостаточно. Но их в основном удалось разрешить. Так что по-прежнему приглашаю к сотрудничеству желающих продвигать технологию ArangoDB.


apapacy@gmail.com
15 марта 2021 года

@apapacy
15.03.2021 04:25 UTC
Первоисточник

Комментарии

@debagger
15.03.2021 01:12 UTC
0
Язык запросов чем-то напоминает LINQ дотнетовский.
@nitrosbase
20.03.2021 18:21 UTC
0
ArangoDB гибридная (документная и графовая) база данных.

Есть слово мультимодельная. И по-английски вполне общепринятое, и сами ArangoDB по отношению к себе его употребляют, и в русский уже просочилось. Или можно сказать про СУБД с гибридной моделью, чтобы отличать от других подходов к мультимодельности.


Из менее существенных, но не менее удобных возможностей...

Я бы ещё отметил различные возможности и удобства для ML. Кстати, делают их в российском подразделении, там ещё делают тестирование и пр.


Пришло время испытаний в реальном приложении.

А в чьих интересах делался «реальный проект», если не секрет? В РФ знаю только про ВТБ, что те использовали (для графа связанности юрлиц, кажется).


Напоследок отмечу, что автор той статьи про KG на главной сайта ArangoDB — человек-легенда в индустрии. До того работал на TigerGraph, AnzoGraph, data.world и др. Так что, возможно, не всё неправильно делают :).

@apapacy
21.03.2021 15:04 UTC
0

Исправил в тексте на мультимодельную. Гибридные у меня как-то закрепились из старых классификаций. Проект был несложный бэкенд мобильного приложения. Основная мотивация была использовать нечетнкий поиск без привлечения elasticsearch и обкатать библиотеку которую я описал в сообщении https://habr.com/ru/post/522992/