НАУЧНАЯ СЕТЬ: АЛГОРИТМЫ И СТРУКТУРЫ ДАННЫХ О.С. Бартунов1, Т.Г. Сигаев2 ГАИШ, МГУ, 2Stack Technologies, г.Москва 1 При построении современных информационных систем приходится решать разнообразные технологические задачи, связанные с хранением, доступом и поиском информации. Учитывая современные требования к производительности, надежности и шкалированию таких систем, такие задачи требуют использования достаточно сложных алгоритмов и специализированных структур данных. Мы опишем несколько подходов, использованные при разработке технологии "Научной Сети" (далее НС). Принципы работы и архитектура НС будет представлены в других докладах. В качестве хранилища метаданных мы используем свободно-распространяемую базу данных PostgreSQL (http://www.postgresql.org), в разработке которой авторы принимают непосредственное участие. Выбор этой базы был обусловлен не только личными симпатиями, но и опытом работы под большими нагрузками в новостном проекте "Рамблер". Кроме того, в архитектуре PostgreSQL были заложены прогрессивные идеи, такие как расширяемость, нашедшие развитие в коммерческих базах данных (например, Informix, Illustra). Начавшись как научный академический проект в Беркли и служивший "полигоном" для проверки многих идей, в 1995 году он был передан сообществу для дальнейшего развития. Именно архитектура PostgreSQL позволила авторам решить много проблем, связанных с необходимостью введения новых типов данных, быстрых методов доступа и специализированных запросов. Авторы являются разработчиками обобщенного поискового дерева (GiST), входящего в ядро PostgreSQL, которое дает возможность специалистам в конкретной области знаний создавать специализированные типы данных и обеспечивает индексный доступ к ним, не будучи экспертами в области баз данных. Информация по GiST, исходные тексты, ссылки на статьи и история развития доступны на страничке авторов по адресу http://www.sai.msu.su/~megera/postgres/gist/. GiST представляет собой сбалансированное (по высоте) дерево, листья которого содержат пары (key, rid), где key – ключ, а rid – указатель на соответствующую запись на странице данных. Внутренние узлы содержат пары (p, ptr), где p – это некий предикат (используется как поисковый ключ), выполняющийся для всех наследных узлов, а ptr – указатель на другой узел в дереве. Для этого дерева определены базовые методы SEARCH, INSERT, DELETE, и интерфейс для написания 7-ми пользовательских методов, которыми можно управлять работой этих (базовых методов). Метод SEARCH управляется функцией Consistent, возвращающая 'true', если узел удовлетворяет предикату, метод INSERT – функциями penalty, picksplit и union, которые позволяют оценить сложность операции вставки в узел, разделить узел при переполнении и перестроить дерево при необходимости, метод DELETE находит лист дерева, содержащий ключ, удаляет пару (key, rid) и, если нужно, с помощью функции union подстраивает родительские узлы. Дополнительные функции compress, decompress используются для оптимизации хранения ключей. Реализации стандартных деревьев, используемых в базах данных (B+-tree и R-tree), с помощью GiST доступны в contrib директории дистрибутива PostgreSQL, начиная с версии 7.1. Интересно, что производительность по сравнению со встроенными деревьями практически не изменилась, в то время как затраты на память и построение R-tree сильно уменьшились. Кроме того, в R-tree мы использовали новый, более оптимальный, алгоритм разделения узлов (C.H.Ang and T.C.Tan). Для работы со множествами мы разработали новый тип intarray, представляющий собой целочисленный массив, с поддержкой индексного доступа. Это позволило эффективно работать с метаданными, поддерживающие множественные значения. Например, все статьи в НС рубрицируются по разделам знаний. Часто возникает необходимость построения списка документов принадлежащих определенным рубрикам. При этом, стандартный путь (операторы OR,IN,EXISTS) является неэффективным и трудно поддается оптимизации. С помощью типа intarray задача решается следующим способом: create table message (mid int not null,sections int[]); - create indices CREATE INDEX message_rdtree_idx on message using gist (sections gist__int_ops); - select some messages with section in 1 OR 2 – OVERLAP operator select message.mid from message where message.sections && '{1,2}'; - select messages contains in sections 1 AND 2 – CONTAINS operator select message.mid from message where message.sections @ '{1,2}'; - the same, CONTAINED operator select message.mid from message where '{1,2}' ~ message.sections; Эксперименты показали существенное ускорение выполнения запроса (на порядок и более). Отметим, что в данном случае ключами являются так называемые наложенные сигнатуры (superimposed signatures), помещенные в RD-tree, которое реализовано с помощью GiST. Идея использования сигнатур восходит к Блюмовским фильтрам (Bloom filters), которые представляют множество как битовый массив (изначально все биты, равны 0), используя 3 хеширование каждого элемента множества в некоторое число бит (соотв. биты становятся равным 1). Для поискового запроса аналогичным образом создается битовый массив и сравнивается совпадение всех битов, имеющих значение 1. Если имеется, хотя бы одно несовпадение, то ответ *точно* отрицательный. Это позволяет эффективно отбирать кандидатов для точной проверки, которая необходима из-за использования хеширования (некоторые биты могут быть использованы несколько раз). Эти сигнатуры хранятся в RD-tree (russian doll), при этом выполнятся условие иерархичности предикатов, необходимое для GiST. Т.е. сигнатура родительского узла является объединением (union) всех сигнатур наследных узлов. Битовые операции, используемые для построения и сравнения сигнатур, обычно поддерживаются на аппаратном уровне. Компрессия массивов в битовый массив позволяет оптимизировать затраты на хранение. Основными характеристиками такого подхода являются кол-во уникальных элементов, длина сигнатуры и кол-во бит, в которые хешируются элементы массива. Этот тип нашел применение во многих приложениях, например для организации полнотекстового поиска. В виду ограниченности объема тезисов мы опишем только идею (исходные тексты и документация доступны на сайте http://openfts.sourceforge.net). Рассмотрим документ как массив целых чисел, полученных хешированием слов. Используя, тип intarray можно эффективно искать все документы, содержащие поисковые слова (которые также хешируются в целочисленный массив). Полнотекстовый поиск, который реализован на уровне базы данных является, на наш взгляд, примечательной особенностью нашей технологии, так как имея доступ к метаданным системы, можно задавать очень сложные запросы, например: найти все статьи, которые содержат поисковый запрос, но при этом разрешены к публикации и относящиеся к определенным категории знаний. При этом используются метаданные – статус статьи и рубрики, к которым она относится. Кроме того, этот тип данных, а следовательно, и поиск, поддерживают инкрементальность, что также очень важно для информационных систем. Практически это означает, что любой документ, попавший в нашу систему, будет мгновенно доступен для поиска. Таким образом, мы имеем полнотекстовый поиск с поддержкой метаданных системы и инкрементальности. Это сильно отличает его от других поисковых машин, которые индексируют статические коллекции и использующие стандартный подход обратных индексов (для тематических поисков по внешним коллекциям документов мы используем подобного типа поисковую машину. Подробнее об этом в другом докладе). Идея использования сигнатур для полнотекстового поиска и желание его полной интеграции с базой данных привела нас к созданию нового типа данных txtidx, доступный как модуль contrib/tsearch. Новая версия OpenFTS, доступная из CVS на сайте openfts.sourceforge.net, нашего полнотекстового поиска базируется уже на этом типе. Более подробно о модуле tsearch можно прочитать в документации к этому модулю. Приведем лишь пример запроса: select title from titles where titleidx ## 'patch&gist'; Здесь таблица title содержит колонку title (тип text) и соответствующую ей колонку titleidx (тип txtidx). Используется индекс по полю titleidx. Запрос означает – найти все заголовки, содержащие слова 'patch' и 'gist'. При этом операция ## означает, что будут найдены все формы слов (используется морфология). Любой полнотекстовый поиск нуждается в языковой поддержке. Нами были изучены и реализованы несколько лингвистических алгоритмов. В частности, мы реализовали поддержку стемминга (проект Snowball – http://snowball.sourceforge.net), который очень важен для научного корпуса документов в виду большого кол-ва специальных терминов, не присутствующих в словарях, морфологии, построенной на основе свободных словарей для широко известной программы ISpell (http://fmg-www.cs.ucla.edu/geoff/ispell.html). Для поддержки иерархических данных нами был разработан модуль tree, который позволяет эффективно работать с деревьями. Основная идея заключается в том, что узел представляется в виде пути по дереву от корня. Путь указывается номером в каждом узле, так что запись '3.4' означает 4-го ребенка 3-го ребенка корня. Таким образом, обычные ресурсоемкие рекурсивные методы поиска по дереву, становятся не нужны, так как вся информация о наследовании доступна в самом пути. Тестовые эксперименты, которые проводились по данным DMOZ (Open Directory Project, http://dmoz.org), показали высокую эффективность поиска. Пример запроса: Получить всех детей узла select * from dmoz where id <* '1.2.3.*.0'; При этом будет использовать индекс, построенный с помощью GiST. Поддерживаются два типа – bitree (bit tree) и entree (enumerated tree), отличающиеся максимальным количеством детей (не потомков!) узла, 65535 – для entree, и 8,16,32(по умолчанию),64 для bitree (задается при компиляции). К сожалению, нами пока не найден способ для эффективной поддержки направленных графов (DAG), который весьма нужен для работы с линками в каталогах. Кроме того, модификация этого типа весьма затруднена. Кратко коснемся проблемы поиска похожих документов и поиска с ошибками. Для поиска похожих документов нами используется метод представления документа как последовательность сигнатур (fingerprints) фрагментов текста, полученных при использовании "окна" фиксированной длины. Этот метод был предложен, Уди Манбером (Udi Manber) и показал высокую эффективность. Вычисление похожести двух текстов производится путем нахождения относительной доли общих для этих текстов фингерпринтов. Эта методика малочувствительна к перестановке блоков, к удалению или вставке блоков в текст и, следовательно, позволяет обнаруживать переработанные тексты. Этот метод может использоваться и как способ нахождения изменчивости документа (подробнее в другом докладе). Аналогичная методика, но с использованием триграм, нами была использована для нечеткого поиска 4 (поддержка операций над буквами insert, delete, transposition). Например, по запросам 'черная дыра', 'дырами черными' в списке ключевых найдется 'черная дыра'. Описанные методики реализованы в виде библиотек и интерфейсов к PostgreSQL и языку Perl. В заключение, авторы выражают благодарность коллективу Научной Сети за плодотворные обсуждения, РФФИ (РФФИ 99-07-90069, РФФИ 02-07-90222), и компании "Стек Технологии" за помощь и поддержку. 5