Новое в СУБД Caché 2013.1: добавление и генерация индексов на «живых» классах

  • Tutorial
Предположим, что у вас есть таблица с большим количеством записей и в неё нужно добавить один или несколько индексов со следующими условиями:

  1. их генерация должна быть максимально быстрой
  2. чтобы генерацию можно было производить порциями.
    К примеру, если есть таблица на 300М записей и работы с ней можно производить только в нерабочее время, то чтобы можно было разбить весь процесс на три ночи по 100М записей
  3. появление новых индексов и сам процесс их генерации не должны мешать текущей работе с классом/таблицей

Для этого можно было бы воспользоваться уже известным методом %BuildIndices(), но в таком случае это не будет удовлетворять нашим условиям.

Каков же выход?

Теория


В версию СУБД Caché 2013.1 был добавлен новый класс %Library.IndexBuilder с одним, но мощным методом %ConstructIndicesParallel().
Из названия уже становится понятно, что генерация будет происходить параллельно с привлечением всех ядер процессоров.

Итак, рассмотрим параметры этого метода подробнее:

ClassMethod %ConstructIndicesParallel(pTaskId="", pStartId As %Integer = 0, pEndId As %Integer = -1, pSortBegin As %Integer = 1, pDroneCount As %Integer = 0, pLockFlag As %Integer = 1, pJournalFlag As %Boolean = 1) as %Status

  • pTaskId — ID фонового процесса. Оставьте пустым/неопределённым для интерактивного вызова
  • pStartId — ID, с которого нужно начать генерацию. По умолчанию 1
  • pEndId — ID, на котором нужно завершить генерацию. По умолчанию -1, означающее последний ID в таблице
  • pSortbegin — 1/0 флаг, определяющий использовать ли $SortBegin при генерации.
  • pDroneCount — количество фоновых процессов для генерации индексов.
    По умолчанию 0. В этом случае код будет самостоятельно определять оптимальное число процессов, основываясь на количестве доступных ядер/процессоров и количестве обрабатываемых записей.
  • pLockFlag — флаг, определяющий поведение блокировки во время выполнения генерации:
    • 0 = Нет блокировки
    • 1 = Extent locking — Получает исключительную блокировку на весь экстент в течение генерации
    • 2 = Row level locking — Получает разделяемую блокировку на каждую обрабатываемую строку и узел индекса для элемента. Когда генерация индекса для конкретной строки завершена, немедленно снимается блокировка этой строки.
  • pJournalFlag — 0/1 флаг, определяющий использование журналирования:
    1 — генерация индекса будет журналироваться, 0 — не будет.


Практика


Теперь рассмотрим пример применения нового класса.

Для начала создадим в области USER учебный класс, заполним его 1М записей строками переменной длины [1-100] и построим индекс с использованием классического %BuildIndices(), чтобы было с чем сравнивать:

Class demo.test Extends %Persistent
{

Index idxn On n As SQLUPPER(6);

Property As %String(MAXLEN 100);

ClassMethod Fill(As %Integer 10000000)
{
  
set data=$Replace($Justify("",100)," ","a")
  
set time=$ZHorolog
  do 
DISABLE^%NOJRN
  
do ..%KillExtent()
  
set ^demo.testD=n
  
set ^demo.testD(1)=$ListBuild("",$Extract(data,1,$Random(100)+1))
  
for i=2:1:set ^(i)=$ListBuild("",$Extract(data,1,$Random(100)+1))
  
do ENABLE^%NOJRN
  
write "вставка= ",$ZHorolog-time," сек.",!
}

ClassMethod BIndex()
{
  
set time=$ZHorolog
  do 
..%BuildIndices(,1,1)
  
write "переиндексация= ",$ZHorolog-time," сек.",!
}

}

Мои результаты:
USER>do ##class(demo.test).Fill()
вставка= 9.706935 сек.

USER>do ##class(demo.test).BIndex()
переиндексация= 71.966953 сек.

Теперь задействуем новый класс %IndexBuilder. Для этого выполним следующие действия:

  1. сперва очистим данные индекса от предыдущего теста методом %PurgeIndices() (необязательный шаг)
  2. унаследуем наш класс от %IndexBuilder
  3. пропишем список индексов через запятую в параметре INDEXBUILDERFILTER.
    Если этот параметр оставить пустым, то будут перегенерированы все индексы
  4. сделаем наш индекс невидимым для SQL, чтобы оптимизатор не использовал ещё не готовый к работе индекс.
    Для этого воспользуемся методом $SYSTEM.SQL.SetMapSelectability():

    ClassMethod SetMapSelectability(pTablename As %Library.String = "", pMapname As %Library.String = "", pValue As %Boolean = "") as %Library.String

    Описание аргументов:
    • pTablename — имя таблицы
    • pMapname — имя индекса
    • pValue — 0/1 флаг, определяющий видимость(1) или невидимость(0) индекса для SQL-оптимизатора
    Примечание: можно cделать индекс невидимым задолго до его добавления в класс.
  5. вызовем метод %ConstructIndicesParallel()
  6. сделаем наш индекс видимым для SQL
  7. Profit!

В итоге наш класс приобретёт следующий вид:

Class demo.test Extends (%Persistent%IndexBuilder)
{

Parameter INDEXBUILDERFILTER = "idxn";

Parameter BITMAPCHUNKINMEMORY = 0;

Index idxn On n As SQLUPPER(6);

Property As %String(MAXLEN 100);

ClassMethod FastBIndex()
{
  
do ..%PurgeIndices($ListBuild("idxn"))
  
do $SYSTEM.SQL.SetMapSelectability($classname(),"idxn",$$$NO)
  
do ..%ConstructIndicesParallel(,,,1,,2,0)
  
do $SYSTEM.SQL.SetMapSelectability($classname(),"idxn",$$$YES)
}
}

Мои результаты:
USER>do ##class(demo.test).FastBIndex()

Building 157 chunks and will use parallel build algorithm with 4 drone processes.
SortBegin is requested.
Started drone process: 3812
Started drone process: 4284
Started drone process: 7004
Started drone process: 7224
Expected time to complete is 43 secs to build 157 chunks of 64,000 objects using 4 processes.
Waiting for processes to complete....done.
Elapsed time using 4 processes was 34.906643.

Как видим, скорость возросла в два раза.

На вашем железе и на ваших данных результаты могут получиться ещё лучше.

Ещё быстрее?


Но есть ли возможность ещё больше ускорить перегенерацию индексов?
Если у вас есть в запасе много RAM, то да.

В процессе генерации индексов конструктором для внутренних нужд временно формируются так называемые bitmap-блоки. По умолчанию они записываются в приватные глобалы, но с помощью булева параметра BITMAPCHUNKINMEMORY можно указать, чтобы они формировались в оперативной памяти. Для этого нужно параметру присвоить 1.
Заметьте, что если RAM выделено мало, а индексы большие, то вы можете получить ошибку <STORE>.
По умолчанию BITMAPCHUNKINMEMORY равен 0.
InterSystems
57,70
InterSystems IRIS: СУБД, ESB, BI, Healthcare
Поделиться публикацией

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

    0
    Скажите, а на ваших данных SQL-таблицы в Каше насколько быстрее работают аналогичных в MySql?
      0
      С MySQL не работаю.

      Вопрос холиварный: дело ведь не только в скорости, но и в поддерживаемых фичах.
      Например, Oracle очень «мудро» поступила, включив поддержку bitmap-индексов и параллельное построение индексов только в Enterprise Edition

      PS: кстати, в MySQL уже появились bitmap-индексы?
        0
        Я это спрашиваю не для холивара. Просто интересно. Суть вопроса в том, что таблицы эмулируются с помощью глобалов. Т.е. для глобалов это не самый естественный режим работы. Поэтому и интересно.

        Если можете сравнить с ораклом, то ещё лучше.
        А интересуют меня операции INSERT, SELECT с INNER JOIN, транзакции.

        P.S. Ничего не слышал о том, что в mysql повились битмап-индексы. К слову говоря — это ведь не индексы, так как у них скорость работы пропорциональна n, а не log(n).

          0
          Я это спрашиваю не для холивара. Просто интересно.
          Если действительно интересно, то можете поискать на sql.ru, где немало было сказано на сей счёт.
          Суть вопроса в том, что таблицы эмулируются с помощью глобалов.
          Если быть точнее, то с помощью регистров процессора.
          Т.е. для глобалов это не самый естественный режим работы
          Да, естественность в наше время сдаёт позиции особенно в Европе, достаточно вспомнить "естественность" различных ORM.
          Если можете сравнить с ораклом, то ещё лучше.
          Я не работаю с Oracle.
          А интересуют меня операции INSERT, SELECT с INNER JOIN, транзакции.
          Поищите информацию про проект GAIA. Много ссылок есть как на sql.ru, так и на intersystems.ru, например:

          Почитайте ещё «белые книги».

          Есть разработчики, которые используют исключительно объекты и SQL, и с глобалами непосредственно не работают, и скорость их вполне устраивает.
          Ничего не слышал о том, что в mysql повились битмап-индексы
          Вопрос был риторический: я ведь ссылку не просто так привёл. Сильно сомневаюсь, что Oracle их "просто так" добавит в MySQL.
          К слову говоря — это ведь не индексы
          Вам виднее. Неужели ГМО?
            0
            Ссылка насчёт GAIA интересная, но из неё неясно используется ли SQL-слой для вставки.

            То что MUMPS может быстро вставлять записи — известный факт (у меня на домашнем компе, внутри виртуальной машины, 660 000 инсертов в секунду GT.M делает).

            В редких БД используется в основном вставка — только во всяких системах логгирования. Для этой задачи, возможно, MUMPS нет равных.

            Поэтому мне было интересно как Cache делает SELECT-запросы с джойнами.

            В «белых книгах» содержится информация, которая выставляет Cache в хорошем свете, так как всё это размещено на корпоративном сайте Intersystems. И эти книги (про сравнение скоростей) я читал. Мне было интересно ваше мнение как частного лица.

            Так что если вы сами не проводили сравнительных тестирований Cache с SQL-базами, то так бы и сказали.
              0
              Для проекта Gaia используется Caché Java Extreme технология. Документация об этой штуке.
              И да, это без использования SQL напрямую из Java в глобалы.
              В принципе можно также работать с Caché и из .NET и из node.js.
                0
                Ссылка насчёт GAIA интересная, но из неё неясно используется ли SQL-слой для вставки.
                Если хорошо поискать, то можно найти, что для вставки использовались Java и Caché eXTreme API.
                Поэтому мне было интересно как Cache делает SELECT-запросы с джойнами.
                К слову, ещё очень популярен вопрос «Почему Caché нет на TPC.org?».

                Вы, наверное, знаете, что в Caché встроен BI (DeepSee), позволяющий использовать SQL/MDX на живых данных без необходимости построения отдельного хранилища. Поэтому, непонятно ваше недоумение.

                Если грамотно спроектированы классы/таблицы и подобраны индексы, собрана статистика, умело используются подсказки оптимизатору, то скорость ничем не уступает даже прямому доступу. Всё зависит от конкретных данных и конкретного запроса.
                Так что если вы сами не проводили сравнительных тестирований Cache с SQL-базами, то так бы и сказали.
                Я не устаю повторять, что самый лучший способ сравнения — это пилотный проект на реальных данных и железе с реальной бизнес-логикой. Такое сравнение, я конечно же проводил, но для Caché. Со стороны других СУБД это делали другие люди. Вы же согласитесь, что настройкой и оптимизацией должны заниматься специалисты по тому или иному продукту.
                  0
                  говоря «Поэтому мне было интересно как Cache делает SELECT-запросы с джойнами» я подразумевал как быстро.

                  Такое сравнение, я конечно же проводил, но для Caché.


                  Сравнение можно проводить если есть хотя бы 2 элемента. А смысла сравнивать каше с каше немного.

                  Я хоть и не являюсь профессионалом MUMPS, тем не менее на скорость вставки сравнение GT.M с MySql провёл.

                  А вот на SELECT с джойнами нет, т.к. это долго для простого любопытства.

                  А ведь это очень важный вопрос. По инсертам всё понятно — MUMPS рвёт всех, а вот по SELECT с джойнами для меня пока вопрос открытый.
                    0
                    Если всё-таки хотите немного «синтетики», то могу дать несколько ссылок:
                    А смысла сравнивать каше с каше немного.
                    Согласен.
                    Чтобы корректно сравнивать, нужны соответствующие специалисты, иначе потом появятся лишние вопросы типа «А можно было ещё вот так и этак» или «А почему не использовали то-то?».
                    В любом случае всё это затратно как по времени так и по средствам.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое