Comments 43
Что только люди не придумают, лишь бы не изучать работу с графами :-) Смотрите, как это делается с графовой субд:
Есть документы — это узлы графа (строчки в терминах рсубд, но иерархические и с перекрёстными ссылками).
Есть классы документов — к ним привязываются схемы, триггеры и пр (таблицы в терминал рсубд).
Есть кластеры — это группы любых узлов, хранящиеся вместе (ленты в ваших терминах).
Есть ноды — физические машинки.
Теперь следите за руками:
CREATE CLASS twomass_psc;
CREATE PROPERTY twomass_psc.ra DOUBLE;
CREATE PROPERTY twomass_psc.decl DOUBLE;
CREATE PROPERTY twomass_psc.xyz LINK twomass_xyz;
CREATE PROPERTY twomass_psc.coadd_key INTEGER;
CREATE PROPERTY twomass_psc.coadd SHORT;
CREATE CLASS twomass_xyz;
CREATE PROPERTY twomass_xyz.j_m FLOAT;
CREATE PROPERTY twomass_xyz.j_cmsig FLOAT;
CREATE PROPERTY twomass_xyz.j_msigcom FLOAT;
CREATE PROPERTY twomass_xyz.j_snr FLOAT;
CREATE PROPERTY twomass_xyz.h_m FLOAT;
CREATE PROPERTY twomass_xyz.h_cmsig FLOAT;
CREATE PROPERTY twomass_xyz.h_msigcom FLOAT;
CREATE PROPERTY twomass_xyz.h_snr FLOAT;
CREATE PROPERTY twomass_xyz.k_m FLOAT;
CREATE PROPERTY twomass_xyz.k_cmsig FLOAT;
CREATE PROPERTY twomass_xyz.k_msigcom FLOAT;
CREATE PROPERTY twomass_xyz.k_snr FLOAT;
Тут мы создали два класса, где документы из первого класса ссылаются на документы из второго. По умолчанию каждый класс будет лежать в своём кластере, но можно и вручную указать какой куда положить:
CREATE CLUSTER project_cucumber;
INSERT INTO twomass_psc CLUSTER project_cucumber SET
...
xyz = ( INSERT INTO twomass_xyz CLUSTER project_cucumber SET ... );
И при репликации и шардировании кластеры будут раскидываться по машинам целиком, что гарантирует, что запросы внутри кластера не потребуют дорогостоящей коммуникации к другим машинам или даже дата-центрам.
XYZ-светимости в разных диапазонах
Ra|Dec — координаты
Где пространственный индекс будет?
Конечно, логично прицепить пространственный индекс к кому классу, который содержит координаты (twomass_psc). Но тогда при фильтрации по пространственному критерию придется сделать лишнее чтение, чтобы получить ссылку на класс со светимостями (twomass_xyz).
Делать (дублировать) пространственный индекс к twomass_xyz нелогично.
Хранить в пространственном индексе ссылки на все экземпляры классов — некрасиво.
Да и позволит ли это СУБД.
Иметь один индекс на всех — а кто будет синхронизировать идентификаторы в разных классах?
Никто ведь не запрещает и в реляционной парадигме иметь разные таблицы (twomass_psc,twomass_xy...) и join-ить их при необходимости. Но возникнут те же самые вопросы.
Вообще, противостояние табличных и сетевых СУБД длится столько же, сколько существуют сами СУБД. Это как противостояние брони и снаряда.
В неуникальный индекс можно по одному ключу засовывать несколько разных документов. Зачем синхронизировать идентификаторы, если они не меняются?
И зачем городить огород с разными классами, когда можно всё сделать в рамках старого доброго SQL?
В сущности и по-колоночный и строчный варианты — крайние случаи одной идеи — нарезать таблицу на “ленточки“ и внутри каждой ленты хранить данные построчно. Просто в одном случае лента одна, в другом ленты вырождаются до одной колонки.
Так почему бы не допустить и промежуточные варианты — если данные некоторых колонок приходят/читаются вместе, пусть и окажутся на одной ленте. А если в ленте не оказалось данных (NULL-ы), то и хранить ничего не надо. Заодно снимается проблема максимального размера строки — можно расщепить таблицу, когда есть риск, что строка не поместится на одной странице.
Каждая дополнительная "ленточка" — это дополнительное чтение.
Прагмы — это не "старый добрый SQL". Я привёл примеры на диалекте SQL — OSQL.
при фильтрации по пространственному критерию не требует дополнительного чтения.
В том и суть, если я знаю как собираюсь искать, то имею возможность настроить данные так, чтобы избежать чтения ненужного.
Если данные лежат в разных местах и нужны, то читать несколько раз в этих разных местах по любому придётся. Если же вы чисто про индексы, то любой индекс — это дерево, а дерево — частный случай графа.
Так же как и в строчных СУБД (и в колоночных, кстати).
Поэтому при фильтрации по индексу нет необходимости делать дополнительное чтение, чтобы узнать ссылку на экземпляр нужного класса.
Уж не знаю как и разжевывать.
если это один документ? Вместе с идентификатором документа придётся хранить и идентификатор таблицы/класса. Конечно можно, но зачем, если можно обойтись без этого?
… is like sleeping with your sister. Sure she's a great piece of tail with a blouse full of goodies, but it's just illegal.
Topper Harley
Если нам нужно по разному хранить части документа, то по факту это разные документы. Идентификатор документа состоит из двух частей: номер кластера и номер документа в нём. @12:345
Но сам так делать не стану и никому не посоветую.
Индексы — это деревья, деревья — это графы. В графовой субд нет деления на таблицы и костыли к таблицами, чтобы первые не тормозили. Вся ваша база — один большой индекс.
Логика "у индексов под капотом графы, значит для графов отдельные индексы не нужны" это примерно как "у лопаты черенок из дерева, значит чтобы в лесу выкопать яму, лопата не нужна, там и так деревья есть".
потом вы собираетесь идентификаторы всех частей записи записывать в индексах. Теперь и индексы не нужны.
Самое время продемонстрировать хоть какие-то преимущества графового подхода. Сейчас я вижу одни только издержки.
Тут неплохо объясняется, чем графовый подход лучше: https://www.slideshare.net/lvca/how-graph-databases-started-the-multi-model-revolution
Вот вы нарисовали одну ссылку из psc в xyz. Пусть есть еще qwe.
Циклы делать нехорошо по понятным причинам. Звезда?
Ок, у нас ссылки psc->xyz и psc->qwe. Рассмотрим select, в котором задействованы только колонки из xyz и qwe. Опять не обойтись без центрального узла с его ссылками.
Циклы делать нехорошо по понятным причинам.
Не понятным.
либо необходимость руками управлять идентификаторами.
И то и другое не слишком красиво.
Кроме того, цикл предполагает наличие упорядочения в обходе разных классов.
А поскольку в самой задаче такое упорядочение не заложено, оно будет привнесено искусственно, а значит обязательно вылезет потом в виде ограничений/издержек.
Обновить 2 документа — это гораздо быстрее, чем обновить один документ и индекс. Ну и если руками расставлять двусторонние ссылки влом, то можно использовать высокоуровневый API который делает это за вас.
-- руками обеспечили обратные ссылки --
UPDATE @12:34 ADD bar = ( INSERT INTO bar SET( foo = @12:34 ) );
-- автоматически создали двусторонние ссылки --
CREATE EDGE foo_bar FROM @12:34 TO ( CREATE VERTEX bar )
Насчёт "упорядоченности" я не понял. Как наличие или отсутствие ссылки может что-то там "упорядочить"?
Обновить 2 документа — это гораздо быстрее, чем обновить один документ и индекс.— не очевидно, документ и сам может быть расположен в индексе.
Упорядоченность возникает в тот момент, когда вам надо обойти цикл.
Обновление индекса может привести к его ребалансировке и как следствие гораздо большему числу записей.
Разумеется обходить цикл вы будете в каком-то не случайном порядке. Какие вы видите в этом проблемы?
Идея ваша вполне понятна, не плоха и хорошо известна, но подана очень плохо.
Все результат действия прагм примерно аналогичен созданию нескольких таблиц связанных по IDENTITY/ROWID. В MySQL это можно воспроизвести буквально, в СУБД с ROWID (PostresSQL) будет несколько иначе и примерно медленнее.
Производительность этой схемы прежде всего зависит от отношения селектов/апдейтов, в том числе для отдельных полей. Этот момент как-то совсем не рассмотрен. Более того, это imho примерно единственная точка, с которой можно без усложнений и полноценно рассмотреть все плюсы-минусы.
- Добавлены отсылки к колоночным БД с их массовым перечислением, но при этом внезапно никак не упомянуты главное принципиальное отличие: Колоночные базы ориентированны на другой набор операций, поэтому как-правило не поддерживают апдейты, либо делают их крайне дорогими и без ACID. Соответственно, за счет этого могут использовать совсем другие индексы (примерно не b-tree, а зональные, битовые маски и column imprints).
В сухом остатке выходит, что в статье:
- описание well-known модели нескольких b-tree с общим PK;
- это описание завуалированно и подано как новый велосипед с расширением синтаксиса SQL:
- модель нескольких b-tree с общим PK сопоставляется с колоночной, без рассмотрения самых главных отличий.
Вишенкой выглядит отсылка к колоночным индексам MS SQL. В целом складывается впечатление, что автор "поймал идею", но не достаточно осведомлен чтобы сопоставить её с уже существующим в индустрии и критически оценить.
Конечно, так можно сделать. Конечному разработчику проще иметь дело с одной таблицей, которая где-то внутри разбита на ленты и деталей знать не надо. Не нужно следить за тем, чтобы ключи не рассогласовались. Не нужно всё обставлять скобками транзакций, если возникает например 3 insert-а вместо одного. Если я хочу в триггере использовать значение колонки из дружественной таблицы, просто беру и использую. Индексы относятся ко всем колонкам, не надо организовывать join-ы.… Разве нет?
Опять же в тексте есть ссылка на статью «ColumnStores vs. RowStores: How Different Are They Really?». В ней пытаются эмулировать колоночные таблицы в обычной СУБД. Один из основных выводов — эмуляция не даёт нам понять, есть преимущества или нет.
2) «Производительность этой схемы прежде всего зависит от отношения селектов/апдейтов, в том числе для отдельных полей». Да, это тема для будущих статей. Работа в процессе и я не готов делиться результатами.
3) Мне бы не хотелось соревноваться с колоночными СУБД на их поле. Один из вложенных в статью смыслов (не знаю, насколько удалось) в том, чтобы показать, что есть смежная зона, где можно получить преимущества обоих подходов.
PS: в статье честно сказано, что фактически новизна лишь в способе подачи подсказок SQL-процессору.
PPS: автор вероятно «не достаточно осведомлен», но для этого и существует площадка Хабра,
чтобы делиться мнениями и идеями, где можно встретить людей со знаниями в самых разных областях.
INSERT INTO foo (auto,text)
VALUES(NULL,'text'); # generate ID by inserting NULL
INSERT INTO foo2 (id,text)
VALUES(LAST_INSERT_ID(),'text'); # use ID in second table
имели ввиду, когда говорили, что «создание нескольких таблиц связанных по IDENTITY… в MySQL можно воспроизвести буквально»?Но это же совсем не то. Вся эта конструкция держится на доверии. На том, что прикладной программист всё сделает правильно. Правильно вставит, нигде не ошибётся, никто не полезет в таблицу вставлять/удалять грязными руками.
Фильтрацию по индексу одной таблицы и использование значений из другой без join-а не организуешь. Сумеет ли оптимизатор всегда распознать, что первую таблицу поднимать не надо?
Constraints уровня объединённой таблицы нетривиальны и остаются на совести разработчика.
Всё это детали реализации, которыми разработчика загружать не надо,
для него это должна быть одна таблица с общими индексами и общими constraints.
Этот вариант с MySQL напоминает обслуживание паровых машин до изобретения автоматического предохранительного клапана.
Колоночные СУБД против строчных, как насчет компромисса?