endtoend_concurrent_read_test.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. package core
  2. import (
  3. "path/filepath"
  4. "sort"
  5. "sync"
  6. "sync/atomic"
  7. "testing"
  8. "time"
  9. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  10. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  11. )
  12. // TestEndToEnd_ConcurrentReads 端到端验证并发读是否真正生效。
  13. //
  14. // 方案: 开真实库, 建表, 插 5000 行数据, 然后同时 N 个 goroutine 做全表 ScanWithPKID。
  15. // 每个 Scan 本身耗时 ~X ms。如果是串行锁, N 个 goroutine 总耗时 ≈ N×X ms;
  16. // 如果 RLock 真正并行, 总耗时应该接近 ≈ X ms (或略多, 远小于 N×X)。
  17. func TestEndToEnd_ConcurrentReads(t *testing.T) {
  18. tmpDir := t.TempDir()
  19. dbPath := filepath.Join(tmpDir, "concurrent_test.db")
  20. db, err := Open(dbPath)
  21. if err != nil {
  22. t.Fatalf("Open: %v", err)
  23. }
  24. defer db.Close()
  25. // 建表
  26. tbl, err := db.CreateTable("items", []types.FieldInfo{
  27. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  28. {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("name")},
  29. {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
  30. })
  31. if err != nil {
  32. t.Fatalf("CreateTable: %v", err)
  33. }
  34. // 主键索引 (独立项目需要手动建, DockerManager 版本在 CreateTable 内自动建)
  35. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  36. t.Fatalf("CreateIndex pk: %v", err)
  37. }
  38. // 批量插 5000 行
  39. txn := db.BeginTransaction()
  40. for i := 0; i < 5000; i++ {
  41. pk := tbl.AllocPKID()
  42. name := []byte("item_" + itoa(i))
  43. if _, err := tbl.InsertRowWithID(pk, []types.FieldValue{
  44. {FieldName: []byte("id"), Value: pk},
  45. {FieldName: []byte("name"), Value: name},
  46. {FieldName: []byte("v"), Value: int64(i)},
  47. }); err != nil {
  48. txn.Rollback()
  49. t.Fatalf("InsertRowWithID(%d): %v", i, err)
  50. }
  51. }
  52. if err := txn.Commit(); err != nil {
  53. t.Fatalf("Commit: %v", err)
  54. }
  55. t.Logf("插入 5000 行完成")
  56. // 验证 accMu 的 readers 计数: 临时注入, RLock 进来 +1, RUnlock -1
  57. // 如果并发, readers 应该同时 > 1
  58. t.Logf("=== 并发读验证 (临时 log) ===")
  59. // 串行基线: 单个 goroutine Scan 5 次取平均
  60. baselineN := 3
  61. baseline := time.Duration(0)
  62. for i := 0; i < baselineN; i++ {
  63. start := time.Now()
  64. c, e := fullScanCount(tbl)
  65. if e != nil {
  66. t.Fatalf("Scan baseline %d: %v", i, e)
  67. }
  68. if c != 5000 {
  69. t.Fatalf("Scan baseline: got %d want 5000", c)
  70. }
  71. baseline += time.Since(start)
  72. }
  73. baseline /= time.Duration(baselineN)
  74. t.Logf("单次全表 Scan 基线: %v", baseline)
  75. // 并发 8 个 goroutine 同时 Scan —— 测 BufferPool 内部锁的竞争
  76. readers := 8
  77. // ====== 阶段 1: 预热 ======
  78. // 先 8 个 goroutine 各跑 2 次全表 Scan, 把 B+ 树节点 + 数据页全读进 BufferPool
  79. t.Logf("=== 预热: 8 goroutine × 2 次全表 Scan ===")
  80. for warm := 0; warm < 2; warm++ {
  81. var wg sync.WaitGroup
  82. wg.Add(readers)
  83. for i := 0; i < readers; i++ {
  84. go func() {
  85. defer wg.Done()
  86. fullScanCount(tbl)
  87. }()
  88. }
  89. wg.Wait()
  90. }
  91. t.Logf("预热完成, BufferPool 应该热了")
  92. // ====== 阶段 2: 热缓存并发读 ======
  93. t.Logf("=== 热缓存并发读 (预期大部分 cache-hit) ===")
  94. start := time.Now()
  95. var wg2 sync.WaitGroup
  96. errs := make([]error, readers)
  97. counts := make([]int, readers)
  98. startTimes := make([]time.Time, readers)
  99. endTimes := make([]time.Time, readers)
  100. wg2.Add(readers)
  101. var activeReaders int32
  102. var maxActive int32
  103. for i := 0; i < readers; i++ {
  104. go func(idx int) {
  105. defer wg2.Done()
  106. startTimes[idx] = time.Now()
  107. cur := atomic.AddInt32(&activeReaders, 1)
  108. for {
  109. m := atomic.LoadInt32(&maxActive)
  110. if cur <= m || atomic.CompareAndSwapInt32(&maxActive, m, cur) {
  111. break
  112. }
  113. }
  114. c, e := fullScanCount(tbl)
  115. counts[idx] = c
  116. errs[idx] = e
  117. atomic.AddInt32(&activeReaders, -1)
  118. endTimes[idx] = time.Now()
  119. }(i)
  120. }
  121. wg2.Wait()
  122. elapsed := time.Since(start)
  123. t.Logf("同时在跑的最大 goroutine 数: %d (如果 = 8 说明真并行)", atomic.LoadInt32(&maxActive))
  124. startDeltas := make([]time.Duration, readers)
  125. for i := 0; i < readers; i++ {
  126. startDeltas[i] = startTimes[i].Sub(start)
  127. }
  128. sort.Slice(startDeltas, func(i, j int) bool { return startDeltas[i] < startDeltas[j] })
  129. t.Logf("8 个 goroutine 开始时间分布 (最早→最晚):")
  130. t.Logf(" 最早: %v", startDeltas[0])
  131. t.Logf(" 最晚: %v", startDeltas[readers-1])
  132. t.Logf(" 开始时间离散度: %v", startDeltas[readers-1]-startDeltas[0])
  133. for i, e := range errs {
  134. if e != nil {
  135. t.Fatalf("reader %d error: %v", i, e)
  136. }
  137. }
  138. for i, c := range counts {
  139. if c != 5000 {
  140. t.Fatalf("reader %d scan: got %d want 5000", i, c)
  141. }
  142. }
  143. speedup := float64(baseline*time.Duration(readers)) / float64(elapsed)
  144. t.Logf("单次全表 Scan 基线: %v", baseline)
  145. t.Logf("串行预估 (baseline × 8): %v", baseline*time.Duration(readers))
  146. t.Logf("8 读者耗时 (热缓存): %v", elapsed)
  147. t.Logf("加速比 (串行/实际): %.2fx", speedup)
  148. if speedup > 1.5 {
  149. t.Logf("✅ 热缓存并发读真的并行了! (加速比 > 1.5x)")
  150. } else {
  151. t.Logf("⚠️ 加速比 %.2fx, 可能瓶颈不在 BufferPool 锁 (磁盘 IO/Go 调度等)", speedup)
  152. }
  153. }
  154. // fullScanCount 全表扫描返回行数 (验证 scanWithPKID 的 RLock 并行)
  155. func fullScanCount(tbl *Table) (int, error) {
  156. q := tbl.Query()
  157. count := 0
  158. err := q.ScanWithPKID(func(pkID uint64, row []types.FieldValue) bool {
  159. count++
  160. return true
  161. })
  162. return count, err
  163. }
  164. // itoa 无分配 int→string
  165. func itoa(i int) string {
  166. if i == 0 {
  167. return "0"
  168. }
  169. neg := i < 0
  170. if neg {
  171. i = -i
  172. }
  173. var buf [20]byte
  174. pos := len(buf)
  175. for i > 0 {
  176. pos--
  177. buf[pos] = byte('0' + i%10)
  178. i /= 10
  179. }
  180. if neg {
  181. pos--
  182. buf[pos] = '-'
  183. }
  184. return string(buf[pos:])
  185. }
  186. // TestAccMu_RLockParallel_EndToEnd 端到端验证 accMu RLock 真能让多个 Scan 并行
  187. // 直接用 db.lockRead() + 模拟 Scan 工作 (sleep), 看 8 个 goroutine 是不是真并行
  188. func TestAccMu_RLockParallel_EndToEnd(t *testing.T) {
  189. tmpDir := t.TempDir()
  190. dbPath := filepath.Join(tmpDir, "test.db")
  191. db, err := Open(dbPath)
  192. if err != nil {
  193. t.Fatalf("Open: %v", err)
  194. }
  195. defer db.Close()
  196. const readers = 8
  197. const hold = 30 * time.Millisecond // 每个 RLock 持有 30ms (模拟 Scan 耗时)
  198. // baseline: 串行拿 RLock
  199. baselineN := 5
  200. var baseline time.Duration
  201. for i := 0; i < baselineN; i++ {
  202. start := time.Now()
  203. unlock := db.lockRead()
  204. time.Sleep(hold)
  205. unlock()
  206. baseline += time.Since(start)
  207. }
  208. baseline /= time.Duration(baselineN)
  209. t.Logf("串行 RLock+sleep baseline: %v", baseline)
  210. // 8 个 goroutine 并发拿 RLock
  211. start := time.Now()
  212. var wg sync.WaitGroup
  213. wg.Add(readers)
  214. for i := 0; i < readers; i++ {
  215. go func() {
  216. defer wg.Done()
  217. unlock := db.lockRead()
  218. time.Sleep(hold)
  219. unlock()
  220. }()
  221. }
  222. wg.Wait()
  223. elapsed := time.Since(start)
  224. speedup := float64(baseline*time.Duration(readers)) / float64(elapsed)
  225. t.Logf("8 并发 RLock+sleep: %v", elapsed)
  226. t.Logf("串行预估: %v", baseline*time.Duration(readers))
  227. t.Logf("加速比: %.2fx", speedup)
  228. if speedup < 4.0 {
  229. t.Errorf("accMu RLock 并发加速比 %.2fx < 4x! 说明有别的锁串住了 Scan", speedup)
  230. } else {
  231. t.Logf("accMu RLock 真并行! 加速比 %.2fx", speedup)
  232. }
  233. }
  234. // TestScanConcurrencyLevels 测不同并发数下的加速比
  235. func TestScanConcurrencyLevels(t *testing.T) {
  236. tmpDir := t.TempDir()
  237. dbPath := filepath.Join(tmpDir, "levels.db")
  238. db, err := Open(dbPath)
  239. if err != nil {
  240. t.Fatalf("Open: %v", err)
  241. }
  242. defer db.Close()
  243. tbl, err := db.CreateTable("items", []types.FieldInfo{
  244. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  245. {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("name")},
  246. {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
  247. })
  248. if err != nil {
  249. t.Fatalf("CreateTable: %v", err)
  250. }
  251. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  252. t.Fatalf("CreateIndex pk: %v", err)
  253. }
  254. txn := db.BeginTransaction()
  255. for i := 0; i < 5000; i++ {
  256. pk := tbl.AllocPKID()
  257. name := []byte("item_" + itoa(i))
  258. if _, err := tbl.InsertRowWithID(pk, []types.FieldValue{
  259. {FieldName: []byte("id"), Value: pk},
  260. {FieldName: []byte("name"), Value: name},
  261. {FieldName: []byte("v"), Value: int64(i)},
  262. }); err != nil {
  263. txn.Rollback()
  264. t.Fatalf("InsertRowWithID(%d): %v", i, err)
  265. }
  266. }
  267. if err := txn.Commit(); err != nil {
  268. t.Fatalf("Commit: %v", err)
  269. }
  270. // baseline
  271. baselineN := 3
  272. var baseline time.Duration
  273. for i := 0; i < baselineN; i++ {
  274. start := time.Now()
  275. fullScanCount(tbl)
  276. baseline += time.Since(start)
  277. }
  278. baseline /= time.Duration(baselineN)
  279. t.Logf("单次 Scan baseline: %v", baseline)
  280. // 预热
  281. for i := 0; i < 3; i++ {
  282. fullScanCount(tbl)
  283. }
  284. for _, readers := range []int{1, 2, 4, 6, 8} {
  285. start := time.Now()
  286. var wg sync.WaitGroup
  287. wg.Add(readers)
  288. for i := 0; i < readers; i++ {
  289. go func() {
  290. defer wg.Done()
  291. fullScanCount(tbl)
  292. }()
  293. }
  294. wg.Wait()
  295. elapsed := time.Since(start)
  296. speedup := float64(baseline*time.Duration(readers)) / float64(elapsed)
  297. perReader := float64(elapsed) / float64(time.Duration(readers))
  298. baselineNs := float64(baseline) / 1e6
  299. t.Logf("%d readers: total %v, per-reader %.1fms (baseline %.1fms), speedup %.2fx",
  300. readers, elapsed, perReader, baselineNs, speedup)
  301. }
  302. }