# Индексы в 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) ```