Отображение моделей данных NoSQL в объектные спецификации Н. А. Скворцов Институт проблем информатики РАН [email protected] RCDL’2012, Переславль-Залесский 15 октября 2012 г. План • Общие черты и разновидности моделей данных NoSQL • Отображение информационных ресурсов в моделях NoSQL в унифицирующую модель – Отображение модели «ключ-значение» • на примере Oracle NoSQL – Отображение модели с колоночным хранением • на примере Cassandra – Отображение документной модели • на примере MongoDB • Общие проблемы и подходы к отображению моделей разных классов NoSQL с выявлением структуры данных • Подходы к отображению моделей при невыявляемых схемах данных Основные принципы баз данных NoSQL • • • • • Технологии NoSQL направлены на горизонтальное масштабирование и доступ к данным сверхбольших объёмов Отказ от хранения данных по кортежам Организация данных с помощью Доступ к данным в узле только по ключам Горизонтальное масштабирование и репликация по узлам по хеш-значениям – распределённые хеш-таблицы (DHT) • Ограниченный язык манипулирования данными – CRUD (create/read/update/delete) • Сдвиг обработки с этапа запросов на этап обновления данных – Возможные разновидности запросов известны заранее – Материализованные взгляды под возможные запросы формируются на этапе обновления • Оптимизация обновлений и буферизация вставок – журнальная структура (log-structured) – eventual consistency – операции чтения и запросы используют уже имеющиеся данные, даже если эти данные ожидают обновления – BASE-транзакции (Basically Available, Soft state, Eventually consistent) Разновидности моделей NoSQL • Хранилища ключ-значение – Пары ключ-значение – Составные ключи – Произвольные значения • Базы данных с колоночным хранением – С ключом связан набор именованных колонок со значениями – Имитация табличной структуры на основ пар ключ-значение • Документные базы данных – Значения могут содержать вложенные наборы пар ключ-значение – Иерархические структуры на основе пар ключ-значение • Иногда (не в данной работе) к NoSQL относят также – хранилища триплетов (RDF) – графовые системы баз данных – объектно-ориентированные системы баз данных Необходимость отображения NoSQL в объектную модель • Решение задач над множественными информационными ресурсами – Задачи выражена в объектной модели • Разрешение модельной неоднородности между спецификациями задачи и ресурсов – Отображение моделей ресурсов в унифицирующую модель • Отображение моделей NoSQL в объектную модель не всегда очевидно – Наряду со структурированными данными могут присутствовать слабоструктурированные и неструктурированные – Схема базы чаще всего не имеет спецификации, а предполагается неявно – Схема может быть нефиксированной, изменяемой динамически, может содержать сложные переплетения экземпляров данных и структурных элементов Отображение модели «ключ-значение» На примере Oracle NoSQL СУБД «ключ/значение» Oracle NoSQL • Элемент хранения - пара – составной ключ: major keys и minor keys – произвольное неструктурированное значение • Ключ: “Smith/John/-/phonenumber/home” – Major key: список из 1 или более компонентов (“Smith/John”) – влияет на выбор узла хранения по хеш-значению; – Minor key: список из 0 или более компонентов (“phonenumber/home”) – все значения гарантированно на одном узле, вместе с marjor key определяет уникальный ключ • Значение: “555 5555“ – Значение – строка байтов – Может быть сериализацией любого набора данных, структурированных или неструктурированных, с любой семантикой Операции • Операции записи – put(key, value), putIfAbsent, putIfPresent, putIfVersion – delete(key), multiDelete • Subrange (keyFirst, keyLast) • Depth.CHILDREN_ONLY • Операции чтения – get(key) – multiGet • также MultiGetIterator, StoreIterator (по major key) • Subrange (keyFirst, keyLast) • Depth.CHILDREN_ONLY – multiGetKeys • Subrange (keyFirst, keyLast) • Depth.CHILDREN_ONLY Пример List<String> majorComponents = new ArrayList<String>(); List<String> minorComponents = new ArrayList<String>(); majorComponents.add("Smith"); majorComponents.add("Bob"); minorComponents.add("phonenumber"); minorComponents.add("home"); Key myKey = Key.createKey (majorComponents, minorComponents); String data = “555 5555"; Value myValue = Value.createValue (data.getBytes()); kvstore.put(myKey, myValue); ValueVersion vv = kvstore.get(myKey); Value v = vv.getValue(); String data = new String(v.getValue()); Фиксированные и нефиксированные ключи {“Smith/Bob/-/phonenumber/home”: “555 5555”} {“Smith/Bob/-/phonenumber/mobile”: “333 3333”} Smith └ Bob └ phonenumber ├ home: “555 5555” └ mobile: “333 3333” • Разделение на major key и minor key влияет только на физическую привязку данных к узлу – Оно не влияет на отображение в объектную модель • В любом месте составного ключа могут быть данные, а могут быть фиксированные имена в структурах данных – Фиксированные (“phonenumber”, “home”, “mobile”) – Нефиксированные (“Smith”, “Bob”) – Дочерние компоненты ключей (“home”, “mobile”) – связанные с единственным значением родительского компонента (“phonenumber”) Отображение структур Oracle NoSQL 1. 2. 3. 4. 5. 6. Связанные по структуре ключей пары отображаются в классы (Class1, Class2, …). Если компонент ключа имеет единственное значение в подобных ключах, то он становится именем класса. Для нефиксированные значений компонентов ключей создаются атрибуты класса (majorKey1, majorKey2, …, minorKey1, minorKey2, …). Тип значений атрибутов строковый. Возможно задание и преобразование типов. Для фиксированных значений компонентов создаётся атрибутом с именем, соответствующим значению компонента ключа. Для дочерних фиксированных компонентов ключей создаются абстрактные типы данных, одноимённый со значением родительского компонента, с атрибутами, соответствующими значениям дочернего компонента. Значение пары отображается в значение атрибута, соответствующего последнему фиксированному значению, у которого нет дочерних компонентов. Либо, если такового нет, то значение пары отображается в атрибут value. Типом значения атрибута является фрейм СИНТЕЗ. Возможно задание и преобразование типов. Если семантика и структура значения не известна, и разобрать его в виде фрейма невозможно, задаётся фрейм – значение типа битовой строки. Набор всех атрибутов типа, образованных компонентами ключей, указывается как уникальный. Если с атрибутом, соответствующим фиксированному значению компонента ключа, связано значение, соответствующее значению в паре, то включать его в набор излишне. Отображённый пример Smith └ Bob └ phonenumber ├ home: “555 5555” └ mobile: “333 3333” • majorKey1, majorKey2 определяют уникальное значение, так как home и mobile фиксированные значения компонента ключа { class1; in: class; instance_section: { majorKey1: String; majorKey2: String; phonenumber: Phonenumber; key: { unique; { majorKey1, majorKey2 } }; }} { Phonenumber; in: type; home: String; mobile: String; } Отображение операций: get majorComponents.add("Smith"); majorComponents.add("Bob"); minorComponents.add("phonenumber"); minorComponents.add("home"); Key myKey = Key.createKey (majorComponents, minorComponents1); ValueVersion vv = kvstore.get(myKey); Value v = vv.getValue(); q([v]) :class1([majorKey1, majorKey2, v : phonenumber.home]) & majorKey1=”Smith” & majorKey2=”Bob” Отображение операций: multiget majorComponents.add("Smith"); majorComponents.add("Bob"); Key myKey = Key.createKey(majorComponents); SortedMap<Key, ValueVersion> x = kvstore.multiGet(myKey); q(x) :class1(x/class1.inst) & x.majorKey1=”Smith” & x.majorKey2=”Bob” • В классе результата будет один объект – из нефиксированных значений компонентов ключа и значений пар будут сформированы все значения атрибутов в объектеэкземпляре класса class1. Отображение операций: multiget и KeyRange majorComponents.add("Smith"); Key myKey = Key.createKey(majorComponents); KeyRange kr = new KeyRange( "Bob", true, "Patricia", true); SortedMap<Key, ValueVersion> x = kvstore.multiget(myKey, kr); q(x) :class1(x/class1.inst) & x.majorKey1=”Smith” & x.majorKey2>=”Bob” & x.majorKey2<=”Patricia” Отображение операций: multiGetKeys majorComponents.add("Smith"); Key myKey = Key.createKey(majorComponents); SortedSet<Key> k = kvstore.multiGetKeys(myKey); q([majorKey1, majorKey2]) :class1([majorKey1, majorKey2]) & majorKey1=”Smith” • Так как у фиксированных ключей нет дочерних нефиксированных, то в результат они не включаются, хотя операция их возвращает – В языке запросов должны быть средства запросов к схеме Вспомогательные пары • Данные дублируются с другой структуризацией для возможности поиска по другим критериям { “Smith/Bob/-/phonenumber/home”: “555 5555” } { “555 5555”: [”Smith”, ”Bob”] } majorComponents.add("555 5555"); Key myKey = Key.createKey (majorComponents); ValueVersion vv = kvstore.get(myKey); q([majorKey1, majorKey2]) :class1([majorKey1, majorKey2, v : phonenumber.home]) & v=”555 5555” Неструктурируемые пары {“majorkey1/…/majorkeyM/-/ minorkey1/…/ minorkeyN/” : “a value”} { db; in: class; instance_section: { minorKey: {sequence; type_of_element: String}; majorKey: {sequence; type_of_element: String}; value: Bitstring; key: { unique; { minorKey, majorKey } }; }} Отображение модели с колоночным хранением На примере Cassandra Основные термины Cassandra • Column (колонка) – множество пар ключзначение (key-value) • Column Family (семейство колонок) – содержит множество колонок, у каждой из которых есть название, значение, и временная метка, и на которые ссылаются с помощью ключей строк • Keyspace (пространство ключей) – содержит набор семейств колонок • SuperColumn (суперколонки) – колонки, состоящие из набора подколонок Колонки и суперколонки Операции чтения • get(): извлечь по имени колонки • multiGet(): по имени колонки для множества ключей • getSlice(): по имени колонки или ряду имён – возвращаются колонки – возвращаются суперколонки • multiGetSlice(): ряд колонок для множества ключей • getCount(): количество колонок или суперколонок • getRangeSlice(): ряд колонок для диапазона ключей Операции записи • insert(): добавить/обновить колонку (по ключу) • batchInsert(): добавить/обновить множество колонок (по ключу) • remove(): удалить колонку • batchMutate(): как batchInsert(), но может и удалять Отображение 1. 2. 3. 4. 5. Семейство колонок отображается в класс, ключ семейства становится атрибутом id, с ним связано свойство уникальности. Если имена колонок фиксированные (в именах не данные, а названия, одни и те же для всех ключей), то имена колонок отображаются в одноимённые атрибуты. Значения колонок, связанные с одним значением ключа отображаются в значения атрибутов у объекта с id, соответствующим значению ключа семейства. Если имена колонок нефиксированные (являются данными), то для пар имя-значение создаётся абстрактный тип данных с двумя атрибутами, а семейство колонок отображается в класс с атрибутом id и атрибутом, значением которого является лист значений созданного типа. Ключи, имена колонок и значения вспомогательных семейств (материализованных взглядов на основные семейства) отображаются в атрибуты уже существующего класса, если все колонки присутствуют в основных семействах Подколонки отображаются в отдельный абстрактный тип данных. Пример users: { "1": { "firstName": "John", "lastName": "Smith", "phoneNumbers": { "home": "555 5555", "mobile": "333 3333"} }, "2": … } { users; in: class; instance_section: { id: String; firstName: String; lastName: String; phoneNumbers: PhoneNumbers; key: { unique; id }; }} { PhoneNumbers; in: type; home: String; mobile: String; } get({“firstName”, “lastName”}, "1“) q([firstName, lastName]) :users(x/[id, firstName, lastName]) & x.id="1" Особенные случаи • Вспомогательные колонки { “555 5555": { "firstName": "John", "lastName": "Smith", } • • Отображается в уже созданную структуру Данные в именах колонок Неограниченное количество колонок { “1": { "home": “555 5555”, “mobile”: “333 3333”, “in S.-Petersburg": “222 2222", …} } id: String; phonenumber: {sequence; type_of_element: PhoneNumber}; { Phonenumber; in: type; home: String; mobile: String; } Отображение документной модели На примере MongoDB Отображение документальной модели • Вложенные структуры пар ключ-значение (JSON) users: { "firstName": "John", "lastName": “Smith", "phoneNumbers": { "home": "555 5555", “mobile": "333 3333” } } • Вторичные индексы db.things.ensureIndex({'lastName':1}); db.users.find({'lastName': 'Smith'}, {'phoneNumbers.home':1}); • { users; in: class; instance_section: { firstName: String; lastName: String; phoneNumbers: PhoneNumbers; }} { PhoneNumbers; in: type; home: String; mobile: String; } Неограниченно вложенные иерархии отображаются в базу фреймов q([v]) :- users([lastName, v : phonenumber.home) & lastName="Smith« Общие проблемы и решения • Значения ключей могут быть фиксированными (имена атрибутов) или нефиксированными (данные) – • Это определяет эксперт В случае выявляемой схемы данных c фиксированные ключи отображаются в атрибуты типов, нефиксированные – в значения атрибутов Слабая структурированность моделей NoSQL – – – не определяется схема данных в явном виде значения в парах «ключ-значение» могут быть произвольными и неструктурированными при отсутствии схемы структура пар может изменяться динамически • – нет ограничений по длине составных ключей или по вложенности пар «ключ-значение» (в случае документных баз) • • системы могут использоваться для решения задач, изначально рассчитанных на использование нефиксированных и неограниченных структур (например, иерархических) При возможности выявляения схемы это необходимо делать в соответствии с семантикой данных В этих случаях используется подход к отображению моделей данных, предполагающий отображение только языка манипулирования данными Возможные разновидности запросов, используемых при решении задач над данным должны быть известны заранее – – – • с помощью операции put (или Create) можно в любой момент ввести новую структуру пар «ключ-значение» Данные, по которым необходимо организовать поиск, должны оказаться ключами в некоторых парах «ключ-значение», а искомые данные – значениями Для этого могут формироваться вспомогательные пары, дублирующие данные в разной организации ключей и значений Материализованные взгляды в этих парах формируются на этапе обновления Вспомогательные пары при отображении не образуют новые классы, а отображаются в типы и классы, образованные основными парами • В случае отображения в реляционную модель в соответствии с дополнительными парами образуются вторичные ключи Разрешены запросы со сравнением по атрибутам, являющимся ключами в дополнительных парах, при обратном отображении они отображаются в операции над дополнительными парами Если схема не выявляется Данные (ключи и значения) отображаются во фреймы (в слоты, значения слотов, вложенные фреймы, с учётом разбора составных ключей и значений) Запросы производятся к базе фреймов и переписываются в операции