Как стать автором
Обновить

Комментарии 26

Есть бенчмарки, которые бы подтвердили это утверждение: «BaseX является [...] высокопроизводительной»? Гугл сходу как-то не находит.
Бенчмарки весьма специфичны, и их не с чем сравнивать. Я попробую подготовить хоть какую-то вменяемую статистику.

Навскидку, если сравнивать с Oracle XML Database и их реализацию XQuery, то на документах в 600 мегабайт BaseX при полном индексировании выбирает по XPath нужный элемент за 5 милисекунд, Oracle меньше чем 150мс не удалось соптимизировать. Не исключено, что у нас были весьма кривые руки, и мы не смогли добиться нужной производительности. Брали несколько вариантов, самым быстрым, если не ошибаюсь, оказался вариант с полностью загруженным в одну ячейку документом :) Если брать честный XML Storage, то аналогичная операция менее секунды не длилась.

На документах в 1.5 млн записей, с построенными индексами, выборка по двум параметрам около 500 мс. Там индекс слабо работает, почти уникальные данные.

Для теста можете потыцкать живую демку. Особенно запросы к базе dblp, там 500 мегабайтный документ
Обычно, когда говорят «NoSQL», подразумевают partition tolerance. А здесь даже репликации нет, не то что шардинга. В целом, Oracle и MSSQL предоставляют подобный инструментарий для хранения и работы с XML, а местами и больше (ну может синтаксис не такой лаконичный), поэтому с первого взгляда не вижу никаких преимуществ, кроме цены.
А с чего вы взяли, что "«NoSQL», подразумевают partition tolerance"? Или berkeleydb, leveldb и т.д. уже не NoSQL?

После провала пробовали заменить oracle'ом. Пока basex выдерживает нагрузку — oracle значительно медленней. Цифр не дам — было 2 месяца назад, по памяти разница времени отклика от порядка до двух на идентичном железе. Если не выдерживает — масштабировать крайне сложно, а переезд превратится в адские муки.
Да, этого нет. Система довольно молодая. Шардинг можно огранизовать средствами XQuery.

Оракл не дает выполнять чистый XQuery, все равно требует PL/SQL обвязку, да и updating спецификацию не поддерживает.
Как по мне, то обычно когда говорят «NoSQL», то подразумевают нереляционные СУБД.
Автору спасибо за статью. Впервые столкнулся с BaseX около 4-х месяцев назад, за это время натыкался на следующие проблемы:
1. Отсутствие инкрементальных бекапов. На выручку пришёл nilfs, благо архитектура проекта позволяла выключать сервер БД на пару секунд каждый час.
2. Блокировки. Возможно, если вдумчиво прочитать всю документацию, этой проблемы можно и избежать. Мы же в свою очередь писали много (почти каждый запрос) и в один документ.
3. Масштабируемость. И если без репликации жить можно, то без многопоточности у нас не было шансов.

Но есть и несомненный плюс — скорость и сложность разработки. Не взлетев, сейчас переходим на PostgreSQL. Теперь лаконичность старого кода вызывает восторг и умиление.
Ошибка вышла, многопоточность есть, в нашем случае нарвались на блокировки.
На блокировки во время чтения или записи? И какого плана вы писали продукт?
Там два типа блокировок, и чтения, и записи. Но страшнее всего блокировка на запись, особенно когда она блокирует много баз одновременно.

CRM для микрофинансирования. Отказались от BaseX больше по политическим мотивам. Как уже сказал Tricesimus, если вдумчиво почитать документацию, разнести последовательно записываемые операции в разные базы на разные инстансы, то оно спокойно бы взлетело.
Как-то исследовал различные NoSQL технологии, и пробовал BaseX.
Во-первых, складывается впечатление, что XML базы данных рассчитаны в основном на чтение, а не на обновление. Для чтения используются стандарт XPath/XQuery, тогда как запись ведется при помощи собственного API. Все попытки стандартизировать API (xqj, xml:db) недоработаны. Отсутствие клиентских транзакций приводит к тому, что всю логику нужно выполнять в одном вызове (запроса либо скрипта).
Непонятно как себя ведут сложные «пересекающиеся» запросы (по типу JOINs и подзапросов в RDBMS) на больших объемах данных, может ли BaseX сам оптимизировать плохие запросы и каким образом использует индексы в данных запросах.
Поэтому основное применение — standalone приложения, которые ищут по большому числу xml документов.

P.S. У BaseX есть два других open-source конкурента: eXist и Sedna. Первый достаточно навороченный, а второй написан на C. Здесь преимущество BaseX — легковесность и возможность запускаться в embedded режиме.
У BaseX для каждого запроса можно посмотреть план выполнения

Например, для запроса
for $i in doc('koatuu')//ITEM[CODE_PARTS/L1/text()='80'] return <ITEM>{$i/NAME/text()}</ITEM>

BaseX предлагает оптимизацию
for $i in db:text("koatuu", "80")/parent::*:L1/parent::*:CODE_PARTS/parent::*:ITEM return element ITEM { ($i/NAME/text()) }

и показывает план запроса
<QueryPlan>
  <GFLWOR>
    <For>
      <Var name="$i" id="0"/>
      <CachedPath>
        <ValueAccess data="koatuu" type="TEXT">
          <Str value="80" type="xs:string"/>
        </ValueAccess>
        <IterStep axis="parent" test="*:L1"/>
        <IterStep axis="parent" test="*:CODE_PARTS"/>
        <IterStep axis="parent" test="*:ITEM"/>
      </CachedPath>
    </For>
    <CElem>
      <QNm value="ITEM" type="xs:QName"/>
      <CachedPath>
        <VarRef>
          <Var name="$i" id="0"/>
        </VarRef>
        <IterStep axis="child" test="NAME"/>
        <IterStep axis="child" test="text()"/>
      </CachedPath>
    </CElem>
  </GFLWOR>
</QueryPlan>


Я не сильно ковырялся в исходниках, поэтому не могу сказать, оптимизирует ли он запросы перед тем, как выполнять, но, скорее всего, нет.

Например, предыдущий запрос выполняется около 900 мс (с учетом сетевого трафика), а переписанный с учетом рекомендаций на
for $i in doc('koatuu')//L1[text()='80'] return <ITEM>{$i/../../NAME/text()}</ITEM>

выполняется уже за 30мс.

И план уже несколько другой
<QueryPlan>
  <GFLWOR>
    <For>
      <Var name="$i" id="0"/>
      <CachedPath>
        <ValueAccess data="koatuu" type="TEXT">
          <Str value="80" type="xs:string"/>
        </ValueAccess>
        <IterStep axis="parent" test="*:L1"/>
      </CachedPath>
    </For>
    <CElem>
      <QNm value="ITEM" type="xs:QName"/>
      <CachedPath>
        <VarRef>
          <Var name="$i" id="0"/>
        </VarRef>
        <IterStep axis="parent" test="node()"/>
        <IterStep axis="parent" test="node()"/>
        <IterStep axis="child" test="NAME"/>
        <IterStep axis="child" test="text()"/>
      </CachedPath>
    </CElem>
  </GFLWOR>
</QueryPlan>
Жуткий синтаксис с for. Не знакомому с XPath (это же он, вроде) кажется абсолютно нечитаемым заклинанием :)

Лучше уж ту же mongo с ее js запросами, или redis с lua… Имхо, конечно же.
Это XQuery. XPath — это вот эта часть: "//L1[text()='80']" (найти все элементы L1, в которых текстовое содержимое — «80»). И XPath, он, как раз, довольно простой и логичный, пока с осями не слишком извращаешься.
Жуткий? После года использования он уже кажется каноничным и логичным. Через определенное время начинаешь замечать, что в других языках пытаешься использовать XQuery синтаксис.
:)
Меня интересует больше как выполняются пересекающиеся запросы, типа:
for $p in doc('company')/Persons/Person[@name='Vasya'] for $d in doc('company')/Departments/Department where $d/Stuff/personRef=$p/@id return <department>$d/@name</department>
То есть все отделы, где работает хотя бы один Вася.
Внутренний и внешний циклы можно поменять местами: внешний лучше поставить тот, где больше записей. Как это будет выполнять XBase непонятно. Например, если в RDBMS поля personRef и id проиндексированы, она вообще убирает внутренний цикл, заменяя параллельным сканом обеих таблиц. Если же XBase каждый раз выполняет внутренний скан, то на больших объемах данных это будет еле ворочаться.
В данном запросе не нужен второй for
let $VasiaIDs := doc('company')/Persons/Person[@name='Vasya']/@id 
(: Выбираем айдишки и заносим их в последовательность (sequence) :)
for $d in doc('company')/Departments/Department[Stuff/personRef/text()=$VasiaIDs] 
(: Перебираем департаменты, у которых в ссылках на персоны, есть хоть один элемент из множества $VasiaIDs :)
return <department>$d/@name</department> 
(: Возвращаем нужный департамент :)

XPath тем и хорош, что умеет перемножать множества, заменяя JOIN'ы. Мало того, XPath может использовать индексы в BaseX, в отличие от for, поэтому мой запрос работает гораздо быстрее.
По поводу конкурентов. eXist несколько не конкурент, потому что в него мы не смогли загрузить XML документ на 600MB, а Sedna давно уже не обновляется.

BaseX за год сменил четыре версии 7.3 -> 7.5 -> 7.6 -> 7.7, сейчас в работе 7.8, и его можно смело признать самым динамично развивающейся XMLDB
Меня лично убивают эти запросы на xQuery.
Поставил на локал загрузил десяток файлов XML. Никак не могу правильный запрос написать. ))) Не знаю надо ли переводить локальное хранилище в xml файлах в эту штуку

Надо чтобы мозги щелкнули :) Это похоже на то как с императивщины на функциональщину переходить.
Не подскажешь как написать запрос
Например к файлу news.xml
Получить элемент item у которого id='news-123'

И вот посложнее
У элемента item есть несколько дочерних элементов tag, у тега тоже есть атрибут id
Нужно выбрать все элементы item у которых есть элементы tag c id = 'видео'
Красиво!
Вот этот вариант работает правильно
//item[./tag[id='video']]
Т.е. выдает все элементы item в которых есть элемент tag c id = 'video'
Первый выдает все элементы tag c id='video'
А где в этом запросе написано что он относится к news.xml, а не к какому либо другому файлу?
Первый выдает все элементы tag c id='video'

Нет, вы, видимо, пропустили две точки в конце. Он находит tag[@id="video"] и берет его родителя (те самые две точки).

А где в этом запросе написано что он относится к news.xml, а не к какому либо другому файлу?

Нигде, это XPath. Он служит для нахождения узлов в дереве одного документа. В вашем случае нужно еще добавить дополнительную обвязку XQuery, например, из этого коммента: habrahabr.ru/post/201166/#comment_6951140
Аааа вот оно что. Спасибо за пример очень интересно, буду ковырять дальше.
Есть маленький нюанс. BaseX имеет два механизма по работе с XML: создание базы на его основе и просто хранения как исходного файла. Во втором случае никакие XQuery на него не действуют (кроме как выковырять файл с диска и загрузить его в память). А вот если загрузить его в базу данных, тогда все ок.

Через GUI вы создаете базу данных, указываете имя, но не указываете источник этой базы. Потом открываете свойства базы данных и добавляете ресурсы (XML-файлы). Только потом можно будет делать запросы.

Например, если базу данных назвали mydb, то доступ к документам и содержимому в ней будет через collection('mydb')/root/a/b

Для конкретно вашего запроса это будет выглядеть как XPath

collection('mydb')//item[tag[@id]]
collection('mydb')//item[tag[@id='видео']]

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории