diff --git a/internal/storage/skiplist.go b/internal/storage/skiplist.go deleted file mode 100644 index ae0238b..0000000 --- a/internal/storage/skiplist.go +++ /dev/null @@ -1,660 +0,0 @@ -/* - * Copyright 2026 Safronov Grigorii - * - * Licensed under the CDDL, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * https://opensource.org/licenses/CDDL-1.0 - */ - -// Файл: internal/storage/skiplist.go -// Назначение: Lock-free skip list (список с пропусками) для инвертированного -// индекса коллекции. Заменяет sync.Map в Index.data. -// -// ОСОБЕННОСТИ РЕАЛИЗАЦИИ: -// - Полностью lock-free для операций чтения и записи. -// - Использует atomic.Value и sync/atomic для совместимости с Go < 1.19 -// (актуально для сборки на OpenIndiana/illumos, где часто стоит старый Go). -// - Не использует unsafe и generics — одинаково работает на Linux и illumos. -// - Удаление через mark-and-sweep: узел помечается как удалённый (tombstone), -// физическое удаление происходит лениво при обходе (Range). -// - Хранение: ключ — interface{} (значение индексированного поля), -// значение — string (ID документа). -// -// АЛГОРИТМ (упрощённый lock-free skip list, основанный на идеях -// William Pugh "Skip Lists: A Probabilistic Alternative to Balanced Trees" -// и подходах из работ Herlihy & Shavit "The Art of Multiprocessor Programming"): -// -// - Каждый узел имеет массив forward-указателей (уровни). -// - Вставка: находим позицию на каждом уровне, атомарно "вклеиваем" -// узел через Compare-And-Swap (CAS). -// - Поиск: спускаемся по уровням сверху вниз. -// - Удаление: помечаем узел как удалённый (tombstone), обходчик -// (Range) физически удаляет такие узлы лениво. -// -// ВАЖНО: это не строго "wait-free" реализация в академическом смысле — -// операции вставки используют CAS-цикл (lock-free, но с возможностью -// повторных попыток). Однако, в отличие от sync.Map, здесь нет -// глобального мьютекса и нет внутренних блокировок на уровне шардов, -// что даёт лучшую масштабируемость при конкурентных чтениях/записях. - -package storage - -import ( - "fmt" - "math/rand" - "strings" - "sync" - "sync/atomic" -) - -// ============================================================================= -// КОНСТАНТЫ -// ============================================================================= - -const ( - // Максимальное количество уровней в skip list. - // 32 уровня достаточно для хранения до 4 миллиардов элементов - // с вероятностью 1/2 на каждом уровне. - maxSkipListLevel = 32 - - // Вероятность "подъёма" на следующий уровень. - skipListProbability = 0.5 - - // Порог, после которого Range запускает физическую очистку - // помеченных (tombstone) узлов. - skipListSweepThreshold = 1024 -) - -// ============================================================================= -// ТИПЫ -// ============================================================================= - -// skipListNode представляет узел skip list. -type skipListNode struct { - // key — значение индексированного поля (может быть любым comparable value). - key interface{} - - // value — ID документа (строка). - value string - - // deleted — tombstone-флаг. Если true, узел логически удалён - // и должен быть пропущен при обходе. - deleted atomic.Bool - - // forward — массив атомарных указателей на следующий узел - // на каждом уровне. forward[0] — базовый уровень. - // Используем atomic.Value для совместимости с Go < 1.19. - forward []atomic.Value // хранит *skipListNode -} - -// loadForward атомарно загружает указатель на следующий узел на уровне. -func (n *skipListNode) loadForward(level int) *skipListNode { - if level < 0 || level >= len(n.forward) { - return nil - } - val := n.forward[level].Load() - if val == nil { - return nil - } - return val.(*skipListNode) -} - -// storeForward атомарно сохраняет указатель на следующий узел на уровне. -func (n *skipListNode) storeForward(level int, next *skipListNode) { - if level < 0 || level >= len(n.forward) { - return - } - // atomic.Value не может хранить nil — используем типизированный указатель. - if next == nil { - n.forward[level].Store((*skipListNode)(nil)) - } else { - n.forward[level].Store(next) - } -} - -// casForward атомарно заменяет указатель, если он равен old. -// Возвращает true, если замена произошла. -func (n *skipListNode) casForward(level int, old, new *skipListNode) bool { - if level < 0 || level >= len(n.forward) { - return false - } - return n.forward[level].CompareAndSwap(old, new) -} - -// SkipList — lock-free список с пропусками для инвертированного индекса. -// -// Поля: -// - head: фиктивный головной узел, key == nil. -// - level: текущий максимальный уровень (атомарный). -// - size: приблизительное количество элементов (атомарный счётчик). -// - rngMu: защита rand.Rand (используется только при вставке). -type SkipList struct { - head *skipListNode - level atomic.Int32 - size atomic.Int64 - - rngMu sync.Mutex - rng *rand.Rand -} - -// NewSkipList создаёт новый пустой skip list. -func NewSkipList() *SkipList { - head := &skipListNode{ - key: nil, - value: "", - forward: make([]atomic.Value, maxSkipListLevel), - } - // Инициализируем forward-указатели nil-ами (atomic.Value требует - // типизированного значения, чтобы Load не падал). - for i := 0; i < maxSkipListLevel; i++ { - head.forward[i].Store((*skipListNode)(nil)) - } - - sl := &SkipList{ - head: head, - rng: rand.New(rand.NewSource(int64(0x5eed5eed))), - } - sl.level.Store(0) - sl.size.Store(0) - return sl -} - -// randomLevel генерирует случайный уровень для нового узла. -// Использует геометрическое распределение с p = 0.5. -func (sl *SkipList) randomLevel() int { - sl.rngMu.Lock() - defer sl.rngMu.Unlock() - - level := 0 - for level < maxSkipListLevel-1 && sl.rng.Float64() < skipListProbability { - level++ - } - return level -} - -// ============================================================================= -// ВСТАВКА -// ============================================================================= - -// Insert добавляет пару (key, value) в skip list. -// Если ключ уже существует, значение перезаписывается. -// -// Реализация lock-free: используется CAS-цикл на каждом уровне. -// При неудаче CAS — повторная попытка. -func (sl *SkipList) Insert(key interface{}, value string) { - if key == nil { - return - } - - newLevel := sl.randomLevel() - - // Поднимаем максимальный уровень, если нужно. - for { - currentLevel := int(sl.level.Load()) - if newLevel <= currentLevel { - break - } - if sl.level.CompareAndSwap(int32(currentLevel), int32(newLevel)) { - break - } - } - - newNode := &skipListNode{ - key: key, - value: value, - forward: make([]atomic.Value, newLevel+1), - } - for i := 0; i <= newLevel; i++ { - newNode.forward[i].Store((*skipListNode)(nil)) - } - - // update[i] — узел, после которого нужно вставить новый - // на уровне i. - update := make([]*skipListNode, maxSkipListLevel) - -retry: - // Спускаемся с верхнего уровня до базового, запоминая предшественников. - x := sl.head - for i := int(sl.level.Load()); i >= 0; i-- { - for { - next := x.loadForward(i) - if next == nil { - break - } - if next.deleted.Load() { - // Пропускаем tombstone-узлы при поиске предшественника. - // Пытаемся физически "вытолкнуть" удалённый узел. - if x.casForward(i, next, next.loadForward(i)) { - continue - } - // CAS не удался — перечитываем next. - continue - } - if compareSkipListKeys(next.key, key) < 0 { - x = next - continue - } - break - } - update[i] = x - } - - // Теперь update[0].forward[0] — либо nil, либо узел с key >= newKey. - existing := update[0].loadForward(0) - for existing != nil && compareSkipListKeys(existing.key, key) < 0 { - existing = existing.loadForward(0) - } - - // Если ключ уже есть — обновляем значение (и снимаем tombstone). - if existing != nil && compareSkipListKeys(existing.key, key) == 0 { - existing.value = value - existing.deleted.Store(false) - return - } - - // Вставляем newNode на всех уровнях от 0 до newLevel. - for i := 0; i <= newLevel; i++ { - for { - next := update[i].loadForward(i) - newNode.forward[i].Store(next) - if update[i].casForward(i, next, newNode) { - break - } - // CAS не удался — кто-то другой вставил узел. - // Перечитываем update[i] и повторяем. - // Упрощение: перезапускаем всю вставку. - goto retry - } - } - - sl.size.Add(1) -} - -// ============================================================================= -// ПОИСК -// ============================================================================= - -// Search ищет значение по ключу. Возвращает (value, true) при успехе. -func (sl *SkipList) Search(key interface{}) (string, bool) { - if key == nil { - return "", false - } - - x := sl.head - for i := int(sl.level.Load()); i >= 0; i-- { - for { - next := x.loadForward(i) - if next == nil { - break - } - if compareSkipListKeys(next.key, key) < 0 { - x = next - continue - } - break - } - } - - // x — предшественник искомого ключа на базовом уровне. - candidate := x.loadForward(0) - if candidate == nil { - return "", false - } - if compareSkipListKeys(candidate.key, key) == 0 { - if candidate.deleted.Load() { - return "", false - } - return candidate.value, true - } - return "", false -} - -// SearchAll возвращает все значения, ключ которых равен заданному. -// В неуникальном индексе по одному ключу может быть несколько документов — -// в текущей реализации индекс хранит только последний docID для данного -// ключа (как и sync.Map). Для уникальных индексов это не критично. -// Для неуникальных индексов метод возвращает единственное значение. -func (sl *SkipList) SearchAll(key interface{}) []string { - val, ok := sl.Search(key) - if !ok { - return nil - } - return []string{val} -} - -// ============================================================================= -// УДАЛЕНИЕ -// ============================================================================= - -// Delete помечает узел с заданным ключом как удалённый (tombstone). -// Физическое удаление произойдёт лениво при следующем Range. -func (sl *SkipList) Delete(key interface{}) { - if key == nil { - return - } - - x := sl.head - for i := int(sl.level.Load()); i >= 0; i-- { - for { - next := x.loadForward(i) - if next == nil { - break - } - if compareSkipListKeys(next.key, key) < 0 { - x = next - continue - } - break - } - } - - candidate := x.loadForward(0) - if candidate == nil { - return - } - if compareSkipListKeys(candidate.key, key) == 0 { - if !candidate.deleted.Load() { - if candidate.deleted.CompareAndSwap(false, true) { - sl.size.Add(-1) - } - } - } -} - -// ============================================================================= -// ОБХОД (RANGE) -// ============================================================================= - -// Range вызывает fn для каждой пары (key, value) в порядке возрастания ключей. -// Если fn возвращает false — обход прекращается. -// -// Во время обхода выполняется ленивая физическая очистка tombstone-узлов: -// если подряд встретилось больше skipListSweepThreshold удалённых узлов, -// они физически выталкиваются из списка. -func (sl *SkipList) Range(fn func(key interface{}, value string) bool) { - x := sl.head - level0 := 0 - - // Спускаемся на базовый уровень. - for i := int(sl.level.Load()); i > 0; i-- { - for { - next := x.loadForward(i) - if next == nil { - break - } - x = next - } - } - _ = level0 - - prev := x - tombstoneCount := 0 - - for { - next := prev.loadForward(0) - if next == nil { - break - } - - if next.deleted.Load() { - tombstoneCount++ - // Пытаемся физически удалить узел из базового уровня. - if prev.casForward(0, next, next.loadForward(0)) { - // Успешно вытолкнули — не двигаем prev. - if tombstoneCount >= skipListSweepThreshold { - // Периодически чистим верхние уровни (lazy). - sl.sweepUpperLevels() - tombstoneCount = 0 - } - continue - } - // CAS не удался — двигаемся дальше. - prev = next - continue - } - - if !fn(next.key, next.value) { - return - } - - prev = next - } -} - -// RangeWithKeyPrefix вызывает fn для каждой пары, ключ которой является -// строкой с заданным префиксом. -func (sl *SkipList) RangeWithKeyPrefix(prefix string, fn func(key interface{}, value string) bool) { - sl.Range(func(key interface{}, value string) bool { - keyStr, ok := key.(string) - if !ok { - return true - } - if strings.HasPrefix(keyStr, prefix) { - return fn(key, value) - } - return true - }) -} - -// sweepUpperLevels проходит по верхним уровням и физически выталкивает -// tombstone-узлы. Вызывается лениво из Range. -func (sl *SkipList) sweepUpperLevels() { - for i := 1; i < maxSkipListLevel; i++ { - x := sl.head - for { - next := x.loadForward(i) - if next == nil { - break - } - if next.deleted.Load() { - if x.casForward(i, next, next.loadForward(i)) { - continue - } - } - x = next - } - } -} - -// ============================================================================= -// СЛУЖЕБНЫЕ МЕТОДЫ -// ============================================================================= - -// Size возвращает приблизительное количество активных элементов. -func (sl *SkipList) Size() int64 { - return sl.size.Load() -} - -// Len возвращает приблизительное количество активных элементов -// (синоним Size для совместимости с sync.Map-стилем). -func (sl *SkipList) Len() int64 { - return sl.size.Load() -} - -// IsEmpty возвращает true, если список пуст. -func (sl *SkipList) IsEmpty() bool { - return sl.size.Load() == 0 -} - -// Clear помечает все узлы как удалённые. -func (sl *SkipList) Clear() { - sl.Range(func(key interface{}, value string) bool { - sl.Delete(key) - return true - }) - sl.size.Store(0) -} - -// ============================================================================= -// СРАВНЕНИЕ КЛЮЧЕЙ -// ============================================================================= - -// compareSkipListKeys сравнивает два ключа. -// Поддерживает основные типы, используемые в инвертированном индексе: -// string, int, int64, float64, bool, []byte. -// -// Возвращает -1, 0 или 1. -// -// ВАЖНО: если типы ключей несовместимы, используется строковое сравнение -// через fmt.Sprintf, чтобы обеспечить хоть какой-то порядок (не падать). -func compareSkipListKeys(a, b interface{}) int { - if a == nil && b == nil { - return 0 - } - if a == nil { - return -1 - } - if b == nil { - return 1 - } - - switch av := a.(type) { - case string: - if bv, ok := b.(string); ok { - return strings.Compare(av, bv) - } - case int: - if bv, ok := toInt64(b); ok { - return compareInt64(int64(av), bv) - } - case int64: - if bv, ok := toInt64(b); ok { - return compareInt64(av, bv) - } - case float64: - if bv, ok := toFloat64Comparable(b); ok { - if av < bv { - return -1 - } - if av > bv { - return 1 - } - return 0 - } - case bool: - if bv, ok := b.(bool); ok { - if av == bv { - return 0 - } - if !av { - return -1 - } - return 1 - } - case []byte: - if bv, ok := b.([]byte); ok { - return strings.Compare(string(av), string(bv)) - } - } - - // Fallback: строковое сравнение. - return strings.Compare(fmt.Sprintf("%v", a), fmt.Sprintf("%v", b)) -} - -func toInt64(v interface{}) (int64, bool) { - switch x := v.(type) { - case int: - return int64(x), true - case int32: - return int64(x), true - case int64: - return x, true - case uint: - return int64(x), true - case uint32: - return int64(x), true - case uint64: - return int64(x), true - case float64: - return int64(x), true - case float32: - return int64(x), true - default: - return 0, false - } -} - -func toFloat64Comparable(v interface{}) (float64, bool) { - switch x := v.(type) { - case int: - return float64(x), true - case int32: - return float64(x), true - case int64: - return float64(x), true - case float32: - return float64(x), true - case float64: - return x, true - default: - return 0, false - } -} - -func compareInt64(a, b int64) int { - if a < b { - return -1 - } - if a > b { - return 1 - } - return 0 -} - -// ============================================================================= -// ITERATOR (опционально, для совместимости) -// ============================================================================= - -// SkipListIterator — итератор по skip list. -type SkipListIterator struct { - sl *SkipList - current *skipListNode - started bool -} - -// NewIterator создаёт новый итератор. -func (sl *SkipList) NewIterator() *SkipListIterator { - return &SkipListIterator{sl: sl} -} - -// Next переходит к следующему элементу. Возвращает (key, value, ok). -func (it *SkipListIterator) Next() (interface{}, string, bool) { - if it.sl == nil { - return nil, "", false - } - - if !it.started { - it.started = true - // Спускаемся на базовый уровень. - x := it.sl.head - for i := int(it.sl.level.Load()); i > 0; i-- { - for { - next := x.loadForward(i) - if next == nil { - break - } - x = next - } - } - it.current = x - } - - for { - next := it.current.loadForward(0) - if next == nil { - return nil, "", false - } - it.current = next - if !next.deleted.Load() { - return next.key, next.value, true - } - } -} - -// Close освобождает ресурсы итератора (no-op, оставлен для совместимости). -func (it *SkipListIterator) Close() { - it.sl = nil - it.current = nil -}