alerts: answer history reads from the shared fold without full copies (#3093)

## Why
A heap profile of the long-running dogfood install (77.6 MB live after a
forced GC) shows the alert history projection holding 36 MB (47%). It
keeps one merged alert per occurrence for the event log's 90-day
retention: 1,895 occurrences there, with resolved snapshots averaging
about 20 KB. Reads made it much worse (#1803):
- every history read deep-copied the whole projection before applying
its limit, so the attention summary's 500-row read copied all 1,895
occurrences;
- every read with a `since` (the 30-day alert-quality window,
date-filtered history) built a fresh projection and re-decoded every
snapshot in its window.

That churn is why the same install holds about 158 MB of Go heap for 78
MB of live data.

## What changed
- One full-history fold, built and extended as before. Each occurrence
also keeps a small mark per folded event (time, durable ID, and the
`LastSeen` it contributed) and the index of its latest acknowledged
snapshot.
- A `since` read is answered from that fold. The window starts at each
occurrence's first event that the log's own `occurred_at >= since` text
comparison admits. `LastSeen` and acknowledgement come from that point
on, and occurrences are ordered by their first in-window event, exactly
as a walk of only the window would produce.
- Overlay, sort and limit run on lightweight rows under the projection
lock. Only returned rows are copied, once, by
`canonicalizeAlertHistoryForOutput`.
- The alerts contract notes the windowed-read rule and the pinning
tests.

## Measured (pulse-dev, log shaped like the dogfood install's: about
1,900 occurrences, 40k events)
| Read | main | this PR |
|---|---|---|
| 30-day window | 68.9 ms, 19.8 MB allocated | 5.2 ms, 2.8 MB |
| Latest 500 | 12.7 ms, 8.5 MB | 3.6 ms, 2.0 MB |
| Full history | 20.8 ms, 12.9 MB | 13.4 ms, 6.9 MB |

The 36 MB the fold itself holds is unchanged. The new marks add about 56
bytes per lifecycle event, about 2 MB at 40k events.

## Checks
- `TestWindowedHistoryMatchesAWalkOfOnlyTheWindow` compares every window
and limit against an independent walk of only the window (the original
algorithm). It covers 25 random histories with acknowledgements on both
sides of the boundary, out-of-order `LastSeen`, uneven fractional
seconds where RFC3339Nano text order and time order disagree,
same-instant events, a history clear, and live overlays.
- `TestWindowedHistoryReusesTheFullHistoryFold`,
`TestLimitedHistoryReadCopiesOnlyReturnedRows` and
`TestHistoryReadResultsDoNotAliasTheSharedFold` pin reuse, copying and
isolation.
- Negative controls: ignoring the window, building a fresh fold per
`since` read, and copying every row before the limit each fail their
test.
- Removing the per-row clone did not fail the aliasing test, because the
output copy already isolates results. That double copy was dropped, and
the test guards isolation end to end.
- Vet and the whole `internal/alerts/...` tree pass on pulse-dev.

## Not established
How much held heap this saves on the dogfood install itself. That needs
this build running there.

Related to #1803
This commit is contained in:
rcourtman
2026-10-21 16:13:31 +01:00
committed by GitHub
3 changed files with 497 additions and 91 deletions
@@ -1028,6 +1028,19 @@ Live active-alert overlays and returned objects are independent clones. Explicit
Since queries preserve their existing event-window semantics without evicting
the normal full-history fold. Repeated attention requests must not replay all
retained snapshots independently or accumulate behind the history connection pool.
Since queries are answered from that same fold (#1803). Each occurrence keeps one
mark per folded event (time, durable ID and the LastSeen it contributed) and the
index of its latest acknowledged snapshot. A window starts at the occurrence's
first event that the log's own `occurred_at >= since` text comparison admits,
takes LastSeen and acknowledgement from that point on, and orders occurrences by
their first in-window event as a walk of only the window would. A read selects
and limits rows before copying, so only returned rows are cloned.
`TestWindowedHistoryMatchesAWalkOfOnlyTheWindow` holds this equal to an
independent walk of only the window, and
`TestLimitedHistoryReadCopiesOnlyReturnedRows`,
`TestWindowedHistoryReusesTheFullHistoryFold` and
`TestHistoryReadResultsDoNotAliasTheSharedFold` pin the copy, reuse and
isolation rules.
PBS node-status availability is distinct from connectivity. While a connected
+221 -91
View File
@@ -17,19 +17,102 @@ import (
"github.com/rcourtman/pulse-go-rewrite/internal/alerts/eventlog"
)
// historyEventMark is what one folded lifecycle event contributed to its
// occurrence. The marks let a Since window be answered from the full fold
// instead of decoding every snapshot in the window again (#1803).
type historyEventMark struct {
at time.Time
id int64
lastSeen time.Time // the snapshot's LastSeen, or the resolution time if later
}
// historyOccurrence accumulates one alert occurrence from its lifecycle
// events, oldest to newest.
type historyOccurrence struct {
alert Alert
firstEvent time.Time
lastEvent time.Time
resolved bool
alert Alert
events []historyEventMark
// lastAcked indexes the latest event whose snapshot was acknowledged, or
// is -1. A window starting after it never saw the acknowledgement.
lastAcked int
// The latest snapshot's own acknowledgement fields, before merging carried
// an earlier acknowledgement forward onto them.
rawAcknowledged bool
rawAckTime *time.Time
rawAckUser string
}
func historyOccurrenceKey(alertID string, snapshot *Alert) string {
return alertID + "\x00" + snapshot.StartTime.UTC().Format(time.RFC3339Nano)
}
// windowStart returns the index of the first event the log's Since filter
// (occurred_at >= since, compared as RFC3339Nano text) admits, or
// len(events) when none is. Events are folded in the log's (occurred_at,
// id) text order, so the comparison is monotonic.
func (occ *historyOccurrence) windowStart(sinceKey string) int {
return sort.Search(len(occ.events), func(i int) bool {
return occ.events[i].at.UTC().Format(time.RFC3339Nano) >= sinceKey
})
}
// windowAck is the acknowledgement that folding events[start:] alone would
// produce: an acknowledgement is carried forward, so only one inside the
// window survives into it.
func (occ *historyOccurrence) windowAck(start int) (bool, *time.Time, string) {
if occ.lastAcked >= start {
return occ.alert.Acknowledged, occ.alert.AckTime, occ.alert.AckUser
}
return occ.rawAcknowledged, occ.rawAckTime, occ.rawAckUser
}
// windowLastSeen is the LastSeen that folding events[start:] alone would
// produce: the latest contribution inside the window.
func (occ *historyOccurrence) windowLastSeen(start int) time.Time {
if start == 0 {
return occ.alert.LastSeen
}
var latest time.Time
for _, mark := range occ.events[start:] {
if mark.lastSeen.After(latest) {
latest = mark.lastSeen
}
}
return latest
}
// historyRow is one candidate history entry. Rows reference the shared fold
// and are copied only when selected for output.
type historyRow struct {
key string
occ *historyOccurrence // nil for an active alert the log never saw fire
start int
firstEvent time.Time
overlay *Alert // the live active alert shown instead of the folded state
}
func (row *historyRow) ack() (bool, *time.Time, string) {
if row.overlay != nil {
return row.overlay.Acknowledged, row.overlay.AckTime, row.overlay.AckUser
}
return row.occ.windowAck(row.start)
}
// materialize returns the row's entry. It shares the fold's maps and pointers;
// projectedHistory deep-copies every returned entry through
// canonicalizeAlertHistoryForOutput before releasing the projection lock, so
// that is the only copy a read makes.
func (row *historyRow) materialize() Alert {
if row.overlay != nil {
return *row.overlay
}
alert := row.occ.alert
if row.start > 0 {
alert.LastSeen = row.occ.windowLastSeen(row.start)
alert.Acknowledged, alert.AckTime, alert.AckUser = row.occ.windowAck(row.start)
}
return alert
}
// AlertHistoryFromEvents projects the alert history list from the event
// log. It reports ok=false when no event log is enabled, letting callers
// fall back to the JSON-backed history manager.
@@ -38,73 +121,19 @@ func (m *Manager) AlertHistoryFromEvents(since time.Time, limit int) ([]Alert, b
if store == nil || !m.eventHistoryAuthoritative.Load() {
return nil, false
}
occurrences, order, err := m.historyOccurrences(store, since)
results, err := m.projectedHistory(store, since, limit)
if err != nil {
return nil, false
}
// Overlay the live active alerts: an occurrence still firing shows its
// current state (fresh LastSeen and value), exactly as the JSON
// history's continuously updated entries do. Active alerts the log
// never saw fire (log enabled mid-flight) are appended.
m.mu.RLock()
active := make([]*Alert, 0, len(m.activeAlerts))
for storageKey, alert := range m.activeAlerts {
if alert == nil {
continue
}
exported := cloneAlertForOutput(alert)
if exported == nil {
continue
}
if exported.ID == "" {
exported.ID = effectiveAlertID(alert, storageKey)
}
active = append(active, exported)
}
m.mu.RUnlock()
for _, alert := range active {
key := historyOccurrenceKey(alert.ID, alert)
if occ, exists := occurrences[key]; exists {
ack := occ.alert.Acknowledged
ackTime, ackUser := occ.alert.AckTime, occ.alert.AckUser
occ.alert = *alert
if ack && !occ.alert.Acknowledged {
occ.alert.Acknowledged = true
occ.alert.AckTime = ackTime
occ.alert.AckUser = ackUser
}
occ.resolved = false
} else if since.IsZero() || alert.StartTime.After(since) {
occurrences[key] = &historyOccurrence{
alert: *alert,
firstEvent: alert.StartTime,
lastEvent: alert.LastSeen,
}
order = append(order, key)
}
}
sort.SliceStable(order, func(i, j int) bool {
return occurrences[order[i]].firstEvent.Before(occurrences[order[j]].firstEvent)
})
results := make([]Alert, 0, len(order))
for i := len(order) - 1; i >= 0; i-- {
if limit > 0 && len(results) >= limit {
break
}
results = append(results, occurrences[order[i]].alert)
}
return m.applyCurrentNodeDisplayNames(canonicalizeAlertHistoryForOutput(results)), true
return m.applyCurrentNodeDisplayNames(results), true
}
// historyProjection is a disposable fold of the durable log, never a second
// source of truth. Its cursor is bounded by a committed event ID. Old imports,
// retention and store replacement rebuild it using the original chronological
// fold. A moving Since window keeps its original event-filter semantics and
// does not evict the full-history fold used by attention and normal polling.
// fold. A Since window is answered from this full-history fold with the same
// result as walking only the window's events (windowStart, windowAck and
// windowLastSeen), so windowed reads neither rebuild nor evict it.
type historyProjection struct {
store *eventlog.Store
boundary eventlog.ReplayBoundary
@@ -119,7 +148,7 @@ func newHistoryProjection(store *eventlog.Store) *historyProjection {
return &historyProjection{store: store, occurrences: make(map[string]*historyOccurrence)}
}
func (p *historyProjection) fold(since time.Time, throughID int64) error {
func (p *historyProjection) fold(throughID int64) error {
if throughID == 0 || throughID == p.boundary.LastID {
return nil
}
@@ -130,7 +159,7 @@ func (p *historyProjection) fold(since time.Time, throughID int64) error {
eventlog.TypeSnoozed, eventlog.TypeUnsnoozed, eventlog.TypeEscalated,
eventlog.TypeHistoryImported, eventlog.TypeHistoryCleared,
},
Since: since, AfterID: p.boundary.LastID, ThroughID: throughID,
AfterID: p.boundary.LastID, ThroughID: throughID,
}, func(event eventlog.Event) error {
// Match the log's (occurred_at, id) ordering. Equal timestamps are
// safe because all appended IDs exceed the preceding boundary.
@@ -156,72 +185,173 @@ func (p *historyProjection) fold(since time.Time, throughID int64) error {
key := historyOccurrenceKey(event.AlertID, &snapshot)
occ, exists := p.occurrences[key]
if !exists {
occ = &historyOccurrence{alert: snapshot, firstEvent: event.OccurredAt}
occ = &historyOccurrence{alert: snapshot, lastAcked: -1}
p.occurrences[key] = occ
p.order = append(p.order, key)
} else {
occ.alert = mergeHistoryAlertSnapshots(occ.alert, snapshot)
}
occ.lastEvent = event.OccurredAt
mark := historyEventMark{at: event.OccurredAt, id: event.ID, lastSeen: snapshot.LastSeen}
if event.Type == eventlog.TypeResolved {
occ.resolved = true
// The JSON history's resolve path stamps the entry's LastSeen
// with the resolution time so the row reflects the true
// duration; mirror that.
if event.OccurredAt.After(occ.alert.LastSeen) {
occ.alert.LastSeen = event.OccurredAt
}
} else {
occ.resolved = false
if event.OccurredAt.After(mark.lastSeen) {
mark.lastSeen = event.OccurredAt
}
}
occ.events = append(occ.events, mark)
if snapshot.Acknowledged {
occ.lastAcked = len(occ.events) - 1
}
occ.rawAcknowledged, occ.rawAckTime, occ.rawAckUser = snapshot.Acknowledged, snapshot.AckTime, snapshot.AckUser
return nil
})
}
func (m *Manager) historyOccurrences(store *eventlog.Store, since time.Time) (map[string]*historyOccurrence, []string, error) {
m.historyProjectionMu.Lock()
defer m.historyProjectionMu.Unlock()
// currentHistoryProjection folds the log through its current boundary and
// returns the shared projection. The caller must hold historyProjectionMu.
func (m *Manager) currentHistoryProjection(store *eventlog.Store) (*historyProjection, error) {
for {
boundary, err := store.ReplayBoundary()
if err != nil {
m.historyProjection = nil
return nil, nil, err
return nil, err
}
p := m.historyProjection
if !since.IsZero() || p == nil || p.store != store ||
if p == nil || p.store != store ||
p.boundary.RetentionRevision != boundary.RetentionRevision || p.boundary.LastID > boundary.LastID {
p = newHistoryProjection(store)
}
err = p.fold(since, boundary.LastID)
err = p.fold(boundary.LastID)
if errors.Is(err, errHistoryEventBeforeCursor) {
p = newHistoryProjection(store)
err = p.fold(since, boundary.LastID)
err = p.fold(boundary.LastID)
}
if err != nil {
m.historyProjection = nil
return nil, nil, err
return nil, err
}
after, err := store.ReplayBoundary()
if err != nil {
m.historyProjection = nil
return nil, nil, err
return nil, err
}
if after.RetentionRevision != boundary.RetentionRevision {
m.historyProjection = nil
continue
}
p.boundary = boundary
if since.IsZero() {
m.historyProjection = p
}
// The live-state overlay and callers may mutate their result. Never
// let those changes contaminate the durable fold or another reader.
occurrences := make(map[string]*historyOccurrence, len(p.occurrences))
for key, occurrence := range p.occurrences {
copy := *occurrence
copy.alert = *occurrence.alert.Clone()
occurrences[key] = &copy
}
return occurrences, append([]string(nil), p.order...), nil
m.historyProjection = p
return p, nil
}
}
// projectedHistory selects the history list, newest first, under the
// projection lock, and copies only the entries it returns. Callers may mutate
// the result without touching the shared fold or another reader's result.
func (m *Manager) projectedHistory(store *eventlog.Store, since time.Time, limit int) ([]Alert, error) {
m.historyProjectionMu.Lock()
defer m.historyProjectionMu.Unlock()
p, err := m.currentHistoryProjection(store)
if err != nil {
return nil, err
}
sinceKey := ""
if !since.IsZero() {
sinceKey = since.UTC().Format(time.RFC3339Nano)
}
rows := make([]historyRow, 0, len(p.order))
for _, key := range p.order {
occ := p.occurrences[key]
start := 0
if sinceKey != "" {
if start = occ.windowStart(sinceKey); start == len(occ.events) {
continue
}
}
rows = append(rows, historyRow{key: key, occ: occ, start: start, firstEvent: occ.events[start].at})
}
if sinceKey != "" {
// Walking only the window appends each occurrence at its first event
// inside the window, in the log's (occurred_at, id) order.
keys := make([]string, len(rows))
for i := range rows {
keys[i] = rows[i].firstEvent.UTC().Format(time.RFC3339Nano)
}
order := make([]int, len(rows))
for i := range order {
order[i] = i
}
sort.SliceStable(order, func(a, b int) bool {
ra, rb := order[a], order[b]
if keys[ra] != keys[rb] {
return keys[ra] < keys[rb]
}
return rows[ra].occ.events[rows[ra].start].id < rows[rb].occ.events[rows[rb].start].id
})
sorted := make([]historyRow, len(rows))
for i, row := range order {
sorted[i] = rows[row]
}
rows = sorted
}
// Overlay the live active alerts: an occurrence still firing shows its
// current state (fresh LastSeen and value), exactly as the JSON
// history's continuously updated entries do. Active alerts the log
// never saw fire (log enabled mid-flight) are appended.
m.mu.RLock()
active := make([]*Alert, 0, len(m.activeAlerts))
for storageKey, alert := range m.activeAlerts {
if alert == nil {
continue
}
exported := cloneAlertForOutput(alert)
if exported == nil {
continue
}
if exported.ID == "" {
exported.ID = effectiveAlertID(alert, storageKey)
}
active = append(active, exported)
}
m.mu.RUnlock()
index := make(map[string]int, len(rows))
for i := range rows {
index[rows[i].key] = i
}
for _, alert := range active {
key := historyOccurrenceKey(alert.ID, alert)
if i, exists := index[key]; exists {
acknowledged, ackTime, ackUser := rows[i].ack()
overlay := *alert
if acknowledged && !overlay.Acknowledged {
overlay.Acknowledged, overlay.AckTime, overlay.AckUser = true, ackTime, ackUser
}
rows[i].overlay = &overlay
} else if since.IsZero() || alert.StartTime.After(since) {
overlay := *alert
index[key] = len(rows)
rows = append(rows, historyRow{key: key, firstEvent: alert.StartTime, overlay: &overlay})
}
}
sort.SliceStable(rows, func(i, j int) bool {
return rows[i].firstEvent.Before(rows[j].firstEvent)
})
size := len(rows)
if limit > 0 && limit < size {
size = limit
}
results := make([]Alert, 0, size)
for i := len(rows) - 1; i >= 0 && len(results) < size; i-- {
results = append(results, rows[i].materialize())
}
return canonicalizeAlertHistoryForOutput(results), nil
}
@@ -10,7 +10,10 @@ package alerts
import (
"encoding/json"
"fmt"
"math/rand"
"reflect"
"runtime"
"sort"
"sync"
"testing"
"time"
@@ -291,3 +294,263 @@ func TestHistoryProjectionWalksEveryLifecycleEventBeyondQueryCap(t *testing.T) {
t.Fatalf("limited projection order = %q ... %q", limited[0].ID, limited[24].ID)
}
}
// Windowed and limited history reads are answered from the one shared
// full-history fold (#1803). Before, every read deep-copied the whole fold and
// every Since read re-decoded each snapshot in its window. These tests hold
// the shared answer equal to an independent walk of only the window's events,
// which is the original algorithm.
// referenceWindowedHistory is the original read path: a fresh fold of only
// the events the log's Since filter admits, the live overlay, then the sort.
func referenceWindowedHistory(t *testing.T, m *Manager, since time.Time, limit int) []Alert {
t.Helper()
type occurrence struct {
alert Alert
firstEvent time.Time
}
occurrences := map[string]*occurrence{}
var order []string
err := m.eventLogStore().WalkOldest(eventlog.Filter{
Types: []string{
eventlog.TypeFired, eventlog.TypeRefired, eventlog.TypeResolved,
eventlog.TypeAcknowledged, eventlog.TypeUnacknowledged,
eventlog.TypeSnoozed, eventlog.TypeUnsnoozed, eventlog.TypeEscalated,
eventlog.TypeHistoryImported, eventlog.TypeHistoryCleared,
},
Since: since,
}, func(event eventlog.Event) error {
if event.Type == eventlog.TypeHistoryCleared {
occurrences = map[string]*occurrence{}
order = order[:0]
return nil
}
var snapshot Alert
if len(event.Snapshot) == 0 || json.Unmarshal(event.Snapshot, &snapshot) != nil {
return nil
}
key := historyOccurrenceKey(event.AlertID, &snapshot)
occ, exists := occurrences[key]
if !exists {
occ = &occurrence{alert: snapshot, firstEvent: event.OccurredAt}
occurrences[key] = occ
order = append(order, key)
} else {
occ.alert = mergeHistoryAlertSnapshots(occ.alert, snapshot)
}
if event.Type == eventlog.TypeResolved && event.OccurredAt.After(occ.alert.LastSeen) {
occ.alert.LastSeen = event.OccurredAt
}
return nil
})
if err != nil {
t.Fatalf("reference walk: %v", err)
}
m.mu.RLock()
var active []*Alert
for storageKey, alert := range m.activeAlerts {
exported := cloneAlertForOutput(alert)
if exported.ID == "" {
exported.ID = effectiveAlertID(alert, storageKey)
}
active = append(active, exported)
}
m.mu.RUnlock()
for _, alert := range active {
key := historyOccurrenceKey(alert.ID, alert)
if occ, exists := occurrences[key]; exists {
ack, ackTime, ackUser := occ.alert.Acknowledged, occ.alert.AckTime, occ.alert.AckUser
occ.alert = *alert
if ack && !occ.alert.Acknowledged {
occ.alert.Acknowledged, occ.alert.AckTime, occ.alert.AckUser = true, ackTime, ackUser
}
} else if since.IsZero() || alert.StartTime.After(since) {
occurrences[key] = &occurrence{alert: *alert, firstEvent: alert.StartTime}
order = append(order, key)
}
}
sort.SliceStable(order, func(i, j int) bool {
return occurrences[order[i]].firstEvent.Before(occurrences[order[j]].firstEvent)
})
results := []Alert{}
for i := len(order) - 1; i >= 0; i-- {
if limit > 0 && len(results) >= limit {
break
}
results = append(results, occurrences[order[i]].alert)
}
return m.applyCurrentNodeDisplayNames(canonicalizeAlertHistoryForOutput(results))
}
// writeRandomHistory appends a lifecycle history with acknowledgements on
// both sides of window boundaries, out-of-order LastSeen values, uneven
// fractional seconds (so RFC3339Nano text order and time order can differ),
// same-instant events and a history clear. It returns every event time.
func writeRandomHistory(t *testing.T, m *Manager, seed int64) []time.Time {
t.Helper()
rng := rand.New(rand.NewSource(seed))
store := m.eventLogStore()
base := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)
kinds := []string{
eventlog.TypeFired, eventlog.TypeRefired, eventlog.TypeAcknowledged,
eventlog.TypeUnacknowledged, eventlog.TypeSnoozed, eventlog.TypeEscalated, eventlog.TypeResolved,
}
fractions := []time.Duration{0, 500 * time.Millisecond, 50 * time.Millisecond, 123456789}
var times []time.Time
at := base
for i := 0; i < 120; i++ {
if rng.Intn(4) != 0 {
at = at.Add(time.Duration(rng.Intn(3)) * time.Second)
}
occurred := at.Add(fractions[rng.Intn(len(fractions))])
times = append(times, occurred)
if i == 60 {
if err := store.AppendDurable(eventlog.Event{Type: eventlog.TypeHistoryCleared, AlertID: "history", OccurredAt: occurred}); err != nil {
t.Fatal(err)
}
continue
}
id := fmt.Sprintf("alert-%d", rng.Intn(5))
snapshot := Alert{
ID: id,
ResourceID: "vm-" + id,
StartTime: base.Add(time.Duration(rng.Intn(3)) * time.Hour),
LastSeen: occurred.Add(time.Duration(rng.Intn(240)-120) * time.Second),
Value: float64(i),
}
kind := kinds[rng.Intn(len(kinds))]
if kind == eventlog.TypeAcknowledged || (kind != eventlog.TypeUnacknowledged && rng.Intn(5) == 0) {
ackTime := occurred
snapshot.Acknowledged, snapshot.AckTime, snapshot.AckUser = true, &ackTime, fmt.Sprintf("user-%d", i)
}
payload, err := json.Marshal(snapshot)
if err != nil {
t.Fatal(err)
}
if err := store.AppendDurable(eventlog.Event{Type: kind, AlertID: id, OccurredAt: occurred, Snapshot: payload}); err != nil {
t.Fatal(err)
}
}
return times
}
func TestWindowedHistoryMatchesAWalkOfOnlyTheWindow(t *testing.T) {
for seed := int64(1); seed <= 25; seed++ {
t.Run(fmt.Sprintf("seed-%d", seed), func(t *testing.T) {
m := newHistoryParityManager(t)
times := writeRandomHistory(t, m, seed)
// Live overlay: one alert the log knows (with a fresh value) and
// one it never saw fire.
m.mu.Lock()
m.activeAlerts["live-known"] = &Alert{ID: "alert-1", ResourceID: "vm-alert-1",
StartTime: time.Date(2026, 9, 1, 1, 0, 0, 0, time.UTC), LastSeen: times[len(times)-1], Value: 999}
m.activeAlerts["live-new"] = &Alert{ID: "alert-new", ResourceID: "vm-new",
StartTime: times[len(times)/2], LastSeen: times[len(times)-1], Value: 7}
m.mu.Unlock()
windows := []time.Time{{}, times[0].Add(-time.Hour), times[len(times)-1].Add(time.Hour)}
for i := 0; i < len(times); i += 7 {
windows = append(windows, times[i], times[i].Add(time.Nanosecond), times[i].Add(-time.Nanosecond))
}
for _, since := range windows {
for _, limit := range []int{0, 1, 3} {
got, ok := m.AlertHistoryFromEvents(since, limit)
if !ok {
t.Fatal("history unavailable")
}
want := referenceWindowedHistory(t, m, since, limit)
if !reflect.DeepEqual(got, want) {
t.Fatalf("since=%s limit=%d: shared fold differs from a walk of only the window\n got: %+v\nwant: %+v",
since.Format(time.RFC3339Nano), limit, got, want)
}
}
}
})
}
}
func TestWindowedHistoryReusesTheFullHistoryFold(t *testing.T) {
m := newHistoryParityManager(t)
times := writeRandomHistory(t, m, 7)
if _, ok := m.AlertHistoryFromEvents(time.Time{}, 0); !ok {
t.Fatal("history unavailable")
}
m.historyProjectionMu.Lock()
full := m.historyProjection
m.historyProjectionMu.Unlock()
if full == nil {
t.Fatal("a full-history read did not keep its fold")
}
for _, since := range []time.Time{times[10], times[90], times[len(times)-1]} {
if _, ok := m.AlertHistoryFromEvents(since, 0); !ok {
t.Fatal("history unavailable")
}
m.historyProjectionMu.Lock()
current := m.historyProjection
m.historyProjectionMu.Unlock()
if current != full {
t.Fatalf("a Since read at %s replaced the shared fold instead of reusing it", since)
}
}
}
func TestHistoryReadResultsDoNotAliasTheSharedFold(t *testing.T) {
m := newHistoryParityManager(t)
times := writeRandomHistory(t, m, 11)
m.mu.Lock()
m.activeAlerts["live-known"] = &Alert{ID: "alert-2", ResourceID: "vm-alert-2",
StartTime: time.Date(2026, 9, 1, 2, 0, 0, 0, time.UTC), LastSeen: times[len(times)-1]}
m.mu.Unlock()
for _, since := range []time.Time{{}, times[30], times[100]} {
first, _ := m.AlertHistoryFromEvents(since, 0)
before := referenceWindowedHistory(t, m, since, 0)
for i := range first {
first[i].Value = -1
first[i].LastSeen = time.Time{}
if first[i].AckTime != nil {
*first[i].AckTime = time.Time{}
}
}
if again, _ := m.AlertHistoryFromEvents(since, 0); !reflect.DeepEqual(again, before) {
t.Fatalf("since=%s: mutating a result changed the next read", since)
}
}
}
// A limited read copies only the rows it returns, not the whole fold.
func TestLimitedHistoryReadCopiesOnlyReturnedRows(t *testing.T) {
m := newHistoryParityManager(t)
store := m.eventLogStore()
at := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)
metadata := map[string]interface{}{}
for i := 0; i < 40; i++ {
metadata[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("value-%d-with-some-detail", i)
}
for i := 0; i < 2000; i++ {
occurred := at.Add(time.Duration(i) * time.Minute)
snapshot, err := json.Marshal(Alert{ID: fmt.Sprintf("alert-%d", i), ResourceID: "vm", StartTime: occurred,
LastSeen: occurred, Message: "disk usage above threshold on a long resource name", Metadata: metadata})
if err != nil {
t.Fatal(err)
}
if err := store.AppendDurable(eventlog.Event{Type: eventlog.TypeFired, AlertID: fmt.Sprintf("alert-%d", i),
OccurredAt: occurred, Snapshot: snapshot}); err != nil {
t.Fatal(err)
}
}
allocated := func(limit int) uint64 {
var before, after runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&before)
if got, ok := m.AlertHistoryFromEvents(time.Time{}, limit); !ok || (limit > 0 && len(got) != limit) {
t.Fatalf("history read with limit %d failed", limit)
}
runtime.ReadMemStats(&after)
return after.TotalAlloc - before.TotalAlloc
}
allocated(0) // build the shared fold
everything, ten := allocated(0), allocated(10)
if ten*5 > everything {
t.Fatalf("a 10-row read allocated %d bytes against %d for all 2000 rows; it must not copy the whole fold", ten, everything)
}
}