- Add db_e2e_test.go: WAL roundtrip, partial write recovery, multi-segment recovery, Get semantics, concurrent read/write, large batch rotation tests - Add bench_test.go: SinglePut, ConcurrentPut, Get, ConcurrentGet, MixedReadWrite, WALRecovery benchmarks with b.ReportAllocs() Wave 5 complete (T20 + T21). All implementation tasks done.
226 lines
4.7 KiB
Go
226 lines
4.7 KiB
Go
package go_kv
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// openBenchDB opens a fresh database in b.TempDir() with default config.
|
|
// Returns the DB handle; caller must Close().
|
|
func openBenchDB(b *testing.B) *DB {
|
|
b.Helper()
|
|
db, err := Open(b.TempDir(), nil)
|
|
if err != nil {
|
|
b.Fatalf("Open: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// BenchmarkSinglePut measures single-threaded sequential Put throughput.
|
|
func BenchmarkSinglePut(b *testing.B) {
|
|
db := openBenchDB(b)
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}()
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
i := 0
|
|
for b.Loop() {
|
|
key := fmt.Appendf(nil, "key-%08d", i)
|
|
val := fmt.Appendf(nil, "value-%08d", i)
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Fatalf("Put: %v", err)
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
// BenchmarkConcurrentPut measures multi-goroutine Put throughput.
|
|
// Uses b.N directly because b.Loop() cannot be called from spawned goroutines.
|
|
func BenchmarkConcurrentPut(b *testing.B) {
|
|
for _, workers := range []int{2, 4, 8} {
|
|
b.Run(fmt.Sprintf("workers=%d", workers), func(b *testing.B) {
|
|
db := openBenchDB(b)
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}()
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
var wg sync.WaitGroup
|
|
opsPerWorker := b.N / workers
|
|
for w := range workers {
|
|
wg.Add(1)
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
for i := range opsPerWorker {
|
|
key := fmt.Appendf(nil, "key-%d-%08d", workerID, i)
|
|
val := fmt.Appendf(nil, "value-%d-%08d", workerID, i)
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Errorf("Put: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}(w)
|
|
}
|
|
wg.Wait()
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkGet measures Get latency after pre-loading 1000 keys.
|
|
func BenchmarkGet(b *testing.B) {
|
|
const numKeys = 1000
|
|
|
|
db := openBenchDB(b)
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Pre-load keys.
|
|
for i := range numKeys {
|
|
key := fmt.Appendf(nil, "key-%08d", i)
|
|
val := fmt.Appendf(nil, "value-%08d", i)
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Fatalf("Put preload: %v", err)
|
|
}
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
i := 0
|
|
for b.Loop() {
|
|
key := fmt.Appendf(nil, "key-%08d", i%numKeys)
|
|
res := db.Get(key)
|
|
if !res.Found {
|
|
b.Fatalf("Get key %s: not found", key)
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
// BenchmarkConcurrentGet measures parallel Get after pre-loading keys.
|
|
func BenchmarkConcurrentGet(b *testing.B) {
|
|
const numKeys = 1000
|
|
|
|
db := openBenchDB(b)
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Pre-load keys.
|
|
for i := range numKeys {
|
|
key := fmt.Appendf(nil, "key-%08d", i)
|
|
val := fmt.Appendf(nil, "value-%08d", i)
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Fatalf("Put preload: %v", err)
|
|
}
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
key := fmt.Appendf(nil, "key-%08d", i%numKeys)
|
|
res := db.Get(key)
|
|
if !res.Found {
|
|
b.Fatalf("Get key %s: not found", key)
|
|
}
|
|
i++
|
|
}
|
|
})
|
|
}
|
|
|
|
// BenchmarkMixedReadWrite measures a 50% Put + 50% Get workload mix.
|
|
func BenchmarkMixedReadWrite(b *testing.B) {
|
|
const numKeys = 1000
|
|
|
|
db := openBenchDB(b)
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Pre-load half the keys so Gets are not all misses.
|
|
for i := range numKeys / 2 {
|
|
key := fmt.Appendf(nil, "key-%08d", i)
|
|
val := fmt.Appendf(nil, "value-%08d", i)
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Fatalf("Put preload: %v", err)
|
|
}
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
i := 0
|
|
for b.Loop() {
|
|
key := fmt.Appendf(nil, "key-%08d", i%numKeys)
|
|
val := fmt.Appendf(nil, "value-%08d", i%numKeys)
|
|
if i%2 == 0 {
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Fatalf("Put: %v", err)
|
|
}
|
|
} else {
|
|
_ = db.Get(key)
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
// BenchmarkWALRecovery measures recovery (Open) time for different data sizes.
|
|
func BenchmarkWALRecovery(b *testing.B) {
|
|
for _, nKeys := range []int{100, 1000, 10000} {
|
|
b.Run(fmt.Sprintf("keys=%d", nKeys), func(b *testing.B) {
|
|
dir := b.TempDir()
|
|
|
|
// Write N keys to populate WAL segments.
|
|
func() {
|
|
db, err := Open(dir, nil)
|
|
if err != nil {
|
|
b.Fatalf("Open for write: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
for i := range nKeys {
|
|
key := fmt.Appendf(nil, "key-%08d", i)
|
|
val := fmt.Appendf(nil, "value-%08d", i)
|
|
if err := db.Put(key, val); err != nil {
|
|
b.Fatalf("Put: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Now benchmark only the recovery (Open) phase.
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for b.Loop() {
|
|
db, err := Open(dir, nil)
|
|
if err != nil {
|
|
b.Fatalf("Open recovery: %v", err)
|
|
}
|
|
if err := db.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|