Расширяемое хеширование
Расширяемое хеширование — это метод организации хеш-таблиц, предназначенный для работы с данными, хранящимися на внешних носителях (например, на жёстких дисках), который позволяет динамически изменять размер таблицы без значительного снижения производительности. В отличие от статических хеш-таблиц, где размер фиксирован, расширяемое хеширование адаптируется к росту или уменьшению объёма данных, минимизируя количество операций ввода-вывода и перестроений структуры. Метод был предложен в 1979 году Рональдом Фэгином, Джеффри Ульманом и Дэвидом Майером.
История
Развитие баз данных и систем управления файлами в 1970-х годах выявило проблему эффективного доступа к данным на медленных носителях (магнитные диски). Традиционные хеш-таблицы требовали перехеширования всех записей при изменении размера, что было дорогостоящим. Расширяемое хеширование было разработано как решение, позволяющее выполнять операции вставки, удаления и поиска с постоянным ожидаемым временем (O(1)) при условии сбалансированного хеширования, но с возможностью плавного расширения. Первая реализация была описана в статье «Extendible Hashing: A Fast Access Method for Dynamic Files» (1979).
Принцип работы
Расширяемое хеширование основано на использовании хеш-функции, которая преобразует ключ записи в битовую строку фиксированной длины (например, 32 бита). Для адресации используется только часть этих битов — глобальная глубина (global depth). Структура состоит из двух основных компонентов:
- Каталог (directory) — массив указателей на страницы (блоки) данных. Размер каталога равен 2^g, где g — глобальная глубина.
- Страницы (buckets) — блоки фиксированного размера, хранящие записи. Каждая страница имеет свою локальную глубину (local depth), которая может быть меньше или равна глобальной.
Поиск
Для поиска записи по ключу:
- Вычисляется хеш-значение ключа.
- Берётся последние g бит хеша (или первые — зависит от реализации).
- Полученное число используется как индекс в каталоге.
- По указателю из каталога находится страница, в которой выполняется линейный поиск записи.
Вставка
При вставке новой записи возможны два сценария:
- Страница не заполнена: запись добавляется в соответствующую страницу.
- Страница заполнена: происходит расщепление страницы (split):
- Если локальная глубина страницы меньше глобальной, создаётся новая страница, и записи из переполненной страницы перераспределяются между двумя страницами на основе одного дополнительного бита хеша (глубина увеличивается на 1). Каталог не меняется, но его указатели корректируются.
- Если локальная глубина равна глобальной, то сначала удваивается каталог (его размер становится 2^(g+1)), а затем выполняется расщепление страницы. Удвоение каталога — операция, требующая выделения памяти и копирования указателей, но она происходит редко.
Удаление
При удалении записи, если страница становится пустой, может быть выполнено слияние (merge) с соседней страницей (если они имеют одинаковую локальную глубину и могут быть объединены). Если после слияния количество страниц уменьшается, каталог может быть уменьшен (сжатие), но это происходит реже, чем расширение.
Классификация и варианты
Расширяемое хеширование существует в нескольких модификациях, адаптированных под разные сценарии:
- Классическое (по Фэгину): использует каталог переменного размера, который удваивается при переполнении.
- Линейное хеширование: альтернативный метод, предложенный Уитни Литвином (1980), который не требует отдельного каталога, а расширяет таблицу постепенно, добавляя одну страницу за раз.
- Сплавное хеширование (spiral hashing): вариант, где хеш-функция генерирует последовательность адресов, что позволяет избежать резкого удвоения каталога.
- Виртуальное хеширование: использует виртуальную память для имитации каталога, что упрощает управление.
Характеристики и производительность
Основные параметры, влияющие на производительность:
- Размер страницы: обычно от 1 до 8 килобайт. Слишком маленькие страницы приводят к частым расщеплениям, слишком большие — к неэффективному использованию памяти.
- Глобальная глубина: определяет размер каталога. Чем больше глубина, тем больше каталог, но меньше вероятность коллизий.
- Локальная глубина: показывает, сколько бит хеша используется для адресации внутри конкретной страницы.
Достоинства:
- Постоянное время поиска в среднем (O(1)).
- Динамическое расширение без полного перестроения.
- Высокая эффективность при работе с внешней памятью (минимум операций ввода-вывода).
- Простота реализации.
Недостатки:
- Неравномерное распределение записей может привести к большому размеру каталога (до 2^32, что неприемлемо для 32-битных систем).
- Удвоение каталога — дорогостоящая операция, особенно при большом количестве страниц.
- Не подходит для данных с неравномерным хешированием (например, короткие строки с низкой энтропией).
- Требуется предварительное знание максимального размера хеш-функции.
Применение
Расширяемое хеширование широко используется в системах управления базами данных (СУБД) и файловых системах, где требуется быстрый доступ к данным на диске:
- Реляционные СУБД: PostgreSQL (в реализации индексов GiST и GIN), Oracle (в некоторых версиях для хеш-индексов).
- NoSQL-системы: MongoDB (для шардирования и хеш-индексов), Redis (в качестве внутренней структуры для хеш-таблиц).
- Файловые системы: ZFS (для организации каталогов), ext4 (в некоторых реализациях).
- Поисковые системы: для индексации документов и быстрого поиска по ключевым словам.
- Кэширующие системы: Memcached, Varnish (для хранения объектов в памяти).
Пример реализации
Рассмотрим простую реализацию на языке C (псевдокод): ```c
define BUCKET_SIZE 4 // Максимальное количество записей в странице
typedef struct { int keys[BUCKET_SIZE]; int values[BUCKET_SIZE]; int count; int local_depth; } Bucket;
typedef struct { Bucket **directory; int global_depth; } ExtendibleHash;
int hash(int key) { return key; // Простая хеш-функция }
void insert(ExtendibleHash ht, int key, int value) { int h = hash(key); int index = h & ((1 << ht->global_depth) - 1); // Берём последние g бит Bucket bucket = ht->directory[index];
if (bucket->count < BUCKET_SIZE) { bucket->keys[bucket->count] = key; bucket->values[bucket->count] = value; bucket->count++; } else { // Расщепление split_bucket(ht, index); // Повторная вставка insert(ht, key, value); } } ```
Критика и ограничения
Основная критика расширяемого хеширования связана с его чувствительностью к качеству хеш-функции. Если хеш-функция генерирует неравномерное распределение, каталог может разрастись до огромных размеров, а производительность упасть. Кроме того, метод не гарантирует постоянного времени вставки в худшем случае (при частых удвоениях каталога). В современных системах часто предпочитают линейное хеширование или B-деревья, которые менее чувствительны к распределению данных.
Интересные факты
- Расширяемое хеширование стало основой для многих алгоритмов в области распределённых систем, например, Consistent Hashing (согласованное хеширование), используемого в CDN и NoSQL-базах данных.
- В 1980-х годах метод активно применялся в системах управления базами данных для мэйнфреймов, таких как IMS от IBM.
- В некоторых реализациях каталог хранится не в памяти, а на диске, что позволяет обрабатывать таблицы с миллиардами записей.
Источники
- Fagin, R., Nievergelt, J., Pippenger, N., & Strong, H. R. (1979). Extendible hashing: A fast access method for dynamic files. ACM Transactions on Database Systems.
- Litwin, W. (1980). Linear hashing: A new tool for file and table addressing. Proceedings of the 6th International Conference on Very Large Data Bases.
- Knuth, D. E. (1998). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.). Addison-Wesley.
- Garcia-Molina, H., Ullman, J. D., & Widom, J. (2009). Database Systems: The Complete Book (2nd ed.). Pearson.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →