| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package main
- import (
- "fmt"
- "math/rand"
- "os"
- "runtime"
- "sync"
- "sync/atomic"
- "time"
- "igit.com/xbase/raft/db"
- )
- const (
- TotalKeys = 100000
- DataDir = "bench_db_data"
- )
- func main() {
- // Scenario: Key Query Performance and Correctness
- fmt.Println("==================================================")
- fmt.Println("SCENARIO: Key Query Performance (Flat Array Index)")
- fmt.Println("==================================================")
-
- // Value Index Disabled
- runBenchmark(false)
- }
- func runBenchmark(enableValueIndex bool) {
- // Clean up previous run
- os.RemoveAll(DataDir)
- fmt.Printf("Initializing DB Engine in %s (ValueIndex=%v)...\n", DataDir, enableValueIndex)
-
- startMem := getMemUsage()
-
- e, err := db.NewEngine(DataDir, db.WithValueIndex(enableValueIndex))
- if err != nil {
- panic(err)
- }
- defer e.Close()
- defer os.RemoveAll(DataDir) // Cleanup after run
- // 1. Bulk Insert 100k keys with varied segments (1 to 5)
- fmt.Println("\n--- Phase 1: Bulk Insert 100k Keys (Varied Segments) ---")
- keys := make([]string, TotalKeys)
- start := time.Now()
-
- // Generate keys with 5 patterns
- // Pattern 1 (Len 1): item.<id> (20k)
- // Pattern 2 (Len 2): user.<id>.profile (20k)
- // Pattern 3 (Len 3): team.<team_id>.user.<user_id> (20k) -> 200 users per team
- // Pattern 4 (Len 4): region.<r>.zone.<z>.node.<n> (20k) -> 100 zones, 200 nodes
- // Pattern 5 (Len 5): a.b.c.d.<id> (20k)
- for i := 0; i < TotalKeys; i++ {
- group := i % 5
- id := i / 5
-
- switch group {
- case 0:
- // Len 1: item.<id>
- // But to test prefix well, let's group them slightly?
- // Actually "item.1" is 2 segments if split by dot.
- // User said "one to 5 segments".
- // Let's treat "." as separator.
- // "root<id>" is 1 segment.
- keys[i] = fmt.Sprintf("root%d", id)
- case 1:
- // Len 2: user.<id>
- keys[i] = fmt.Sprintf("user.%d", id)
- case 2:
- // Len 3: group.<id%100>.member.<id>
- // group.0.member.0 ... group.0.member.199
- keys[i] = fmt.Sprintf("group.%d.member.%d", id%100, id)
- case 3:
- // Len 4: app.<id%10>.ver.<id%10>.config.<id>
- keys[i] = fmt.Sprintf("app.%d.ver.%d.config.%d", id%10, id%10, id)
- case 4:
- // Len 5: log.2023.01.01.<id>
- keys[i] = fmt.Sprintf("log.2023.01.01.%d", id)
- }
- }
- var wg sync.WaitGroup
- workers := 10
- chunkSize := TotalKeys / workers
- var insertOps int64
- for w := 0; w < workers; w++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- base := id * chunkSize
- for i := 0; i < chunkSize; i++ {
- idx := base + i
- if idx >= TotalKeys { continue }
- // Small value
- val := "v"
- if err := e.Set(keys[idx], val, uint64(idx)); err != nil {
- panic(err)
- }
- atomic.AddInt64(&insertOps, 1)
- }
- }(w)
- }
- wg.Wait()
-
- duration := time.Since(start)
- qps := float64(TotalKeys) / duration.Seconds()
- currentMem := getMemUsage()
- printStats("Insert", TotalKeys, duration, qps, getFileSize(DataDir+"/values.data"), currentMem - startMem)
- // 2. Query Consistency Verification
- fmt.Println("\n--- Phase 2: Query Consistency Verification ---")
-
- verifyQuery(e, "Exact Match (Len 1)", `key = "root0"`, 1)
- verifyQuery(e, "Exact Match (Len 2)", `key = "user.0"`, 1)
- verifyQuery(e, "Prefix Scan (Len 3)", `key like "group.0.member.*"`, 200) // 20000 items / 5 types = 4000 items. id%100 -> 100 groups. 4000/100 = 40? Wait.
- // Total items in group 2: 20,000.
- // id goes from 0 to 19999.
- // id%100 goes 0..99.
- // So each group (0..99) appears 20000/100 = 200 times.
- // So "group.0.member.*" should have 200 matches. Correct.
-
- verifyQuery(e, "Prefix Scan (Len 4)", `key like "app.0.ver.0.config.*"`, 2000)
- // Group 3 items: 20000.
- // Key format: app.<id%10>.ver.<id%10>.config.<id>
- // Condition: id%10 == 0.
- // Since id goes 0..19999, exactly 1/10 of ids satisfy (id%10 == 0).
- // Count = 20000 / 10 = 2000.
-
- verifyQuery(e, "Prefix Scan (Len 5)", `key like "log.2023.*"`, 20000) // All items in group 4
- // 3. Query Performance Test (Prefix)
- fmt.Println("\n--- Phase 3: Query Performance (Prefix Scan) ---")
-
- start = time.Now()
- qCount := 10000
- wg = sync.WaitGroup{}
- chunkSize = qCount / workers
-
- for w := 0; w < workers; w++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for i := 0; i < chunkSize; i++ {
- // Query overlapping prefixes to stress test binary search + scan
- // "group.50.member.*" -> 200 items scan
- _, _ = e.Query(`key like "group.50.member.*"`)
- }
- }()
- }
- wg.Wait()
- duration = time.Since(start)
- qps = float64(qCount) / duration.Seconds()
- printStats("Query(Prefix)", qCount, duration, qps, 0, 0)
- // Final Memory Report
- runtime.GC()
- finalMem := getMemUsage()
- fmt.Printf("\n[Final Stats] Total Keys: %d | Memory Usage: %.2f MB\n", TotalKeys, float64(finalMem-startMem)/1024/1024)
- }
- func verifyQuery(e *db.Engine, name, sql string, expected int) {
- start := time.Now()
- res, err := e.Query(sql)
- if err != nil {
- fmt.Printf("FAIL: %s - Error: %v\n", name, err)
- return
- }
- duration := time.Since(start)
-
- if len(res) == expected {
- fmt.Printf("PASS: %-25s | Count: %5d | Time: %v\n", name, len(res), duration)
- } else {
- fmt.Printf("FAIL: %-25s | Expected: %d, Got: %d\n", name, expected, len(res))
- }
- }
- func randomString(n int) string {
- const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- b := make([]byte, n)
- for i := range b {
- b[i] = letters[rand.Intn(len(letters))]
- }
- return string(b)
- }
- func getFileSize(path string) int64 {
- fi, err := os.Stat(path)
- if err != nil {
- return 0
- }
- return fi.Size()
- }
- func getMemUsage() uint64 {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
- return m.Alloc
- }
- func printStats(op string, count int, d time.Duration, qps float64, size int64, memDelta uint64) {
- sizeStr := ""
- if size > 0 {
- sizeStr = fmt.Sprintf(" | Disk: %.2f MB", float64(size)/1024/1024)
- }
- memStr := ""
- if memDelta > 0 {
- memStr = fmt.Sprintf(" | Mem+: %.2f MB", float64(memDelta)/1024/1024)
- }
- fmt.Printf("%-15s: %6d ops in %7v | QPS: %8.0f%s%s\n", op, count, d, qps, sizeStr, memStr)
- }
|