Перевод - Сергей Кузнецов
Оригинал: B-tree indexes for high update rates
(http://www.sigmod.org/sigmod/record/issues/0603/p39-article-graefe.pdf), SIGMOD Record, Vol. 35, No. 1, Mar. 2006
Статья Грейфа привлекла меня тем, что уже давно не попадались обзоры, посвященные алгоритмам работы с B-деревьями. Должен сразу сказать, что во многом статья не оправдала мои ожидания: многие, по-видимому, интересные методы и структуры данных описаны слишком кратко и сумбурно, очень не хватает иллюстраций. Во время перевода мне постоянно хотелось исправить недостатки статьи, благо, что большая часть источников, упоминаемых в списке литературы, свободно доступна в Internet. Однако я сдержал себя и представляю вам чистый перевод статьи. В целом он дает представление о существующих подходах, а с подробностями вы можете при желании ознакомиться сами, воспользовавшись списком литературы и поставленными мной ссылками на публикации в Web.
Для этих приложений хорошим вариантом являются индексы на основе B-деревьев, если принять некоторые компромиссные решения, направленные на оптимизацию скорее операций обновления, чем запросов. В этой статье приводится обзор некоторых методов, которые за счет снижения эффективности обработки запросов позволяют B-деревьям выдерживать очень высокий темп обновлений, на несколько порядков более высокий, чем могут допустить традиционные B-деревья. Не удивительно, что некоторые из этих методов напоминают методы, используемые при создании, перестройке индексов и т.д., а другие выводятся из известных технологий, таких как дифференциальные файлы и файловые системы с журнальной структурой (log-structured file system).
Другой областью применения методов, обсуждаемых в этом обзоре, является индексирование непрерывных потоков данных. Достаточно хорошо понятна фильтрация потоков "на лету", но для потоков, содержащих идентификаторы объектов реального мира, часто требуется сопоставление со статическими данными, а также с другими потоками. Поэтому крайне необходима возможность сбора потоковых данных, обычно в порядке их поступления, а также их индексирования по атрибутам, отличным от времени поступления. Иногда требуется несколько индексов, поддерживающих разные порядки. Например, для обеспечения эффективного и близкого к мгновенному обнаружения случаев мошенничества в потоке данных о транзакциях с кредитными карточками может потребоваться индексирование по номеру карты, идентификационным данным клиента (клиент может потерять несколько кредитных карт в одно и то же время) и данным о продавце (нечестный служащий может мошенническим образом расплачиваться кредитными картами многих клиентов).
Далее мы предполагаем, что эффективность операций обновления и вставки является более важной, чем эффективность обработки запросов. Если читателя не интересуют такие приложения, то к его B-деревьям следует применять традиционные оптимизационные методы, а не методы, обозреваемые в этой статье. Кроме того, мы предполагаем, уже применен какая-либо регулировка рабочей нагрузки, например, выработана "наилучшая" политика регистрации данных о текущем местоположении автомобилей, так что оставшиеся запросы на обновление действительно должны быть отражены во всех рассматриваемых индексах. Наконец, мы предполагаем, что учитывается возможность аппаратной поддержки, и она используется, насколько это возможно и уместно, например, используются расслоение дисков и твердотельные диски или дисковые буфера.
Имеется несколько очень общих технологий совершенствования производительности, например, сжатие данных [WKH 00]. В рабочих нагрузках с интенсивным обновлением данных существенное сжатие применяется не только к данным, но и к журналу транзакций. Здесь достаточно заметить, что некоторые методы сжатия являются удивительно простыми, например, отбрасывание лидирующих или хвостовых нулей или пробелов и объединение нескольких журнальных записей от одной транзакции в одну журнальную запись для сокращения накладных расходов, вызываемых наличием в журнале транзакций множественных заголовков записей.
Отложенная запись (write-behind) страниц журнала и данных является хорошо известным методом. Сама по себе отложенная запись не повышает пропускную способность системы, поскольку объем записи не сокращается. Однако отложенная запись часто обеспечивает возможность записи данных большого объема, что является еще более эффективным, чем поддержка очередей ввода/вывода. Кроме того, отложенная запись полезна в случае пиков рабочей нагрузки и позволяет производить дополнительную оптимизацию. Например, в современных дисковых устройствах поддерживаются собственные очереди команд, и обмены выполняются лучше, если всегда имеются десятки ждущих операций ввода/вывода [ADR 03].
Упреждающее чтение (read-ahead, обычно используемое при сканировании) не применяется к операциям обновления, но оно применяется для присоединения всей ветви модификаций к существующему B-дереву. При слиянии нескольких разделов B-дерева (обсуждаемом ниже) в один раздел упреждающее чтение с использованием прогнозирования может улучшить производительность, поскольку слияние разделов, по существу, представляет ту же проблему, что и слияние прогонов при выполнении внешней сортировки со слиянием [G 03a]
Предварительная выборка (prefetch), основанная на индивидуальных ключах, применяется не только к операциям выборки, например, при передвижении из некластеризованного индекса в кластеризованный индекс, но и к операциям обновления. Однако, подобно упреждающему чтению и отложенной записи, предварительная выборка также не приводит к непосредственному улучшению производительности или пропускной способности системы, а лишь уменьшает время реакции или задержку выполнения отдельных операций, что может косвенным образом привести к повышению производительности за счет сокращения конкуренции при управлении параллельным выполнением операций.
Чтобы избежать последующего обновления соседних страниц, традиционная цепочка страниц с использованием физических идентификаторов страниц заменяется логической цепочкой страниц с использованием разделительных ключей (separator key), т.е. каждая страница несет в качестве нижнего и верхнего барьеров разделительный ключ, копируемый в страницу родительского узла при отщеплении страницы от ее соседей. В дополнение к поддержке тех же проверок согласованности и других технических операций, что и те, которые поддерживаются традиционными физическими цепочками страниц, барьерные ключи упрощают и улучшают блокировку диапазонов значений ключа, поскольку никогда не требуется перемещаться к соседней листовой странице для нахождения правого значения ключа в диапазоне. После замены физических цепочек страниц логическими барьерными ключами физические идентификаторы страниц остаются только в указателях детей, и только их требуется изменять при перемещении узла на новое место на диске.
В традиционных алгоритмов B-деревьев новое место выделяется при принятии решения менеджером B-дерева о расщеплении узла, так что в последующих журнальных записях могут иметься ссылки на идентификатор этой страницы. В B-деревьях, оптимизированных для записи, новой странице дается временный идентификатор, который может использоваться в журнальных записях, и страница перемещается при выполнении операции записи, что очень напоминает перемещение страницы при выполнении дефрагментации B-дерева. Поэтому применяются испытанные механизмы управления параллельным выполнением операций и восстановления.
Воздействие на производительность B-деревьев, оптимизированных для записи, состоит в том, что случайные операции записи преобразуются в крупные последовательные операции записи, обеспечивающие повышение пропускной способности более чем в 10 раз ценой дополнительной поддержки родителя каждого узла при каждой его записи на новое место на диске.
Для корректного транзакционного выполнения требуется журнализировать и вставки, и удаления в буфере; поэтому объем журнала может превосходить объем традиционного журнала более чем в три раза. Однако пользовательской транзакцией является только исходная вставка в первый буфер, а все следующие перемещения записи могут быть системными транзакциями, которые можно фиксировать дешевым образом без выталкивания заключительной части журнала в стабильную память.
Как кажется, записи должны удерживаться только для тех детей, которые не находятся непосредственно в буфере ввода-вывода. На основе того, что в большинстве B-деревьев имеется не менее ста ветвей, что в большинстве серверов баз данных размер памяти превосходит 1% размера диска, и что обсуждаемые здесь B-деревья являются наиболее активно используемыми и критически влияющими на производительность индексами внутри базы данных, можно заключить, что такая буферизация применима только к узлам, являющимся непосредственными предками листьев. Другими словами, могут быть возможны дополнительные усовершенствования опубликованных методов, позволяющие буферизовать узлы всех уровней B-дерева.
Использование новых структур предполагает наличие новых механизмов для управления параллельным выполнением операций и восстановления. Таким образом, предпочтительным механизмом была бы уже реализованная индексная структура. В противном случае для новых моделей или протоколов блокировки потребуются доказательства корректности, реализация, тестирование и т.д. Возможно, в наиболее желательной реализации должны избегаться и отдельные структуры, и модификации существующих структур, а вместо этого существующие механизмы должны использоваться другим образом.
Традиционное управление буферами вместе с ограничениями на размер заново добавленных разделов обеспечивают, что вставки данных пользовательскими транзакциями могут полностью происходить в основной памяти. В предельном случае разделы новых вставок могут состоять из одной записи, т.е. каждая вставка определяет новый раздел и, таким образом, может выполняться практически без поиска или реорганизации страницы внутри B-дерева. Таким образом, максимизируется скорость вставок и пропускная способность пользовательских транзакций за счет потребности больших усилий для оптимизации и реорганизации индекса.
При выполнении запросов требуется производить поиск в каждом разделе с использованием традиционных методов, ограничивающих значения некоторых столбцов индекса, за исключением лидирующего [LJB 95], которые, возможно, дополняются оптимизацией на основе того факта, что в качестве идентификаторов разделов используются последовательные целые значения. В качестве альтернативы, при выполнении запросов могут возникать некоторые слияния, выполняемые до реальной выборки данных и реализуемые с использованием системных транзакций. Таким образом, работа по поддержке B-дерева, традиционно выполняемая как часть операций обновления, перемещается в состав операций запросов или реорганизации, которая может случаться в любое время между вставкой и запросом. В крайнем случае, запрос может привести к полному слиянию и оптимизации всех разделов за исключением, может быть, одного раздела, предназначенного для текущих вставок.
Некоторые интересные аспекты таких B-деревьев состоят в том, что
Описанные выше буферизованные вставки с использованием разделенных B-деревьев представляют собой третий способ применению обновлений к индексу на основе B-дерева, и тем самым обеспечивают еще один вариант плавного снижения эффективности. Обработка в режиме "строка-за-строкой", нацеленная на новый раздел, сулит лучшие модель ввода/вывода и производительность, чем те, которые обеспечиваются при обработке в режиме индекс-за-индексом, хотя при этом сохраняется недостаток неоптимальности индексов. Для обеспечения плавного снижения эффективности план выполнения операции обновления может предусматривать выполнение обновления в режиме "строка-за-строкой" в главном разделе до тех пор, пока не выяснится реальный размер множества обновлений, а затем переключение на буферизованные или разделенные обновления. Хотя можно реализовать плавное снижение эффективности путем смены режима "строка-за-строкой" на режим "индекс-за-индексом" с использованием условного выполнения в традиционном плане выполнения запроса, назначение индексным изменениям нового идентификатора раздела (искусственного лидирующего столбца ключа) является гораздо более простым действием и содействует повышению эффективности обновлений.
Основной подход состоит в добавлении записей, которые делают недействительными предыдущие записи, без реального изменения этих предыдущих записей. При обновлении новая запись замещает предыдущий элемент B-дерева с тем же ключом. При удалении заново добавляемая запись просто указывает на конец истории данного ключа, или, по крайней мере, на конец истории до тех пор, пока впоследствии не произойдет новая вставка с тем же ключом.
При выполнении запросов требуется искать в истории каждого конкретного ключа либо его текущее состояние (при традиционной семантике запросов), либо состояние в заданное время (при обработке исторических запросов, привязанных к точке во времени). Историю ключей можно уплотнять с помощью операций слияния, в зависимости от желательных возможностей будущих запросов.
Другими словами, подобно буферизованным вставкам, буферизованные модификации и удаления в дифференциальных B-деревьях жертвуют эффективностью запросов в пользу эффективности обновлений. Преобразование случайных вставок, удалений и модификаций по одной записи в операции добавления с использованием крупных последовательных операций записи обещает повышение пропускной способности обработки обновлений на два порядка.
Конечно, имеется также взаимосвязь между дифференциальными файлами и реализацией версионной изоляции на основе мгновенных снимков (multi-version snapshot isolation). Однако основное различие состоит в том, что в дифференциальных файлах сохраняется самая старая версия плюс набор "дельт" в прямом времени, а реализации версионной изоляции на основе мгновенных снимков обычно ориентированы на обеспечение доступа к наиболее свежим версиям, т.е. в них обычно сохраняется наиболее свежая версия плюс набор "дельт" в обратном времени.
Эту идею можно расширить следующим образом. Если индекс является действительно избыточным, подобно традиционному кэшу, и если допустимо разрушение индекса в течение восстановления носителя данных или системы, то все операции над этим индексом могут не журнализоваться, т.е. журнализуется только распределение памяти. Это включает и пользовательские транзакции, выполняемые после завершения создания индекса. Откат пользовательской транзакции управляется виртуальными журнальными записями, подсоединенными к описателю транзакции в основной памяти, аналогично виртуальным журнальным записям, которые используются в других методах управления транзакциями [G 04, GK 85]. Детали этого метода в настоящее время не опубликованы, но для некоторых приложений этот метод кажется вполне обещающим, в частности, для временных кэшей и индексов, существующих только в основной памяти.
В этом обзоре мы старались перечислить разнообразные возможные методы. Новые методы включают B-деревья, оптимизированные для записи, разделяемые B-деревья, в которых разделы используются для буферизации вставок или всех модификаций в манере дифференциальных файлов, и B-деревья без журнализации. Однако наша интуитивная оценка нуждается в подтверждении с использованием прототипирования или даже производственной реализации.
Очевидно наличие многочисленных открытых вопросов, включая вопрос о дополнительных или улучшенных компромиссах между эффективностью обновлений и запросов; сравнение эффективности описанных методов на основе подходящего тестового набора; адаптация описанных методов для других индексных структур, таких как UB-деревья и R-деревья, а также материализованные и индексные представления; интеграция обработки запросов и операций обновления с операциями поддержки базы данных, такими как проверка согласованности, дефрагментация и обновление статистики для оптимизации запросов. Может быть, этот обзор послужит стимулированию и структуризации таких исследований.
| Lars Arge: Efficient External-Memory Data Structures and Applications. University of Aarhus (Denmark), 1996. http://www.brics.dk/DS/96/3/BRICS-DS-96-3.pdf | |
| Dave Anderson, Jim Dykes, Erik Riedel: More Than an Interface - SCSI vs. ATA. Conference on File and Storage Technology (FAST), March 2003. http://www.seagate.com/content/docs/pdf/whitepaper/ D2c_More_than_Interface_ATA_vs_SCSI_042003.pdf | |
| Lars Arge, Klaus Hinrichs, Jan Vahrenhold, Jeffrey Scott Vitter: . Algorithmica 33(1): 104-128 (2002). http://www.cs.duke.edu/~jsv/Papers/AHV99.bulk.pdf | |
| Goetz Graefe: Sorting and Indexing with Partitioned B-Trees. Conference on Innovative Data Systems Research, 2003. http://www-db.cs.wisc.edu/cidr/cidr2003/program/p1.pdf | |
| Goetz Graefe: Partitioned B-trees - a user's guide. Datenbanksysteme f?r Business, Technologie und Web (BTW) 2003: 668-671. http://doesen0.informatik.uni-leipzig.de/proceedings/paper/IP11.pdf | |
| Goetz Graefe: Write-Optimized B-Trees. VLDB Conference 2004: 672-683. http://www.vldb.org/conf/2004/RS18P2.PDF | |
| Dieter Gawlick, David Kinkade: Varieties of Concurrency Control in IMS/VS Fast Path. IEEE Data Eng. Bulletin 8(2): 3-10 (1985). | |
| Andreas G?rtner, Alfons Kemper, Donald Kossmann, Bernhard Zeller: Efficient Bulk Deletes in Relational Databases. IEEE ICDE 2001: 183-192. http://www.hpl.hp.com/techreports/tandem/TR-85.6.pdf | |
| H. V. Jagadish, P. P. S. Narayan, S. Seshadri, S. Sudarshan, Rama Kanneganti: Incremental Organization for Data Recording and Warehousing. VLDB Conference 1997: 16-25 http://www.vldb.org/conf/1997/P016.PDF | |
| Harry Leslie, Rohit Jain, Dave Birdsall, Hedieh Yaghmai: Efficient Search of Multi-Dimensional B-Trees. VLDB Conference 1995: 710-719. http://www.vldb.org/conf/1995/P710.PDF | |
| Peter Muth, Patrick E. O'Neil, Achim Pick, Gerhard Weikum: The LHAM Log-Structured History Data Access Method. VLDB J. 8(3-4): 199-221 (2000). http://www.cs.umb.edu/~poneil/LHVLDBJ.pdf | |
| Patrick E. O'Neil, Edward Cheng, Dieter Gawlick, Elizabeth J. O'Neil: The Log-Structured Merge-Tree (LSM-Tree). Acta Inf. 33(4): 351-385 (1996). http://www.cs.umb.edu/~poneil/lsmtree.ps | |
| John K. Ousterhout, Fred Douglis: Beating the I/O Bottleneck: A Case for Log-Structured File Systems. Operating Systems Review 23(1): 11-28 (1989). http://www.ugrad.cs.ubc.ca/~cs415/X/Info/papers-2003/ousterhout88beating.ps | |
| David A. Patterson, Garth A. Gibson, Randy H. Katz: A Case for Redundant Arrays of Inexpensive Disks (RAID). ACM SIGMOD Conference 1988: 109-116. http://www.cs.cmu.edu/~garth/RAIDpaper/Patterson88.pdf | |
| Dennis G. Severance, Guy M. Lohman: Differential Files: Their Application to the Maintenance of Large Databases. ACM Trans. Database Syst. 1(3): 256-267 (1976). | |
| David Ungar: Generation Scavenging: A Non-Disruptive High Performance Storage Reclamation Algorithm. Software Development Environments (SDE), ACM SIGPLAN Notices 19(5): 157-167 (1984). | |
| Jochen Van den Bercken, Bernhard Seeger, Peter Widmayer: A Generic Approach to Bulk Loading Multidimensional Index Structures. VLDB Conference 1997: 406-415. http://www.vldb.org/conf/1997/P406.PDF | |
| Till Westmann, Donald Kossmann, Sven Helmer, Guido Moerkotte: The Implementation and Performance of Compressed Databases. ACM SIGMOD Record 29(3): 55-67 (2000). http://www.dbis.ethz.ch/research/publications/sigrec00.pdf |