ВВЕДЕНИЕ.............................................................................................................. 4 Глава 1. Обзор NoSQL систем........................................................................... 6 1.1. Термин NoSQL .............................................................................................. 6 1.2. Сравнение существующих систем............................................................. 8 1.2.1. Масштабируемость ................................................................................... 8 1.2.2. Модель данных .......................................................................................... 8 1.2.3. Система хранения данных ....................................................................... 9 Глава 2. Описание системы............................................................................. 11 2.1. Компоненты системы ................................................................................ 14 2.1.1. Server .......................................................................................................... 14 2.1.2. ServerKnot ................................................................................................. 14 2.1.3. Worker ........................................................................................................ 15 2.1.4. Configuration Util ..................................................................................... 15 2.1.5. DataCountUtil. ........................................................................................... 17 2.1.6. DataLayerUtil ............................................................................................ 18 2.2. Хранение данных. ....................................................................................... 18 2.2.1. Документно-ориентированное хранилище ........................................ 18 2.2.2. XML – формат хранения данных ......................................................... 19 2.2.3. Идентификация данных ......................................................................... 20 2.2.4. Интерфейс взаимодействия с хранилищем данных ......................... 20 2.3. Взаимодействие с данными. ..................................................................... 21 2.3.1. Выборка ..................................................................................................... 21 2.3.2. Добавление ................................................................................................ 22 2 2.3.3. Обновление ............................................................................................... 23 2.3.4. Удаление .................................................................................................... 25 2.3.5. Транзакции ............................................................................................... 26 2.3.5.1 ReadCommitied ...................................................................................... 28 2.3.5.2 RepeatableCommited ............................................................................. 29 2.3.6. Полнотекстовый поиск .......................................................................... 30 2.3.6.1 Системы полнотекстового поиска .................................................... 30 2.3.6.2 Xapian как хранилище данных.......................................................... 33 2.3.7. Интеграция ............................................................................................... 33 Заключение ............................................................................................................ 35 СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ. ............................................ 37 Приложение 1. Модели данных. ........................................................................ 40 Приложение 2. Системы хранения данных. ................................................... 41 Приложение 3. Схема алгоритма MapReduce. ............................................... 42 Приложение 4. Характеристики поисковых решений. ................................ 43 3 ВВЕДЕНИЕ На сегодняшний день часто возникают задачи, связанные с хранением и обработкой плохо структурированных данных. Иногда необходимые данные являются простым текстом, который содержится, например, в объявлении, а значит, требуется полнотекстовый поиск для работы с такими данными. Ключевыми критериями решения этой проблемы являются гибкость структуры хранимых объектов и быстрый релевантный поиск. Недостатки жесткой структуры данных, связанные с общими проблемами классификации объектов, в частности с классической категоризацией. Классическая кластеризация подразумевает, что в качестве критерия похожести объектов используется родственность их свойств, в частности объекты можно разбивать на непересекающиеся множества, в зависимости от наличия или отсутствия некоторого свойства. Однако это порождает проблему. Какие свойства считать существенными, а какие не принимать во внимание при классифицировании. В природе не существует универсальной классификации, которая подходила бы к любым задачам и условиям. Сейчас скорость изменения данных возрастает с каждым годом. Можно сказать, что на первое место выходит индивидуальность и часто индивидуальные признаки становятся важнее, чем обобщенные. Поэтому все больше проявляется «коренной порок» классической категоризации, который заключается в том, что практически невозможно выделить определяющие свойства естественной категории так, чтобы не было исключений. Попытки обойти недостатки классической категоризации привели к понятиям концептуальной кластеризации и теории прототипов. В подходе концептуальной кластеризации объект может принадлежать к нескольким категориям одновременно с разной степенью точности. Теория прототипов подразумевает существование абстракций, которые не имеют ни четких свойств, ни четкого определения. В теории прототипов классификация 4 объектов производится по степени их сходства с конкретным прототипом. NoSQL подход рассматривает объекты и данные с точки зрения концептуальной кластеризации или теории прототипов. [20] Постановка задачи. Цель данной работы заключается в построении системы на базе распределённой платформы, для обработки данных, которая позволяет выполнять основные операции с данными (поиск, добавление, обновление и удаление), поддерживает транзакционность операций, работает с гибкой структурой данных, легко масштабируется. Также требуется обеспечить интеграцию полнотекстового поиска в системе. Система функционирует в пределах локальной сети и рассчитана на малые и средние предприятия (до 100 компьютеров). 5 Глава 1. Обзор NoSQL систем 1.1. Термин NoSQL Существует несколько способов организации данных. Традиционным является реляционный подход, кратко его особенности можно описать следующим образом: 1)данные хранятся в таблицах, состоящих из столбцов и строк; 2)на пересечении каждого столбца и строчки стоит в точности одно значение; 3)у каждого столбца есть своё имя, которое служит его названием, и все значения в одном столбце имеют один тип; 4)столбцы располагаются в определённом порядке, который определяется при создании таблицы, в отличие от строк, которые располагаются в произвольном порядке. В таблице может не быть ни одной строчки, но обязательно должен быть хотя бы один столбец; 5)запросы к базе данных возвращают результат в виде таблиц, которые тоже могут выступать как объект запросов. Реляционная модель имеет ряд неоспоримых достоинств, однако она жёстко определяет структуру записей, поэтому плохо реагирует на изменение специфики данных. Корректное хранение документов, формат которых изменяется со временем, довольно непросто реализовывается при таком подходе. К тому же реляционные СУБД плохо масштабируются, репликации не реализуют этот аспект в полной мере. Что касается полнотекстового поиска, то здесь реляционные СУБД существенно проигрывают, а некоторые из них просто не поддерживают такую функциональность. Поиск новых способов хранения и обработки данных привёл к формированию другой концепции, известной как NoSQL (Not only SQL). 6 Под термином NoSQL скрывается большое количество продуктов с абсолютно разными архитектурами и иногда при обсуждении разговор может идти о разных системах, но все они имеют общие черты: 1)масштабируемость (без копирования данных); 2)использование гибкой, не ограниченной схемой структуры базы данных. Все NoSQL базы данных имеют свой собственный синтаксис запросов и некоторые из них частично поддерживают SQL. Зачастую такие базы имеют некоторый предел количества хранимых данных, после превышения которого наблюдается резкое падение производительности. Однако масштабируемость в большей степени покрывает этот недостаток. Огромное множество библиотек для доступа к объектам нереляционных баз данных не имеет единства. А универсальный интерфейс для построения полноценной масштабируемой системы есть далеко не у каждой из них. На данный момент существует более 120 NoSQL баз, и с каждым годом их количество увеличивается [16]. 7 1.2. Сравнение существующих систем Основными критериями сравнения NoSQL баз данных являются уровень масштабируемости, модель данных и система хранения данных. 1.2.1. Масштабируемость Под масштабируемостью подразумевается увеличение производительности за счет распределения данных между несколькими серверами. Такие системы называются распределенными базами данных. В них входят Cassandra, HBase, Riak, Scalaris и Voldemort. Это возможный вариант, если используется объем данных, который не может быть обработан на одной машине. Существует две важные возможности в таких базах данных: поддержка нескольких датацентров и возможность добавления новых машин в работающий кластер прозрачно для ваших приложений. Не распределенные базы данных включают в себя CouchDB, MongoDB, Neo4j, Redis и Tokyo Cabinet. Данные системы могут служить прослойкой для хранения данных для распределенных систем; MongoDB предоставляет ограниченную поддержку горизонтального масштабирования, так же как и отдельный проект Lounge для CouchDB, и Tokyo Cabinet может использоваться как система хранения файлов для Voldemort. 1.2.2. Модель данных Основными моделями данных в NoSQL системах являются: 1) ключ-значение: самая простая форма, как для организации данных, так и для реализации. Каждый объект данных имеет уникальный ключ, данные произвольны, это может быть просто число или текст, а может быть серялизованный объект. К недостаткам относятся проблематичность реализации сложных структур поверх такого хранилища и сравнительно 8 небольшая производительность, если используются только запросы на выборку и обновление части данных. 2) документно-ориентированная: каждая запись хранится как отдельный документ, имеющий собственный набор полей, который может отличаться в различных документах; 3) колоночно-ориентированная: данные хранятся в столбцах вместо хранения в строках без жёсткой типизации колонки, количество строк не велико: каждая строка имеет больше или меньше столбцов, в зависимости от необходимости (используется в Cassandra и HBase); 4)графовая (или семантическая сеть): объекты и связи между ними хранятся в качестве узлов и помеченных ролями ребер графа. Для запросов, которые соответствуют этой модели (например, иерархических данных), они могут быть в тысячу раз быстрее, чем альтернативные варианты (Neo4J) [5]. (см. приложение 1) 1.2.3. Система хранения данных Под системой хранения данных подразумевается вид, в котором данные представлены в системе. Во многом от системы хранения данных зависит, какие нагрузки база может нормально выдерживать. Системы, хранящие данные в памяти, очень быстрые (некоторые могут выполнять до 100,000 операций в секунду), но не могут работать с данными превышающими размер доступной оперативной памяти. Сохранность таких данных при сбое или отключении питания может стать проблемой (хотя в новых версиях обещается поддержка append-only log). Количество данных, которые могут ожидать записи на диск потенциально велико. Некоторые системы, решают данную проблему с помощью репликации, но не 9 поддерживают масштабирование на несколько датацентров., так что потеря данных вероятна и тут - в случае отключения питания. Memtables / SSTables (Sorted String Table) буферизируют запросы на запись в памяти (Memtable), а после записи добавляют в лог [19]. После накопления достаточного количества записей, Memtable сортируется и записывается на диск, уже как SSTable. Это дает производительность близкую к производительности памяти, в тоже время система лишена проблем актуальных при хранении только в памяти. B-деревья используются в базах данных уже очень давно. Они обеспечивают надежную поддержку индексирования, однако производительность, при использовании на машинах с магнитными жесткими дисками (которые попрежнему наиболее экономически эффективны), далеко не самая высокая, так как происходит большое количество позиционирований головки при записи или чтении данных. Интересным вариантом является использование append-only B-Tree бинарное дерево, которое не нужно перестраивать при добавлении элементов, что позволяет получить неплохую производительность при записи данных на диск (см. приложение 2). 10 Глава 2. Описание системы. Данный программный продукт позволяет хранить xml документы на нескольких машинах локальной сети, выполнять поисковые запросы с условиями, являющимися XPath выражениями или текстовыми фразами. Также предусмотрена возможность хранения и изменения метаданных для документов и включения их в условия поиска. Позволяет добавлять новые документы (с возможностью указать метаданные), удалять и модифицировать документы. Также реализован механизм транзакций. Все запросы выполняются асинхронно. Предусмотрена возможность конфигурирования системы. Система имеет синтаксис запросов, отличный от SQL. Также реализован консольный клиент для работы с системой. Система может встраиваться как библиотека (managed dll) в приложение. Система реализована на C# +.Net Framework, в качестве распределённой платформы используется библиотека CCP (автор Меньшов А. В.). Архитектура системы основана на использовании Map Reduce. Программная модель MapReduce была придумана в компании Google, и там же была выполнена первая реализация этой модели на основе распределенной файловой системы той же компании GFS (Google File System) [6]. Сам алгоритм, по сути, прост. Есть огромное количество объектов, из него требуется получить некий результат. Это может быть некоторое подмножество, или вычисленное значение на этих объектах. Делается следующее. Исходное множество разбивается на несколько частей. Над каждой частью работает некоторая функция (Map), которая на выходе выдаёт множество пар ключ-значение, далее часть пар, имеющая одинаковый ключ отдаётся другой функции (Reduce) которая совершает преобразование значений и выдаёт конечный результат. Такой подход легко масштабируется, поскольку Map-ы не зависят друг от друга, как и Reduce-ы и является 11 универсальным, нам достаточно определить реализацию Map и Reduce для данной задачи. Каноническим примером MapReduce является задача подсчёта различных слов в предложении. Map просто создаёт пары (<слово>, 1), где ключом, очевидно, является слово, а Reduce объединяет пары (суммирует единички при одинаковых ключах) и выдаёт множество пар вида (<слово>, m), где m и есть искомое число вхождений [7]. (Схему MapReduce см. в приложении 3) В системе объекты Map и Reduce представляются вспомогательными объектами класса Worker (подробнее смотри 1.3) При поступлении запроса на Server он делегирует выполнение всем своим узлам (ServerKnot). ServerKnot, в свою очередь, делит запрос на более простые и отправляет их Worker’ам. Обращаясь к DataLayer, Worker формирует список идентификаторов документов, удовлетворяющих запросу, и возвращают его ServerKnot. ServerKnot выполняет окончательную обработку результата и возвращает его на Server. Server анализирует результаты, полученные с каждого ServerKnot, и отправляет окончательный ответ. 12 Server ServerKnot Request PartRequest Worker PartRequest Response PartRequest Worker Worker Worker DataLayer Xapian 13 2.1. Компоненты системы 2.1.1. Server Этот класс отвечает за управление узлами ServerKnot в системе, он владеет адресами всех ServerKnot. На уровне Server обработка запросов заключается в их рассылке объектам ServerKnot и сортировке и объединении результатов, полученных из ответов от этих узлов. Server всегда точно может определить, какому исходному запросу соответствует текущий ответ от ServerKnot. Все запросы, посланные к системе, проходят через Server. 2.1.2. ServerKnot Собственно, узел системы. Этот класс инициирует выполнение запроса на данном узле. ServerKnot владеет информацией об адресе Server. Также, только Server напрямую отправляет запросы для этого класса. ServerKnot известны адреса всех Worker, действующих на этом же узле. Обработка запроса на этом уровне отличается от предыдущего уровня, хотя она также асинхронная. ServerKnot уже имеет доступ к данным через интерфейс IDataLayer, но его функция заключается в том, чтобы разбить запрос на более простые части, определить Map-ы и Reduce среди Worker-ов, разослать им полученные части, затем, получить конечный результат от одного из Worker и обработать его в зависимости от исходного запроса или содержания ответа. 14 2.1.3. Worker Объекты этого класса обмениваются запросами и ответами как с управляющим ими ServerKnot, так и между собой. Worker, который был назначен в качестве Reduce, собирает все результаты с других Worker-ов на данном узле и создаёт из них общий результат запроса, который отправляется на ServerKnot. Все Worker-ы так же имеют доступ к IDataLayer. 2.1.4. Configuration Util Для системы предоставлен конфигурационный файл, содержащий всю необходимую информацию для разворачивания системы. Для файла выбран JSON формат, поскольку он достаточно легко воспринимается визуально [8]. Есть 2 конфигурационных файла: серверный и клиентский. Серверный содержит: имя узла (“NodeName”) имя главного процесса, в котором работает сервер (“TaskName”) имя самого сервера (“Name”) хост для управляющего объекта Server (“Host”) массив из конфигураций для ServerKnot (“ServerKnots”) Конфигурация для ServerKnot содержит те же данные, что и конфигурация для Server, за исключением 4-го пункта, разумеется. И так же содержит: * номер узла (“Number”) 15 * порт (“Port”) * определение хранилища данных (“Database”) * максимальное количество документов на узле (“MaxDBCount”) * максимальное количество документов в кэше (“MaxCacheCount”) Конфигурация клиента содержит те же поля, что и конфигурация для Server, кроме 4-го пункта. Структура конфигурации сервера с единственным узлом, на которых хранятся данные, будет выглядеть следующим образом: { "Server":{ "NodeName": …, "Host": …, "TaskName": …, "Name": …, "ServerKnots":[{ "NodeName": …, "Host": …, "TaskName": …, "Name": …, "Number": …, "Port": …, 16 "Database": …, "MaxDBCount»: …, "MaxCacheCount": … }] } Класс ConfigurationUtil использует этот файл для заполнения конфигурации системы и предоставляет эти данные соответствующим объектам на этапе инстанцирования. Особенности: 1)Желательно разные хосты для всех ServerKnots, допускается размещение Server и одного из ServerKnot на одном хосте. 2)Все имена должны быть уникальными 3)На поле Number отведено 2 байта. 2.1.5. DataCountUtil. Утилитарный класс, который ведёт учёт общего количества документов в системе и на каждом её узле в частности. Также учитывает количество новых документов, которое появится после коммитов всех транзакций на узле и закладывает его в расчёте свободного места на узле. (Подробнее смотри 3.5 Транзакции). 17 2.1.6. DataLayerUtil IDataLayer – интерфейс для работы с данными. Его реализует класс DataLayer инкапсулирующий зависимость от хранилища данных. На каждом узле ServerKnot находится только один экземпляр IDataLayer. DataLayer реализован как потокобезопасный, поскольку с одним и тем же экземпляром работает ServerKnot и все Worker-ы, которыми он управляет. Именно DataLayer реализовывает кэширование данных, которое используются при выборке. Также на этом уровне создаются и выполняются транзакции (подробнее 3.5) и запросы (глава 3). Некоторые методы интерфейса: bool CreateTranzaction(…) void CommitTranzaction(…) void RollbackTranzaction(…) void MakeInsert(…) void MakeUpdate(…) void MakeDelete(…) 2.2. Хранение данных. 2.2.1. Документно-ориентированное хранилище В реализации я выбрала именно такой тип хранилища по нескольким причинам. Во-первых, эта структура проста, есть только документ и его идентификатор. Документы с одной стороны не зависимы, с другой стороны 18 достаточно легко установить между ними связи. В частности, в документе можно указать идентификаторы тех документов, на которые он ссылается. Независимость документов облегчает их распределение между узлами системы. В то же время документ является цельным объектом, содержащим всю информацию в одном месте. 2.2.2. XML – формат хранения данных Данные, лишённые всякой структуры, обычно плохо воспринимаются и неудобны для анализа, но в то же время целью является достижение максимальной гибкости в структуре данных. Поэтому был выбран XMLформат для представления документа. XML – это язык разметки документов, позволяющий структурировать информацию разного типа, используя для этого произвольный набор инструкций (тэгов) [10]. Язык XML Path (XPath) является набором синтаксических и семантических правил для ссылок на части XML-документов. Выражения XPath идентифицируют набор узлов в XML-документе. Этот набор узлов содержит ноль или более узлов. Допустимые выражения XPath могут включать в себя предикаты. Предикаты содержат логические выражения, которые проверяются для каждого узла в контексте набора узлов. Если истина, узел сохраняется в наборе идентифицируемых узлов; иначе, узел отбрасывается. Выражения XPath могут ссылаться на атрибуты так же, как и на элементы (тэги) в XMLдокументе. При ссылке на атрибут используется символ @ [9]. .Net Framework предоставляет внутренние средства для работы с XMLдокументами и выполнением XPath-запросов (пространства имён System.Xml и System.Xml.Xpath) [17]. Помимо этого XPath в системе используется как условие поиска документов. 19 2.2.3. Идентификация данных Поскольку все документы располагаются на нескольких узлах, требуется идентификатор, по которому можно точно найти документ в системе. Идентификатор представляет собой значение из 6 байт. Первые 2 байта – номер узла, на котором находится документ, остальное – номер документа внутри узла. Полный идентификатор из 6 байт используется только для поиска ссылающихся документов и документов по ссылкам. В пределах каждого ServerKnot и всех его Worker-ов для обработки данных используются только локальные идентификаторы. 2.2.4. Интерфейс взаимодействия с хранилищем данных Реализация IDataLayer (DataLayer) работает с конкретным хранилищем данных через интерфейс IDatabase. IDatabase позволяет осуществить только атомарные запросы на выборку, добавление, обновление или удаление данных. Именно на этом уровне происходит фактическое соединение с хранилищем данных, поэтому IDatabase содержит фабричный метод IDatabase Open(string dbpath) который предоставляет доступ к хранилищу данных на узле. Некоторые методы из интерфейса IDatabase: uint AddDocument(string doc) void AddDocuments(IList<string> docs) void DeleteDocuments(IList<uint> ids) 20 void UpdateDocuments(IList<uint> ids, IList<IUpdation> updations) IList<uint> Search(ICondition condition) uint GetDocCount() 2.3. Взаимодействие с данными. Фактическое выполнение всех операций происходит на уровне IDataLayer. Изменения в хранилище данных вносятся на уровне IDatabase. 2.3.1. Выборка Интерфейс ICondition: public interface ICondition { bool IsSatisfied(string doc); } позволяет определить соответствует ли документ данному условию. Одной из реализаций является класс XPathCondition, фактическое условие для которого – строка XPath [9]. Запрос на выборку содержит список условий и/или включает стартовый и конечный идентификатор документа. В этом случае однозначно определяется множество узлов, на которых могут содержаться искомые данные, а также стартовые идентификаторы и количества запрашиваемых документов. Таким образом, список рассылки подзапросов может сузиться, что ускорит выполнение запроса. 21 Когда запрос на выборку приходит на ServerKnot для каждого ICondition в списке создаётся свой подзапрос для Worker (Map). Worker-ы работают только с идентификаторами документов. Worker, назначенный как Reduce, выбирает общие идентификаторы из полученных результатов и возвращает их ServerKnot-у. ServerKnot по идентификаторам получает сами документы и возвращает их на Server, который, в свою очередь, отправляет ответ. Пример кода, отправляющего запрос: Message select = new SelectRequest(ClientXPath, new XPathSelectStrategy(MainXPath)); IList<ICondition> xpathCond = new List<ICondition>(); xpathCond.Add(new XPathCondition("/object[@name=\"aaa\"]")); xpathCond.Add(new XPathCondition("/object[@role]")); select[Parameters.SessionKey] = SessionKey; select[Parameters.Conditions] = xpathCond; RootNode.Router.Send(select); 2.3.2. Добавление Количество документов на каждом ServerKnot ограничено, поэтому перед добавлением нового документа, Server получает из DataCountUtil информацию о возможности добавить документы в систему. Новые документы будут добавлены на самый «свободный» узел, либо в ответ придёт требование добавить новый узел в систему, если ни на одном из текущих в данный момент нет места. Добавление документа осуществляется на 22 ServerKnot. При этом узел обновляет информацию о свободном месте в DataCountUtil, отнимая количество добавленных записей. Пример кода, отправляющего запрос: Message insert = new InsertRequest(ClientXPath, new XPathSelectStrategy(MainXPath)); insert[Parameters.SessionKey] = SessionKey; insert[Parameters.NewData] = new string[] { "<object name=\"aaa\" target=\"bbb\" role=\"ccc\" reference=\"/object[@id=1]\"/>" }; RootNode.Router.Send(insert); 2.3.3. Обновление Обновление представляется как выборка данных с последующим изменением. Интерфейс IUpdation: public interface IUpdation { bool Update(ref string oldDoc); } позволяет обновить данные в документе. Метод Update возвращает true, если обновление завершилось корректно и перезаписывает переданный документ. Если в процессе обновления произошла ошибка, метод вернёт false и документ не изменится. Обновлениями в XML-документе служат обновление узлов и обновление атрибутов. Для каждого вида обновления документа определён класс, реализующий интерфейс IUpdate. Например, класс AddAttributeXmlUpdation 23 позволяет добавить атрибут в уже существующий узел документа, XPath к которому указывается при инстанцировании экземпляра класса. В этом случае, если узел, в который нужно добавить атрибут, не существует, метод Update вернёт false и исходный документ не изменится. Подобным образом ведут себя классы DeleteAttributeXmlUpdation, UpdateAttributeXmlUpdation, AddNodeXmlUpdation, UpdateNodeXmlUpdation, DeleteNodeXmlUpdation. Запрос на обновление содержит все те же параметры, что и запрос на выборку. К ним добавляется список IUpdation – те изменения, которые нужно применить к выбранным документам. Если хотя бы одно из обновлений в списке не применилось к документу, никакое другое не будет применяться и документ останется неизмененным. При получении запроса на обновление ServerKnot выполняет поиск, по окончании которого обновляются выбранные документы. Ошибки, которые возникают при обновлении, отправляются в ответе. Пример кода, отправляющего запрос: Message update = new UpdateRequest(ClientXPath, new XPathSelectStrategy(MainXPath)); IList<ICondition> xpathCond = new List<ICondition>(); xpathCond.Add(new XPathCondition("/object[@name=\"aaa\"]")); xpathCond.Add(new XPathCondition("/object[@role]")); IList<IUpdation> xpathUpd = new List<IUpdation>(); xpathUpd.Add(new AddAttributeXmlUpdation("/object", new KeyValuePair<string, object>("range", 1))); 24 xpathUpd.Add(new AddXmlNodeUpdation("/object","<body />")); update[Parameters.SessionKey] = SessionKey; update[Parameters.Conditions] = xpathCond; update[Parameters.Updations] = xpathUpd; RootNode.Router.Send(update); 2.3.4. Удаление Запрос на удаление содержит все те же параметры, что и запрос на выборку. Выполняется подобно обновлению: сначала выборка, потом удаление выбранных документов. Пример кода, отправляющего запрос: Message delete = new DeleteRequest(ClientXPath, new XPathSelectStrategy(MainXPath)); IList<ICondition> xpathCond = new List<ICondition>(); xpathCond.Add(new XPathCondition("/object[@name=\"aaa\"]")); xpathCond.Add(new XPathCondition("/object[@role]")); delete[Parameters.SessionKey] = SessionKey; delete[Parameters.Conditions] = xpathCond; RootNode.Router.Send(delete); 25 2.3.5. Транзакции Основной смысл понятия транзакции заключается в завершенности. То есть это набор действий, которые должны быть либо полностью выполнены, либо отменены совсем. В основу систем транзакций положено 4 принципа ACID (atomicity элементарность, consistency последовательность, isolation изолированность, durability устойчивость) [11]. Элементарность. Этот принцип требует, чтобы транзакция была либо выполнена, либо отменена (аварийно прекращена). В любом из этих двух случаев данные должны быть правильно сохранены в конечном состоянии, либо в начальном в случае аварии. Последовательность. Этот принцип основан на логичности. То есть транзакция должна быть максимально простой и давать предсказуемый результат. Реализация этого принципа ложится на инициатора транзакции. Изолированность. При рассмотрении транзакции возникает проблема промежуточного состояния, то есть того промежутка времени, в течение которого данные изменились, но транзакция ещё не завершена и не подтверждена. Таким образом, транзакции, работающие с одними и теми же данными, должны быть изолированными друг от друга. Устойчивость. Этот принцип подразумевает, что при прерывании транзакции нет возможности потерять данные. Сюда входит, в том числе и надежность оборудования. Элементарность реализуется запросами BeginTranzaction, CommitTranzaction и RollbackTranzaction. BeginTranzaction – запрос, инициирующий транзакцию, в ответ на него приходит идентификатор новой транзакции. Далее в каждый запрос, помещается дополнительный параметр (TranzactionID) - полученный идентификатор транзакции. Этот параметр позволяет выполнять запросы в 26 контексте определённой транзакции. Запросы CommitTranzaction и RollbackTranzaction также содержат идентификатор транзакции, только commit записывает сделанные изменения в хранилище перед закрытием транзакции, а rollback просто закрывает транзакцию без внесения изменений. Есть несколько уровней изоляции транзакций: ReadUncommited – изменения, сделанные одной транзакцией, становятся доступными для других транзакций до подтверждения данной транзакции. ReadCommited транзакция – изменения, сделанные одной транзакцией, доступны другим транзакциям только после подтверждения данной. Таким образом, при выполнении повторной выборки внутри транзакции может получиться другой результат, если другая транзакция зафиксировала новые изменения, удовлетворяющие выборке. RepeatableRead транзакция – внутри транзакции один и тот же запрос на выборку возвращает результат, не зависящий от изменений существующих данных, зафиксированных другими транзакциями. Однако, если другая транзакция, зафиксировала новые данные, удовлетворяющие выборке, то эти данные также будут доступны при повторном чтении. Serializable транзакция – требует того, чтобы конечный эффект от параллельного выполнения был таким, как при последовательном выполнении транзакций, при этом порядок их следования фактически не важен [12]. В своей реализации я отказалась от ReadUncommited транзакций, как редко используемых. А уровень RepeatableRead исключает фантомное чтение [18] и поддерживает версионность [15]. То есть при попытке применения изменений к документу, состояние которого при первом чтении не соответствует состоянию при подтверждении транзакции, выдаётся ошибка о 27 несоответствии версий. В случае если документ был удалён из хранилища, его обновление игнорируется. Транзакции реализуют интерфейс ITranzaction, позволяющий добавить, обновить или удалить документы в контексте данной транзакции. 2.3.5.1 ReadCommitied Транзакция ведет учет добавленных документов, без их идентификаторов. Также сохраняет обновленные документы, вместе с идентификатором и идентификаторы, удаленных документов. При чтении транзакция выбирает документы из хранилища в его текущем состоянии. Также она выбирает документы из своего списка обновленных, исключая удаленные документы. Среди документов из хранилища отбрасываются те, которые содержатся в списке обновленных и удаленных. К ним добавляются выбранные из списка обновленных. Это и составляет результат выборки. При добавлении документов они просто сохраняются внутри транзакции. Но транзакция обновляет состояние узла в DataCountUtil, резервируя место под возможное добавление документов. При обновлении документов их новое состояние сохраняется внутри транзакции. И при удалении документов их идентификаторы также сохраняются внутри транзакции. При удалении документов обновляется список обновленных, если среди них есть документы, подлежащие удалению. При подтверждении транзакции сначала происходит удаление документов, затем обновление и, наконец, добавление новых. 28 2.3.5.2 RepeatableCommited RepeatableRead также сохраняет обновленные документы и идентификаторы удаленных. Для добавленных документов определяются временные идентификаторы, которые используются внутри транзакции. Результат при первом чтении для каждого нового запроса сохраняется внутри транзакции и используется для последующего чтения по этому же запросу. Этот результат формируется из списка документов, полученных из хранилища, и списка обновленных документов, исключая удаленные документы. Список добавленных документов модифицируется, если на момент первого чтения в хранилище уже присутствуют документы с такими же идентификаторами, в этом случае для добавленных документов они пересчитываются. Это может происходить всего один раз для каждого нового запроса. Добавленные документы также присоединяются к общему результату. При добавлении нового документа в контексте транзакции его временный идентификатор считается как инкремент последнего идентификатора хранилища. При обновлении документа его новое состояние сохраняется внутри транзакции. При обновлении документа добавленного в контексте транзакции его состояние в списке добавленных документов перезаписывается. При подтверждении транзакции он будет считаться только как новый. При удалении обновляется список добавленных и список обновленных, если среди них есть документы, подлежащие удалению. Таким образом, транзакция внутри себя не содержит лишних данных. При подтверждении транзакции сначала добавляются все новые документы, затем происходит обновление документов и удаление. Добавленные документы, скорее всего, будут иметь идентификаторы, отличные от временных, поэтому при удалении в контексте транзакции до ее подтверждения документы, находящиеся в списке добавленных, удаляются сразу. Аналогично и с обновлением. 29 2.3.6. Полнотекстовый поиск Полнотекстовый поиск - поиск документа в базе данных текстов на основании содержимого этих документов. 2.3.6.1 Системы полнотекстового поиска Под поисковой системой понимается библиотека или компонент, вообще, программное решение, которое самостоятельно ведёт свое хранилище (на самом деле это может быть и СУБД, и просто файлы и распределенная платформа хранения) документов, в которых и происходит поиск. Поисковая система также предоставляет сторонним приложениям возможность добавлять, удалять и обновлять документы в этом хранилище. Этот процесс называется индексированием, и может быть реализовано отдельным компонентом (индексатором). Другой компонент, поисковый механизм, принимает запрос на поиск и, обрабатывая созданную базу, производит выборку данных, которые соответствуют запросу. Кроме этого, он может вычислять дополнительные параметры для результатов поиска (ранжировать документы, вычислять степень соответствия поисковому запросу и т. п.). Это самые важные части поисковой системы, и они могут быть, как монолитно реализованы в одной библиотеке, так и быть самостоятельными приложениями, доступ к которым реализуется через различные прикладные протоколы и API. На сегодняшний день самыми распространенными поисковыми системами являются Xapian, Sphinx, Lucene [1]. Сравнительная характеристика приведена в приложении Характеристика поисковых решений. Все остальные известные поисковые системы (сервисы) основаны на базе одного из этих трех продуктов. Рассмотрим данные системы подробнее. 30 Скорость индексирования – скорость, с которой поисковая система разбирает документ на слова и добавляет их основы в индекс. Скорость переиндексации - в процессе работы документы изменяются или добавляются новые, поэтому приходится заново индексировать информацию. Если поисковая система поддерживает инкрементное индексирование, то обрабатываются только новые документы, а обновление всего индекса происходит позже или не происходит вообще. Поддерживаемые API – возможность использования средств поисковой системы на разных программных платформах. Поддерживаемые протоколы – протоколы, использование которых возможно в поисковой системе. Размер базы и скорость поиска – взаимосвязанные параметры. В общем случае речь не идет об ограничении объема данных. Но в действительности скорость полнотекстового поиска может существенно зависеть от количества документов. Как правило, на сравнительно небольшом объеме (порядка 10000 документов) все поисковые системы работают примерно одинаково. Но если задача состоит в хранении миллионов документов, то показатели скорости сильно рознятся. Поддерживаемые типы документов – Все системы, поддерживающие полнотекстовый поиск, конечно, могут хранить данные как обычный текст (хотя следует смотреть на возможность работы с многоязычными документами и кодировкой UTF-8), но если необходимо индексировать разные типы файлов, например, HTML, XML, DOC или PDF, то предпочтительней та система, где есть встроенный компонент для этого. Сюда же относится и поддержка индексирования и поиска информации, 31 которая хранится в СУБД – не секрет, что такое хранение распространено для веб-приложений. Работа с разными языками и stemming - для корректного поиска с использованием разных языков необходима поддержка не только кодировок, но и работа с особенностями языка. Стемминг - это процесс нахождения основы слова для заданного исходного слова. Основа слова необязательно совпадает с морфологическим корнем слова [14]. Также важна поддержка алгоритмов стемминга для разных языков. Все системы поддерживают английский язык, который для поиска и обработки достаточно простой. Модуль стемминга позволяет склонять и разбирать слова в поисковом запросе для более корректного поиска. Поддержка дополнительных типов полей в документах - кроме самого текста, который индексируется и в котором производится поиск, необходимо наличие возможности хранить в документе неограниченное количество других полей, которые хранят метаинформацию о документе, что необходимо для дальнейшей работы с результатами поиска. Хорошим показателем является неограниченное количество и типы полей и их настраиваемая индексируемость. Например: в одном поле хранится название, во втором аннотация, в третьем ключевые слова, в четвёртом - идентификатор документа в вашей системе. Наличие встроенных механизмов ранжирования и сортировки - имеет значение, если поисковая система может быть расширена пользовательским алгоритмами сортировки и ранжирования, так как существует множество разных алгоритмов, а используемый по умолчанию не всегда подходящий. (Сравнительную характеристику Xapian, Sphinx, Lucene см в приложении 4) 32 2.3.6.2 Xapian как хранилище данных Для реализации своей системы я выбрала Xapian, в качестве хранилища данных, как наиболее легко встраиваемую во внешнее C#-приложение, без создания дополнительной нагрузки [13]. Xapian - система полнотекстового поиска, выполненная в виде кроссплатформенной библиотеки, обладает «живым» индексом, которой не требует перестройки при добавлении документов, очень мощным языком запросов, включая встроенный стемминг, проверку орфографии, и поддержку синонимов [3]. Поддерживает хранение метаданных в документе и поиск по ним [4], поддерживает удаленное соединение с хранилищем по tcp/ip (утилита xapian-tcpsrv). Поддерживает интеграцию с другими языками программирования с помощью SWIG (Simplified Wrapper and Interface Generator). Поддерживает поиск по фразе и близко стоящим друг от друга словам на уровне конструкции запросов. Поддерживает ранжированный поиск, поиск по шаблону и структурированным булевым запросом (AND OR XOR NOT). Поддерживает сортировку результатов поиска по релевантности. Поддерживает запросы на множество документов, начиная со стартового индекса [2]. Xapian используется в aptitude — оболочке для системы управления пакетами в debian-based дистрибутивах GNU/Linux. Xapian составляет конкуренцию Sphinx и Lucene. 2.3.7. Интеграция Для полноценной интеграции Xapian в мою систему необходимо было решить следующие задачи. - поиск по XPath среди документов Xapian; 33 - реализация возможностей API Xapian со стороны клиента средствами уже существующих запросов. - обеспечение возможности подключения к базе xapian. Для обеспечения поиска по XPath среди документов Xapian необходимо было интерпретировать XPath в полнотекстовый запрос Xapian. Причём требовалась именно смысловая интерпретация XPath, а не его текстовый эквивалент. Для этого было написан простой парсер, исключающий из строки XPath все специальные символы, и расширение XPathMatchDecider для класса Xapian.MatchDecider, в котором проверяется соответствие документа конкретному XPath запросу и который непосредственно включён в поиск. Каждый ServerKnot в системе соединяется с базой данных Xapian, как с локальной, через класс DataLayer. Тот, в свою очередь, работает с хранилищем данных идет через IDatabase интерфейс. 34 Заключение Поставленная задача полностью решена. Реализована система, отвечающая концепции NoSQL. Распределенность обеспечивается интеграцией с платформой CCP. Реализованы CRUD операции для работы с данными. Реализована поддержка основных типов транзакций. Хранимые документы имеют гибкую структуру (формат XML). Масштабируемость достигается за счет модели MapReduce, которая лежит в основе архитектуры системы. Реализована поддержка полнотекстового поиска, за счет интеграции с Xapian. В ходе тестирования были получены следующие результаты: Общее Среднее Кол-во Количество Среднее количество кол-во компьютеров. документов в время документов слов в результирующей поискового документе выборке запроса (с.) 1 000 000 158 1 10 ~0.08 2 000 000 163 1 10 ~0.093 2 000 000 161 2 10 ~ 0.083 Размер документа Количество термов Количество атрибутов Время выполнения запроса на добавление (с.) 0.5 Kb 49 10 ~0.23 0.5 Kb 49 5 ~0.157 2.7 Mb 17987 1 ~3.5 35 9.7 Mb 24915 1 ~12 В ходе выполнения работы были приобретены навыки проектирования и рефакторинга, использование системы контроля версий, написания модульных, интеграционных, регрессионных тестов. Навыки написания и отладки многопоточных приложений. 36 СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ. 1. Обзор решений для полнотекстового поиска в веб-проектах: Sphinx, Apache Lucene, Xapian: URL http://dou.ua/lenta/articles/full-text-search-enginesoverview-sphinx-apache-lucene-xapian/ (дата обращения: 08.04.2012) 2. Xapian - the open source search engine: URL:http://xapian.org/docs/ (дата обращения: 03.05.2012) 3. Xapian URL http://xapian.org/ (дата обращения: 03.05.2012) 4. Xapian Search Architecture URL: http://www.flax.co.uk/blog/2009/04/02/xapian-search-architecture/ (дата обращения: 03.05.2012) 5. Универсальное NoSQL - введение в теорию URL: http://blogerator.ru/page/nosql-vvedenie-v-teoriju-bd (дата обращения: 17.03.2012) 6. MapReduce: URL:http://citforum.ru/database/articles/dw_appliance_and_mr/2.shtml (дата обращения: 20.10.2011) 7. Introduction to Parallel Programming and MapReduce URL: http://code.google.com/intl/ru-RU/edu/parallel/mapreduce-tutorial.html (дата обращения: 20.03.2012) 8 . Введение в JSON URL: http://www.json.org/json-ru.html (дата обращения: 11.03.2012) 9. XML Path Language (XPath) URL:http://www.w3.org/TR/xpath/ (дата обращения: 11.03.2012) 37 10. Extensible Markup Language (XML) URL: http://www.w3.org/XML/ (дата обращения: 11.03.2012) 11. Что такое транзакция и ACID? URL: http://www.firststeps.ru/mfc/msdn/r.php?95 (дата обращения: 19.04.2012) 12 . Транзакционные параллельные СУБД: новая волна URL: http://www.softpoint.ru/article_id368.html (дата обращения: 19.04.2012) 13. C# bindings for Xapian URL: http://xapian.org/docs/bindings/csharp/ (дата обращения: 03.05.2012) 14. Стеммер URL: http://www.solarix.ru/for_developers/api/stemmer.shtml (дата обращения: 03.05.2012) 15. Версионность в Yukon URL: http://www.rsdn.ru/article/db/yukonvers.xml (дата обращения: 20.04.2012) 16. NoSQL databases URL: http://nosql-database.org/ (дата обращения: 17.03.2012) 17. XML Standards Reference URL:http://msdn.microsoft.com/enus/library/ms256177 (дата обращения: 12.03.2012) 18. Isolation and Database Locking URL: http://docstore.mik.ua/orelly/javaent/ebeans/ch08_03.htm (дата обращения: 17.04.2012) 38 19. Cassandra Wiki Architecture Overview URL: http://wiki.apache.org/cassandra/ArchitectureOverview (дата обращения 19.03.2012) 20. Объектно-ориентированный анализ и проектирование с примерами приложений на С++ URL: http://vmk.ugatu.ac.ru/book/buch/index.htm (дата обращения 19.03.2012) 39 Приложение 1. Модели данных. База данных Модель данных API запросов Cassandra Семейство столбцов Thrift CoutchDB Документы Map / Reduce HBase Семейство столбцов Thrift, REST MongoDB Документы Cursor Neo4j Графы Graph Redis Коллекции Collection Riak Документы Nested hashes, REST Scalaris Ключ / Значение Get / Put Tokyo Cabinet Ключ / Значение Get / Put Voldemort Ключ / Значение Get / Put 40 Приложение 2. Системы хранения данных. База данных Вид данных Cassandra Memtable / SSTable CoutchDB Append-only B-Tree HBase Memtable / SSTable on HDFS MongoDB B-Tree Neo4j On-disk linked list Redis In-memory with background snapshots Riak Hash Scalaris In-memory only Tokyo Cabinet Hash or B-Tree Voldemort Pluggable (primarily BDB MySQL) 41 Приложение 3. Схема алгоритма MapReduce. 42 Приложение 4. Характеристики поисковых решений. Тип Платформа Индекс Варианты поиска Sphinx(GPL 2 или коммерческая) отдельный сервер или MySQL storage engine Apache Lucene (Apache License 2.0) отдельный сервер или сервлет, встраиваемая библиотека C++ Java (порты на PHP, C#/.NET, Perl, Ruby, Python) монолитный + дельта-индекс, инкрементный индекс, возможность распределённого но требующий операции поиска слияния сегментов (оптимизации) булевый поиск, поиск булевый поиск, поиск по фразам, учёт близости слов по фразам, нечёткий поиск, учёт близости слов, поиск по маске API и протоколы SQL DB, собственный XMLпротокол,встроенные API для РНР, Ruby, Python, Java, Perl Java API Поддержка языков встроенный английский и русский стемминг, soundex для реализации морфологии отсутствует морфология, есть стемминг (Snowball) и анализаторы для ряда языков (включая русский) Xapian (GPL) встраиваемая библиотека C++ инкрементный «живой» индекс, inmemory индексы для небольших баз. булевый поиск, поиск по фразам, поиск с ранжированием, поиск по маске, поиск по синонимам С++, Perl API, Java JINI, Python, PHP, TCL, C# и Ruby, CGI интерфейс с XML/CSV форматом отсутствует морфология, есть стемминг для ряда языков (включая русский), проверка правописания в поисковых запросах 43 Дополнительные поля документов Форматы неограниченное количество неограниченное количество только текст или SQL DB Размер индекса/скорость очень быстрый, индексация около 10 Мб/сек (зависит от CPU), поиск около 0.1 сек/~2 — 4 Гб индексе, поддерживает размеры индекса в сотни Гб и сотни миллионов документов, однако есть примеры работ на терабайтных базах данных. текст, возможно индексация базы данных через JDBC около 20 Мб/минута, размер индексных файлов ограничен 2 Гб (на 32-bit ОС). Есть возможности параллельного поиска по нескольким индексам и кластеризация (требует сторонних платформ) неограниченное количество только текст тесты скорости на офф. сайте отсутствуют. Известны работающие инсталляции на 1.5 Тб индекса 44