Files
EvoBGP/internal/store/memory_revision_prune.go
T
DenozordecandCursor f8e0520817 feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 17:23:59 +07:00

126 lines
3.1 KiB
Go

package store
import (
"context"
"strings"
"time"
)
func (m *Memory) prunableRevisionIDsLocked(tenantID string, cutoff time.Time) []string {
var newest *Revision
for _, rev := range m.revisions {
if rev == nil || rev.TenantID != tenantID {
continue
}
if newest == nil || rev.CreatedAt.After(newest.CreatedAt) {
newest = rev
}
}
if newest == nil {
return nil
}
protected := map[string]struct{}{newest.ID: {}}
for _, sp := range m.speakers {
if sp == nil || sp.TenantID != tenantID {
continue
}
if sp.LastAppliedRevisionID != nil && strings.TrimSpace(*sp.LastAppliedRevisionID) != "" {
protected[strings.TrimSpace(*sp.LastAppliedRevisionID)] = struct{}{}
}
}
for speakerID, info := range m.publishedRevision {
sp, ok := m.speakers[speakerID]
if !ok || sp == nil || sp.TenantID != tenantID {
continue
}
if strings.TrimSpace(info.RevisionID) != "" {
protected[strings.TrimSpace(info.RevisionID)] = struct{}{}
}
}
var ids []string
for id, rev := range m.revisions {
if rev == nil || rev.TenantID != tenantID {
continue
}
if !rev.CreatedAt.Before(cutoff) {
continue
}
if _, keep := protected[id]; keep {
continue
}
ids = append(ids, id)
}
return ids
}
func revisionBytesEstimate(rev *Revision, prefixes []PrefixRow) int64 {
if rev == nil {
return 0
}
var n int64
n += int64(len(rev.ContentHash))
for _, v := range rev.PreviewFragments {
n += int64(len(v))
}
for _, pr := range prefixes {
n += int64(len(pr.Prefix) + len(pr.Source))
}
return n
}
func (m *Memory) EstimateRevisionPrune(tenantID string, cutoff time.Time, retentionMinutes int) (RevisionPruneEstimate, error) {
m.mu.Lock()
defer m.mu.Unlock()
ids := m.prunableRevisionIDsLocked(tenantID, cutoff)
var bytes int64
for _, id := range ids {
bytes += revisionBytesEstimate(m.revisions[id], m.revPrefixes[id])
}
prefixRows := 0
for _, id := range ids {
prefixRows += len(m.revPrefixes[id])
}
return RevisionPruneEstimate{
RetentionMinutes: retentionMinutes,
CutoffAt: cutoff.UTC(),
RevisionCount: len(ids),
PrefixRowCount: prefixRows,
OrphanSnapshotCount: 0,
BytesEstimate: bytes,
}, nil
}
func (m *Memory) PruneRevisionsWithStats(tenantID string, cutoff time.Time) (RevisionPruneResult, error) {
est, err := m.EstimateRevisionPrune(tenantID, cutoff, 0)
if err != nil {
return RevisionPruneResult{}, err
}
m.mu.Lock()
defer m.mu.Unlock()
ids := m.prunableRevisionIDsLocked(tenantID, cutoff)
for _, id := range ids {
delete(m.revisions, id)
delete(m.revPrefixes, id)
}
return RevisionPruneResult{
DeletedRevisions: len(ids),
DeletedPrefixSnapshots: 0,
DeletedPrefixRows: est.PrefixRowCount,
BytesEstimate: est.BytesEstimate,
}, nil
}
func (m *Memory) PruneRevisionsBefore(ctx context.Context, tenantID string, cutoff time.Time) (int, error) {
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return 0, err
}
res, err := m.PruneRevisionsWithStats(tenantID, cutoff)
if err != nil {
return 0, err
}
return res.DeletedRevisions, nil
}