B* дерево (B* tree)
Предпосылки: оценка сложности в O(…), B-дерево — split/merge, инвариант заполнения от 1/2 до полного узла.
После split в B-дереве оба новых узла обычно получаются заполненными примерно наполовину. При t = 128 и индексе на 10 миллионов записей минимальное заполнение 1/2 означает, что в худшем случае индекс может занимать вдвое больше страниц, чем при полном заполнении — сотни мегабайт впустую. При последовательности вставок и удалений узлы могут долго оставаться близко к минимальному заполнению, и значительная часть страниц индекса оказывается наполовину пустой.
Главная идея
B* дерево повышает минимальное заполнение узла с 1/2 до 2/3. Звёздочка (*) обозначает вариант B-дерева с более плотным заполнением. Чтобы поддерживать этот инвариант, изменяются алгоритмы вставки и удаления.
Вставка
В B-дереве переполненный узел расщепляется на два, каждый заполненный примерно наполовину. B* дерево добавляет промежуточный шаг: прежде чем расщеплять, проверить соседний узел и попытаться перераспределить ключи. Только если оба узла заполнены и перераспределять некуда, два соседних узла расщепляются на три.
Когда узел переполняется, B* дерево проверяет соседний узел (с тем же родителем). Если у соседа есть свободное место, ключи перераспределяются между двумя узлами через родителя:
До: [...| 30 |...]
/ \
[10|20|25|28] [35|40] ← левый переполнен, у правого есть место
После: [...| 28 |...]
/ \
[10|20|25] [30|35|40] ← перераспределили через родителяЕсли у соседа тоже почти полный узел, выполняется split двух узлов в три: содержимое обоих узлов и разделитель из родителя распределяется по трём новым узлам, каждый заполненный на 2/3.
До: [...| 30 |...]
/ \
[10|20|25|28] [35|38|40|45] ← оба переполнены
После: [...| 25 | 38 |...]
/ | \
[10|20] [28|30|35] [40|45] ← три узла вместо двухУдаление
Удаление зеркально вставке. Если узел стал слишком пустым (меньше 2/3), проверяется сосед. Если у соседа есть лишние ключи — перераспределяем (как borrowing в B-дереве). Если соседи тоже на минимуме — merge трёх узлов в два:
До: [...| 20 | 40 |...]
/ | \
[10] [25|30] [45] ← все на минимуме
После: [...| 30 |...]
/ \
[10|20|25] [40|45] ← два узла на 2/3Сравнение B-дерева, B+ дерева и B* дерева
| Характеристика | B-дерево | B+ дерево | B* дерево |
|---|---|---|---|
| Минимальное заполнение | 1/2 | 1/2 | 2/3 |
| Данные | Во всех узлах | Только в листьях | Зависит от варианта |
| Связь листьев | Нет | Связный список | Зависит от варианта |
| Split | 1 → 2 узла | 1 → 2 узла | 2 → 3 узла |
| Merge | 2 → 1 узел | 2 → 1 узел | 3 → 2 узла |
| Перераспределение при вставке | Нет | Нет | Да (перед split) |
| Сложность реализации | Базовая | Средняя | Высокая |
Применение
B* дерево поднимает нижнюю границу заполнения с 1/2 до 2/3. Для того же индекса на 10 миллионов записей максимальный оверхед пустого места падает с 50% до 33% — экономия десятков мегабайт. Более плотное заполнение узлов означает меньше узлов, а меньше узлов — потенциально на один уровень ниже дерево, то есть на одно чтение с диска меньше. Цена — более сложные обновления: перераспределение и split/merge с участием трёх узлов увеличивают число чтений и записей страниц на операцию.
B-дерево и его варианты оптимизируют поиск по ключу целого значения: найти запись, где salary = 50000, или все записи, где salary BETWEEN 40000 AND 60000. Но что если нужно искать внутри составных значений — найти все документы, содержащие слово «ruby», или все строки, где массив тегов содержит элемент «postgresql»? Полный перебор всех записей — O(n). Нужна структура, которая «переворачивает» отношение: от элемента к списку записей, где он встречается. Эту задачу решает инвертированный индекс.
Sources
- Knuth, D. The Art of Computer Programming, Vol. 3: Sorting and Searching. https://www-cs-faculty.stanford.edu/~knuth/taocp.html
- Comer, D. (1979). The Ubiquitous B-Tree. https://doi.org/10.1145/356770.356776
Sources
- Knuth, D. The Art of Computer Programming, Vol. 3: Sorting and Searching. https://www-cs-faculty.stanford.edu/~knuth/taocp.html
- Comer, D. (1979). The Ubiquitous B-Tree. https://doi.org/10.1145/356770.356776