Keep a condition's looks in the store, so a look that changes nothing writes no bucket (issue 420)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

Every look rewrote the condition's value in the conditions bucket, because the
value carried the count of looks, the last look and the newest evidence: 131 647
writes in four days for twelve open conditions, against ADR 0289's rule that a
bucket is written only when what it describes changed. The looks now live in the
controller's store (migration 0095, condition_looks), the bucket holds what a
condition says and is written only when that changes, and the conditions verb and
the condition events put the looks back.
This commit is contained in:
jochen
2026-10-12 12:11:13 +02:00
parent 22a2f73f7b
commit dcdafd37f0
18 changed files with 803 additions and 38 deletions
+18 -2
View File
@@ -12,10 +12,14 @@ import (
// InMemory is a store and a history held in this process: for tests, and for nothing else — a
// condition kept here is forgotten by a restart, which is the fault the store exists to remove.
type InMemory struct {
// MemoryLooks keeps its conditions' looks, as the controller's store does beside the bucket.
MemoryLooks
mu sync.Mutex
values map[string]Entry
revision uint64
events []Event
// writes counts every create, update and delete, so a test can say a look wrote nothing.
writes int
events []Event
// fail, when set, is what every read and write answers: a store that is away. It is set only
// through SetFail, under the lock, because the keeper's telling goroutine reads it while a test
// takes the store away.
@@ -23,7 +27,9 @@ type InMemory struct {
}
// NewInMemory is an empty store.
func NewInMemory() *InMemory { return &InMemory{values: map[string]Entry{}} }
func NewInMemory() *InMemory {
return &InMemory{values: map[string]Entry{}, MemoryLooks: MemoryLooks{looks: map[string]Looks{}}}
}
// SetFail makes every read and write answer err from now on, or none when err is nil.
func (m *InMemory) SetFail(err error) {
@@ -52,10 +58,18 @@ func (m *InMemory) Create(_ context.Context, key string, value []byte) error {
return ErrMoved
}
m.revision++
m.writes++
m.values[key] = Entry{Value: value, Revision: m.revision}
return nil
}
// Writes is how many creates, updates and deletes the store took.
func (m *InMemory) Writes() int {
m.mu.Lock()
defer m.mu.Unlock()
return m.writes
}
func (m *InMemory) Update(_ context.Context, key string, value []byte, revision uint64) error {
m.mu.Lock()
defer m.mu.Unlock()
@@ -66,6 +80,7 @@ func (m *InMemory) Update(_ context.Context, key string, value []byte, revision
return ErrMoved
}
m.revision++
m.writes++
m.values[key] = Entry{Value: value, Revision: m.revision}
return nil
}
@@ -79,6 +94,7 @@ func (m *InMemory) Delete(_ context.Context, key string, revision uint64) error
if e, ok := m.values[key]; !ok || e.Revision != revision {
return ErrMoved
}
m.writes++
delete(m.values, key)
return nil
}