package storage import ( "fmt" "path/filepath" "sync" "sync/atomic" "testing" ) // BenchmarkConcurrentRecordWrites is the ingest shape the single-writer model // has to absorb: many goroutines (feeder flushes, gateway persists, plugin // callbacks) each writing a ~40-field record. SQLite serialises them; this // measures what that costs. Reported as records/s. func BenchmarkConcurrentRecordWrites(b *testing.B) { c := NewClient() if err := c.Connect(filepath.Join(b.TempDir(), "bench.db")); err != nil { b.Fatal(err) } defer c.Close() record := func(i int64) map[string]string { m := map[string]string{ "title": fmt.Sprintf("Some Show S01E%02d", i%40), "fileName": fmt.Sprintf("some.show.s01e%02d.1080p.mkv", i%40), "filePath": fmt.Sprintf("/files/watch/some.show.%d.mkv", i), "fileType": "video", "tmdbid": fmt.Sprintf("%d", 1000+i%500), fmt.Sprintf("cids/bafkbench%020d", i): "true", } for f := 0; len(m) < 40; f++ { m[fmt.Sprintf("categories/%d", f)] = "true" } return m } const writers = 16 var next int64 b.ResetTimer() var wg sync.WaitGroup for w := 0; w < writers; w++ { wg.Add(1) go func() { defer wg.Done() for { i := atomic.AddInt64(&next, 1) if i > int64(b.N) { return } if err := c.SetMetadataFlat(fmt.Sprintf("01BENCH%019d", i), record(i)); err != nil { b.Error(err) return } } }() } wg.Wait() b.ReportMetric(float64(b.N)/b.Elapsed().Seconds(), "records/s") } // BenchmarkConcurrentRepersistUnchanged is the dominant write on a permanent // store: a gateway re-persisting hits it already holds. The kv layer skips // every unchanged field, so this should cost reads only. func BenchmarkConcurrentRepersistUnchanged(b *testing.B) { c := NewClient() if err := c.Connect(filepath.Join(b.TempDir(), "bench.db")); err != nil { b.Fatal(err) } defer c.Close() const distinct = 2000 rec := func(i int64) map[string]string { m := map[string]string{ "title": fmt.Sprintf("Some Show S01E%02d", i%40), "fileType": "video", fmt.Sprintf("cids/bafkbench%020d", i): "true", } for f := 0; len(m) < 40; f++ { m[fmt.Sprintf("categories/%d", f)] = "true" } return m } for i := int64(0); i < distinct; i++ { if err := c.SetMetadataFlat(fmt.Sprintf("01BENCH%019d", i), rec(i)); err != nil { b.Fatal(err) } } const writers = 16 var next int64 b.ResetTimer() var wg sync.WaitGroup for w := 0; w < writers; w++ { wg.Add(1) go func() { defer wg.Done() for { i := atomic.AddInt64(&next, 1) if i > int64(b.N) { return } if err := c.SetMetadataFlat(fmt.Sprintf("01BENCH%019d", i%distinct), rec(i%distinct)); err != nil { b.Error(err) return } } }() } wg.Wait() b.ReportMetric(float64(b.N)/b.Elapsed().Seconds(), "records/s") }