benchmark.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "os"
  6. "runtime"
  7. "sync"
  8. "sync/atomic"
  9. "time"
  10. "igit.com/xbase/raft/db"
  11. )
  12. const (
  13. TotalKeys = 100000
  14. DataDir = "bench_db_data"
  15. )
  16. func main() {
  17. // Scenario: Key Query Performance and Correctness
  18. fmt.Println("==================================================")
  19. fmt.Println("SCENARIO: Key Query Performance (Flat Array Index)")
  20. fmt.Println("==================================================")
  21. // Value Index Disabled
  22. runBenchmark(false)
  23. }
  24. func runBenchmark(enableValueIndex bool) {
  25. // Clean up previous run
  26. os.RemoveAll(DataDir)
  27. fmt.Printf("Initializing DB Engine in %s (ValueIndex=%v)...\n", DataDir, enableValueIndex)
  28. startMem := getMemUsage()
  29. e, err := db.NewEngine(DataDir, db.WithValueIndex(enableValueIndex))
  30. if err != nil {
  31. panic(err)
  32. }
  33. defer e.Close()
  34. defer os.RemoveAll(DataDir) // Cleanup after run
  35. // 1. Bulk Insert 100k keys with varied segments (1 to 5)
  36. fmt.Println("\n--- Phase 1: Bulk Insert 100k Keys (Varied Segments) ---")
  37. keys := make([]string, TotalKeys)
  38. start := time.Now()
  39. // Generate keys with 5 patterns
  40. // Pattern 1 (Len 1): item.<id> (20k)
  41. // Pattern 2 (Len 2): user.<id>.profile (20k)
  42. // Pattern 3 (Len 3): team.<team_id>.user.<user_id> (20k) -> 200 users per team
  43. // Pattern 4 (Len 4): region.<r>.zone.<z>.node.<n> (20k) -> 100 zones, 200 nodes
  44. // Pattern 5 (Len 5): a.b.c.d.<id> (20k)
  45. for i := 0; i < TotalKeys; i++ {
  46. group := i % 5
  47. id := i / 5
  48. switch group {
  49. case 0:
  50. // Len 1: item.<id>
  51. // But to test prefix well, let's group them slightly?
  52. // Actually "item.1" is 2 segments if split by dot.
  53. // User said "one to 5 segments".
  54. // Let's treat "." as separator.
  55. // "root<id>" is 1 segment.
  56. keys[i] = fmt.Sprintf("root%d", id)
  57. case 1:
  58. // Len 2: user.<id>
  59. keys[i] = fmt.Sprintf("user.%d", id)
  60. case 2:
  61. // Len 3: group.<id%100>.member.<id>
  62. // group.0.member.0 ... group.0.member.199
  63. keys[i] = fmt.Sprintf("group.%d.member.%d", id%100, id)
  64. case 3:
  65. // Len 4: app.<id%10>.ver.<id%10>.config.<id>
  66. keys[i] = fmt.Sprintf("app.%d.ver.%d.config.%d", id%10, id%10, id)
  67. case 4:
  68. // Len 5: log.2023.01.01.<id>
  69. keys[i] = fmt.Sprintf("log.2023.01.01.%d", id)
  70. }
  71. }
  72. var wg sync.WaitGroup
  73. workers := 10
  74. chunkSize := TotalKeys / workers
  75. var insertOps int64
  76. for w := 0; w < workers; w++ {
  77. wg.Add(1)
  78. go func(id int) {
  79. defer wg.Done()
  80. base := id * chunkSize
  81. for i := 0; i < chunkSize; i++ {
  82. idx := base + i
  83. if idx >= TotalKeys { continue }
  84. // Small value
  85. val := "v"
  86. if err := e.Set(keys[idx], val, uint64(idx)); err != nil {
  87. panic(err)
  88. }
  89. atomic.AddInt64(&insertOps, 1)
  90. }
  91. }(w)
  92. }
  93. wg.Wait()
  94. duration := time.Since(start)
  95. qps := float64(TotalKeys) / duration.Seconds()
  96. currentMem := getMemUsage()
  97. printStats("Insert", TotalKeys, duration, qps, getFileSize(DataDir+"/values.data"), currentMem - startMem)
  98. // 2. Query Consistency Verification
  99. fmt.Println("\n--- Phase 2: Query Consistency Verification ---")
  100. verifyQuery(e, "Exact Match (Len 1)", `key = "root0"`, 1)
  101. verifyQuery(e, "Exact Match (Len 2)", `key = "user.0"`, 1)
  102. 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.
  103. // Total items in group 2: 20,000.
  104. // id goes from 0 to 19999.
  105. // id%100 goes 0..99.
  106. // So each group (0..99) appears 20000/100 = 200 times.
  107. // So "group.0.member.*" should have 200 matches. Correct.
  108. verifyQuery(e, "Prefix Scan (Len 4)", `key like "app.0.ver.0.config.*"`, 2000)
  109. // Group 3 items: 20000.
  110. // Key format: app.<id%10>.ver.<id%10>.config.<id>
  111. // Condition: id%10 == 0.
  112. // Since id goes 0..19999, exactly 1/10 of ids satisfy (id%10 == 0).
  113. // Count = 20000 / 10 = 2000.
  114. verifyQuery(e, "Prefix Scan (Len 5)", `key like "log.2023.*"`, 20000) // All items in group 4
  115. // 3. Query Performance Test (Prefix)
  116. fmt.Println("\n--- Phase 3: Query Performance (Prefix Scan) ---")
  117. start = time.Now()
  118. qCount := 10000
  119. wg = sync.WaitGroup{}
  120. chunkSize = qCount / workers
  121. for w := 0; w < workers; w++ {
  122. wg.Add(1)
  123. go func() {
  124. defer wg.Done()
  125. for i := 0; i < chunkSize; i++ {
  126. // Query overlapping prefixes to stress test binary search + scan
  127. // "group.50.member.*" -> 200 items scan
  128. _, _ = e.Query(`key like "group.50.member.*"`)
  129. }
  130. }()
  131. }
  132. wg.Wait()
  133. duration = time.Since(start)
  134. qps = float64(qCount) / duration.Seconds()
  135. printStats("Query(Prefix)", qCount, duration, qps, 0, 0)
  136. // Final Memory Report
  137. runtime.GC()
  138. finalMem := getMemUsage()
  139. fmt.Printf("\n[Final Stats] Total Keys: %d | Memory Usage: %.2f MB\n", TotalKeys, float64(finalMem-startMem)/1024/1024)
  140. }
  141. func verifyQuery(e *db.Engine, name, sql string, expected int) {
  142. start := time.Now()
  143. res, err := e.Query(sql)
  144. if err != nil {
  145. fmt.Printf("FAIL: %s - Error: %v\n", name, err)
  146. return
  147. }
  148. duration := time.Since(start)
  149. if len(res) == expected {
  150. fmt.Printf("PASS: %-25s | Count: %5d | Time: %v\n", name, len(res), duration)
  151. } else {
  152. fmt.Printf("FAIL: %-25s | Expected: %d, Got: %d\n", name, expected, len(res))
  153. }
  154. }
  155. func randomString(n int) string {
  156. const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  157. b := make([]byte, n)
  158. for i := range b {
  159. b[i] = letters[rand.Intn(len(letters))]
  160. }
  161. return string(b)
  162. }
  163. func getFileSize(path string) int64 {
  164. fi, err := os.Stat(path)
  165. if err != nil {
  166. return 0
  167. }
  168. return fi.Size()
  169. }
  170. func getMemUsage() uint64 {
  171. var m runtime.MemStats
  172. runtime.ReadMemStats(&m)
  173. return m.Alloc
  174. }
  175. func printStats(op string, count int, d time.Duration, qps float64, size int64, memDelta uint64) {
  176. sizeStr := ""
  177. if size > 0 {
  178. sizeStr = fmt.Sprintf(" | Disk: %.2f MB", float64(size)/1024/1024)
  179. }
  180. memStr := ""
  181. if memDelta > 0 {
  182. memStr = fmt.Sprintf(" | Mem+: %.2f MB", float64(memDelta)/1024/1024)
  183. }
  184. fmt.Printf("%-15s: %6d ops in %7v | QPS: %8.0f%s%s\n", op, count, d, qps, sizeStr, memStr)
  185. }