Начало — см. часть 1.

3. Варианты структур при использовании глобалов


Такая структура как упорядоченное дерево имеет разные частные случаи. Рассмотрим те, которые имеют практическую ценность при работе с глобалами.

3.1 Частный случай 1. Один узел без ветвей



Глобалы можно использовать не только подобно массиву, но и как обычные переменные. Например как счётчик:

Set ^counter = 0  ; установка счётчика
Set id=$Increment(^counter) ;  атомарное инкрементирование

При этом глобал, кроме значения, может ещё иметь ещё и ветви. Одно не исключает второго.

3.2 Частный случай 2. Одна вершина и множество ветвей


Вообще — это классическая key-value база. А если в качестве значения мы будем сохранять кортеж значений, то получим самую обыкновенную таблицу с первичным ключом.



Для реализации таблицы на глобалах нам придётся самим формировать строки из значений колонок, а потом сохранять их в глобал по первичному ключу. Чтобы при считывании было возможно разделить строку опять на колонки можно использовать:

  1. символы-разделители.
    Set ^t(id1) = "col11/col21/col31"
    Set ^t(id2) = "col12/col22/col32"
  2. жёсткую схему, по которой каждое поле занимает заранее определённое число байт. Как и делается в реляционных БД.
  3. специальную функцию $LB (есть в Cache), которая составляет строку из значений.

    Set ^t(id1) = $LB("col11", "col21", "col31")
    Set ^t(id2) = $LB("col12", "col22", "col32")

Что интересно, не представляет труда на глобалах сделать нечто подобное вторичным индексам в реляционных БД. Назовём такие структуры индексными глобалами. Индексный глобал — это вспомогательное дерево для быстрого поиска по полям, не являющимися составными частями первичного ключа основного глобала. Для его заполнения и использования нужно написать дополнительный код.

Давайте создадим индексный глобал по первой колонке.

Set ^i("col11", id1) = 1
Set ^i("col12", id2) = 1

Теперь для быстрого поиска информации по первой колонке нам предстоит заглянуть в глобал ^i и найти первичные ключи (id) соответствующие нужному значению первой колонки.

При вставке значения мы можем сразу создавать и значение и индексные глобалы по нужным полям. А для надёжности обернём всё это в транзакцию.

TSTART
Set ^t(id1) = $LB("col11", "col21", "col31")
Set ^i("col11", id1) = 1
TCOMMIT

Подробноcти как сделать на M таблицы на глобалах, эмуляцию вторичных индексов.

Работать такие таблицы будут также быстро как и в традиционных БД (или даже быстрее), если функции вставки/обновления/удаления строк написать на COS/M и скомпилировать.
Это утверждение я проверял тестами на массовых INSERT и SELECT в одну двухколоночную таблицу, в том числе с использованием команд TSTART и TCOMMIT (транзакций).

Более сложные сценарии с конкурентным доступом и параллельными транзакциями не тестировал.

Без использования транзакций скорость инсертов была на миллионе значений 778 361 вставок/секунду.
При 300 миллионов значений — 422 141 вставок/секунду.

При использовании транзакций — 572 082 вставок/секунду на 50М вставок. Все операции проводились из скомпилированного M-кода.
Жёсткие диски обычные, не SSD. RAID5 с Write-back. Процессор Phenom II 1100T.

Для аналогичного тестирования SQL-базы нужно написать хранимую процедуру, которая в цикле будет делать вставки. При тестировании MySQL 5.5 (хранилище InnoDB) по такой методике получал цифры не больше, чем 11К вставок в секунду.

Да, реализация таблиц на глобалах выглядит более сложной, чем в реляционных БД. Поэтому промышленные БД на глобалах имеют SQL-доступ для упрощения работы с табличными данными.

Вообще, если схема данных не будет часто меняться, скорость вставки некритична и всю базу можно легко представить в виде нормализованных таблиц, то проще работать именно с SQL, так как он обеспечивает более высокий уровень абстракции.
В этом частном случае я хотел показать, что глобалы могут выступать как конструктор для создания других БД. Как ассемблер, на котором можно написать другие языки. А вот примеры, как можно создать на глобалах аналоги key-value, списков, множеств, табличных, документо-ориентированных БД.
Если нужно создать какую-то нестандартную БД минимальными усилиями, то стоит взглянуть в сторону глобалов.

3.3 Частный случай 3. Двухуровневое дерево, у каждого узла второго уровня фиксированное число ветвей


Вы, наверное, догадались: это альтернативная реализация таблиц на глобалах. Сравним эту реализацию с предыдущей.
Таблицы на двухуровневом дереве vs. на одноуровневом дереве.
Минусы Плюсы
  1. Медленнее на вставку, так как нужно устанавливать число узлов равное числу колонок.
  2. Больше расход дискового пространства. Так как индексы глобала (в понимании как индексы у массивов) с названиями колонок занимают место на диске и дублируются для каждой строки.

  1. Быстрее доступ к значениям отдельных колонок, так как не нужно парсить строку. По моим тестам быстрее на 11,5% на 2-х колонках и более на большем числе колонок.
  2. Проще менять схему данных
  3. Нагляднее код

Вывод: на любителя. Поскольку скорость — одно из самых ключевых преимуществ глобалов, то почти нет смысла использовать эту реализацию, так как она скорее всего будет работать не быстрее таблиц на реляционных базах данных.

3.4 Общий случай. Деревья и упорядоченные деревья


Любая структура данных, которая может быть представлена в виде дерева, прекрасно ложится на глобалы.

3.4.1 Объекты с подобъектами




Это область традиционного применения глобалов. В медицинской сфере огромно число болезней, лекарств, симптомов, методов лечения. Создавать на каждого пациента таблицу с миллионом полей нерационально. Тем более, что 99% полей будут пустыми.

Представьте SQL БД из таблиц: «пациент» ~ 100 000 полей, «Лекарство» — 100 000 полей, «Терапия» — 100 000 полей, «Осложнения» — 100 000 полей и т.д. и т.п. Либо можно создать БД из многих тысяч таблиц, каждая под определённый тип пациента (а ведь они могут пересекаться!), лечения, лекарства, и ещё тысячи таблиц для связей между этими таблицами.

Глобалы идеально подошли для медицины, так как позволяют создать для каждого пациента точное описание его истории болезни, различных терапий, действий лекарств, в виде дерева, не тратя при этом лишнее дисковое пространство на пустые колонки, как было бы в реляционном случае.

На глобалах удобно делать БД с данными о людях, когда важно накопить и систематизировать максимум разнообразной информации о клиенте. Это востребовано в медицине, банковской сфере, маркетинге, архивном деле и других областях
.
Безусловно, на SQL тоже можно эмулировать дерево всего несколькими таблицами (EAV, 1,2,3,4,5,6,7,8,9,10), однако это существенно сложнее и будет медленнее работать. По сути пришлось бы написать глобал работающий на таблицах и спрятать всю работу с таблицами под слоем абстракции. Неправильно эмулировать более низкоуровневую технологию (глобалы) средствами более высокоуровневой (SQL). Нецелесообразно.

Не секрет, что изменение схемы данных на гигантских таблицах (ALTER TABLE) может занять приличное время. MySQL, например, делает ALTER TABLE ADD|DROP COLUMN полным копированием информации из старой в новую таблицу (тестировал движки MyISAM, InnoDB). Что может повесить рабочую базу с миллиардами записей на дни, если не недели.

Изменение структуры данных, если мы используем глобалы, нам ничего не стоят. В любой момент мы можем добавить любые нужные нам новые свойства к любому объекту, на любом уровне иерархии. Изменения связанные с переименованием ветвей можно запускать в фоновом режиме на работающей БД.

Поэтому, когда речь идёт о хранении объектов с огромным количеством необязательных свойств, глобалы — отличный выбор.

Причём, напомню, доступ к любому из свойств — моментален, так как в глобале все пути представляют собой B-tree.

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

  1. Размер документа. Единицей хранения является текст в формате JSON (точнее BSON) максимального объёма около 16МБ. Ограничение сделано специально, чтобы JSON-база не тормозила при парсинге, если в ней сохранят огромный JSON-документ, а потом будут обращаться к нему по полям. В этом документе должна быть сосредоточена вся информация о пациенте. Мы все знаем какими толстыми могут быть карты пациентов. Максимальный размер карты в 16МБ сразу ставит крест на пациентах, в карту болезней которых включены файлы МРТ, сканы ренгенографии и других исследований. В одной ветви глобала же можно иметь информацию на гигабайты и терабайты. В принципе на этом можно поставить точку, но я продолжу.
  2. Время сознания/изменения/удаления новых свойств в карте пациента. Такая БД должна считать в память всю карту целиком (это большой объём!), распарсить BSON, внести/изменить/удалить новый узел, обновить индексы, запаковать в BSON, сохранить на диск. Глобалу же достаточно только обратиться к конкретному свойству и произвести с ним манипуляции.
  3. Быстрота доступа к отдельным свойствам. При множестве свойств в документе и его многоуровневой структуре доступ к отдельным свойствам будет быстрее за счёт того, что каждый путь в глобале это B-tree. В BSON же придётся линейно распарсить документ, чтобы найти нужное свойство.

3.3.2 Ассоциативные массивы


Ассоциативные массивы (даже с вложенными массивами) прекрасно ложатся на глобалы. Например такой массив из PHP отобразится в первую картинку 3.3.1.

$a = array(
  "name" => "Vince Medvedev",
  "city" => "Moscow",
  "threatments" => array(
    "surgeries" => array("apedicectomy", "biopsy"),
    "radiation" => array("gamma", "x-rays"),
    "physiotherapy" => array("knee", "shoulder")
  )
);


3.3.3 Иерархические документы: XML, JSON


Также легко хранятся в глобалах. Для хранения можно разложить разными способами.

XML
Самый простой способ раскладки XML на глобалы, это когда в узлах храним атрибуты тэгов. А если будет нужен быстрый доступ к аттрибутам тэгов, то мы можем их вынести в отдельные ветви.

<note id=5>
<to>Вася</to>
<from>Света</from>
<heading>Напоминание</heading>
<body>Позвони мне завтра!</body>
</note>

На COS этому будет соответствовать код:

Set ^xml("note")="id=5"
Set ^xml("note","to")="Саша"
Set ^xml("note","from")="Света"
Set ^xml("note","heading")="Напоминание"
Set ^xml("note","body")="Позвони мне завтра!"

Замечание: Для XML, JSON, ассоциативных массивов можно придумать много разных способов отображения на глобалах. В данном случае мы не отразили порядок вложенных тэгов в тэге note. В глобале ^xml вложенные тэги будут отображаться в алфавитном порядке. Для строго отражения порядка можно использовать, например, такое отображение:


JSON.
На первой картинке из раздела 3.3.1 показано отражение этого JSON-документа:

var document = {
  "name": "Vince Medvedev",
  "city": "Moscow",
  "threatments": {
    "surgeries": ["apedicectomy", "biopsy"],
    "radiation": ["gamma", "x-rays"],
    "physiotherapy": ["knee", "shoulder"]
  },
};


3.3.4 Одинаковые структуры, связанные иерархическими отношениями


Примеры: структура офисов продаж, расположение людей в МЛМ-структуре, база дебютов в шахматах.

База дебютов. Можно в качестве значения индекса узла глобала использовать оценку силы хода. Тогда, чтобы выбрать самый сильный ход достаточно будет выбрать ветвь с наибольшим весом. В глобале все ветви на каждом уровне будут отсортированы по силе хода.



Структура офисов продаж, структура людей в МЛМ. В узлах можно хранить некие кеширующие значения отражающие характеристики всего поддерева. Например, объём продаж данного поддерева. В любой момент мы можем получить цифру, отражающую достижения любой ветви.



4. В каких случаях наиболее выгодно использовать глобалы


В первой колонке представлены случаи, когда вы получите существенный выигрыш в скорости при использовании глобалов, а во второй когда упростится разработка или модель данных.
Скорость Удобство обработки/представления данных
  1. Вставка [с автоматической сортировкой на каждом уровне], [индексированием по главному ключу]
  2. Удаление поддеревьев
  3. Объекты с массой вложенный свойств, к которым нужен индивидуальный доступ
  4. Иерархическая структура с возможностью обхода дочерних ветвей с любой, даже не существующей
  5. Обход поддеревьев в глубину
  1. Объекты/сущности с огромным количеством необязательных [и/или вложенных] свойств/сущностей
  2. Безсхемные данные (schema-less). Когда часто могут появляться новые свойства и исчезать старые.
  3. Нужно создать нестандартную БД.
  4. Базы путей и деревья решений. Когда пути удобно представлять в виде дерева.
  5. Удаление иерархических структур без использования рекурсии


Планируется продолжение «Глобалы — мечи-кладенцы для хранения данных. Разреженные массивы. Часть 3»

Disclaimer: данная статья и мои комментарии к ней является моим мнением и не имеют отношения к официальной позиции корпорации InterSystems.

Комментарии (192)


  1. 4dmonster
    12.08.2015 15:25
    +2

    Скажите пожалуйста а ваше утверждение, что у варианта «Двухуровневое дерево» будет «Быстрее доступ к значениям отдельных колонок, так как не нужно парсить строку.» проверено на каких объёмах данных и на каких паттернах данных?


    1. inetstar
      12.08.2015 15:30

      Это теоретическое предположение, основанное на понимании внутренней структуры глобалов.

      Я предполагаю, что оно будет справедливо на числе колонок большем, чем 10. Или на объёме колонки с несколькими столбцами большем 10KБ.

      Будет интересно увидеть комментарии от сотрудников InterSystems, которые имеют дело с глобалами каждый день.


      1. 4dmonster
        12.08.2015 15:38
        +1

        Как раз теоритически всё будет зависить от паттерна данных и настроек хранения глобалов(думаю что их в кашэ можно менять каки в gt.m).

        Может проведёте тесты?


        1. inetstar
          12.08.2015 15:57

          А почему бы и нет. Уже на 2-х колонках выигрыш 11,4%. При чтении 100M значений. Чем больше колонок или объём полей, тем больше будет выигрыш 2-х ярусного дерева. Тестировал в GT.M.


        1. inetstar
          12.08.2015 18:56

          Тестовый код на M такой:

          mytest1
          
           set ^a(1)="abcd bla bla   #asdf bla bla   "
           for i=0:1:100000000 do
           . set b=$piece(^a(1),"#",2)

          mytest2
          
           set ^a(1)="abcd#asdf"
           set ^a(1,1)="abcd bla bla   "
           for i=0:1:100000000 do
           . set b=^a(1,1)

          Второй подход побеждает.


          1. 4dmonster
            12.08.2015 20:30

            Но это кажется неверный пример для сравнения. Должно быть что-то типа:

            mytest1
            
             for i=0:1:100000000 do
             . set ^a(i) = "bla bla bla" _ "#" _  i
            
            
             for i=0:1:100000000 do
             . set b=$piece(^a(i),"#",2)
            
            
            mytest2
            
             for i=0:1:100000000 do
             . set ^a(i,1) = "bla bla bla"
             . set ^a(i,2) = i
            
             for i=0:1:100000000 do
             . set b=^a(i,1)
            
            


            1. inetstar
              12.08.2015 21:37
              -1

              В моём случае я сравнивал скорости чтения.
              Если делать по вашей методе — мы включим в общее время ещё и время вставки.

              На таких значениях никакие кеши уже не сработают и поэтому разница будет микроскопическая.
              Так как начнут шуршать винты, которые будут конкретно замедлять систему.

              Если рассматривать с точки зрения, что у нас настолько большая БД, что никакие данные не закешированы, то разница будет, дай бог, в 1-2%.

              В своём тесте я специально читал одну ячейку, чтобы исключить фактор винтов и проверить только скорость чтения свойства.

              То, что на вставку будет медленнее второй вариант — очевидно.


              1. vedenin1980
                12.08.2015 22:09

                Вообще-то, в большинстве баз данных такие чтения одной ячейки миллион раз кэшируются, неужели нет в языке возможности замерить время конкретных циклов чтения? Каких нибудь getTime функций?


                1. inetstar
                  12.08.2015 22:13

                  В моём тесте тоже всё закешировалось. И в первом и во втором, поэтому я посчитал, что можно доверять. Тем более практика совпала с теорией.

                  Мы исключили время на перемещение головок и чтение с поверхности, и осталось только время разбора структуры глобала и парсинга строки.


                  1. vedenin1980
                    12.08.2015 22:44
                    +2

                    Тогда что вы вообще мерили? В первом тесте запрос к дереву один, но идет обработка результатов в памяти, во втором запросов к дереву два (точнее запрос идет к двум деревьям), но нет обработки результатов в памяти.

                    Естественно, если все запросы к деревьям закешировались, второй будет всегда работать быстрее, так как в нем просто читается значение кэша, а в первом читается значение кэша и идет ещё какая-то обработка. Да, логично что получить константу из памяти проще чем получить константу и ещё с ней что-то делать, только как часто в реальном приложении нужно миллион раз работать с одной константой?

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


                    1. inetstar
                      12.08.2015 22:54
                      -2

                      Вы что-то путаете. Тот код, который вы прокомментировали, предложил 4dmonster. Мой код — первый в треде.

                      У глобалов кешируются не значения, а блоки базы.

                      Вопрос был в том, что быстрее сработает — проход по двухуровневому дереву, или по одноуровневому с парсингом строки.

                      А измерялось это для того, чтобы доказать моё утверждение о том, что доступ к отдельным свойствам у второго типа таблиц на глобалах быстрее. В реальных базах кеш широко используется. И все частоиспользуемые области в нём.


                      1. vedenin1980
                        12.08.2015 23:00

                        Ещё раз, категорически неверно брать значение одной ячейки миллион раз и считать что это тест, так как этот тест ничего реального не покажет. Ведь вы же не берете в реальных приложениях значение одной ячейки столько раз? Поэтому сначала заполняете миллион значений в таблице, а потом тестируйте чтение из каждой ячейки. Чтобы чтение не влияло, заполняете таблицу ДО замеров времени теста.


                        1. inetstar
                          12.08.2015 23:09

                          Я тестировал скорость доступа к отдельному свойству без влияния HDD.

                          Доступа к закешированному значению. Это было сознательно.

                          Если влезет винт, то разница будет маленькой, незаметной. Это вопрос чисто теоретический, нужный только для сравнительной таблицы.


                          1. vedenin1980
                            12.08.2015 23:25

                            Вы не тестировали вообще НИЧЕГО. Плохо, что не понимая даже основ тестирования, вы пытаетесь писать статьи и что-то кому-то доказывать.


                          1. 4dmonster
                            13.08.2015 08:26
                            +1

                            Вот код:

                            Заголовок спойлера
                            cat tests.m 
                            alltests
                             DO mytest1
                             DO mytest2
                             DO msrtest1
                             DO msrtest2
                             DO cleanUp
                             QUIT
                            
                            cleanUp
                             set start=$PIECE($HOROLOG,",",2)
                             kill ^a
                             kill ^aa
                             kill ^ma
                             kill ^na
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for cleanUP  "_(stop-start),!
                             QUIT
                            
                            
                            repeatsCount()
                             QUIT 100000000
                            
                            mytest1
                             set start=$PIECE($HOROLOG,",",2)
                             set ^a(1)="abcd bla bla   #asdf bla bla   "
                             set repeats=$$repeatsCount()
                             for i=0:1:repeats do
                             . set b=$piece(^a(1),"#",2)
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for mytest1 "_(stop-start),!
                             QUIT
                            
                            mytest2
                             set start=$PIECE($HOROLOG,",",2)
                             set repeats=$$repeatsCount()
                             set ^aa(1)="abcd#asdf"
                             set ^aa(1,1)="abcd bla bla   "
                             for i=0:1:repeats do
                             . set b=^aa(1,1)
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for mytest2 "_(stop-start),!
                             QUIT
                            
                            msrtest1
                             set start=$PIECE($HOROLOG,",",2)
                             set repeats=$$repeatsCount()
                             for i=0:1:repeats do
                             . set ^ma(i)="bla bla bla"_"#"_i
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for msrtest1 write "_(stop-start),!
                             set start=$PIECE($HOROLOG,",",2)
                             for i=0:1:repeats do
                             . set b=$piece(^ma(i),"#",2)
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for msrtest1 read "_(stop-start),!
                             QUIT
                            
                            msrtest2
                             set start=$PIECE($HOROLOG,",",2)
                             set repeats=$$repeatsCount()
                             for i=0:1:repeats do
                             . set ^na(i,1)="bla bla bla"
                             . set ^na(i,2)=i
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for msrtest2 write "_(stop-start),!
                             set start=$PIECE($HOROLOG,",",2)
                             for i=0:1:repeats do
                             . set b=^na(i,1)
                             set stop=$PIECE($HOROLOG,",",2)
                             write "for msrtest2 read "_(stop-start),!
                             QUIT
                            



                            1. inetstar
                              13.08.2015 12:14
                              -1

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

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

                              Тест на запись: нужно мерять время не внутренними функциями, а командой линукса time. Тоже из-за буферов. Так как ваш тест может показать что работа выполнена, а на самом деле буфера ещё не сброшены.

                              Далее: вообще-то из ваших тестов видно, что mytest2 быстрее, как я и говорил.

                              То что на вставку 2-я реализация таблиц будет медленнее — это я тоже говорил.

                              Теперь смотрим результат по чтению: 88 и 89 секунд — слишком мало отличие. Если сделать тесты на чтение отдельными программками, то тогда вы увидите истинный результат.


                              1. 4dmonster
                                13.08.2015 12:40
                                +1

                                mytest — это ваш подход и по нему кажется что $PIECE отстаёт.
                                А msrtest — мой подход. И кажется наоборот.

                                Я учту пункты про буферы и малое различие в выигрыше $PIECE. И выложу что получилось.


                            1. tsafin
                              13.08.2015 12:58
                              +2

                              FWIW

                              Вот результаты того-же самого на Haswell-ULT ноутбуке (в режиме экономии батареи) под Cache' 2015.2

                              DEVLATEST:12:22:59:USER>do ^gtmtests
                              for mytest1 34
                              for mytest2 31
                              for msrtest1 write 335
                              for msrtest1 read 152
                              for msrtest2 write 520
                              for msrtest2 read 185
                              for cleanUP  0
                              


    1. inetstar
      12.08.2015 15:37
      -1

      Это я очень осторожно ответил. На самом деле я думаю, что выигрыш уже будет с 2-х колонок. Так как для поиска нужного индекса (substring) достаточно линейно просмотреть несколько байт (так устроен глобал), а для парсинга строки придётся выделить память под всю строку, распарсить строку, выделить память под нужное поле.


  1. lair
    12.08.2015 15:41
    +2

    Работать такие таблицы будут также быстро как и в традиционных БД (или даже быстрее), если функции вставки/обновления/удаления строк написать на COS/M и скомпилировать.

    Нет, ну опять? Вы можете это утверждение чем-нибудь подтвердить, тестами там какими-нибудь?

    Если нужно создать какую-то нестандартную БД минимальными усилиями, то стоит взглянуть в сторону глобалов.

    В чем преимущество глобалов по сравнению с другими БД для «создания нестандартных БД»? Предположим у меня в проекте уже есть три разнотипных БД (Event Store, RDBMS, key-value) — что я выиграю, добавив в этот зоопарк еще и глобалы?

    Это область традиционного применения глобалов. В медицинской сфере огромно число болезней, лекарств, симптомов, методов лечения. Создавать на каждого пациента таблицу с миллионом полей нерационально. Тем более, что 99% полей будут пустыми. Представьте SQL БД из таблиц: «пациент» ~ 100 000 полей, «Лекарство» — 100 000 полей, «Терапия» — 100 000 полей, «Осложнения» — 100 000 полей и т.д. и т.п.

    Типично неправильно декомпонованная предметная область. Не нужно там тысяч полей. Достаточно представить ту же информацию не в виде дерева, а в виде E-R, как станет видно, как ее реализовывать.

    Поэтому, когда речь идёт о хранении объектов с огромным количеством необязательных свойств, глобалы — отличный выбор.


    Ага. Вот только

    Общая длина ключа (сумма длин всех индексов) может достигать 511 байт [...] Число уровней в дереве (число измерений) — 31.


    Максимальный размер карты в 16МБ сразу ставит крест на пациентах, в карту болезней которых включены файлы МРТ, сканы ренгенографии и других исследований. В одной ветви глобала же можно иметь информацию на гигабайты и терабайты.

    Да-да-да-да, конечно же.

    Общая длина [...] значения 3.6 МБ для Cache.


    При множестве свойств в документе и его многоуровневой структуре доступ к отдельным свойствам будет быстрее за счёт того, что каждый путь в глобале это B-tree. В BSON же придётся линейно распарсить документ, чтобы найти нужное свойство.

    Ну так индексы же.

    Но в принципе, спор «иерархический kv против документо-ориентированой БД», очевидно, определяется сценарием использования. Если обычно документ достается для обработки целиком (например, возможно, врач открывает карту пациента перед приемом), то ДОБД неплохо справится. Если чаще нужны конкретные части — то нужна декомпозиция, и дальше начинаются варианты (кстати, ДОБД тоже неплохо декомпонуются, тут все упирается в транзакционную семантику).

    Иерархические документы: XML, JSON [...] Также легко хранятся в глобалах.

    Вот только в них легко можно встретить вложенность больше 31 уровня. И что делать?

    Нужно создать нестандартную БД.

    И все-таки я не понимаю, что такое «нестандартная БД». Ну или наоборот, что такое «стандартная»?


    1. inetstar
      12.08.2015 16:04
      -1

      В прошлом посте я приводил свои тесты на вставку на 1M и 100M значений. Это аналогично двухколоночной таблице.

      Выигрыш: в конце статьи есть таблица, где показано для каких случаев вы получите выигрыш.

      Вот только в них легко можно встретить вложенность больше 31 уровня. И что делать?


      Хранить целиком, без декомпозиции на глобал. Как и делают JSON СУБД. Или использовать специализированную JSON СУБД.


      1. lair
        12.08.2015 16:06
        +1

        В прошлом посте я приводил свои тесты на вставку на 1M и 100M значений. Это аналогично двухколоночной таблице.

        С индексами? А если полей порядка пятидесяти, индексов полтора десятка, и из них половина — покрывающие (что и активно используется запросами)?

        Хранить целиком, без декомпозиции на глобал.

        А ограничение на размер?


        1. inetstar
          12.08.2015 21:57

          Тут всё просто. В двухколоночном варианте достаточно 1-го дерева. В этом варианте глобалы выигрывают по вставке. А в таблице также строится 1 индекс (тоже B*-tree)

          Для эмуляции каждого дополнительного индекса потребуется ещё дерево. Но оно также будет строиться для индекса в реляционной базе.

          Число индексов и индексных глобалов будет одинаковым. Вывод: глобал будет быстрее.

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


          1. lair
            12.08.2015 22:22

            Не, не убедили. Вы сравниваете яблоки с бочками, и говорите, что яблоки дешевле.

            Во-первых, вы исходите из того, что деревья в глобалах и индексы в реляционных базах работают с одинаковой скоростью. Но почему? На чем основано это убеждение?

            Во-вторых, вы совершенно забываете про сценарии массовых операций, где в глобалах вам придется иметь дело с каждой записью отдельно, а в реляционной БД — группой, со всеми вытекающими отсюда оптимизациями.

            Так что тесты и только тесты, и ничего кроме тестов.

            (И это мы еще не затронули тот занимательный вопрос, сколько кода вам придется написать, чтобы это реализовать, и сколько ошибок будет при его поддержке. Кто говорил «не надо разрабатывать свою БД»?)


            1. inetstar
              12.08.2015 22:36
              -1

              Во-первых, вы исходите из того, что деревья в глобалах и индексы в реляционных базах работают с одинаковой скоростью


              Уже скоро язык отвалится объяснять. Когда я делал таблицу с одним индексом и глобал из одного дерева глобал был в разы быстрее.

              Во-вторых, вы совершенно забываете про сценарии массовых операций, где в глобалах вам придется иметь дело с каждой записью отдельно, а в реляционной БД — группой, со всеми вытекающими отсюда оптимизациями.


              Всё я помню. С группой — это только в SQL. А уровнем ниже будет тот же поиск строк по дереву и та же обработка строк.

              И это мы еще не затронули тот занимательный вопрос, сколько кода вам придется написать, чтобы это реализовать


              Если бы вы прочитали мою статью, то увидели бы, что я рекомендую работать с табличными данными через SQL.


              1. lair
                12.08.2015 22:44
                +1

                Уже скоро язык отвалится объяснять. Когда я делал таблицу с одним индексом и глобал из одного дерева глобал был в разы быстрее.

                И почему вы считаете, что этот ваш опыт масштабируется?

                С группой — это только в SQL. А уровнем ниже будет тот же поиск строк по дереву и та же обработка строк.

                Какая обработка строк, о чем вы? Не говоря уже о том, что когда мы имеем дело с группой операций, мы можем оптимизировать наши действия (начиная с банальной преаллокации нужного места и заканчивая последовательной, а не рандомной вставкой в индекс).

                Если бы вы прочитали мою статью, то увидели бы, что я рекомендую работать с табличными данными через SQL.

                Что и говорит нам о том, что не надо реализовывать табличные структуры на глобалах, ибо незачем. А SQL-решений вокруг и так хватает, чего уж.


                1. inetstar
                  12.08.2015 22:59
                  -1

                  И почему вы считаете, что этот ваш опыт масштабируется?


                  Тоже уже объяснил. Каждый индекс эквивалентен индексному глобалу. Если на одном выигрывает, то и на 10 выиграет.

                  Какая обработка строк, о чем вы?


                  Например UPDATE t SET a=a+b WHERE b=5

                  Нужно найти строки по индексу b (если есть), а далее каждую строку обработать. Какие тут можно сделать улучшения, которые невозможны для глобалов?


                  1. lair
                    12.08.2015 23:10
                    +1

                    Тоже уже объяснил. Каждый индекс эквивалентен индексному глобалу. Если на одном выигрывает, то и на 10 выиграет.

                    Вы почему-то считаете, что это линейная зависимость. Почему бы? Никаких доказательств.

                    Тесты, только тесты.

                    Нужно найти строки по индексу b (если есть), а далее каждую строку обработать. Какие тут можно сделать улучшения, которые невозможны для глобалов?

                    Например, получить в качестве результата прохода по индексу не значение основного ключа, а сразу физический адрес, где это лежит.

                    Вот вам контрпример:

                    INSERT Customers (Name, City)
                    SELECT CustomerName, CustomerCity
                    FROM OtherSystemCustomers
                    


                    По City — индекс IX_City. Первичный ключ не указан и создается автоматически. Так вот, вместо того, чтобы делать столько рандомных вставок в IX_City, сколько у нас записей вставлено, мы можем собрать массив туплов (City, ID), сгруппировать его по City, а затем вставить получившиеся группы, тем самым уменьшив количество проходов по индексу.

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


                    1. inetstar
                      12.08.2015 23:15

                      Для каждой отдельной операции, с которой вы столкнетесь.


                      Я точно также могу получить выборку отсортированную по City и последовательно её вставить. Никаких проблем.

                      Только я таблицы на глобалах буду использовать в крайнем случае, когда у меня 99% глобалов и тут нужен 1% таблиц.

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


                      1. lair
                        12.08.2015 23:26

                        Я точно также могу получить выборку отсортированную по City и последовательно её вставить. Никаких проблем.

                        … и дальше снова будет вопрос, почему вы считаете, что ваши операции (которые в пользовательском коде) равноэффективны операциям в ядре RDBMS.

                        Тесты, тесты, тесты. Ничего кроме тестов.


                        1. inetstar
                          12.08.2015 23:36
                          -2

                          Это не пользовательский код. А код, который компилируется в машинные коды у GT.M, и в байт-коды в Cache (там как и в Java есть своя виртуальная машина).

                          Обычный SQL — это интерпретатор (хранимые процедуры у всех по разному). А COS/M компилируется. Две большие разницы.


                          1. lair
                            12.08.2015 23:39

                            А код, который компилируется в машинные коды у GT.M, и в байт-коды в Cache (там как и в Java есть своя виртуальная машина).

                            Он все равно пользовательский, а не системный.

                            Обычный SQL — это интерпретатор (хранимые процедуры у всех по разному). А COS/M компилируется. Две большие разницы.

                            А обычный SQL тут никого не волнует. Обычный SQL закончился в тот момент, когда мы сказали INSERT, а парсер на той стороне это обработал. А все индексы — они уже внутри ядра, сильно позже и на другом уровне.


                            1. inetstar
                              12.08.2015 23:43
                              -2

                              Он все равно пользовательский, а не системный.


                              Чем отличается скомпилированный в машинные коды пользовательский код от системного?


                              1. lair
                                12.08.2015 23:45
                                -1

                                Оптимизациями, конечно. Вы же утверждали, в свое время, что программа, написанная на ассемблере, быстрее, чем программа, написанная на ЯПВУ. А компилируются они в одни и те же машинные коды. Вот и тут то же самое: то, на чем вы пишете работу с глобалами — это ЯПВУ.

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


                                1. inetstar
                                  13.08.2015 00:15

                                  Код, который написан на COS/М, компилируется в высокооптимизированный системными программистами код. Было бы иначе, я бы не получил 789К/сек инсертов в «пользовательском» коде.


                                  1. lair
                                    13.08.2015 00:21

                                    Вы это серьезно? Вы правда не понимаете, что ваш ^SET компилируется просто в вызов системной (предоставленной движком) функции?


                                    1. inetstar
                                      13.08.2015 00:33
                                      -2

                                      А такое ощущение, что вы не можете догнать. Даже, если и вызывается функция движка, а не inline она в коде, то эта функция и есть высокооптимизированный системными программистами код.


                                      1. lair
                                        13.08.2015 00:39

                                        А теперь задумайтесь о разнице между десятью вызовами «высокооптимизированного кода», и одним вызовом «высокооптимизированного кода» (выполняющего такую же работу, как те десять). Во втором случае, очевидно, больше пространство для оптимизаций.


                                        1. inetstar
                                          13.08.2015 00:42
                                          -1

                                          Вообще неочевидно.

                                          В таком случае бы все высокоуровневые языки, где каждая команда выполняет много работы, были бы быстрее низкоуровневых.

                                          Больше пространство для оптимизаций там, куда можно залезть руками.

                                          Ну и глобалы работали бы медленнее таблиц. Но по моим тестам всё не так. Так что тестируйте, тестируйте…


                                          1. lair
                                            13.08.2015 00:46
                                            -2

                                            В таком случае бы все высокоуровневые языки, где каждая команда выполняет много работы, были бы быстрее низкоуровневых.

                                            Вообще никак не связано.

                                            Больше пространство для оптимизаций там, куда можно залезть руками.

                                            Так там и залезли.

                                            Как по вашему, что эффективнее:

                                            foreach(var item in source)
                                               target.Add(item);
                                            


                                            или

                                            target.AddRange(source);
                                            


                                            1. inetstar
                                              13.08.2015 00:50
                                              -1

                                              Если первое написать написать на ASM, а второе на Basic, то первое.

                                              У какого SQL, из которых вы знаете, есть компиляция SQL-запросов в машинные коды? Или хотя бы в байт-коды с возможностью сохранения между сессиями?


                                              1. lair
                                                13.08.2015 00:57

                                                Если первое написать написать на ASM, а второе на Basic, то первое.

                                                Это один и тот же язык, если что.

                                                У какого SQL, из которых вы знаете, есть компиляция SQL-запросов в машинные коды?

                                                А зачем мне компилировать SQL-запрос в машинные коды?

                                                Вот смотрите. У меня есть таблица на, скажем, полсотни полей, с парой десятков индексов. Я делаю в нее один инсерт: INSERT INTO Target VALUES (...). Это одна команда, пятьдесят один аргумент (имя таблицы + значения колонок). Эта команда превращается в один вызов системной функции блаблаблаPerformInsert с этими же 51 аргументом. Дальше внутри этой функции программист БД (который, наверное, умнее меня и лучше знает ее внутренности) может делать что угодно, применять любые оптимизации, напрямую работать с физическими адресами и так далее. Его воля.

                                                А теперь все то же самое для псевдотаблиц на глобалах: нужно сделать порядка пятидесяти вставок в глобал с данными, плюс пара десятков вставок в «индексы». Порядка семидесяти независимых вызовов системной функции «вставка в глобал», где каждый из них не знает о соседнем (и, как следствие, не может делать кросс-оптимизации).


                                                1. inetstar
                                                  13.08.2015 12:16
                                                  -1

                                                  70 вставок я вот делать не буду. Согласно моему тексту это медленный подход.
                                                  Я сделаю 1 вставку с сочетании с функцией $LB. А на одной вставке, как я уже многократно рассказывал, глобалы быстрее.


                                                  1. lair
                                                    13.08.2015 12:28

                                                    Я сделаю 1 вставку с сочетании с функцией $LB.

                                                    А как же индексы?

                                                    Ну и после этого вы (по вашим же утверждениям) потеряете гибкость схемы и скорость чтения отдельных элементов.


                                              1. lair
                                                13.08.2015 01:22

                                                Простой вопрос: что быстрее, команда INSERT из Cache SQL, или аналогичная по функциональности «процедура», написанная вами на M или COS/M?

                                                (естественно, мы считаем, что функциональность идентична — один набор и структура данных, индексы, транзакции, блаблабла)


                                                1. 4dmonster
                                                  13.08.2015 01:39

                                                  Скорее всего так:

                                                  Если требуется абсолютная функциональная идентичность — они будут одинаково работать.
                                                  А если достаточно того, чтобы потом Cache SQL мог бы сделать SELECT — тогда написанная на M или COS/M.


                                                1. tsafin
                                                  13.08.2015 12:35
                                                  +3

                                                  Недавно проводил тестирование «для одного большого банка», где, конечно же, мы проверяли в очередной раз разницу между CacheSQL vs Direct Global Access. В 2015.1 разница, для того простого случая что мы смоделировали, в разы (x2-x3), для 2015.2 на примерно 15%.

                                                  Т.е. сейчас, в 2015.2, я легко пожертвую 15% ухудшения по сравнению с идеальным случаем на голом глобале, но буду использовать более высокий уровень абстракции. Ибо так проще в долговременном плане.


                                                  1. lair
                                                    13.08.2015 12:37

                                                    Т.е. CacheSQL медленнее? Это при полном сохранении функциональности?


                                                    1. inetstar
                                                      13.08.2015 12:50
                                                      -1

                                                      А что удивительного в том, что более высокоуровневое средство (CacheSQL) медленнее низкоуровневого прямого доступа к глобалам?


                                                      1. lair
                                                        13.08.2015 13:17
                                                        -1

                                                        Я уже объяснял это выше, повторяться не буду.


                                                    1. tsafin
                                                      13.08.2015 12:53
                                                      +1

                                                      Да, в случае голой заливки данных (я не говорю про запросы), INSERT в CacheSQL всегда будет медленнее чем прямая манипуляция глобалом, т.к. добавляется некоторый объем дополнительной работы необходимый для эмуляции ACID. Программист, работающий с глобалом напрямую обычно игнорирует некоторую транзакционную строгость, выигрывая по скорости. Иногда, при первичной загрузке данных, или в монопольном режиме на это можно закрыть глаза.
                                                      (Ну т.е. нет, конечно, полного сохранения функциональности, COS/M программист обычно дяже не знает о таких деталях о которых должен заботиться транслятор SQL в Cache' для правильной поддержки транзакционной целостности в SQL)

                                                      Еще раз, относитесь к прямому доступу к глобалам в Cache'/GT.M как к «ассемблеру», а SQL/объектной обертке в Cache' как C/C++. Большие системы правильнее писать, применяя абстракции более высокого уровня, но иногда, на критичном участке, «можно и на ассемблере пописать».


                                                      1. lair
                                                        13.08.2015 13:21
                                                        +2

                                                        Да, в случае голой заливки данных (я не говорю про запросы), INSERT в CacheSQL всегда будет медленнее чем прямая манипуляция глобалом, т.к. добавляется некоторый объем дополнительной работы необходимый для эмуляции ACID. [...] (Ну т.е. нет, конечно, полного сохранения функциональности, COS/M программист обычно дяже не знает о таких деталях о которых должен заботиться транслятор SQL в Cache' для правильной поддержки транзакционной целостности в SQL)

                                                        Тогда это не то, что меня интересовало. Я изначально ставил вопрос о сохранении функциональности. Понятно, что если мы выкинем оптимизации под чтение и ACID, то простые операции вставки дешевле, чем более сложная структура, но автор-то утверждал, что даже в режиме функциональной имитации глобалы все равно быстрее:

                                                        Работать такие таблицы будут также быстро как и в традиционных БД (или даже быстрее), если функции вставки/обновления/удаления строк написать на COS/M и скомпилировать.


                                                        Программист, работающий с глобалом напрямую обычно игнорирует некоторую транзакционную строгость, выигрывая по скорости.

                                                        Вот этот компромис мне очень хорошо понятен, да. Одним пожертвовали, другое выиграли. Меня просто второй пост подряд удивляют (неявные) утверждения, что глобалы — это выиграй все, пожертвовав ничем.


                                                        1. inetstar
                                                          13.08.2015 13:40
                                                          -2

                                                          Вы сами признали уровень изоляции аналогичным snapshot (что весьма высокий уровень), а также с ACID (вопрос не в том, есть ли в глобалсах ACID. Вопрос в том, какой именно).

                                                          Я не понимаю, зачем вы теперь отказываетесь от своих слов.

                                                          Понятно, что если мы выкинем оптимизации под чтение и ACID

                                                          Никто ACID не выкидывает. И про какие оптимизации, которые на уровне глобалов, сделать принципиально невозможно тоже не ясно.


                                                          1. lair
                                                            13.08.2015 14:00
                                                            +2

                                                            Ну вы уж как-нибудь обсудите это с tsafin, который утверждает, что INSERT добавляет дополнительный объем работы именно на ACID, и что в его тестах функциональность INSERT и прямой работы с глобалами отличается.

                                                            Что возвращает нас к вопросу — а какова же будет сравнительная скорость «ручных» операций на глобалах и INSERT с одинаковой функциональностью? (не умозрительно, а в реальных тестах)

                                                            Я не отказываюсь от своих слов, просто вы почему-то считаете, что если вы в одном сценарии доказали, что ACID достижим, то он (ACID) автоматически будет везде. Удивительно, но реальный сотрудник InterSystems утверждает обратное.


                                                            1. 4dmonster
                                                              13.08.2015 14:06

                                                              Вероятно, tsafin имел в виду ACID для слоя SQL а inetstar ACID для самих глобалов.


                                                              1. lair
                                                                13.08.2015 14:09

                                                                А в чем фундаментальные отличия?


                                                                1. 4dmonster
                                                                  13.08.2015 14:15

                                                                  SQL слой накручен поверх глобалов и, вероятно, требует дополнительных условий / проверок для своей работы.
                                                                  Например, для ссылочной целостности.


                                                                  1. lair
                                                                    13.08.2015 14:29
                                                                    +1

                                                                    До ссылочной целостности мы даже не доходили еще, пока ограничились одной таблицей и индексами.

                                                                    Я, на всякий случай, повторю предмет спора: inetstar утверждал, что таблицы, написанные на самих глобалах вручную (примеры есть в статье), не медленнее, а то и быстрее, чем таблицы в традиционных БД. Я утверждаю, что при сохранении функциональности для этого нет никаких логических оснований, и я хотел бы увидеть подтверждающие утверждение inetstar тесты.

                                                                    Ключевой пункт тут — при сохранении функциональности. tsafin, приводя свои результаты, признал, что функциональность различна.


                                                                    1. 4dmonster
                                                                      13.08.2015 14:39
                                                                      -1

                                                                      Это зависит от того какая функциональность имеется в виду. Ну и конечно от того, ограничиваемся ли мы одной таблицей и её индексами.


                                                                      1. lair
                                                                        13.08.2015 14:41
                                                                        +2

                                                                        Это зависит от того какая функциональность имеется в виду.

                                                                        Бинго. Что возвращает нас к вопросу методики тестирования (впрочем, чего уж, тестов-то нет никаких).


                                                                        1. 4dmonster
                                                                          13.08.2015 14:45

                                                                          Именно!

                                                                          Вот для MVC фреймворков на JavaScript придумали задачу ToDo, и на ней демострируют.
                                                                          Тут надо также.
                                                                          Может автор в следующей статье дойдёт до конкретных примеров и можно будет сравнить что-то.


                                                                    1. inetstar
                                                                      13.08.2015 15:11

                                                                      Я в статью вставил спойлер, где несколько раскрыл на основании каких тестов я утверждаю, что таблицы на глобалах будут работать также или быстрее.

                                                                      И я нигде не постулировал абсолютно идеентичную функциональность таблиц в SQL-базах и таблиц на глобалах, на которую вы так напираете.

                                                                      Например, в моих тестах я не делал защиты от вставки строки с тем же ID. Это, наверное, бы замедлило бы мои тесты на несколько процентов.

                                                                      Глобалы — низкоуровневое средство, дающее отличные скоростные характеристики.

                                                                      Я специально сказал в статье, что с таблицами лучше работать на SQL. Удобнее.

                                                                      Программист на глобалах должен знать, что о всех их потенциальных возможностях. В том числе и о той, что на глобалах можно сделать таблицы.

                                                                      СтОит или не стоит — вопрос уже другой. Если нужно сверхбыстро сохранять информацию, идущую от какого-то источника, то похоже что стоит.

                                                                      А если вам нужны конкурентные и очень хитрые транзакции над табличными данными, то не факт. Незачем программировать вручную, то что уже запрограммировано в SQL.


                                                                      1. lair
                                                                        13.08.2015 15:18

                                                                        Я в статью вставил спойлер, где несколько раскрыл на основании каких тестов я утверждаю, что таблицы на глобалах будут работать также или быстрее.


                                                                        Угу.

                                                                        Это утверждение я проверял тестами на массовых INSERT и SELECT в одну таблицу, в том числе с использованием команд TSTART и TCOMMIT (транзакций).

                                                                        Без использования транзакций скорость инсертов была на миллионе значений 778 361 вставок/секунду.
                                                                        При 300 миллионов значений — 422 141 вставок/секунду.

                                                                        При использовании транзакций — 572 082 вставок/секунду. Все операции проводились из скомпилированного M-кода.


                                                                        И с чем вы это сравниваете?

                                                                        И я нигде не постулировал абсолютно идеентичную функциональность таблиц в SQL-базах и таблиц на глобалах, на которую вы так напираете.

                                                                        Тогда какой смысл говорить, что вы реализовали «таблицы» или вообще сравнивать производительность?

                                                                        Ну да, есть какая-то операция вставки чего-то куда-то, которая в глобалах выполняется за x. Есть какая-то другая операция вставки чего-то куда-то, которая в другой СУБД выполняется за y. x < y… ну и что? Апельсин легче танкера с нефтью, это правда.

                                                                        СтОит или не стоит — вопрос уже другой. Если нужно сверхбыстро сохранять информацию, идущую от какого-то источника, то похоже что стоит.

                                                                        Зачем для этого «таблицы»? Для таких задач лучше всего append-only хранилища.

                                                                        Незачем программировать вручную, то что уже запрограммировано в SQL.

                                                                        Бинго.


                                                                        1. inetstar
                                                                          13.08.2015 15:27
                                                                          -1

                                                                          Добавил в спойлер сравнение с MySQL. Хранимая процедура с циклом, котрая делает вставки — 11К вставок в секунду.


                                                                          1. lair
                                                                            13.08.2015 15:39
                                                                            +1

                                                                            Операции вставки в глобалах и MySQL семантически идентичны? Если нет, то в этих цифрах нет никакого смысла.


                                                                            1. inetstar
                                                                              13.08.2015 15:46

                                                                              Идеентичны.

                                                                              Set ^a(10) = «blabla»
                                                                              INSERT INTO a(id, txt) Values(10, «blabla»)


                                                                              1. lair
                                                                                13.08.2015 15:54
                                                                                +1

                                                                                Во-первых, для RDBMS это вырожденный пример, в котором она заведомо лишена смысла. Сравнивайте с KV, потому что это все, что вы тут делаете. Чтобы говорить о «таблицах», нужно реализовывать поведение реляционной системы.

                                                                                Во-вторых, выше этого сравнения вы говорите об имитации индексов через глобалы. Ваша фраза «Работать такие таблицы будут также быстро как и в традиционных БД (или даже быстрее)» относится к каким «таким» таблицам — с имитацией индексов или без?


                                                                                1. inetstar
                                                                                  13.08.2015 15:56

                                                                                  К обоим вариантам. Но таблицы с несколькими индексами я не тестировал.


                                                                                  1. lair
                                                                                    13.08.2015 16:03
                                                                                    +2

                                                                                    Но таблицы с несколькими индексами я не тестировал.

                                                                                    … и возвращаемся к началу — к вопросу о том, почему вы считаете, что если вы протестировали часть функциональности, то вы имеете право распространять свои выводы о производительности на всю функциональность.


                                                                                1. inetstar
                                                                                  13.08.2015 16:02

                                                                                  В любом случае таблица с 2-мя индексами быстрее работать не станет, а вставка в глобал замедлится в 2 раза. Допустим даже в 3, с учётом транзакции.

                                                                                  Учитывая 70-ти кратное преимущество по скорости при одной вставке в глобал, результат будет таким же — глобалы победят. В сравнении с MySQL.


                                                                                  1. lair
                                                                                    13.08.2015 16:04

                                                                                    Учитывая 70-ти кратное преимущество по скорости при одной вставке в глобал, результат будет таким же — глобалы победят. В сравнении с MySQL.

                                                                                    Угу, во-первых, в сравнении с MySQL. Во-вторых, только до определенного количества индексов. Ну и в-третьих — только если ваши предположения о динамике верны.


                                                                1. inetstar
                                                                  13.08.2015 14:29
                                                                  -1

                                                                  Ещё я хочу напомнить, что я говорил о скорости эмулированных таблиц, я говорил не про CacheSQL, подразумевая чистый MUMPS.

                                                                  Я предполагаю что CacheSQL в интерактивном режиме — как и любой SQL — интерпретаторный язык. Не вижу ничего удивительного, что он на 15% медленнее глобалов, программы для работы с которыми компилируемы.


                                                                  1. lair
                                                                    13.08.2015 14:31
                                                                    +1

                                                                    То есть вы утверждаете, что 15% разницы (а в предыдущей версии — 200-300% разницы) между CacheSQL и прямым обращением к глобалам возникают только из-за разницы «интерпретаторный-компилируемый»?


                                                                    1. inetstar
                                                                      13.08.2015 14:37

                                                                      Думаю, что это одна из причин. Я не знаю как внутри работает CacheSQL.


                                                                  1. lair
                                                                    13.08.2015 14:40

                                                                    А еще CacheSQL может исполняться во встроенном режиме, где, как утверждается," SQL statements are converted to optimized, executable code at compilation time".

                                                                    Интересно, какой из режимов тестировал tsafin, и есть ли существенная разница по производительности между одни и другим.


                                                                  1. tsafin
                                                                    13.08.2015 17:12
                                                                    +4

                                                                    Ок, попытаюсь объяснить.

                                                                    Высокоуровневая идея всегда применяемая во всех надстройках Cache' — любой другой язык должен d bnjut транслироваться в INT-код (Cache' ObjectScript) и объектный код, вне зависимости от количества препроцессинга и кодогенерации. Будь это SQL, манифест инсталлятора или описание домена iKnow. В случае компиляции встроенного SQL работает макро-препроцессор (так получилось исторически), в большинстве других случаев работают методы генераторы.
                                                                    (Вам не удастся встроиться в препроцессор, но генераторы, если надо, Вам помогут реализовать еще какой встроенный доменный язык для любой вашей цели. Степеней свободы достаточно)

                                                                    Возвращаясь к SQL — кроме упомянутого уже выше и ниже встроенного SQL (embedded SQL), компилируемого препроцессором в низкоуровневый COS в момент компиляции MAC программы или класса, есть еще и динамический SQL, компилируемый в похожую программу в момент первого исполнения.

                                                                    Если вам интересно посмотреть, то вот упрощенный пример:

                                                                    1. Создадим персистентный класс

                                                                    Class User.Habra.BankData Extends %Persistent
                                                                    {
                                                                    
                                                                    Property Amount As %Numeric;
                                                                    
                                                                    Property Account As %String;
                                                                    
                                                                    }
                                                                    


                                                                    И для такого простого метода со встроенным SQL выражением:

                                                                    ClassMethod InsertAsSQL(Value As %Numeric, AccountName As %String) As %Status
                                                                    {
                                                                        &sql(insert into User_Habra.BankData(account, amount) values (:AccountName,:Value))
                                                                        return $$$OK
                                                                    }
                                                                    


                                                                    после компиляции получаем следующий сгенерированный код (Ctrl+Shift+V в Студии):

                                                                    ...
                                                                    zInsertAsSQL(Value,AccountName) [ AccountName,SQLCODE,Value ] public { New %ROWCOUNT,%ROWID,%msg,SQLCODE 
                                                                         ;---&sql(insert into User_Habra.BankData(account, amount) values (:AccountName,:Value))
                                                                          ;--- ** SQL PUBLIC Variables: %ROWCOUNT, %ROWID, %msg, AccountName, SQLCODE, Value
                                                                        Do %0Fo
                                                                        return 1 }
                                                                     q
                                                                    %0Fo try { n sqldata5d
                                                                     	if $zu(115,1)=1||('$TLEVEL&&($zu(115,1)=2)) { TSTART  s sqldata5d=1 }
                                                                     	n %i
                                                                     	s %i(2)=$g(AccountName),%i(3)=$g(Value)
                                                                     	s %ROWID=##class(User.Habra.BankData).%SQLInsert(.%i,$c(0,0,0,0,0,0,0),,,'$g(sqldata5d)),%ROWCOUNT='SQLCODE
                                                                     	if $zu(115,1)=1,$g(sqldata5d) { TCOMMIT:SQLCODE'<0  TROLLBACK:SQLCODE<0 1 }
                                                                     }
                                                                     catch { d SQLRunTimeError^%apiSQL($ze,.SQLCODE,.%msg) if $zu(115,1)=1,$g(sqldata5d) { TROLLBACK 1 } }
                                                                     quit  // From %0Fo
                                                                    ...
                                                                    %SQLInsert(%d,%check,%inssel,%vco,%tstart=1,%mv=0)
                                                                    	new bva,%ele,%itm,%key,%l,%n,%nc,%oper,%pos,%s,sqlcode,sn,subs,icol set %oper="INSERT",sqlcode=0,%l=$c(0,0,0)
                                                                    	if $a(%check,7) { new %diu merge %diu=%d }
                                                                    	if $d(%d(1)),'$zu(115,11) { if %d(1)'="" { set SQLCODE=-111,%msg=$$FormatMessage^%occMessages(,"%SQL.Filer","SQLFiler6",,"ID","User_Habra"_"."_"BankData") QUIT ""  } kill %d(1) } 
                                                                    	if '$a(%check),'..%SQLValidateFields(.sqlcode) { set SQLCODE=sqlcode QUIT "" }
                                                                    	do ..%SQLNormalizeFields()
                                                                    	if %tstart { TSTART:($zu(115,1)=1)||('$TLEVEL&&($zu(115,1)=2))  } set $zt="ERRORInsert"
                                                                    	if '$a(%check) {
                                                                    		do {
                                                                    			if $g(%vco)'="" { d @%vco quit:sqlcode<0  }
                                                                    		} while 0
                                                                    		if sqlcode<0 { set SQLCODE=sqlcode do ..%SQLEExit() QUIT "" }		// A constraint failed
                                                                    	}
                                                                    	if '$d(%d(1)) { set %d(1)=$i(^User.Habra.BankDataD) } elseif %d(1)>$g(^User.Habra.BankDataD) { if $i(^User.Habra.BankDataD,$zabs(%d(1)-$g(^User.Habra.BankDataD))) {}} elseif $d(^User.Habra.BankDataD(%d(1))) { set SQLCODE=-119,%msg=$$FormatMessage^%occMessages(,"%SQL.Filer","SQLFiler33",,"ID",%d(1),"User_Habra"_"."_"BankData"_"."_"ID") do ..%SQLEExit() QUIT "" }
                                                                    	set:'($d(%d(4))#2) %d(4)=""
                                                                    	if '$a(%check,2) {
                                                                    		new %ls lock +^User.Habra.BankDataD(%d(1))#"E":$zu(115,4) set %ls=$s('$t:0,1:$case($SYSTEM.Lock.ReturnCode(),0:1,4:2,2:2,1:3,:""))
                                                                    		set:%ls=2 $e(%check,2)=$c(1) set:%ls=2&&($tlevel) %0CacheLock("User.Habra.BankData","E")=1 set:$case(%ls,1:1,2:1,:0) $e(%l)=$c(1)
                                                                    		if '%ls||(%ls=3) { set SQLCODE=-110,%msg=$$FormatMessage^%occMessages(,"%SQL.Filer",$s('%ls:"SQLFiler40",1:"SQLFiler55"),,%oper,"User_Habra"_"."_"BankData",$g(%d(1))) do ..%SQLEExit() QUIT ""  }
                                                                    	}
                                                                    	set ^User.Habra.BankDataD(%d(1))=$lb($g(%d(4)),$g(%d(3)),$g(%d(2)))
                                                                    	lock:$a(%l) -^User.Habra.BankDataD(%d(1))#"E"
                                                                    	TCOMMIT:%tstart&&($zu(115,1)=1)
                                                                    	set SQLCODE=0
                                                                    	QUIT %d(1) 			// %SQLInsert
                                                                    
                                                                    


                                                                    Я не представляю себе COS программиста, который будет в своем коде так заботиться об уровнях транзакции, блокировках, откатах и обработке ошибок. COS/M код, созданный человеком, будет значительно короче и проще, и не будет заботиться (скорее всего) о всех граничных условиях. Хотя и не факт, что будет более читабельным (вон смотрите, там даже комментарии иногда генерируются :) )

                                                                    В любом случае, прошу запомнить — у нас нет интерпретации при работе с SQL, транслятор SQL генерирует COS код, и, при разборе запросов, применяет все полагающиеся оптимизирующие эвристики. SQL это не просто и борьба за улучшения в генерируемом коде происходит постоянная.

                                                                    P.S.

                                                                    Большая просьба к участникам дискуссии — давайте попытаемся не выходить за пределы компетенции, и, в любом случае, попытаемся вести себя уважительно.
                                                                    Мне нравится тенденция, наблюдаемая у некоторых участников дискуссии, пойти и почитать документы про Cache' (что я всячески приветствую), и давайте все остальные вопросы, которые возникнут по результатам тестирования задавать нам по другим каналам (письмом или через социальные сети). С удовольствием ответим!


                                                      1. mird
                                                        13.08.2015 13:23
                                                        -2

                                                        Да, в случае голой заливки данных (я не говорю про запросы), INSERT в CacheSQL всегда будет медленнее чем прямая манипуляция глобалом, т.к. добавляется некоторый объем дополнительной работы необходимый для эмуляции ACID. Программист, работающий с глобалом напрямую обычно игнорирует некоторую транзакционную строгость, выигрывая по скорости. Иногда, при первичной загрузке данных, или в монопольном режиме на это можно закрыть глаза.

                                                        То есть все же полная функциональность не сохраняется? Так зачем вы об этом пишете?


  1. laphroaig
    12.08.2015 15:56
    +3

    >> Вот только в них легко можно встретить вложенность больше 31 уровня. И что делать?
    На самом деле не легко, если только искусственно создать или спецом не оптимально, даже в самых монстрах не будет и 20. А 31 уровня более чем достаточно чтобы систематизировать все знания человечества.


    1. lair
      12.08.2015 15:59

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

      Это же не структура классификатора, это программные данные, а там всякое случается.


      1. tsafin
        12.08.2015 16:04

        А вот здесь подробнее, пожалуйста! Ну 3 вложенных конверта, и данные, как тут больше 31 уровня подындексов получается?

        Можно примерчик (чисто для развития)


        1. vedenin1980
          12.08.2015 16:11
          +1

          Вложенность html тегов в произвольной странице сети может зашкаливать и за 50 уровень, при использовании кучи div'ов и table для верстки.


          1. tsafin
            12.08.2015 16:28
            +1

            Это я понимаю, но я спрашивал про другой тип данных — xml структуру данных, представляющее собой сериализацию (в xml, json, yaml, toml, что-нибудь еще более модное) какой-то определенной иерархии объектов с полями и с вложенностью. Что предполагает немного другой сценарий использования, и больший, скажем так, «порядок».

            Хотелось бы посмотреть именно на «enterprise xml» с адской вложенностью, чтобы понять где наши ограничения будут упираться в пределы движка.


            1. vedenin1980
              12.08.2015 17:23

              Ну вы же понимаете, что вам никто реальный «enterprise xml» реальной компании никогда не покажет. Ну примерно, в одной фирме где я работал xml выглядели как-то так (сознательно извратил насколько мог, но принцип понятен, причем в нем может быть бесконечная вложенность устройств друг в друга):

              Внимание большой xml
              <soap:Envelope
                      xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
                      soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
              
                  <soap:Body xmlns:m="http://www.example.org/stock">
                      <m:GetInventoryResponse>
                          <m:Countries>
                              <m:Country>
                                  <m:Name>Russia</m:Name>
                                  <m:Regions>
                                      <m:Region>
                                          <m:Name>Omsk</m:Name>
                                          <m:Cities>
                                              <m:City>
                                                  <m:Name>Omsk</m:Name>
                                                  <m:Offices>
                                                      <m:Office>
                                                          ...
                                                          <m:Networks>
                                                              <m:Network>
                                                                  <m:Servers>
                                                                      <m:Server>
                                                                          <m:cards>
                                                                              <m:card>
                                                                                  <m:ports>
                                                                                      <m:port>
                                                                                          <m:links>
                                                                                              <m:link>
                                                                                                  <m:devices>
                                                                                                      <m:device>
                                                                                                          <m:cards>
                                                                                                              <m:card>
                                                                                                                  <m:ports>
                                                                                                                      <m:port>
                                                                                                                          // Вложенная структура других device
                                                                                                                      </m:port>
                                                                                                                  </m:ports>
                                                                                                              </m:card>
                                                                                                          </m:cards>
                                                                                                      </m:device>
                                                                                                  </m:devices>
                                                                                              </m:link>
                                                                                          </m:links>
                                                                                      </m:port>
                                                                                  </m:ports>
                                                                              </m:card>
                                                                          </m:cards>
                                                                      </m:Server>
                                                                  </m:Servers>
                                                              </m:Network>
                                                          </m:Networks>
                                                      </m:Office>
                                                  </m:Offices>
                                              </m:City>
                                          </m:Cities>
                                      </m:Region>
                                  </m:Regions>
                              </m:Country>
                          </m:Countries>
                      </m:GetInventoryResponse>
                  </soap:Body>
              </soap:Envelope>
              


              1. inetstar
                12.08.2015 17:28

                Собственно говоря, это тоже SOAP. Не вижу смысла такое декомпозировать на глобалы. Подобного рода сообщения удобнее хранить целиком.


                1. lair
                  12.08.2015 17:31

                  … а потом вам надо в этом «хранить целиком» найти ответ, в котором содержится узел, удовлетворяющим конкретным критериям. Желательно все-таки не полным перебором и десериализацией.

                  (и да, это реальный сценарий использования, я такую систему разработал и внедрил)


                  1. inetstar
                    12.08.2015 17:36

                    И как же вы реализовали быстрый доступ к отдельным узлам XML-документа? Разве не парсингом с использованием библиотек?


                    1. lair
                      12.08.2015 17:40
                      -2

                      Конечно, нет. Взял MS SQL, у того встроенная поддержка XML, дальше просто строил запросы на смеси T-SQL и XPath.


                      1. inetstar
                        13.08.2015 00:23
                        -2

                        XPath — это и есть библиотека, которая парсит XML.


                        1. lair
                          13.08.2015 00:24
                          +2

                          Конечно, нет. XPath — это язык запросов к XML.


                1. vedenin1980
                  12.08.2015 17:34

                  Это обычная inventory.xml, описывающая обычную сеть обычной распределенной компании. Иногда уровни вложенности могут быть бесконечными, особенно если там прибавить связи разными сервисами в облаках, один из которых указывает на другой и т.д… Это рядовая задача энтерпрайза.


                  1. inetstar
                    12.08.2015 17:38

                    Разве есть смысл такое декомпозировать на глобалы для быстрого доступа к отдельным узлам?
                    Глобалы и XML — это специфичные ситуации, а не волшебная пилюля от всего.


                    1. vedenin1980
                      12.08.2015 17:46

                      Если вы хотите конкурировать с mongoDB, то естественно нужно. Смысл mongoDB в том что она очень быстро записывает и отдает как целый документ, так и его отдельные части. Предлагать клиентам вон ту структуру данных пишите нам, а с вон с той идите лесом в другую базу — верный способ их потерять. Проблема в том что ваши глобалы предлагают либо писать все целиком, либо отдельными узлами, но при этом с ограничениями вложенности, размера и не факт что сборка целого json'a документа будет быстрой.


                      1. inetstar
                        12.08.2015 17:54

                        Отдельные части Mongo не сможет отдать так же быстро как глобалы, из-за того, что придётся парсить документ и вынимать из него отдельную часть. Я не проводил тестов. Мне это ясно из устройства баз. Моё мнение.

                        У Mongo для JSON свои сильные преимущества. К ней не нужно реализовывать свой язык запросов.
                        Глобалы тоже прекрасно хранят JSON, но для этого нужно писать свой код или использовать EWD, например.

                        Этот пост вообще не для клиентов. Просто о возможностях глобалов для разработчиков.


                        1. vedenin1980
                          12.08.2015 18:02

                          Мне это ясно из устройства баз. Моё мнение.

                          Боюсь ваше устройство баз весьма слабое, в монге храниться не обычный JSON, специально проиндексированный и заранее распарсенный BSON (как я понимаю с индексом с помощью того же бинарного дерева). Так что чье кун-фу сильнее должны решать тесты, а не чье-то мнение о неправильном устройстве баз.


                          1. inetstar
                            12.08.2015 18:07

                            Я же писал о том, что это BSON. Будьте внимательнее.

                            И читал устройство формата BSON, который нужно парсить программно. В BSON не используются внутри B*-tree, и это не словарь со сложностью доступа O(1). Парсинг нужен.

                            Больше вложенности — больше парсинга.


        1. lair
          12.08.2015 16:25
          +2

          Возьмем простой SOAP (неймспейсы я повырезаю):

          <s:Envelope>
          <s:Body>
          <MessagePart/>
          </s:Body>
          </s:Envelope>
          


          Посмотрим, сколько уровней нам надо для его представления:
          "156" //идентификатор сообщения
          "156","s:Envelope" //корневой элемент
          "156","s:Envelope", "0", "s:Body" //тело SOAP
          "156","s:Envelope", "0", "s:Body", "0", "MessagePart" //добрались до собственно данных
          


          (Откуда нули? Из того банального соображения, что элементов с одним наименованием может быть много, а глобалы, насколько я понял, не умеют несколько одинаково названных подиндексов. Ну и порядок тегов тоже имеет значение. Плюс надо атрибуты отдельно выносить, для них тоже поможет, благо, вот они не имеют порядка.)

          Итого один уровень вложенности в XML дает нам два индекса, соответственно, чтобы получить 31 уровень, нужно, округляя, 16 уровней вложенности.

          Вот вам пример сообщения в СМЭВ при обмене с одним малоизвестным ведомством:

          <S:Envelope>
            <S:Body>
              <ws:DXSmev>
                <smev:MessageData>
                  <smev:AppData>
                    <dx:DXBox>
                      <dx:DXPack>
                        <dx:Documents>
                          <ns2:exitRestrictionActRequest>
                            <data>
                              <AddressData>
                                <Address>
                                  <OKATO>46248501000</OKATO>
                                </Address>
                              </AddressData>
                            </data>
                            <data>
                              <personDatum>
                                <FIO>
                                  <Surname>Коняв</Surname>
                                </FIO>
                              </personDatum>
                            </data>
                          </ns2:exitRestrictionActRequest>
                        </dx:Documents>
                      </dx:DXPack>
                    </dx:DXBox>
                  </smev:AppData>
                </smev:MessageData>
              </ws:DXSmev>
            </S:Body>
          </S:Envelope>
          


          Я насчитал 13 уровней. И это не самый заковыристый вариант (в нем только три конверта — SOAP, СМЭВ, ведомство, в реальности я видел ситуации, когда внутри конверта ведомства лежал следующий конверт с семантикой «пакет», а еще дальше — отдельный документ со структурой). И да, в СМЭВ 3 будет веселее, потому что там другие требования к структуре объектов.

          Для пущего развлечения хочу заметить, что в этой же структуре легко могут быть бинарные элементы объемом до 5 (а иногда и больше) Мб.


          1. tsafin
            12.08.2015 16:48
            +4

            Хороший пример, имеет смысл, спасибо!
            (Есть некоторые «стилистичекие» замечания к авторам _такого_ формата, но раз уж выросло такое...)

            При десериализации такого в объекты я бы в нескольких местах пропускал лишние промежуточные конверты (например, smev:MessageData/smev:AppData, или AddressData/Address), но наверное я чего-то не понимаю, и они там действительно нужны.

            В-любом случае, я пока не вижу как, даже при таком адовом дизайне схемы, мы упрёмся в 32 вложенных подындекса. Все же хотелось бы примера из жизни, который имел бы смысл.

            P.S.
            И да, для больших BLOB-ов всегда и везде придется разбивать блоки на подузлы. В этом нет ничего специального — везде есть верхний предел блока базы, или страницы в хранилище, или ограничения по длине примитива, которые приходится обходить разбивая на куски, например, по 32КБ.

            Трюк, при работе с базами данных, минимизировать количество обращений к диску, при заданных параметрах файловой системы и дискового хранилища. Понятно, что ни в какой ОС, нет возможности прочитать 32МБ BLOB за одно обращение к IO. И, понятно, что на практике придется читать блоками определенного размера (приближенными, например, к размеру блока файловой-системы). Так вот, при большом количестве возможных вариантов оказывается что хранение BLOB-ов кусками по 32КБ вполне хороший вариант. :)


            1. lair
              12.08.2015 16:53
              +2

              (Есть некоторые «стилистичекие» замечания к авторам _такого_ формата, но раз уж выросло такое...)

              Это бесполезно. Спорить с госуровнем — гиблое дело. Я пробовал, не помогло.

              При десериализации такого в объекты я бы в нескольких местах пропускал лишние промежуточные конверты (например, smev:MessageData/smev:AppData, или AddressData/Address), но наверное я чего-то не понимаю, и они там действительно нужны.

              Иногда нужны, если вам нужен полный аудит.

              В-любом случае, я пока не вижу как, даже при таком адовом дизайне схемы, мы упрёмся в 32 вложенных подындекса.

              Ну, от этого конкретного примера до предела три уровня осталось. К сожалению, я не могу взять реальные данные из обмена, где было и побольше, пришлось публично доступные искать.

              И да, для больших BLOB-ов всегда и везде придется разбивать блоки на подузлы.

              Ключевой вопрос — кому придется. Лично я предпочитаю, чтобы это за меня делала БД (включая оптимизацию этого доступа и прочее счастье). То есть я понимаю, что у БД есть страницы, а у хранилища ниже уровнем — блоки, но можно я об этом не буду думать?


              1. tsafin
                12.08.2015 17:31
                +1

                То есть я понимаю, что у БД есть страницы, а у хранилища ниже уровнем — блоки, но можно я об этом не буду думать?

                Так в Cache' и не надо об этом думать: поточные поля (символьные или бинарные) автоматически при сохранении бьются на куски.

                Но в этом случае мы уже оперируем не чистым иерархическим хранилищем/глобалами, описанными автором, а пользуемся надстройкой над ними в виде объектов/классов, предоставленными Cache', но в основе своей использующими те же самые глобалы.


                1. lair
                  12.08.2015 17:34

                  Автор-то про глобалы и их применимость рассказывает, понятно, что поверх можно фигачить разные абстракции.


            1. lair
              12.08.2015 17:08

              В-любом случае, я пока не вижу как, даже при таком адовом дизайне схемы, мы упрёмся в 32 вложенных подындекса. Все же хотелось бы примера из жизни, который имел бы смысл.

              На самом деле, в xml мы заодно влетаем в соседнее ограничение: максимальная длина ключа в 511 байт. Предположим, что мы добрые, и у нас однобайтовая кодировка (на самом деле, конечно, нет; ненавижу тебя, госуха) — это все равно 511 символов, а по-хорошему, имена надо хранить квалифицированными (я подозреваю, что если пример выше квалифицировать — то он уже вылетит за длину).


              1. tsafin
                12.08.2015 17:34
                +3

                Со СМЭВом этим вы заинтриговали, коллега, который этим занимается сейчас в отпуске, но у меня было впечатление что эта адовая XML схема вообще без проблем использовалась ими в их инсталляциях HealthShare/Ensemble в России. Вернется из отпуска — посмотрю на то как это проецируется в Cache'. Интересно.


              1. inetstar
                12.08.2015 17:42

                А у некоторых БД на глобалах — 1019 байт. У GT.M.

                Чтобы упереться даже в 511, нужно чтобы каждый индекс глобала был в среднем больше 16.5 байт.

                Глобалы — это специфический инструмент, весьма универсальный, но со своими ограничениями.


                1. lair
                  12.08.2015 17:43
                  +1

                  Угу. Вот и я про то, что специфический.


      1. inetstar
        12.08.2015 16:06

        Вообще я не говорил, что глобалы самый лучший способ хранения XML. Это один из способов, когда приемлемая вложенность и нужен быстрый доступ к узлам.


        1. lair
          12.08.2015 16:30

          Вы говорили, что xml легко хранится в глобалах. Легко-то легко, только есть ограничения. Специализированные хранилища справляются с этим сущееественно лучше.


          1. inetstar
            12.08.2015 16:35

            Что же это вы исключений не обозначили? Вы утверждаете что специализированные хранилища справляются с этим существенно лучше в абсолютно всех случаях?


            1. lair
              12.08.2015 16:38

              Нет, зачем же во всех? В общих.


              1. inetstar
                12.08.2015 16:44

                Вообще расплывчатое определение. Общий случай.
                Что это такое общий случай?

                Если нужно хранить документы целиком, то я не удивлюсь, если кто-нибудь выберет глобалы из-за их скорости.

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

                Есть бесконечное множество сценариев использования иерархических документов. И для приличной части этого бесконечного количества могут подойти глобалы. Всё зависит от задачи.


                1. lair
                  12.08.2015 16:45

                  Если нужно хранить документы целиком, то я не удивлюсь, если кто-нибудь выберет глобалы из-за их скорости.

                  … ограничение в 3.6Мб.

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

                  … ограничение по глубине вложенности.

                  Нет, несомненно, глобалы могут подойти. Просто каждый раз есть вопрос — а нет ли чего-то, что подойдет лучше?


                  1. inetstar
                    12.08.2015 16:51

                    Ограничения есть у всех баз. Если хранить документы целиком, то 16МБ, на весь документ, очень жёсткое ограничение.
                    А если нужно декомпозировать на иерархическую структуру с быстрым доступом к свойствам, то 3.6Мб на отдельное свойство не такое и большое ограничение.

                    Технологию подбирают подзадачу. Я постарался систематизировать сильные стороны глобалов.


                    1. lair
                      12.08.2015 16:55
                      +1

                      Если хранить документы целиком, то 16МБ, на весь документ, очень жёсткое ограничение.

                      Ну так возьмите БД, в которой нет такого ограничения. Это не обязательно глобалы.

                      А если нужно декомпозировать на иерархическую структуру с быстрым доступом к свойствам, то 3.6Мб на отдельное свойство не такое и большое ограничение.

                      … пока мы не пытаемся хранить в свойствах файлы.

                      Я постарался систематизировать сильные стороны глобалов.

                      Ну вот XML, файлы и индексы — как-то не сильная сторона оказалась.


                      1. inetstar
                        12.08.2015 17:01

                        Сильная, только для специфических случаев. Каких — уже говорил.

                        Специализированная БД для файлов — это файловая система.


                      1. inetstar
                        12.08.2015 17:47

                        Если хранить документы целиком, то 16МБ, на весь документ, очень жёсткое ограничение.

                        Ну так возьмите БД, в которой нет такого ограничения. Это не обязательно глобалы.


                        16МБ — это у Mongo. У подобных Mongo баз есть ограничения на объём документа, весьма жёсткие. И на число уровней (у Mongo — 100). Так как иначе парсер (при доступе к конкретным свойствам) будет вешать сервер.


                        1. lair
                          12.08.2015 17:48

                          У подобных Mongo баз есть ограничения на объём документа, весьма жёсткие.

                          Ага. Вот например у RavenDB нет (точнее, есть — около пары гигабайт).

                          Конечно, можно сказать, что равен не подобен монге (и в чем-то это правда даже), но равен — честная ДОДБ.


                        1. vedenin1980
                          12.08.2015 17:51

                          Сказки, у монго доступ к конкретным свойствам очень быстрый. И между 16Мб и 3,6 Мб и 100 и 31 уровней вложенности — четыре большие разницы.


                          1. inetstar
                            12.08.2015 18:02

                            Сказки, у монго доступ к конкретным свойствам очень быстрый.


                            По сравнению с чем? Сколько выборок в секунду ты можешь сделать из разных документов объёмом 3МБ с 15-го уровня вложенности?

                            Тут есть чёткие дихотомии.

                            Упрощённый алгоритм для выбора иерархической документно-ориентированной базы с возможностью быстрого доступа к свойствам.

                            0) Документы больше 16МБ
                            — но свойства меньше 3.6 — глобалы
                            — свойства не укладываются в 16МБ — не то, ни другое (если не использовать разрезание документов на части)
                            2) Документы целиком больше 3.6 и меньше 16 — Mongo
                            3) Меньше 3.6МБ и больше 100 уровней вложенности — Mongo
                            4) Меньше 3.6МБ и менее 31 уровня вложенности — глобалы
                            5) Документы около 1К — на вкус и цвет товарищей нет


                            1. lair
                              12.08.2015 18:05

                              Вы так говорите, как будто кроме монги и глобалов жизни не существует.

                              Не говоря уже о том, что задача («выбор иерархической документно-ориентированной базы с возможностью быстрого доступа к свойствам») изначально неверна, плясать надо от сценариев использования. Ну и да, polyglot persistence рулит.


                              1. inetstar
                                12.08.2015 18:52
                                -1

                                vedenin1980 — специалист по Mongo, я пишу про глобалы, и с ним мы обсуждали именно Mongo vs глобалы.

                                А вот за вами я заметил сомнительную привычку влезать в чужие диалоги. Вы делаете это постоянно. Из-за чего в пылу дискуссии разок я вас уже перепутал.


                                1. lair
                                  12.08.2015 18:54
                                  +1

                                  Ну и пишите тогда «алгоритм по выбору между монгой и глобалами».

                                  (а вообще, конечно, диалоги — это в личной переписке, а здесь публичное обсуждение, все-таки)


                1. 4dmonster
                  12.08.2015 16:49

                  Скажите, а для документа выше как надо поступить?
                  Какая структура для его компонентного хранения?

                  Мне как не специалисту кажется, что требуется аналог Parent_ID и/или Child_ID а не пытаться уровни иерархии документа вписать в уровни иерархии индексов.


                  1. inetstar
                    12.08.2015 16:59

                    Вообще, как я понял, приведённый документ, это некое сообщение. То есть вещь, которую нужно доставить из А в Б.

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


    1. vedenin1980
      12.08.2015 16:07

      даже в самых монстрах не будет и 20

      Максимальная вложенность исходного кода этой страницы как раз 20. Она монстр?


      1. inetstar
        12.08.2015 16:15

        А есть хоть какой-то смысл хранить HTML этой страницы в БД с обеспечением быстрого доступа к каждому узлу?

        Подобные документы достаются из БД целиком.

        А вот например в XML медкарты добавить новый узел со сканом МРТ будет удобно.


        1. 4dmonster
          12.08.2015 16:21

          А есть хоть какой-то смысл хранить HTML этой страницы в БД с обеспечением быстрого доступа к каждому узлу?


          Самое очевидное — если её надо сформировать.


          1. inetstar
            12.08.2015 16:57

            Формировать страницу через то, что записать всё по тэгам в иерархическую БД?
            Это будет медленнее, чем формировать напрямую в памяти.

            Использовать глобалы как кеш? В этом случае имеет смысл хранить куски документа целиком без разбиения по свойствам.


            1. 4dmonster
              12.08.2015 17:01

              Прямо на М и формировать. Данные в глобалах, мета-данные тоже, пусть М программы и формируют страничку.
              Можно использовать глобалы во время отладки.
              А в продакшене — локальные переменные.


              1. inetstar
                12.08.2015 17:06

                Как я понимаю, на Cache Server Pages так и делают.

                Вообще я удивлён, что M в своё время не захватил нишу веб-разработки как PHP. Языки-то сильно похожие.


        1. lair
          12.08.2015 16:27

          Ага, скан у вас помещается в 3.6Мб? Мне когда последний раз выдавали на руки сканы (не МРТ правда, а рентгена) — они как раз заняли половину CD-болванки (какой-то чертов пропьетарный формат для медицинского ПО). Да что там, у меня в 3.6 мегабайта фотографии с моего фотоаппарата не помещаются.


          1. inetstar
            12.08.2015 16:40

            Болванка МРТ состоит из множества файлов в формате DICOM. Как кино из отдельных кадров.
            Я открыл свою МРТ — каждый кадр это 586КБ.

            Тем более, я не уверен, что есть смысл в медкарте хранить все эти файлы. Достаточно выбрать самый характерный.
            Но таких отдельных кадров, сканов анализов может быть очень много.


            1. lair
              12.08.2015 16:43
              +1

              К сожалению, то, что отдельный скан может быть больше 3.6Мб — это такая реальность, данная нам в ощущениях. Не МРТ — так рентген, не рентген — так еще что-то.

              Нет, я понимаю, что объяснить заказчику можно любое ограничение (чего уж скрывать, все мы так рано или поздно делали), но на этом фоне придирки к аналогичным ограничениям у монги выглядят мелко.


              1. inetstar
                12.08.2015 16:46

                Поэтому эти придирки и в спойлере. И там жёстко обозначена область, в которой глобалы подходят лучше.
                Структуры хранения в Mongo и глобалы логически идеентичны. Различия только в реализациях и во встроенных языках.

                Если интересно, то Mongo хочет влезть на этот рынок и готовит презентации, где говорит, что Mongo — это почти глобалы)


                1. lair
                  12.08.2015 16:59

                  И там жёстко обозначена область, в которой глобалы подходят лучше.

                  Угу. Та, где вам, на самом деле, не нужно хранилище документов, а нужно хранилище свойств. Дальше вопрос к сценариям использования и архитектуре приложения.


                  1. inetstar
                    12.08.2015 17:09

                    Документы состоят из свойств. Глобалы дают возможность красиво и быстро декомпозировать документы по свойствам.
                    Это и есть ключевое преимущество перед БД, которые хранят целиком.

                    Глобалы — это одна из самых первых документно-ориентированных баз, если не первая вообще (66г!).


                    1. lair
                      12.08.2015 17:15

                      Документы состоят из свойств.

                      Это неоднозначный вопрос, сводящийся к тому, состоит ли целое из частей. Смысл ДОДБ, обычно, все-таки в том, чтобы работать с документом, как с целым (я приводил сценарий использования, он не единственный).


                      1. inetstar
                        12.08.2015 17:17

                        В любом случае: глобалы позволяют как хранить документ целиком, так и разбивая на части. С известными ограничениями.
                        По логической структуре глобалы — это надмножество над документно-ориентированными базами, которые хранят документы целиком.


                        1. lair
                          12.08.2015 17:21

                          По логической структуре глобалы — это надмножество над документно-ориентированными базами, которые хранят документы целиком.

                          Не-а. Глобалы — надмножество над key-value, потому что глобалы не умеют анализ содержимого документа (а именно оно специфично для документо-ориентированных хранилищ).


                          1. 4dmonster
                            12.08.2015 17:30
                            +1

                            А может Глобалы — key-value, где key может быть массивом?


                            1. inetstar
                              12.08.2015 17:31

                              Да. Почему нет. Следующая статья будет об этом.


                            1. lair
                              12.08.2015 17:33

                              Эта идея приходила мне в голову, но она, мне кажется, все-таки слишком упрощает ситуацию: групповые удаления, обходы — это все не ложится в семантику композитного ключа.


                              1. inetstar
                                12.08.2015 17:35

                                Согласен. Глобалы — иерархическое хранилище, с интерфейсом доступа к конкретным узлам как у многомерного массива. Я бы так определил.


                          1. inetstar
                            12.08.2015 17:33

                            Расскажите мне как анализирует, например, MongoDB документы.
                            Желательно как оно это делает само. Без запросов со стороны.

                            Так как к любой БД на глобалах также можно написать интерфейс для запросов. И код для анализа.


                            1. lair
                              12.08.2015 17:39

                              Ну как-как, вы скармливаете СУБД документ (кому что, в Mongo — JSON/BSON, RavenDB — .net-объект), та его сохраняет в понятном для себя виде, и в случае необходимости (например, при индексации или запросе) парсит содержимое.


        1. vedenin1980
          12.08.2015 16:34
          +1

          Это лишь простейший пример, на самом деле зачастую в энтерпрайзе вообще требуется бесконечное кол-во уровней иерархии, например вы хотите сделать сервис который строит дерево всех страниц любого сайта инета, доступных с главной страницы сайта, естественно вы должны считать что вложенность тут может быть бесконечной (ну или заведомо очень большой). И таких случаев в реальном энтерпрайзе довольно много.


          1. tsafin
            12.08.2015 16:52
            +2

            В данном случае Вы строите не дерево а _граф_


  1. anjensan
    12.08.2015 17:08

    Как и первая статья — много маркетологической шелухи, никакой конкретики.
    Тут что-то про скорость говорили… Так а где повторяемые и открытые тесты?
    Где техническое описание, как же оно работает «под капотом»?
    Напоминаю, что это хабр, а вовсе не гиктаймс или мегамозг.


    1. inetstar
      12.08.2015 17:25

      Цель этой статьи — обозначить области применения глобалов, поскольку многие вообще не знают об этой технологии и о её сильных сторонах.
      На техническое описание формата хранения я дал ссылку в первых же комментах.

      По реализации — уже говорилось, что известные БД на глобалах работают на B*-tree, но в принципе могут работать и на других алгоритмах.

      Не все читатели тут являются разработчиками БД. Моя цель в этих статьях — показать для чего нужны глобалы, а не как конкретно они работают и какие оптимизации были использованы.


  1. tsafin
    12.08.2015 17:13
    +5

    Давайте, все же я попытаюсь вернуть дискуссию в правильное с методической точки зрения русло?

    Моя позиция такова — с логической точки зрения разницы многомерного хранилища (глобалов), документной базы (JSON/BSON) и XML иерархии нет. Они все оперируют эквивалентным множеством моделей и данных.
    (Что я попытался донести в своей презентации на RIT 2015 Cache'Conf —
    www.slideshare.net/TimurSafin/multimodel-database-cach )

    Интересна позиция Mongo в этом отношении osehra.org/sites/default/files/NoSQLtheModernMUMPS.pdf
    При разговоре с группой пользователей VistA они пытались донести до аудитории мысль, что Mongo/документные базы, это тоже самое, что и иерархические базы (MUMPS/Cache'). С чем я не могу не согласиться :)

    Еще один важный аспект, в большинстве случае движки СУБД оперируют вариациями b-tree формата (когда они не SSTable/LSM, конечно). И Cache' с его b*-tree тут не сильно отличается, хотя и отличается в деталях, облегчающих множество операций с глобалами, упомянутых Сергеем inetstar в этих двух статьях. Детали формата b*-tree мы попытались раскрыть в двух презентациях на недавнем RIT (моей ретроспективной, упомянутой выше, и Сергея Шутова про внутренности собственно Cache' b*-tree. Как оказалось его презентации в онлайне нет, как только исправим ситуацию сообщу в эту ветку)


  1. BalinTomsk
    12.08.2015 21:22

    — не тратя при этом лишнее дисковое пространство на пустые колонки, как было бы в реляционном случае.

    вы когда делаете такие заявления хотя бы подкрепляте фактами. Например: MSSQL 2014

    SELECT t.NAME AS TableName, p.rows AS RowCounts, SUM(a.used_pages) * 8 AS UsedSpaceKB, (SUM(a.total_pages) — SUM(a.used_pages)) * 8 AS UnusedSpaceKB
    FROM sys.tables t INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
    INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.schema_id
    WHERE t.NAME LIKE 'slnx_job' AND t.is_ms_shipped = 0 AND i.OBJECT_ID > 255 GROUP BY t.Name, s.Name, p.Rows ORDER BY UsedSpaceKB DESC

    table | rows | used space | unused space
    slnx_job | 1626829 | 1019848 | 392

    Вставляю 10 колонок — размер не изменился.

    alter table slnx_job add c1 int
    alter table slnx_job add c2 int
    alter table slnx_job add c3 int
    alter table slnx_job add c4 int
    alter table slnx_job add c5 int
    alter table slnx_job add c6 int
    alter table slnx_job add c7 int
    alter table slnx_job add c8 int
    alter table slnx_job add c9 int

    Уверяю вас даже после добавления 10,000 колонок размер не поменяется. Реляционные базы не хранят пустые пространства.

    Насчет ХМЛ. Например DB2 хранит XML как внутри набор настояших реляционых таблиц со всей соотвествуюшей скоростью доступа.


    1. inetstar
      12.08.2015 21:44

      Это потому что вы вставили колонки без значений по-умолчанию.
      Попробуйте задать такие значения.

      Далее, для корректности вашего теста, вам нужно было определить в каждой строке случайным образом один аттрибут и посмотреть на какой размер изменилась таблица.

      P.S. Я тестировал на MySQL 5.5.

      Ждёт результатов тестирований. Надеюсь, таблица slnx_job у вас большая.


      1. lair
        12.08.2015 22:24
        +1

        Это потому что вы вставили колонки без значений по-умолчанию.

        А зачем использовать значения по умолчанию в БД? Точнее, не так: значение по умолчанию — это не пустая колонка, это именно имеющее смысл значение (например, текущая дата). И неудивительно, что на него тратится место. А на несуществующие значения — не тратится (точнее, зависит от реализации).


        1. inetstar
          12.08.2015 22:43

          А зачем использовать значения по умолчанию в БД?


          Во многих БД таблицы, где NULL не используются работают существенно быстрее.


          1. lair
            12.08.2015 22:48

            Во многих БД таблицы, где NULL не используются работают существенно быстрее.

            У вас, конечно же, есть тесты, подтверждающие это заявление. Скажем, в MS SQL я такого никогда не наблюдал (и, собственно, не понимаю, откуда там этому логически взяться).


            1. inetstar
              12.08.2015 23:03
              -2

              For best performance, and to reduce potential code bugs, columns should ideally be set to NOT NULL. For example, use of the IS NULL keywords in the WHERE clause makes that portion of the query non-sargeble, which means that portion of the query cannot make good use an index.

              И подобных статей много. Ещё одна:

              Using NULL values has the following drawbacks:

              1) Using NULL values in the Database is always going to be overhead in terms of querying the tables’ columns containing the NULL values especially the larger tables. This is because, to query the column which has the NULL values from the table, index won’t be used as we need to query using IS NULL clause. As a result full table scan is performed.

              2) When NULL value is used as one of the value for the column, it takes up the whole length of the column size if the column is of fixed length. However, using variable length column does not have this impact but otherwise with fixed length columns, always to store NULL value it occupies the entire column length resulting in growing the Database size which is totally unnecessary.

              3) Using NULL values in the Columns of the tables lead to convoluted Transact SQL code resulting in code not running efficiently or error prone.

              Hence, considering above, always try to avoid using NULL values and instead one can use some constants to represent NULL values like N/A.


              1. lair
                12.08.2015 23:22
                +2

                Еще раз спрашиваю, тесты есть?

                По мелочам:

                1. В статье, на которую вы ссылаетесь первой ссылкой, нет приведенного вами текста («For best performance....»; собственно, в ней вообще нет слова «performance»)
                2. Что касается второй «статьи», то она говорит, что при поиске по NULL (обратите внимание, не просто по колонке, которая может содержать такие значения, а именно поиск строк содержащих такие значения) индексы не используются, т.е. стоимость такого поиска (и того такого, а не любого другого) эквивалентна полному перебору. Во-первых, она ничего не говорит о любых других операциях по такой таблице. Во-вторых, просветите меня, пожалуйста, какова алгоритмическая сложность поиска узла, не содержащего заданный подиндекс, в глобале? Т.е., как найти всех пользователей, у которых не задан город?


                1. inetstar
                  12.08.2015 23:39

                  А в глобалах я легко могу сделать подобный индекс. В индексном глобале (и только в нём), например, в качестве NULL буду использовать "\0"

                  Set ^index_global("\0", id1) = 1
                  Set ^index_global("Moscow", id2) = 1


                  1. lair
                    12.08.2015 23:43

                    В RDBMS тоже можно сделать подобный индекс, технически ничего не мешает.

                    (кстати, а что вы будете делать, когда \0 — валидное значение?)


                    1. lair
                      12.08.2015 23:56

                      Собственно, постгре и индексирует: «Also, an IS NULL or IS NOT NULL condition on an index column can be used with a B-tree index.»


                    1. lair
                      13.08.2015 00:02
                      -1

                      Даже больше, есть мнение, что NULL не только не замедляют, но и ускоряют работу постгре.


                      1. smagen
                        13.08.2015 00:17
                        +2

                        NOT NULL, в некоторых случаях, действительно ускоряет работу постгреса. Если в таблице в начале идут NOT NULL атрибуты фиксированной длины, то доступ к этим атрибутам осуществляется по предвычисленным значениям. Однако, почувствовать это преимущество на паре десятков атрибутов, вряд ли получится.

                        P.S. Умоляю вас, «постгрес» :)


                      1. inetstar
                        13.08.2015 00:19

                        Разговор был о MS SQL.
                        Условный индекс — это не то. Придётся для каждого столбца делать 2 индекса — по колонкам и условия. Это убьёт производительность. А в моём случае это будет один индексный глобал на одну колонку (set колонок).

                        Напоминаю исходную точку разговора: не рекомендуется вручную программировать таблицы на глобалах. Не к тому цепляетесь.


                        1. lair
                          13.08.2015 00:24
                          +2

                          Разговор был о MS SQL.

                          Разговор был о «во многих БД». Впрочем, вы и в MS SQL падение производительности не подтвердили.

                          Условный индекс — это не то.

                          А я и не про условные индексы.

                          Придётся для каждого столбца делать 2 индекса — по колонкам и условия. Это убьёт производительность.

                          С чего вы это взяли?

                          Напоминаю исходную точку разговора: не рекомендуется вручную программировать таблицы на глобалах.

                          Я вообще не понимаю, зачем программировать таблицы на глобалах, когда есть нормальные табличные инструменты.


                        1. lair
                          13.08.2015 11:31
                          +1

                          Разговор был о MS SQL.

                          … который тоже поддерживает индексы с NULL.

                          SELECT [Id], [Text]
                          FROM [dbo].[NV]
                          WHERE Text IS NULL
                          
                          // Execution plan
                          Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV]), SEEK:([Speed].[dbo].[NV].[Text]=NULL) ORDERED FORWARD)
                          


                          Передаю пламенный привет (устаревшим) статьям, которые вы цитировали.


                          1. inetstar
                            13.08.2015 12:27

                            1) Вы не показали определение таблицы, в котором это поле указано как индексируемое

                            2) Приведите ссылку на документацию

                            3) И что это действительно не только для текстовых полей.

                            4) Сделайте ещё поиск по NOT NULL по текстовому и не по текстовому полю

                            5) Вы не сможете сделать строку из 10 000 столбцов, размером более 8КБ. Так что глобалы на разреженных данных MS SQL в общем случае заменить не сможет.


                            1. lair
                              13.08.2015 12:36
                              +1

                              Вы не показали определение таблицы, в котором это поле указано как индексируемое

                              Вы серьезно считаете, что MS SQL способен использовать индекс, который не задан? Но мне не сложно.

                              CREATE TABLE [dbo].[NV](
                              	[Id] [uniqueidentifier] NOT NULL,
                              	[Text] [nvarchar](50) NULL,
                                  CONSTRAINT [PK_NV] PRIMARY KEY CLUSTERED ([Id] ASC)
                              ) ON [PRIMARY]
                              GO
                              
                              CREATE NONCLUSTERED INDEX [IX_NV] ON [dbo].[NV] ([Text] ASC) ON [PRIMARY]
                              GO
                              


                              Приведите ссылку на документацию

                              msdn.microsoft.com/en-us/library/ms188783%28v=sql.110%29.aspx

                              И что это действительно не только для текстовых полей.

                              StmtText
                              ---------------------------------------------------
                              SELECT Id, Int
                              FROM [dbo].[NV]
                              WHERE Int IS NULL
                              
                              StmtText
                              -------------------------------------------------------------------------------------------------------------
                                |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_1]), SEEK:([Speed].[dbo].[NV].[Int]=NULL) ORDERED FORWARD)
                              


                              Вы не сможете сделать строку из 10 000 столбцов, размером более 8КБ. Так что глобалы на разреженных данных MS SQL в общем случае заменить не сможет.

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


                              1. inetstar
                                13.08.2015 12:47
                                -1

                                1) Вообще-то в документации ничего не говорится об ускорении выборок строк с NULL-значениями.

                                2) И я просил привести примеры с NOT NULL ещё. Я-то легко на глобалах сделаю индексированную выборку по NOT NULL. О чём вы как-то забыли. Приводите.

                                3) Таблица в общем случае никогда не была деревом. Сэмулировать дерево на таблицах можно, но это нерационально. В этом тоже в статье написано.


                                1. lair
                                  13.08.2015 13:16

                                  Вообще-то в документации ничего не говорится об ускорении выборок строк с NULL-значениями.

                                  А должно? Но вот вам: msdn.microsoft.com/en-us/library/ms190457%28v=sql.110%29.aspx

                                  An index is an on-disk structure associated with a table or view that speeds retrieval of rows from the table or view. An index contains keys built from one or more columns in the table or view. These keys are stored in a structure (B-tree) that enables SQL Server to find the row or rows associated with the key values quickly and efficiently.


                                  Никаких специальных оговорок про NULL нет.

                                  И я просил привести примеры с NOT NULL ещё. Я-то легко на глобалах сделаю индексированную выборку по NOT NULL. О чём вы как-то забыли. Приводите.

                                  А вы меньше редактируйте комментарий после отправки, меньше будет «забываться».

                                  CREATE TABLE [dbo].[NV](
                                  	[Id] [uniqueidentifier] NOT NULL,
                                  	[TextNull] [nvarchar](50) NULL,
                                  	[IntNull] [int] NULL,
                                  	[TextNotNull] [nvarchar](50) NOT NULL,
                                  	[IntNotNull] [int] NOT NULL
                                  )
                                  
                                  SELECT Id FROM NV WHERE TextNull IS NULL
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_4]), SEEK:([Speed].[dbo].[NV].[TextNull]=NULL) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE TextNull IS NOT NULL
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_4]), SEEK:([Speed].[dbo].[NV].[TextNull] IsNotNull) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE TextNull = '123'
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_4]), SEEK:([Speed].[dbo].[NV].[TextNull]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE IntNull IS NULL
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_1]), SEEK:([Speed].[dbo].[NV].[IntNull]=NULL) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE IntNull IS NOT NULL
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_1]), SEEK:([Speed].[dbo].[NV].[IntNull] IsNotNull) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE IntNull = 1
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_1]), SEEK:([Speed].[dbo].[NV].[IntNull]=CONVERT_IMPLICIT(int,[@1],0)) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE TextNotNull = '123'
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_3]), SEEK:([Speed].[dbo].[NV].[TextNotNull]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)) ORDERED FORWARD)
                                  
                                  SELECT Id FROM NV WHERE IntNotNull = 1
                                    |--Index Seek(OBJECT:([Speed].[dbo].[NV].[IX_NV_2]), SEEK:([Speed].[dbo].[NV].[IntNotNull]=CONVERT_IMPLICIT(int,[@1],0)) ORDERED FORWARD)
                                  


                                  Таблица в общем случае никогда не была деревом. Сэмулировать дерево на таблицах можно, но это нерационально.

                                  Не более нерационально, чем имитировать таблицы на дереве. Но речь-то не об этом, речь о том, чтобы сохранить конкретные данные в реляционной структуре, а это совсем не то же самое, что «сэмулировать дерево».


                            1. BalinTomsk
                              13.08.2015 20:51
                              -1

                              1. Для это есть специальный индексный сервер.

                              2.https://msdn.microsoft.com/en-us/library/ms142571.aspx

                              3.
                              create table AAA
                              (
                              id int not null primary key,
                              c1 int null index c1_index,
                              c2 float null index c2_index
                              )

                              5)идея хранить дерево всего в одной таблице — неверная с архитектурной точки зрения вреляционых базах и мне видится неверной точка зрения что нельзя сохранить медицинскую историю человека в реляционной структуре.

                              Число людей, болезней, симптомов, процедур, снимков, мнений, да чего угодно настолько конечно, что даже SAP с 36000 таблиц кажется монстром, по сравнению базой хранения всего типичного госпиталя, которую вы считаете нельзя положить в реляционную таблицу.

                              Watson использует DB2 и им в голову не пришло использовать нереляционную базу. Вы же не будете отрицать что ничего близко похожего на ваших глобалах не сделано.

                              Если вам нужно более чем 10000 или миллион колонок — это не проблема сделать нужное количество таблиц с отношением 1:1


                              1. inetstar
                                13.08.2015 22:40

                                1. Для это есть специальный индексный сервер.


                                Я просто попросил показать определение таблицы

                                2) Приведите ссылку на документацию
                                2.https://msdn.microsoft.com/en-us/library/ms142571.aspx


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

                                5)идея хранить дерево всего в одной таблице — неверная с архитектурной точки зрения в реляционых базах и мне видится неверной точка зрения что нельзя сохранить медицинскую историю человека в реляционной структуре.


                                Можно использовать несколько таблиц. И это всё равно будет ошибка с точки зрения архитектуры.

                                Watson использует DB2
                                достоверной информации о том, что Watson использует DB2 как единственное хранилище не нашёл. Однако у IBM есть технология аналогичная глобалам.

                                Самая большая медицинская система с самым большим числом пользователей VistA, что говорит в пользу глобалов и об их подходящести для данной сферы. Собственно говоря, глобалы именно для этой сферы и придумали.


                1. inetstar
                  13.08.2015 00:29

                  Статья откуда цитата про «best perfomance»
                  Нам лучше вернуться к теме. Так как больше 1024 колонок вы не сделаете в MS SQL. А в спец. типе таблиц wide строка не более 8КБ.


                  1. lair
                    13.08.2015 00:37
                    +1

                    Статья откуда цитата про «best perfomance»

                    Это ровно тот же аргумент, что уже обсуждался — про индексы и поиск с IS NULL. Не интересно по второму разу обсуждать.

                    Так как больше 1024 колонок вы не сделаете в MS SQL.

                    А надо?

                    А в спец. типе таблиц wide строка не более 8КБ.

                    Я вам честно скажу, это ограничение есть во всех таблицах MS SQL, не только в wide. Правда, начиная с 2008-го, есть row overflow для строковых и бинарних типов, но он подтормаживает.


                    1. inetstar
                      13.08.2015 00:46

                      Так как больше 1024 колонок вы не сделаете в MS SQL.

                      А надо?


                      В статье была фраза, что глобалы хороши, когда есть много необязательных свойств, например в медкартах, а на таблицах тоже самое было реализовано десятками тысяч столбцов.

                      Человек мне сказал, что разреженные столбцы не занимают место и похвастался, что мог бы вставить 10 000 колонок в MS SQL. Так вот. Не смог бы. А если и смог бы, то там свои ограничения.

                      8КБ на строку с пациентом — это абсолютно нереально. И там ещё нельзя по одной новые колонки добавлять в этом типе таблиц.


                      1. lair
                        13.08.2015 00:50
                        +1

                        Во-первых, для медкарт лучше ДОДБ. Но, во-вторых, если бы заказчик настаивал на RDBMS, то немного разумной декомпозиции — и все будет хорошо.

                        Я ни разу в жизни не видел ни одного объекта, у которого бы реально было хотя бы несколько сотен индивидуальных, недекомпонуемых на группы свойств. Человеческий мозг так не работает, ему некомфортно в таких моделях.


                        1. inetstar
                          13.08.2015 12:30

                          Во-первых, для медкарт лучше ДОДБ


                          Базы на глобалах — это и есть ДОБД.

                          Я ни разу в жизни не видел ни одного объекта


                          А вот я видел. Даже 40 свойств с десятками подсвойств заставляют разработчиков уже сходить с ума.
                          Я встречал это в базе, где хранилась персональная информация о людям.

                          Пришлось использовать EAV — я тогда ещё не знал о глобалах.


                          1. lair
                            13.08.2015 12:38

                            Базы на глобалах — это и есть ДОБД.

                            Это не единственные существующие ДОДБ. Более того, я уже писал, почему я не считаю глобалы ДОДБ.

                            А вот я видел. Даже 40 свойств с десятками подсвойств заставляют разработчиков уже сходить с ума.

                            Извините, но это не «сотни свойств», это именно «40 свойств с десятками подсвойств». У вас уже есть декомпозиция, но вы ее почему-то не видите.


                            1. inetstar
                              13.08.2015 12:55

                              почему я не считаю глобалы ДОБД


                              Вы просто не разбираетесь в ДОБД.
                              Почитайте википедию.
                              Там есть MUMPS.

                              У вас уже есть декомпозиция, но вы ее почему-то не видите.

                              Я вижу многоуровневое дерево. Ясно и отчётчиво.

                              На таблицах это либо EOV или 160 таблиц (с декомпозицией). А на глобалах — одно красивое дерево.


                              1. lair
                                13.08.2015 13:23
                                +3

                                Вы просто не разбираетесь в ДОБД. Почитайте википедию.

                                Давайте не будем доставать и меряться, кто в чем разбирается, хорошо?

                                Я вижу многоуровневое дерево. Ясно и отчётчиво.

                                ...eye of the beholder. Вы привыкли так декомпоновать системы, потому что вы привыкли так работать с глобалами. Это не значит, что это единственная возможная декомпозиция этой системы.

                                На таблицах это либо EOV или 160 таблиц (с декомпозицией).

                                Откуда взялась цифра «160»? Вы посчитали сущности в E-R?


                                1. inetstar
                                  13.08.2015 14:13
                                  -1

                                  Вы просто не разбираетесь в ДОБД. Почитайте википедию.

                                  Давайте не будем доставать и меряться, кто в чем разбирается, хорошо?


                                  Более скромно было сказать: Извиняюсь, вы правы.

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

                                  Откуда взялась цифра «160»?


                                  Это примерная цифра. Слава богу, хватило ума, не делать полную нормализацию, а использовать EAV.

                                  40 необязательных свойств, у которых у каждого может быть начиная от 4-х вложенных свойств. У которых также могут быть вложенные свойства. Все эти свойства разнотипны, а не просто текстовые поля определённой длины.

                                  Так что при полной нормализации без EAV будет, как минимум, 1 таблица 40 колонками для свойств + 160 таблиц связей с подсвойствами + 4*40 таблиц подсвойствами = 321 даже. И это только при двухкратном уровне вложенности.

                                  А на EAV 4 таблицы (из-за неограниченных вложенностей, а так вообще 3).

                                  Но меня всё время не оставляла мысль, что это решение (EAV) не идеальное. Когда я узнал о глобалах, то понял то, что они то что нужно.


                                  1. lair
                                    13.08.2015 14:24
                                    +1

                                    Вы сами себе выдумали определение ДОБД и решили, что оно самое лучшее. Так что ваше определение ДОБД — субъективное, а не то, которое реально используется в мире. А MUMPS — это ДОБД.


                                    Субъективное, говорите?

                                    Document databases store documents in the value part of the key-value store; think about document databases as key-value stores where the value is examinable. [...] Some of the popular document databases we have seen are MongoDB, CouchDB, Terrastore, OrientDB, RavenDB, and of course the well-known and often reviled Lotus Notes that uses document storage.


                                    (Pramod J. Sadalage, Martin Fowler, NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence)

                                    Я же говорю, не надо доставать и меряться. На поле с неустойчивой терминологией это заведомо бесполезное действие.

                                    40 необязательных свойств, у которых у каждого может быть начиная от 4-х вложенных свойств. У которых также могут быть вложенные свойства. Все эти свойства разнотипны, а не просто текстовые поля определённой длины.

                                    Вы мыслите свойствами, а не сущностями. Это опасный подход (хотя я и понимаю, откуда он у вас растет). Повторюсь, есть отличная от нуля вероятность, что существует иная декомпоновка этой модели, позволяющая получить более разумную структуру БД. К сожалению, дать более разумных комментариев, не видя модели, я не могу.

                                    Ограничусь одним примером. Возьмем одну из ваших структур данных:

                                    image

                                    Если бы я это моделировал в ER, то единственными свойствами здесь оказались бы INN, Name и City. Все прочее декомпонуется в связи и сущности (к сожалению, я не вижу, какие семантические атрибуты есть на следующем уровне, и, как следствие, во что их надо отразить).


      1. BalinTomsk
        13.08.2015 20:30
        +1

        ----Это потому что вы вставили колонки без значений по-умолчанию.
        — Попробуйте задать такие значения.

        A зачем? мы все еше дерево пытаемся уместить — или вы на ходу решили подменить условие задачи?

        ---Далее, для корректности вашего теста, вам нужно было определить в каждой строке случайным образом один аттрибут и посмотреть на какой размер изменилась таблица.

        DECLARE @intFlag INT
        SET @intFlag = 1
        WHILE ( @intFlag <= 900)
        BEGIN
        declare @cn sysname = N'k' + CAST( @intFlag as sysname);
        declare script sysname = N'alter table slnx_job add ' + @cn + N' int'
        EXECUTE sp_executesql script
        SET @intFlag = @intFlag + 1
        END
        GO

        3 раза проапдейтил случайное значение в каждую из колонок — в целом то слегка больше то меньше — это связано с тем что данные хранятся в 8К страницах. Ничего ненужного MSSQL не хранит.

        1,626,829 rows


        1. inetstar
          13.08.2015 20:57

          Хорошо, что вы решили провести тесты.

          Однако я не вижу в вашей процедуре вставок значений. Вы просто добавили 900 пустых колонок.
          И не вставили ни одного реального значения.
          Данные должны быть разреженными, т.е. в некоторых колонках должны быть значения.
          В вашем случае просто изменилось описание таблицы, а не её содержимое.
          В некоторых БД оно вообще в отдельном файле хранится.

          Далее, для корректности вашего теста, вам нужно было определить в каждой строке случайным образом один аттрибут и посмотреть на какой размер изменилась таблица.

          А лучше 1-3 случайных аттрибута.

          Я уже писал про ограничение на 8КБ строку в MS SQL в wide типе — это очень мало для документа.
          А в обычную таблицу вы 9000 колонок не добавите.

          1,626,829 rows

          Ещё нужно было размер таблицы в байтах указывать.


          1. BalinTomsk
            13.08.2015 21:15

            Вы внимательно читали что я ответил? 3 раза проапдейтил случайное значение в каждую из колонок

            DECLARE @intFlag INT, @cnt int = 0
            SET @intFlag = 1
            WHILE ( @intFlag <= 900)
            BEGIN
            declare @cn sysname = N'k' + CAST( @intFlag as sysname);
            declare val sysname = CAST(CAST(1000*RAND(CHECKSUM(NEWID())) AS int) as sysname);
            declare script sysname = N'update slnx_job set ' + @cn + N' =' + val + N' WHERE slnxjob_id = ' + val
            EXECUTE sp_executesql script
            SET @cnt = @cnt + @@ROWCOUNT
            SET @intFlag = @intFlag + 1
            END
            select @cnt
            GO

            Было 291272 стало 294992
            Было 294992 стало 299052

            — А в обычную таблицу вы 9000 колонок не добавите.

            А надо? Добавление очередных 10,000 (аля колонок) атрибутов решается добавление поля с типом атрибута и значением 1-10000.

            Если хотите хранить честное дерево то это спасет отца демократии:

            https://msdn.microsoft.com/en-us/library/bb677173.aspx


            1. inetstar
              13.08.2015 22:06

              Я вижу проблему таблица с 1,626,829 rows и N+900 колонок занимает у вас 291272 байт.
              Т.е. 0.17 байт на строку. Вы явно в чём-то ошиблись.

              Даже если бы вы в каждую строку вставили по одному int — 4 байта, то размер был бы у вас не менее 6 000 000 байт, а у вас всего 290КБ.

              Т.е. явно в чём-то ошибка. Объяснить это рационально предлагаю вам.


            1. inetstar
              13.08.2015 22:47

              Если хотите хранить честное дерево то это спасет отца демократии:
              msdn.microsoft.com/en-us/library/bb677173.aspx


              Это нечестное дерево, так как оно предполагает одинаковую структуру полей для всех узлов, на всех уровнях.
              Но я соглашусь, что это хорошее подспорье в некоторых случаях.


    1. inetstar
      12.08.2015 21:50

      То есть нужен цикл по всем id, со вставкой в случайную колонку.
      Хранимку придётся писать. Ну или на ASP/PHP.

      И ещё. Не знаю как MS SQL, но в MySQL с таблицы с колонками NULL работают медленнее.
      Поэтому я, например, их избегаю.


    1. inetstar
      12.08.2015 22:08

      Уверяю вас даже после добавления 10,000 колонок размер не поменяется.


      Я могу такое допустить только в случае, когда абсолютно все эти колонки пустые.
      Если же вы начнёте их хоть чуть-чуть использовать, то БД сразу начнёт выделять, как минимум, 1 бит на колонку.

      В том тесте, что я предложил, если одна неточность. Вставлять нужно не одно значение в случайную колонку, так база может увеличить все строки на 4 байта, а случайное число значений (допустим 1-3) в случайные колонки каждой строки, вот тогда тест будет более менее точным.


    1. inetstar
      12.08.2015 22:42

      Уверяю вас даже после добавления 10,000 колонок


      А вот тут вы очень глубоко ошибаетесь. В MS SQL максимум 1024 колонки.

      Исключение: есть специальный тип таблиц — wide — там до 30 000 колонок. Но в этом типе строка не больше 8КБ. Что сразу ставит крест на многих видах применений.

      Второй минус этого типа — The column set cannot be changed. To change a column set, you must delete and re-create the column set.


    1. inetstar
      12.08.2015 22:49

      И репликацию использовать нельзя для таких таблиц. Остальные детали


  1. lair
    13.08.2015 16:44
    +1

    Таблицы на двухуровневом дереве vs. на одноуровневом дереве.
    Минусы
    Медленнее на вставку и удаление, так как нужно устанавливать/удалять число узлов равное числу колонок.


    Но:

    Удаление поддеревьев — ещё одна сильная сторона глобалов. Для этого не нужна рекурсия. Это происходит невероятно быстро.


    Так все-таки, как соотносятся по скорости удаление поддерева и удаление одиночного узла в глобале?


    1. inetstar
      13.08.2015 18:14

      Это вы нашли что-то типа опечатки. Сейчас исправлю.
      Устанавливать придётся дольше, конечно, чем удалять.

      Групповое удаление работает по моим тестам в 260-455 раз быстрее, чем индивидуальное удаление узлов. Для двухярусного дерева.


      1. lair
        13.08.2015 18:15

        Групповое удаление работает по моим тестам в 260-455 раз быстрее, чем индивидуальное удаление узлов. Для двухярусного дерева.

        Я спрашивал об обратной ситуации: как соотносится удаление одного дочернего узла с удалением дочернего поддерева?


        1. inetstar
          13.08.2015 18:19

          В зависимости от числа элементов. Скорость удаления поддерева зависит от числа узлов. Ориентировочно, если в поддереве 520 узлов, то его удаление будет в 2 раза дольше, чем одиночного узла. Я тестировал удаление поддерева со 50M элементами.