Просмотр исходного кода

perf: maintain warm indexes and counts across exclusive commits (#22)

Danilo Fragoso 2 дней назад
Родитель
Сommit
f1e4223c8c
6 измененных файлов с 290 добавлено и 50 удалено
  1. 4 0
      README.md
  2. 66 0
      pkg/executor/cache_commit_test.go
  3. 151 0
      pkg/storage/cache_commit.go
  4. 38 45
      pkg/storage/table.go
  5. 28 2
      pkg/storage/tx.go
  6. 3 3
      pkg/storage/unique_cache.go

+ 4 - 0
README.md

@@ -546,6 +546,10 @@ Rollback, compare failure, DDL, restart, and ambiguous transport errors never
 leave speculative ownership entries. NULL-bearing keys remain exempt; composite
 leave speculative ownership entries. NULL-bearing keys remain exempt; composite
 keys use the constraint encoder, not the SELECT candidate encoder. Expression
 keys use the constraint encoder, not the SELECT candidate encoder. Expression
 UNIQUE constraints retain scan validation until evaluator determinism is proven.
 UNIQUE constraints retain scan validation until evaluator determinism is proven.
+Exclusive-gate commits also maintain warm column-based SELECT indexes and exact
+counts incrementally. Shared-gate optimistic commits still invalidate those read
+caches to avoid publishing them out of durable CAS order. Expression read indexes
+are rebuilt after such commits instead of assuming evaluator determinism.
 
 
 **PRAGMA:**
 **PRAGMA:**
 ```sql
 ```sql

+ 66 - 0
pkg/executor/cache_commit_test.go

@@ -0,0 +1,66 @@
+package executor
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/danfragoso/pizzasql-next/pkg/storage"
+	"github.com/danfragoso/pizzasql-next/pkg/testkv"
+)
+
+func TestExclusiveCommitsMaintainWarmIndexesAndCounts(t *testing.T) {
+	kv := testkv.New(t)
+	pool := kv.Pool(8)
+	defer pool.Close()
+	schema := storage.NewSchemaManager(pool, "cache_commit")
+	table := storage.NewTableManager(pool, schema, "cache_commit")
+	e := newExec(schema, table)
+	execMust(t, e, "CREATE TABLE t (pk TEXT PRIMARY KEY, v INTEGER, u INTEGER UNIQUE)")
+	execMust(t, e, "CREATE INDEX idx_v ON t(v)")
+	execMust(t, e, "INSERT INTO t VALUES ('a',1,10),('b',2,20)")
+	execMust(t, e, "SELECT pk FROM t WHERE v=1")
+	execMust(t, e, "SELECT pk FROM t WHERE u=10")
+	execMust(t, e, "SELECT COUNT(*) FROM t")
+	check := func(sql string, want [][]interface{}) {
+		t.Helper()
+		before := kv.RequestCount(10)
+		got := execMust(t, e, sql).Rows
+		if len(got) != len(want) || (len(got) > 0 && !reflect.DeepEqual(got, want)) {
+			t.Fatalf("%s: got %v want %v", sql, got, want)
+		}
+		if pages := kv.RequestCount(10) - before; pages != 0 {
+			t.Fatalf("%s rebuilt a warm cache (%d pages)", sql, pages)
+		}
+	}
+	execMust(t, e, "INSERT INTO t VALUES ('c',3,30)")
+	check("SELECT pk FROM t WHERE v=3", [][]interface{}{{"c"}})
+	check("SELECT COUNT(*) FROM t", [][]interface{}{{int64(3)}})
+	execMust(t, e, "UPDATE t SET v=4,u=40 WHERE pk='c'")
+	check("SELECT pk FROM t WHERE v=3", nil)
+	check("SELECT pk FROM t WHERE v=4", [][]interface{}{{"c"}})
+	check("SELECT pk FROM t WHERE u=30", nil)
+	check("SELECT pk FROM t WHERE u=40", [][]interface{}{{"c"}})
+	execMust(t, e, "UPDATE t SET pk='z' WHERE pk='c'")
+	check("SELECT pk FROM t WHERE v=4", [][]interface{}{{"z"}})
+	check("SELECT pk FROM t WHERE u=40", [][]interface{}{{"z"}})
+	execMust(t, e, "BEGIN")
+	execMust(t, e, "DELETE FROM t WHERE pk='a'")
+	execMust(t, e, "INSERT INTO t VALUES ('a',9,90)")
+	execMust(t, e, "COMMIT")
+	check("SELECT pk FROM t WHERE v=1", nil)
+	check("SELECT pk FROM t WHERE v=9", [][]interface{}{{"a"}})
+	check("SELECT COUNT(*) FROM t", [][]interface{}{{int64(3)}})
+	execMust(t, e, "UPDATE t SET u=110-u WHERE pk IN ('a','b')")
+	check("SELECT pk FROM t WHERE u=20", [][]interface{}{{"a"}})
+	check("SELECT pk FROM t WHERE u=90", [][]interface{}{{"b"}})
+	execMust(t, e, "BEGIN")
+	execMust(t, e, "INSERT INTO t VALUES ('c',3,30)")
+	execMust(t, e, "ROLLBACK")
+	check("SELECT pk FROM t WHERE v=3", nil)
+	execMust(t, e, "DELETE FROM t WHERE pk='b'")
+	check("SELECT pk FROM t WHERE u=90", nil)
+	check("SELECT COUNT(*) FROM t", [][]interface{}{{int64(2)}})
+	execMust(t, e, "DELETE FROM t")
+	check("SELECT pk FROM t WHERE v=4", nil)
+	check("SELECT COUNT(*) FROM t", [][]interface{}{{int64(0)}})
+}

+ 151 - 0
pkg/storage/cache_commit.go

@@ -0,0 +1,151 @@
+package storage
+
+import "strings"
+
+type cachedRowChange struct {
+	pk     string
+	rowid  int64
+	absent bool
+	values map[string]string
+}
+
+type tableCachePlan struct {
+	table      string
+	rows       []cachedRowChange
+	indexes    []string
+	drop       []string
+	countDelta int
+}
+
+// Prepare under an exclusive table gate, before durable publication. Shared-gate
+// optimistic commits still invalidate: otherwise same-key commits could publish
+// derived state in the opposite order from their durable CAS operations.
+func (m *TableManager) prepareTableCache(table string, entries map[string]*overlayEntry, reads map[string]uint64) (*tableCachePlan, error) {
+	indexes, err := m.schema.ListTableIndexes(table)
+	if err != nil {
+		return nil, err
+	}
+	plan := &tableCachePlan{table: strings.ToLower(table)}
+	var warm []*Index
+	m.cacheMu.RLock()
+	for _, idx := range indexes {
+		name := strings.ToLower(idx.Name)
+		if _, ok := m.indexCache[name]; !ok {
+			continue
+		}
+		if !cacheableColumnIndex(idx) {
+			plan.drop = append(plan.drop, name)
+			continue
+		}
+		warm = append(warm, idx)
+		plan.indexes = append(plan.indexes, name)
+	}
+	m.cacheMu.RUnlock()
+	for key, entry := range entries {
+		if entry.absent {
+			if reads[key] != 0 {
+				plan.countDelta--
+			}
+		} else if reads[key] == 0 {
+			plan.countDelta++
+		}
+		if len(warm) == 0 {
+			continue
+		}
+		row := cachedRowChange{pk: strings.TrimPrefix(key, m.dataPrefix(table)), absent: entry.absent, values: make(map[string]string, len(warm))}
+		if !entry.absent {
+			row.rowid, _ = rowIDFromRow(entry.row)
+			for _, idx := range warm {
+				value, err := m.buildIndexValue(entry.row, idx.Columns)
+				if err != nil {
+					return nil, err
+				}
+				row.values[strings.ToLower(idx.Name)] = formatIndexValue(value)
+			}
+		}
+		plan.rows = append(plan.rows, row)
+	}
+	return plan, nil
+}
+
+func (m *TableManager) applyTableCache(plan *tableCachePlan) {
+	m.cacheMu.Lock()
+	defer m.cacheMu.Unlock()
+	for _, name := range plan.drop {
+		delete(m.indexCache, name)
+		delete(m.indexRowValues, name)
+		delete(m.indexTable, name)
+	}
+	keys := m.rowKeyCache[plan.table]
+	reverse := m.keyRowCache[plan.table]
+	// Remove every previous owner before adding any replacement (PK moves, rowid
+	// replacement and swaps must not depend on Go map iteration order).
+	for _, row := range plan.rows {
+		id, ok := reverse[row.pk]
+		if !ok {
+			continue
+		}
+		for _, name := range plan.indexes {
+			if value, ok := m.indexRowValues[name][id]; ok {
+				m.removeIndexEntryLocked(name, value, id)
+			}
+		}
+		delete(keys, id)
+		delete(reverse, row.pk)
+	}
+	for _, row := range plan.rows {
+		if row.absent {
+			continue
+		}
+		keys[row.rowid] = row.pk
+		reverse[row.pk] = row.rowid
+		for name, value := range row.values {
+			m.indexCache[name][value] = append(m.indexCache[name][value], row.rowid)
+			m.indexRowValues[name][row.rowid] = value
+		}
+	}
+	if m.countsInit[plan.table] {
+		m.counts[plan.table] += plan.countDelta
+	}
+}
+
+// Call with cacheMu held when publishing a complete durable index snapshot.
+func (m *TableManager) installIndexCacheLocked(name, table string, values map[string][]int64, keys map[int64]string) {
+	m.indexCache[name] = values
+	m.indexTable[name] = table
+	m.rowKeyCache[table] = keys
+	reverse := make(map[string]int64, len(keys))
+	for id, key := range keys {
+		reverse[key] = id
+	}
+	m.keyRowCache[table] = reverse
+	indexed := make(map[int64]string, len(keys))
+	for value, ids := range values {
+		for _, id := range ids {
+			indexed[id] = value
+		}
+	}
+	m.indexRowValues[name] = indexed
+}
+
+func (m *TableManager) removeIndexEntryLocked(name, value string, id int64) {
+	values := m.indexCache[name]
+	if values == nil {
+		return
+	}
+	ids := values[value]
+	remaining := ids[:0]
+	for _, candidate := range ids {
+		if candidate != id {
+			remaining = append(remaining, candidate)
+		}
+	}
+	if len(remaining) == 0 {
+		delete(values, value)
+	} else {
+		values[value] = remaining
+	}
+	if indexed, ok := m.indexRowValues[name][id]; ok && indexed == value {
+		delete(m.indexRowValues[name], id)
+	}
+}

+ 38 - 45
pkg/storage/table.go

@@ -31,11 +31,13 @@ type TableManager struct {
 	schema   *SchemaManager
 	schema   *SchemaManager
 	database string
 	database string
 
 
-	cacheMu     sync.RWMutex
-	uniqueCache map[string]*uniqueIndexCache
-	indexCache  map[string]map[string][]int64 // index name → indexed value → rowids
-	indexTable  map[string]string             // index name → table name
-	rowKeyCache map[string]map[int64]string   // table name → rowid → primary key
+	cacheMu        sync.RWMutex
+	uniqueCache    map[string]*uniqueIndexCache
+	indexRowValues map[string]map[int64]string   // index name → rowid → candidate key
+	keyRowCache    map[string]map[string]int64   // table name → primary key → rowid
+	indexCache     map[string]map[string][]int64 // index name → indexed value → rowids
+	indexTable     map[string]string             // index name → table name
+	rowKeyCache    map[string]map[int64]string   // table name → rowid → primary key
 	// disabledIndexes prevents a concurrent lookup from rebuilding an index
 	// disabledIndexes prevents a concurrent lookup from rebuilding an index
 	// after DROP has cleared it but before the schema entry is removed.
 	// after DROP has cleared it but before the schema entry is removed.
 	disabledIndexes map[string]bool
 	disabledIndexes map[string]bool
@@ -92,6 +94,8 @@ func NewTableManager(pool *KVPool, schema *SchemaManager, database string) *Tabl
 		schema:               schema,
 		schema:               schema,
 		database:             database,
 		database:             database,
 		uniqueCache:          make(map[string]*uniqueIndexCache),
 		uniqueCache:          make(map[string]*uniqueIndexCache),
+		indexRowValues:       make(map[string]map[int64]string),
+		keyRowCache:          make(map[string]map[string]int64),
 		indexCache:           make(map[string]map[string][]int64),
 		indexCache:           make(map[string]map[string][]int64),
 		indexTable:           make(map[string]string),
 		indexTable:           make(map[string]string),
 		rowKeyCache:          make(map[string]map[int64]string),
 		rowKeyCache:          make(map[string]map[int64]string),
@@ -206,12 +210,14 @@ func (m *TableManager) invalidateCache(table string) {
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
 	key := strings.ToLower(table)
 	key := strings.ToLower(table)
 	delete(m.rowKeyCache, key)
 	delete(m.rowKeyCache, key)
+	delete(m.keyRowCache, key)
 	delete(m.counts, key)
 	delete(m.counts, key)
 	delete(m.countsInit, key)
 	delete(m.countsInit, key)
 	delete(m.countGeneration, key)
 	delete(m.countGeneration, key)
 	for indexName, tableName := range m.indexTable {
 	for indexName, tableName := range m.indexTable {
 		if tableName == key {
 		if tableName == key {
 			delete(m.indexCache, indexName)
 			delete(m.indexCache, indexName)
+			delete(m.indexRowValues, indexName)
 			delete(m.indexTable, indexName)
 			delete(m.indexTable, indexName)
 		}
 		}
 	}
 	}
@@ -607,7 +613,7 @@ func (m *TableManager) InsertWithRowID(table string, row Row) (int64, error) {
 	}
 	}
 
 
 	m.applyUniquePlan(uniquePlan, nil)
 	m.applyUniquePlan(uniquePlan, nil)
-	if err := m.updateIndexesForRow(table, nr, true); err != nil {
+	if err := m.updateIndexesForRow(table, nr, true, key); err != nil {
 		return 0, err
 		return 0, err
 	}
 	}
 	// Publish derived index state before its generations. A reader that races
 	// Publish derived index state before its generations. A reader that races
@@ -856,7 +862,7 @@ func (m *TableManager) InsertBulkWithLastRowID(table string, rows []Row) (int, i
 		m.applyUniquePlan(uniquePlan, accepted)
 		m.applyUniquePlan(uniquePlan, accepted)
 	}
 	}
 	for i := 0; i < numOK; i++ {
 	for i := 0; i < numOK; i++ {
-		if err := m.updateIndexesForRow(table, encoded[i], true); err != nil {
+		if err := m.updateIndexesForRow(table, encoded[i], true, string(ops[i].Key)); err != nil {
 			return numOK, 0, err
 			return numOK, 0, err
 		}
 		}
 	}
 	}
@@ -881,7 +887,7 @@ func (m *TableManager) InsertBulkWithLastRowID(table string, rows []Row) (int, i
 // updateIndexesForRow adds or removes entries from already-built in-memory
 // updateIndexesForRow adds or removes entries from already-built in-memory
 // indexes. Index entries are rebuildable from durable row data, so this method
 // indexes. Index entries are rebuildable from durable row data, so this method
 // intentionally does not write idx:* keys to KV.
 // intentionally does not write idx:* keys to KV.
-func (m *TableManager) updateIndexesForRow(table string, row Row, add bool) error {
+func (m *TableManager) updateIndexesForRow(table string, row Row, add bool, actualKey string) error {
 	indexes, err := m.schema.ListTableIndexes(table)
 	indexes, err := m.schema.ListTableIndexes(table)
 	if err != nil || len(indexes) == 0 {
 	if err != nil || len(indexes) == 0 {
 		return err
 		return err
@@ -892,18 +898,19 @@ func (m *TableManager) updateIndexesForRow(table string, row Row, add bool) erro
 		return nil
 		return nil
 	}
 	}
 	tableKey := strings.ToLower(table)
 	tableKey := strings.ToLower(table)
-	tableSchema, schemaErr := m.schema.GetSchema(table)
-	if schemaErr == nil {
-		m.cacheMu.Lock()
-		if keys, initialized := m.rowKeyCache[tableKey]; initialized {
-			if add {
-				keys[rowid] = fmt.Sprintf("%v", row[tableSchema.PrimaryKey])
-			} else {
-				delete(keys, rowid)
-			}
+	m.cacheMu.Lock()
+	if keys, initialized := m.rowKeyCache[tableKey]; initialized {
+		pk := strings.TrimPrefix(actualKey, m.dataPrefix(table))
+		reverse := m.keyRowCache[tableKey]
+		if add {
+			keys[rowid] = pk
+			reverse[pk] = rowid
+		} else {
+			delete(keys, rowid)
+			delete(reverse, pk)
 		}
 		}
-		m.cacheMu.Unlock()
 	}
 	}
+	m.cacheMu.Unlock()
 
 
 	for _, idx := range indexes {
 	for _, idx := range indexes {
 		indexName := strings.ToLower(idx.Name)
 		indexName := strings.ToLower(idx.Name)
@@ -1112,10 +1119,10 @@ func (m *TableManager) UpdateByPK(table, pk string, updateFn func(Row) (Row, err
 	}
 	}
 
 
 	m.applyUniquePlan(uniquePlan, nil)
 	m.applyUniquePlan(uniquePlan, nil)
-	if err := m.updateIndexesForRow(table, oldRow, false); err != nil {
+	if err := m.updateIndexesForRow(table, oldRow, false, key); err != nil {
 		return nil, false, err
 		return nil, false, err
 	}
 	}
-	if err := m.updateIndexesForRow(table, row, true); err != nil {
+	if err := m.updateIndexesForRow(table, row, true, key); err != nil {
 		return nil, false, err
 		return nil, false, err
 	}
 	}
 	if err := m.bumpIndexPredicates(table, oldRow, row); err != nil {
 	if err := m.bumpIndexPredicates(table, oldRow, row); err != nil {
@@ -1180,7 +1187,7 @@ func (m *TableManager) DeleteByPK(table, pk string) (Row, bool, error) {
 	}
 	}
 
 
 	m.removeUniqueKey(table, key)
 	m.removeUniqueKey(table, key)
-	if err := m.updateIndexesForRow(table, row, false); err != nil {
+	if err := m.updateIndexesForRow(table, row, false, key); err != nil {
 		return nil, false, err
 		return nil, false, err
 	}
 	}
 	if err := m.bumpIndexPredicates(table, row); err != nil {
 	if err := m.bumpIndexPredicates(table, row); err != nil {
@@ -1400,9 +1407,7 @@ func (m *TableManager) ensureIndex(index *Index) error {
 
 
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
 	if _, initialized := m.indexCache[indexKey]; !initialized {
 	if _, initialized := m.indexCache[indexKey]; !initialized {
-		m.indexCache[indexKey] = values
-		m.indexTable[indexKey] = key
-		m.rowKeyCache[key] = rowKeys
+		m.installIndexCacheLocked(indexKey, key, values, rowKeys)
 	}
 	}
 	m.cacheMu.Unlock()
 	m.cacheMu.Unlock()
 
 
@@ -1428,6 +1433,10 @@ func (m *TableManager) AddIndexEntry(indexName string, colValue interface{}, row
 		}
 		}
 	}
 	}
 	values[valueKey] = append(rowids, rowid)
 	values[valueKey] = append(rowids, rowid)
+	if m.indexRowValues[indexKey] == nil {
+		m.indexRowValues[indexKey] = make(map[int64]string)
+	}
+	m.indexRowValues[indexKey][rowid] = valueKey
 	return nil
 	return nil
 }
 }
 
 
@@ -1439,24 +1448,7 @@ func (m *TableManager) RemoveIndexEntry(indexName string, colValue interface{},
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
 	defer m.cacheMu.Unlock()
 	defer m.cacheMu.Unlock()
 
 
-	values, ok := m.indexCache[indexKey]
-	if !ok {
-		return nil
-	}
-	rowids := values[valueKey]
-	newRowids := make([]int64, 0, len(rowids))
-	for _, r := range rowids {
-		if r != rowid {
-			newRowids = append(newRowids, r)
-		}
-	}
-
-	if len(newRowids) == 0 {
-		delete(values, valueKey)
-		return nil
-	}
-
-	values[valueKey] = newRowids
+	m.removeIndexEntryLocked(indexKey, valueKey, rowid)
 	return nil
 	return nil
 }
 }
 
 
@@ -1496,6 +1488,7 @@ func (m *TableManager) ClearIndex(indexName, tableName string, columns []string)
 	tableKey := strings.ToLower(tableName)
 	tableKey := strings.ToLower(tableName)
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
 	delete(m.indexCache, indexKey)
 	delete(m.indexCache, indexKey)
+	delete(m.indexRowValues, indexKey)
 	delete(m.indexTable, indexKey)
 	delete(m.indexTable, indexKey)
 	delete(m.uniqueCache, indexKey)
 	delete(m.uniqueCache, indexKey)
 	m.disabledIndexes[indexKey] = true
 	m.disabledIndexes[indexKey] = true
@@ -1508,6 +1501,7 @@ func (m *TableManager) ClearIndex(indexName, tableName string, columns []string)
 	}
 	}
 	if !rowKeysNeeded {
 	if !rowKeysNeeded {
 		delete(m.rowKeyCache, tableKey)
 		delete(m.rowKeyCache, tableKey)
+		delete(m.keyRowCache, tableKey)
 	}
 	}
 	m.cacheMu.Unlock()
 	m.cacheMu.Unlock()
 	return nil
 	return nil
@@ -1519,6 +1513,7 @@ func (m *TableManager) BuildIndex(indexName, tableName string, columns []string)
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
 	delete(m.disabledIndexes, indexKey)
 	delete(m.disabledIndexes, indexKey)
 	delete(m.indexCache, indexKey)
 	delete(m.indexCache, indexKey)
+	delete(m.indexRowValues, indexKey)
 	delete(m.indexTable, indexKey)
 	delete(m.indexTable, indexKey)
 	m.cacheMu.Unlock()
 	m.cacheMu.Unlock()
 
 
@@ -1557,9 +1552,7 @@ func (m *TableManager) BuildIndex(indexName, tableName string, columns []string)
 	}
 	}
 
 
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
-	m.indexCache[indexKey] = values
-	m.indexTable[indexKey] = strings.ToLower(tableName)
-	m.rowKeyCache[strings.ToLower(tableName)] = rowKeys
+	m.installIndexCacheLocked(indexKey, strings.ToLower(tableName), values, rowKeys)
 	m.cacheMu.Unlock()
 	m.cacheMu.Unlock()
 
 
 	return nil
 	return nil

+ 28 - 2
pkg/storage/tx.go

@@ -206,6 +206,28 @@ func (s *Session) CommitWithLSN() (uint64, error) {
 		uniquePlans[t] = plan
 		uniquePlans[t] = plan
 	}
 	}
 
 
+	exclusiveTables := make(map[string]bool)
+	for table := range uniqueTables {
+		exclusiveTables[table] = true
+	}
+	for table := range s.scanGens {
+		exclusiveTables[table] = true
+	}
+	for _, predicate := range s.predicateGens {
+		exclusiveTables[predicate.table] = true
+	}
+	cachePlans := make(map[string]*tableCachePlan)
+	for table, entries := range s.overlay {
+		if exclusiveTables[table] {
+			plan, err := s.table.prepareTableCache(table, entries, s.reads)
+			if err != nil {
+				s.resetLocked()
+				return 0, err
+			}
+			cachePlans[table] = plan
+		}
+	}
+
 	// Build the compare set from every observed key.
 	// Build the compare set from every observed key.
 	checks := make([]CompareCheck, 0, len(s.reads))
 	checks := make([]CompareCheck, 0, len(s.reads))
 	for key, lsn := range s.reads {
 	for key, lsn := range s.reads {
@@ -261,9 +283,13 @@ func (s *Session) CommitWithLSN() (uint64, error) {
 		return 0, ErrSerialization
 		return 0, ErrSerialization
 	}
 	}
 
 
-	// Advance generations and invalidate derived caches for written tables.
+	// Publish derived state before generations while retaining the table gates.
 	for t := range s.overlay {
 	for t := range s.overlay {
-		s.table.invalidateCache(t)
+		if plan := cachePlans[t]; plan != nil {
+			s.table.applyTableCache(plan)
+		} else {
+			s.table.invalidateCache(t)
+		}
 		s.table.applyUniquePlan(uniquePlans[t], nil)
 		s.table.applyUniquePlan(uniquePlans[t], nil)
 		s.table.bumpIndexPredicateWildcard(t)
 		s.table.bumpIndexPredicateWildcard(t)
 		s.table.bumpGeneration(t)
 		s.table.bumpGeneration(t)

+ 3 - 3
pkg/storage/unique_cache.go

@@ -70,7 +70,7 @@ func (m *TableManager) ensureUniqueCaches(table string, indexes []*Index) (map[s
 	var missing []*Index
 	var missing []*Index
 	m.cacheMu.RLock()
 	m.cacheMu.RLock()
 	for _, idx := range indexes {
 	for _, idx := range indexes {
-		if cache := m.uniqueCache[strings.ToLower(idx.Name)]; cache != nil && cacheableUniqueIndex(idx) {
+		if cache := m.uniqueCache[strings.ToLower(idx.Name)]; cache != nil && cacheableColumnIndex(idx) {
 			caches[idx.Name] = cache
 			caches[idx.Name] = cache
 		} else {
 		} else {
 			missing = append(missing, idx)
 			missing = append(missing, idx)
@@ -123,7 +123,7 @@ func (m *TableManager) ensureUniqueCaches(table string, indexes []*Index) (map[s
 	}
 	}
 	m.cacheMu.Lock()
 	m.cacheMu.Lock()
 	for _, idx := range missing {
 	for _, idx := range missing {
-		if cacheableUniqueIndex(idx) {
+		if cacheableColumnIndex(idx) {
 			m.uniqueCache[strings.ToLower(idx.Name)] = caches[idx.Name]
 			m.uniqueCache[strings.ToLower(idx.Name)] = caches[idx.Name]
 		}
 		}
 	}
 	}
@@ -133,7 +133,7 @@ func (m *TableManager) ensureUniqueCaches(table string, indexes []*Index) (map[s
 
 
 // Expression evaluators have no determinism contract. Rebuild these constraints
 // Expression evaluators have no determinism contract. Rebuild these constraints
 // for each validation rather than freezing time/random/function-dependent values.
 // for each validation rather than freezing time/random/function-dependent values.
-func cacheableUniqueIndex(index *Index) bool {
+func cacheableColumnIndex(index *Index) bool {
 	for _, column := range index.Columns {
 	for _, column := range index.Columns {
 		if column.Expression != "" {
 		if column.Expression != "" {
 			return false
 			return false