endtoend_concurrent_read_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package core
  2. import (
  3. "path/filepath"
  4. "sync"
  5. "testing"
  6. "time"
  7. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  8. )
  9. // TestEndToEnd_ConcurrentReads 端到端验证并发读是否真正生效。
  10. //
  11. // 方案: 开真实库, 建表, 插 5000 行数据, 然后同时 N 个 goroutine 做全表 ScanWithPKID。
  12. // 每个 Scan 本身耗时 ~X ms。如果是串行锁, N 个 goroutine 总耗时 ≈ N×X ms;
  13. // 如果 RLock 真正并行, 总耗时应该接近 ≈ X ms (或略多, 远小于 N×X)。
  14. func TestEndToEnd_ConcurrentReads(t *testing.T) {
  15. tmpDir := t.TempDir()
  16. dbPath := filepath.Join(tmpDir, "concurrent_test.db")
  17. db, err := Open(dbPath)
  18. if err != nil {
  19. t.Fatalf("Open: %v", err)
  20. }
  21. defer db.Close()
  22. // 建表
  23. tbl, err := db.CreateTable("items", []types.FieldInfo{
  24. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  25. {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("name")},
  26. {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
  27. })
  28. if err != nil {
  29. t.Fatalf("CreateTable: %v", err)
  30. }
  31. // 批量插 5000 行
  32. txn := db.BeginTransaction()
  33. for i := 0; i < 5000; i++ {
  34. pk := tbl.AllocPKID()
  35. name := []byte("item_" + itoa(i))
  36. if _, err := tbl.InsertRowWithID(pk, []types.FieldValue{
  37. {FieldName: []byte("id"), Value: pk},
  38. {FieldName: []byte("name"), Value: name},
  39. {FieldName: []byte("v"), Value: int64(i)},
  40. }); err != nil {
  41. txn.Rollback()
  42. t.Fatalf("InsertRowWithID(%d): %v", i, err)
  43. }
  44. }
  45. if err := txn.Commit(); err != nil {
  46. t.Fatalf("Commit: %v", err)
  47. }
  48. t.Logf("插入 5000 行完成")
  49. // 串行基线: 单个 goroutine Scan 5 次取平均
  50. baselineN := 5
  51. baseline := time.Duration(0)
  52. for i := 0; i < baselineN; i++ {
  53. start := time.Now()
  54. c, e := fullScanCount(tbl)
  55. if e != nil {
  56. t.Fatalf("Scan baseline %d: %v", i, e)
  57. }
  58. if c != 5000 {
  59. t.Fatalf("Scan baseline: got %d want 5000", c)
  60. }
  61. baseline += time.Since(start)
  62. }
  63. baseline /= time.Duration(baselineN)
  64. t.Logf("单次全表 Scan 基线: %v", baseline)
  65. // 并发 8 个 goroutine 同时 Scan
  66. readers := 8
  67. start := time.Now()
  68. var wg sync.WaitGroup
  69. errs := make([]error, readers)
  70. counts := make([]int, readers)
  71. wg.Add(readers)
  72. for i := 0; i < readers; i++ {
  73. go func(idx int) {
  74. defer wg.Done()
  75. c, e := fullScanCount(tbl)
  76. counts[idx] = c
  77. errs[idx] = e
  78. }(i)
  79. }
  80. wg.Wait()
  81. elapsed := time.Since(start)
  82. for i, e := range errs {
  83. if e != nil {
  84. t.Fatalf("reader %d error: %v", i, e)
  85. }
  86. }
  87. for i, c := range counts {
  88. if c != 5000 {
  89. t.Fatalf("reader %d scan: got %d want 5000", i, c)
  90. }
  91. }
  92. // 判定: 真正并行时 elapsed 应 < baseline * readers * 40%
  93. serialEstimate := baseline * time.Duration(readers)
  94. parallelThreshold := serialEstimate * 40 / 100
  95. t.Logf("=== 并发读验证结果 ===")
  96. t.Logf("读者 goroutine 数: %d", readers)
  97. t.Logf("单次 Scan 基线: %v", baseline)
  98. t.Logf("串行预估耗时: %v (baseline × readers)", serialEstimate)
  99. t.Logf("实际并发耗时: %v", elapsed)
  100. t.Logf("并行判定阈值: %v (串行预估 × 40%%)", parallelThreshold)
  101. if elapsed > parallelThreshold {
  102. t.Errorf("❌ 读没有真正并行! 耗时 %v > 阈值 %v (串行预估 %v)",
  103. elapsed, parallelThreshold, serialEstimate)
  104. } else {
  105. speedup := float64(serialEstimate) / float64(elapsed)
  106. t.Logf("✅ 读真正并行! 加速比 %.1fx (串行/实际)", speedup)
  107. }
  108. }
  109. // fullScanCount 全表扫描返回行数 (验证 scanWithPKID 的 RLock 并行)
  110. func fullScanCount(tbl *Table) (int, error) {
  111. q := tbl.Query()
  112. count := 0
  113. err := q.ScanWithPKID(func(pkID uint64, row []types.FieldValue) bool {
  114. count++
  115. return true
  116. })
  117. return count, err
  118. }
  119. // itoa 无分配 int→string
  120. func itoa(i int) string {
  121. if i == 0 {
  122. return "0"
  123. }
  124. neg := i < 0
  125. if neg {
  126. i = -i
  127. }
  128. var buf [20]byte
  129. pos := len(buf)
  130. for i > 0 {
  131. pos--
  132. buf[pos] = byte('0' + i%10)
  133. i /= 10
  134. }
  135. if neg {
  136. pos--
  137. buf[pos] = '-'
  138. }
  139. return string(buf[pos:])
  140. }