Files
elowdb-go/INDEXES.md

173 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Индексы в LineDB
## Обзор
Индексы ускоряют поиск записей по полям при вызове `ReadByFilter`. Без индексов каждый запрос сканирует весь JSONLфайл. С индексами для простых фильтров вида `{ field: value }` используется предварительно построенный индекс, и возвращаются только подходящие записи.
## Как это работает
1. **Конфигурация**: в `JSONLFileOptions` задаётся `IndexedFields` — список полей, по которым строится индекс.
2. **Хранилище**: данные индекса лежат в `IndexStore` — реализация интерфейса, которую можно подключать.
3. **При Init**: если есть коллекции с `IndexedFields`, индекс строится из текущих данных.
4. **При Insert**: новые записи добавляются в индекс.
5. **При Update/Delete**: индекс коллекции полностью перестраивается.
6. **При ReadByFilter**: если фильтр — одно поле из `IndexedFields` и точное совпадение, используется индекс; иначе — полный скан файла.
## Интерфейс IndexStore
```go
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`:
```go
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)
// Индекс создаётся в памяти автоматически
```
Явная установка:
```go
store := linedb.NewInMemoryIndexStore()
db := linedb.NewLineDb(&linedb.LineDbOptions{IndexStore: store})
db.Init(false, initOptions)
```
### 2. MemcachedIndexStore
Хранит индексы в Memcached. Требуется реализовать интерфейс `MemcachedClient`:
```go
type MemcachedClient interface {
Get(key string) ([]byte, error)
Set(key string, value []byte, expireSeconds int) error
Delete(key string) error
}
```
Пример с [gomemcache](https://github.com/bradfitz/gomemcache):
```go
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 индекс:
```go
db := linedb.NewLineDb(nil)
initOptions.Collections[0].IndexedFields = []string{"id", "email"}
db.Init(false, initOptions)
```
### Вариант B: Явно in-memory
```go
store := linedb.NewInMemoryIndexStore()
db := linedb.NewLineDb(&linedb.LineDbOptions{IndexStore: store})
db.Init(false, initOptions)
```
### Вариант C: Memcached
```go
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).
## Примеры
```go
// Поиск по 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)
```