before refactor index store to complex file-line pattern

This commit is contained in:
2026-03-12 16:13:44 +06:00
parent 491ccbea89
commit 8ba956d8c5
21 changed files with 7804 additions and 57 deletions

View File

@@ -0,0 +1,177 @@
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
}