feat: Add retention#5
Conversation
Pull Request Review: feat: Add retentionSummaryThis PR adds a retention policy feature to the Squid event database, allowing automatic cleanup of old events. The implementation includes a configurable retention policy, background cleanup goroutine, and proper index cleanup. Overall, the implementation is solid with excellent test coverage. Code Quality: ✅ GoodStrengths
Issues & Recommendations🔴 Critical Issues1. Race Condition in Close() - squid.go:48 if db.retention != nil && db.retention.running {
db.retention.cancel()
<-db.retention.done
}Problem: The Fix: Either:
// Recommended fix:
if db.retention != nil {
db.retention.cancel()
<-db.retention.done
}2. Error Handling Silently Ignores Deletion Failures - retention.go:120-123 for _, entry := range toDelete {
if err := db.deleteEventAndIndices(txn, entry); err != nil {
continue // ⚠️ Silently ignores errors!
}
deleted++
}Problem: Individual deletion errors are silently ignored, making debugging difficult and potentially leaving orphaned indices. Fix: Log errors or collect them: var errs []error
for _, entry := range toDelete {
if err := db.deleteEventAndIndices(txn, entry); err != nil {
errs = append(errs, fmt.Errorf("failed to delete event %s: %w", entry.id, err))
continue
}
deleted++
}
// Consider logging errs if len(errs) > 0🟡 Important Issues3. Potential Memory Issue with Large Deletions - retention.go:139-175 func (db *DB) findExpiredEvents(txn *badger.Txn, before time.Time) ([]deleteEntry, error) {
var toDelete []deleteEntry
// ... accumulates ALL expired events in memoryProblem: For databases with millions of expired events, this loads everything into memory at once. Impact: Could cause OOM on large databases with long retention periods. Fix: Consider batch processing with a configurable limit: const maxBatchSize = 10000 // configurable
func (db *DB) findExpiredEvents(txn *badger.Txn, before time.Time) ([]deleteEntry, error) {
var toDelete []deleteEntry
// ...
for it.Seek(prefix); it.ValidForPrefix(prefix) && len(toDelete) < maxBatchSize; it.Next() {
// ...
}
return toDelete, nil
}4. Missing Immediate Cleanup Synchronization - retention.go:82-84 // Run cleanup immediately on start
cutoff := time.Now().Add(-state.policy.MaxAge)
db.deleteBefore(cutoff)Problem: The immediate cleanup runs asynchronously, so Consideration: Document this behavior clearly, or provide a synchronous option for initial cleanup. 🟢 Minor Issues5. Inconsistent Error Handling in DeleteBefore() - retention.go:99-107 func (db *DB) DeleteBefore(before time.Time) (int64, error) {
db.mu.RLock()
if db.closed {
db.mu.RUnlock()
return 0, ErrClosed
}
db.mu.RUnlock()
return db.deleteBefore(before)
}Suggestion: The internal 6. JSON Unmarshal Error Handling - retention.go:159-163 err := item.Value(func(val []byte) error {
return json.Unmarshal(val, &event)
})
if err != nil {
continue // Silently skips corrupted events
}Suggestion: Consider logging corrupted events for debugging purposes. Performance Considerations✅ Good Choices
|
retention.gowith RetentionPolicySetRetention()configurationgoroutine