Files
elowdb-go/INDEXES.md

6.5 KiB
Raw Permalink Blame History

Индексы в LineDB

Обзор

Индексы ускоряют поиск записей по полям при вызове ReadByFilter. Без индексов каждый запрос сканирует весь JSONLфайл. С индексами для простых фильтров вида { field: value } используется предварительно построенный индекс, и возвращаются только подходящие записи.

Как это работает

  1. Конфигурация: в JSONLFileOptions задаётся IndexedFields — список полей, по которым строится индекс.
  2. Хранилище: данные индекса лежат в IndexStore — реализация интерфейса, которую можно подключать.
  3. При Init: если есть коллекции с IndexedFields, индекс строится из текущих данных.
  4. При Insert: новые записи добавляются в индекс.
  5. При Update/Delete: индекс коллекции полностью перестраивается.
  6. При ReadByFilter: если фильтр — одно поле из IndexedFields и точное совпадение, используется индекс; иначе — полный скан файла.

Интерфейс IndexStore

type IndexStore interface {
    // Возвращает индексы строк (0-based) для заданного значения поля.
    Lookup(collection, field, value string) ([]int, error)

    // Полная пересборка индекса по всем записям коллекции.
    Rebuild(collection string, fields []string, records []any) error

    // Очистить индекс коллекции.
    Clear(collection string) error
}
  • Lookup — поиск по (collection, field, value), возвращает номера строк в файле.
  • Rebuild — полная пересборка индекса коллекции (использует порядок записей как номера строк).
  • Clear — очистить индекс коллекции.

Реализации

1. InMemoryIndexStore (по умолчанию)

Используется автоматически при IndexedFields и IndexStore == nil:

initOptions := &linedb.LineDbInitOptions{
    DBFolder: "./data",
    Collections: []linedb.JSONLFileOptions{
        {
            CollectionName: "users",
            AllocSize:      256,
            IndexedFields:  []string{"id", "email", "name"},
        },
    },
}
db := linedb.NewLineDb(nil)
db.Init(false, initOptions)
// Индекс создаётся в памяти автоматически

Явная установка:

store := linedb.NewInMemoryIndexStore()
db := linedb.NewLineDb(&linedb.LineDbOptions{IndexStore: store})
db.Init(false, initOptions)

2. MemcachedIndexStore

Хранит индексы в Memcached. Требуется реализовать интерфейс MemcachedClient:

type MemcachedClient interface {
    Get(key string) ([]byte, error)
    Set(key string, value []byte, expireSeconds int) error
    Delete(key string) error
}

Пример с gomemcache:

import "github.com/bradfitz/gomemcache/memcache"

// Адаптер под MemcachedClient
type memcacheAdapter struct {
    c *memcache.Client
}

func (m *memcacheAdapter) Get(key string) ([]byte, error) {
    it, err := m.c.Get(key)
    if err == memcache.ErrCacheMiss {
        return nil, nil
    }
    if err != nil {
        return nil, err
    }
    return it.Value, nil
}

func (m *memcacheAdapter) Set(key string, value []byte, exp int) error {
    return m.c.Set(&memcache.Item{Key: key, Value: value, Expiration: int32(exp)})
}

func (m *memcacheAdapter) Delete(key string) error {
    return m.c.Delete(key)
}

// Использование
client := memcache.New("127.0.0.1:11211")
store, err := linedb.NewMemcachedIndexStore(linedb.MemcachedIndexStoreOptions{
    Client:        &memcacheAdapter{c: client},
    KeyPrefix:     "linedb:idx:",
    ExpireSeconds: 3600,
})
if err != nil {
    log.Fatal(err)
}
db := linedb.NewLineDb(&linedb.LineDbOptions{IndexStore: store})
db.Init(false, initOptions)

Подключение реализации

Вариант A: In-memory по умолчанию

Ничего не указывать — при наличии IndexedFields создаётся in-memory индекс:

db := linedb.NewLineDb(nil)
initOptions.Collections[0].IndexedFields = []string{"id", "email"}
db.Init(false, initOptions)

Вариант B: Явно in-memory

store := linedb.NewInMemoryIndexStore()
db := linedb.NewLineDb(&linedb.LineDbOptions{IndexStore: store})
db.Init(false, initOptions)

Вариант C: Memcached

store, _ := linedb.NewMemcachedIndexStore(linedb.MemcachedIndexStoreOptions{
    Client: myMemcachedClient,
})
db := linedb.NewLineDb(&linedb.LineDbOptions{IndexStore: store})
db.Init(false, initOptions)

Вариант D: Собственная реализация

Реализуйте интерфейс IndexStore и передайте её в LineDbOptions.IndexStore.

Ограничения

  • Индекс используется только при одном поле в фильтре и точном совпадении значения.
  • Для партиционированных коллекций индекс не используется.
  • MemcachedIndexStore.Clear не удаляет ключи (Memcached не поддерживает поиск по префиксу); старые ключи исчезнут по TTL.
  • При Update и Delete индекс перестраивается полностью (чтение всех записей и Rebuild).

Примеры

// Поиск по email (если email в IndexedFields)
users, err := db.ReadByFilter(map[string]any{"email": "alice@test.com"}, "users", opts)

// Поиск по id
user, err := db.ReadByFilter(map[string]any{"id": 1}, "users", opts)

// Фильтр по нескольким полям — индекс не используется, идёт полный скан
users, err := db.ReadByFilter(map[string]any{"name": "alice", "active": true}, "users", opts)