before refactor index store to complex file-line pattern
This commit is contained in:
172
INDEXES.md
Normal file
172
INDEXES.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Индексы в 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)
|
||||
```
|
||||
Reference in New Issue
Block a user