You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
6.4 KiB
195 lines
6.4 KiB
//go:build !(js && wasm) |
|
|
|
package database |
|
|
|
import ( |
|
"bytes" |
|
"sync/atomic" |
|
|
|
"github.com/dgraph-io/badger/v4" |
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/log" |
|
"next.orly.dev/pkg/database/indexes" |
|
) |
|
|
|
// CompactStorageStats holds statistics about compact vs legacy storage. |
|
type CompactStorageStats struct { |
|
// Event counts |
|
CompactEvents int64 // Number of events in compact format (cmp prefix) |
|
LegacyEvents int64 // Number of events in legacy format (evt/sev prefixes) |
|
TotalEvents int64 // Total events |
|
|
|
// Storage sizes |
|
CompactBytes int64 // Total bytes used by compact format |
|
LegacyBytes int64 // Total bytes used by legacy format (would be used without compact) |
|
|
|
// Savings |
|
BytesSaved int64 // Bytes saved by using compact format |
|
PercentSaved float64 // Percentage of space saved |
|
AverageCompact float64 // Average compact event size |
|
AverageLegacy float64 // Average legacy event size (estimated) |
|
|
|
// Serial mappings |
|
SerialEventIdEntries int64 // Number of sei (serial -> event ID) mappings |
|
SerialEventIdBytes int64 // Bytes used by sei mappings |
|
} |
|
|
|
// CompactStorageStats calculates storage statistics for compact event storage. |
|
// This scans the database to provide accurate metrics on space savings. |
|
func (d *D) CompactStorageStats() (stats CompactStorageStats, err error) { |
|
if err = d.View(func(txn *badger.Txn) error { |
|
// Count compact events (cmp prefix) |
|
cmpPrf := new(bytes.Buffer) |
|
if err = indexes.CompactEventEnc(nil).MarshalWrite(cmpPrf); chk.E(err) { |
|
return err |
|
} |
|
|
|
it := txn.NewIterator(badger.IteratorOptions{Prefix: cmpPrf.Bytes()}) |
|
for it.Rewind(); it.Valid(); it.Next() { |
|
item := it.Item() |
|
stats.CompactEvents++ |
|
stats.CompactBytes += int64(len(item.Key())) + int64(item.ValueSize()) |
|
} |
|
it.Close() |
|
|
|
// Count legacy evt entries |
|
evtPrf := new(bytes.Buffer) |
|
if err = indexes.EventEnc(nil).MarshalWrite(evtPrf); chk.E(err) { |
|
return err |
|
} |
|
|
|
it = txn.NewIterator(badger.IteratorOptions{Prefix: evtPrf.Bytes()}) |
|
for it.Rewind(); it.Valid(); it.Next() { |
|
item := it.Item() |
|
stats.LegacyEvents++ |
|
stats.LegacyBytes += int64(len(item.Key())) + int64(item.ValueSize()) |
|
} |
|
it.Close() |
|
|
|
// Count legacy sev entries |
|
sevPrf := new(bytes.Buffer) |
|
if err = indexes.SmallEventEnc(nil).MarshalWrite(sevPrf); chk.E(err) { |
|
return err |
|
} |
|
|
|
it = txn.NewIterator(badger.IteratorOptions{Prefix: sevPrf.Bytes()}) |
|
for it.Rewind(); it.Valid(); it.Next() { |
|
item := it.Item() |
|
stats.LegacyEvents++ |
|
stats.LegacyBytes += int64(len(item.Key())) // sev stores data in key |
|
} |
|
it.Close() |
|
|
|
// Count SerialEventId mappings (sei prefix) |
|
seiPrf := new(bytes.Buffer) |
|
if err = indexes.SerialEventIdEnc(nil).MarshalWrite(seiPrf); chk.E(err) { |
|
return err |
|
} |
|
|
|
it = txn.NewIterator(badger.IteratorOptions{Prefix: seiPrf.Bytes()}) |
|
for it.Rewind(); it.Valid(); it.Next() { |
|
item := it.Item() |
|
stats.SerialEventIdEntries++ |
|
stats.SerialEventIdBytes += int64(len(item.Key())) + int64(item.ValueSize()) |
|
} |
|
it.Close() |
|
|
|
return nil |
|
}); chk.E(err) { |
|
return |
|
} |
|
|
|
stats.TotalEvents = stats.CompactEvents + stats.LegacyEvents |
|
|
|
// Calculate averages |
|
if stats.CompactEvents > 0 { |
|
stats.AverageCompact = float64(stats.CompactBytes) / float64(stats.CompactEvents) |
|
} |
|
if stats.LegacyEvents > 0 { |
|
stats.AverageLegacy = float64(stats.LegacyBytes) / float64(stats.LegacyEvents) |
|
} |
|
|
|
// Estimate savings: compare compact size to what legacy size would be |
|
// For events that are in compact format, estimate legacy size based on typical ratios |
|
// A typical event has: |
|
// - 32 bytes event ID (saved in compact: stored separately in sei) |
|
// - 32 bytes pubkey (saved: replaced by 5-byte serial) |
|
// - For e-tags: 32 bytes each (saved: replaced by 5-byte serial when known) |
|
// - For p-tags: 32 bytes each (saved: replaced by 5-byte serial) |
|
// Conservative estimate: compact format is ~60% of legacy size for typical events |
|
if stats.CompactEvents > 0 && stats.AverageCompact > 0 { |
|
// Estimate what the legacy size would have been |
|
estimatedLegacyForCompact := float64(stats.CompactBytes) / 0.60 // 60% compression ratio |
|
stats.BytesSaved = int64(estimatedLegacyForCompact) - stats.CompactBytes - stats.SerialEventIdBytes |
|
if stats.BytesSaved < 0 { |
|
stats.BytesSaved = 0 |
|
} |
|
totalWithoutCompact := estimatedLegacyForCompact + float64(stats.LegacyBytes) |
|
totalWithCompact := float64(stats.CompactBytes + stats.LegacyBytes + stats.SerialEventIdBytes) |
|
if totalWithoutCompact > 0 { |
|
stats.PercentSaved = (1.0 - totalWithCompact/totalWithoutCompact) * 100.0 |
|
} |
|
} |
|
|
|
return stats, nil |
|
} |
|
|
|
// compactSaveCounter tracks cumulative bytes saved by compact format |
|
var compactSaveCounter atomic.Int64 |
|
|
|
// LogCompactSavings logs the storage savings achieved by compact format. |
|
// Call this periodically or after significant operations. |
|
func (d *D) LogCompactSavings() { |
|
stats, err := d.CompactStorageStats() |
|
if err != nil { |
|
log.W.F("failed to get compact storage stats: %v", err) |
|
return |
|
} |
|
|
|
if stats.TotalEvents == 0 { |
|
return |
|
} |
|
|
|
log.I.F("📊 Compact storage stats: %d compact events, %d legacy events", |
|
stats.CompactEvents, stats.LegacyEvents) |
|
log.I.F(" Compact size: %.2f MB, Legacy size: %.2f MB", |
|
float64(stats.CompactBytes)/(1024.0*1024.0), |
|
float64(stats.LegacyBytes)/(1024.0*1024.0)) |
|
log.I.F(" Serial mappings (sei): %d entries, %.2f KB", |
|
stats.SerialEventIdEntries, |
|
float64(stats.SerialEventIdBytes)/1024.0) |
|
|
|
if stats.CompactEvents > 0 { |
|
log.I.F(" Average compact event: %.0f bytes, estimated legacy: %.0f bytes", |
|
stats.AverageCompact, stats.AverageCompact/0.60) |
|
log.I.F(" Estimated savings: %.2f MB (%.1f%%)", |
|
float64(stats.BytesSaved)/(1024.0*1024.0), |
|
stats.PercentSaved) |
|
} |
|
|
|
// Also log serial cache stats |
|
cacheStats := d.SerialCacheStats() |
|
log.I.F(" Serial cache: %d/%d pubkeys, %d/%d event IDs, ~%.2f MB memory", |
|
cacheStats.PubkeysCached, cacheStats.PubkeysMaxSize, |
|
cacheStats.EventIdsCached, cacheStats.EventIdsMaxSize, |
|
float64(cacheStats.TotalMemoryBytes)/(1024.0*1024.0)) |
|
} |
|
|
|
// TrackCompactSaving records bytes saved for a single event. |
|
// Call this during event save to track cumulative savings. |
|
func TrackCompactSaving(legacySize, compactSize int) { |
|
saved := legacySize - compactSize |
|
if saved > 0 { |
|
compactSaveCounter.Add(int64(saved)) |
|
} |
|
} |
|
|
|
// GetCumulativeCompactSavings returns total bytes saved across all compact saves. |
|
func GetCumulativeCompactSavings() int64 { |
|
return compactSaveCounter.Load() |
|
} |
|
|
|
// ResetCompactSavingsCounter resets the cumulative savings counter. |
|
func ResetCompactSavingsCounter() { |
|
compactSaveCounter.Store(0) |
|
}
|
|
|