Files
elowdb-go/pkg/linedb/index_memcached.go

178 lines
5.8 KiB
Go
Raw 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.

package linedb
import (
"encoding/json"
"fmt"
"sync"
)
// MemcachedClient — минимальный интерфейс для работы с Memcached.
// Реализуйте его поверх вашего клиента (например github.com/bradfitz/gomemcache/memcache).
type MemcachedClient interface {
Get(key string) ([]byte, error)
Set(key string, value []byte, expireSeconds int) error
Delete(key string) error
}
// MemcachedIndexStore — реализация IndexStore с хранением индексов в Memcached.
type MemcachedIndexStore struct {
client MemcachedClient
prefix string
expireSec int
mu sync.Mutex
}
// MemcachedIndexStoreOptions — опции для MemcachedIndexStore.
type MemcachedIndexStoreOptions struct {
Client MemcachedClient
KeyPrefix string // префикс ключей (по умолчанию "linedb:idx:")
ExpireSeconds int // TTL записей (0 = без истечения)
}
// NewMemcachedIndexStore создаёт IndexStore с бэкендом Memcached.
func NewMemcachedIndexStore(opts MemcachedIndexStoreOptions) (*MemcachedIndexStore, error) {
if opts.Client == nil {
return nil, fmt.Errorf("MemcachedClient is required")
}
prefix := opts.KeyPrefix
if prefix == "" {
prefix = "linedb:idx:"
}
return &MemcachedIndexStore{
client: opts.Client,
prefix: prefix,
expireSec: opts.ExpireSeconds,
}, nil
}
func (s *MemcachedIndexStore) memKey(collection, field, value string) string {
// Memcached ограничивает длину ключа (обычно 250 байт)
k := s.prefix + collection + ":" + field + ":" + value
if len(k) > 250 {
// Хэшируем длинные значения — упрощённо берём последние 200 символов
if len(value) > 200 {
k = s.prefix + collection + ":" + field + ":" + value[len(value)-200:]
}
}
return k
}
// IndexRecord добавляет запись в индекс.
func (s *MemcachedIndexStore) IndexRecord(collection string, fields []string, record map[string]any, lineIndex int) {
if len(fields) == 0 || record == nil {
return
}
for _, field := range fields {
val := getFieldValue(record, field)
key := s.memKey(collection, field, val)
var list []int
if data, err := s.client.Get(key); err == nil && len(data) > 0 {
_ = json.Unmarshal(data, &list)
}
list = append(list, lineIndex)
if data, err := json.Marshal(list); err == nil {
_ = s.client.Set(key, data, s.expireSec)
}
}
}
// UnindexRecord удаляет запись из индекса.
func (s *MemcachedIndexStore) UnindexRecord(collection string, fields []string, record map[string]any, lineIndex int) {
if len(fields) == 0 || record == nil {
return
}
for _, field := range fields {
val := getFieldValue(record, field)
key := s.memKey(collection, field, val)
data, err := s.client.Get(key)
if err != nil || len(data) == 0 {
continue
}
var list []int
if json.Unmarshal(data, &list) != nil {
continue
}
var newList []int
for _, i := range list {
if i != lineIndex {
newList = append(newList, i)
}
}
if len(newList) == 0 {
_ = s.client.Delete(key)
} else if data2, err := json.Marshal(newList); err == nil {
_ = s.client.Set(key, data2, s.expireSec)
}
}
}
// Lookup ищет индексы строк по полю и значению.
func (s *MemcachedIndexStore) Lookup(collection, field, value string) ([]int, error) {
key := s.memKey(collection, field, value)
data, err := s.client.Get(key)
if err != nil {
return nil, nil
}
if len(data) == 0 {
return nil, nil
}
var indexes []int
if json.Unmarshal(data, &indexes) != nil {
return nil, nil
}
// Возвращаем копию, чтобы вызывающий код не модифицировал внутренний срез
out := make([]int, len(indexes))
copy(out, indexes)
return out, nil
}
// Rebuild перестраивает индекс коллекции (удаляет старые ключи по префиксу и записывает новые).
func (s *MemcachedIndexStore) Rebuild(collection string, fields []string, records []any) error {
s.mu.Lock()
defer s.mu.Unlock()
// Memcached не поддерживает перечисление ключей по шаблону.
// Стратегия: перезаписываем все ключи для текущих записей.
// Старые ключи с другими значениями останутся до TTL.
// Строим value -> []lineIndex для каждого поля
byFieldValue := make(map[string]map[string][]int)
for idx, rec := range records {
recMap, ok := rec.(map[string]any)
if !ok {
continue
}
for _, field := range fields {
val := getFieldValue(recMap, field)
if byFieldValue[field] == nil {
byFieldValue[field] = make(map[string][]int)
}
byFieldValue[field][val] = append(byFieldValue[field][val], idx)
}
}
for field, valMap := range byFieldValue {
for val, list := range valMap {
key := s.memKey(collection, field, val)
data, err := json.Marshal(list)
if err != nil {
continue
}
if err := s.client.Set(key, data, s.expireSec); err != nil {
return fmt.Errorf("memcached set %s: %w", key, err)
}
}
}
return nil
}
// Clear очищает индекс коллекции. Memcached не поддерживает delete by pattern,
// поэтому метод является заглушкой — старые ключи истекут по TTL.
func (s *MemcachedIndexStore) Clear(collection string) error {
_ = collection
// Опционально: сохранить в отдельном ключе список всех ключей индекса
// и удалять их по одному. Упрощённо — ничего не делаем.
return nil
}