| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- package core
- import (
- "path/filepath"
- "sort"
- "sync"
- "sync/atomic"
- "testing"
- "time"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // TestEndToEnd_ConcurrentReads 端到端验证并发读是否真正生效。
- //
- // 方案: 开真实库, 建表, 插 5000 行数据, 然后同时 N 个 goroutine 做全表 ScanWithPKID。
- // 每个 Scan 本身耗时 ~X ms。如果是串行锁, N 个 goroutine 总耗时 ≈ N×X ms;
- // 如果 RLock 真正并行, 总耗时应该接近 ≈ X ms (或略多, 远小于 N×X)。
- func TestEndToEnd_ConcurrentReads(t *testing.T) {
- tmpDir := t.TempDir()
- dbPath := filepath.Join(tmpDir, "concurrent_test.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("Open: %v", err)
- }
- defer db.Close()
- // 建表
- tbl, err := db.CreateTable("items", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("name")},
- {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
- })
- if err != nil {
- t.Fatalf("CreateTable: %v", err)
- }
- // 主键索引 (独立项目需要手动建, DockerManager 版本在 CreateTable 内自动建)
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- t.Fatalf("CreateIndex pk: %v", err)
- }
- // 批量插 5000 行
- txn := db.BeginTransaction()
- for i := 0; i < 5000; i++ {
- pk := tbl.AllocPKID()
- name := []byte("item_" + itoa(i))
- if _, err := tbl.InsertRowWithID(pk, []types.FieldValue{
- {FieldName: []byte("id"), Value: pk},
- {FieldName: []byte("name"), Value: name},
- {FieldName: []byte("v"), Value: int64(i)},
- }); err != nil {
- txn.Rollback()
- t.Fatalf("InsertRowWithID(%d): %v", i, err)
- }
- }
- if err := txn.Commit(); err != nil {
- t.Fatalf("Commit: %v", err)
- }
- t.Logf("插入 5000 行完成")
- // 验证 accMu 的 readers 计数: 临时注入, RLock 进来 +1, RUnlock -1
- // 如果并发, readers 应该同时 > 1
- t.Logf("=== 并发读验证 (临时 log) ===")
- // 串行基线: 单个 goroutine Scan 5 次取平均
- baselineN := 3
- baseline := time.Duration(0)
- for i := 0; i < baselineN; i++ {
- start := time.Now()
- c, e := fullScanCount(tbl)
- if e != nil {
- t.Fatalf("Scan baseline %d: %v", i, e)
- }
- if c != 5000 {
- t.Fatalf("Scan baseline: got %d want 5000", c)
- }
- baseline += time.Since(start)
- }
- baseline /= time.Duration(baselineN)
- t.Logf("单次全表 Scan 基线: %v", baseline)
- // 并发 8 个 goroutine 同时 Scan —— 测 BufferPool 内部锁的竞争
- readers := 8
- // ====== 阶段 1: 预热 ======
- // 先 8 个 goroutine 各跑 2 次全表 Scan, 把 B+ 树节点 + 数据页全读进 BufferPool
- t.Logf("=== 预热: 8 goroutine × 2 次全表 Scan ===")
- for warm := 0; warm < 2; warm++ {
- var wg sync.WaitGroup
- wg.Add(readers)
- for i := 0; i < readers; i++ {
- go func() {
- defer wg.Done()
- fullScanCount(tbl)
- }()
- }
- wg.Wait()
- }
- t.Logf("预热完成, BufferPool 应该热了")
- // ====== 阶段 2: 热缓存并发读 ======
- t.Logf("=== 热缓存并发读 (预期大部分 cache-hit) ===")
- start := time.Now()
- var wg2 sync.WaitGroup
- errs := make([]error, readers)
- counts := make([]int, readers)
- startTimes := make([]time.Time, readers)
- endTimes := make([]time.Time, readers)
- wg2.Add(readers)
- var activeReaders int32
- var maxActive int32
- for i := 0; i < readers; i++ {
- go func(idx int) {
- defer wg2.Done()
- startTimes[idx] = time.Now()
- cur := atomic.AddInt32(&activeReaders, 1)
- for {
- m := atomic.LoadInt32(&maxActive)
- if cur <= m || atomic.CompareAndSwapInt32(&maxActive, m, cur) {
- break
- }
- }
- c, e := fullScanCount(tbl)
- counts[idx] = c
- errs[idx] = e
- atomic.AddInt32(&activeReaders, -1)
- endTimes[idx] = time.Now()
- }(i)
- }
- wg2.Wait()
- elapsed := time.Since(start)
- t.Logf("同时在跑的最大 goroutine 数: %d (如果 = 8 说明真并行)", atomic.LoadInt32(&maxActive))
- startDeltas := make([]time.Duration, readers)
- for i := 0; i < readers; i++ {
- startDeltas[i] = startTimes[i].Sub(start)
- }
- sort.Slice(startDeltas, func(i, j int) bool { return startDeltas[i] < startDeltas[j] })
- t.Logf("8 个 goroutine 开始时间分布 (最早→最晚):")
- t.Logf(" 最早: %v", startDeltas[0])
- t.Logf(" 最晚: %v", startDeltas[readers-1])
- t.Logf(" 开始时间离散度: %v", startDeltas[readers-1]-startDeltas[0])
- for i, e := range errs {
- if e != nil {
- t.Fatalf("reader %d error: %v", i, e)
- }
- }
- for i, c := range counts {
- if c != 5000 {
- t.Fatalf("reader %d scan: got %d want 5000", i, c)
- }
- }
- speedup := float64(baseline*time.Duration(readers)) / float64(elapsed)
- t.Logf("单次全表 Scan 基线: %v", baseline)
- t.Logf("串行预估 (baseline × 8): %v", baseline*time.Duration(readers))
- t.Logf("8 读者耗时 (热缓存): %v", elapsed)
- t.Logf("加速比 (串行/实际): %.2fx", speedup)
- if speedup > 1.5 {
- t.Logf("✅ 热缓存并发读真的并行了! (加速比 > 1.5x)")
- } else {
- t.Logf("⚠️ 加速比 %.2fx, 可能瓶颈不在 BufferPool 锁 (磁盘 IO/Go 调度等)", speedup)
- }
- }
- // fullScanCount 全表扫描返回行数 (验证 scanWithPKID 的 RLock 并行)
- func fullScanCount(tbl *Table) (int, error) {
- q := tbl.Query()
- count := 0
- err := q.ScanWithPKID(func(pkID uint64, row []types.FieldValue) bool {
- count++
- return true
- })
- return count, err
- }
- // itoa 无分配 int→string
- func itoa(i int) string {
- if i == 0 {
- return "0"
- }
- neg := i < 0
- if neg {
- i = -i
- }
- var buf [20]byte
- pos := len(buf)
- for i > 0 {
- pos--
- buf[pos] = byte('0' + i%10)
- i /= 10
- }
- if neg {
- pos--
- buf[pos] = '-'
- }
- return string(buf[pos:])
- }
- // TestAccMu_RLockParallel_EndToEnd 端到端验证 accMu RLock 真能让多个 Scan 并行
- // 直接用 db.lockRead() + 模拟 Scan 工作 (sleep), 看 8 个 goroutine 是不是真并行
- func TestAccMu_RLockParallel_EndToEnd(t *testing.T) {
- tmpDir := t.TempDir()
- dbPath := filepath.Join(tmpDir, "test.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("Open: %v", err)
- }
- defer db.Close()
- const readers = 8
- const hold = 30 * time.Millisecond // 每个 RLock 持有 30ms (模拟 Scan 耗时)
- // baseline: 串行拿 RLock
- baselineN := 5
- var baseline time.Duration
- for i := 0; i < baselineN; i++ {
- start := time.Now()
- unlock := db.lockRead()
- time.Sleep(hold)
- unlock()
- baseline += time.Since(start)
- }
- baseline /= time.Duration(baselineN)
- t.Logf("串行 RLock+sleep baseline: %v", baseline)
- // 8 个 goroutine 并发拿 RLock
- start := time.Now()
- var wg sync.WaitGroup
- wg.Add(readers)
- for i := 0; i < readers; i++ {
- go func() {
- defer wg.Done()
- unlock := db.lockRead()
- time.Sleep(hold)
- unlock()
- }()
- }
- wg.Wait()
- elapsed := time.Since(start)
- speedup := float64(baseline*time.Duration(readers)) / float64(elapsed)
- t.Logf("8 并发 RLock+sleep: %v", elapsed)
- t.Logf("串行预估: %v", baseline*time.Duration(readers))
- t.Logf("加速比: %.2fx", speedup)
- if speedup < 4.0 {
- t.Errorf("accMu RLock 并发加速比 %.2fx < 4x! 说明有别的锁串住了 Scan", speedup)
- } else {
- t.Logf("accMu RLock 真并行! 加速比 %.2fx", speedup)
- }
- }
- // TestScanConcurrencyLevels 测不同并发数下的加速比
- func TestScanConcurrencyLevels(t *testing.T) {
- tmpDir := t.TempDir()
- dbPath := filepath.Join(tmpDir, "levels.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("Open: %v", err)
- }
- defer db.Close()
- tbl, err := db.CreateTable("items", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("name")},
- {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
- })
- if err != nil {
- t.Fatalf("CreateTable: %v", err)
- }
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- t.Fatalf("CreateIndex pk: %v", err)
- }
- txn := db.BeginTransaction()
- for i := 0; i < 5000; i++ {
- pk := tbl.AllocPKID()
- name := []byte("item_" + itoa(i))
- if _, err := tbl.InsertRowWithID(pk, []types.FieldValue{
- {FieldName: []byte("id"), Value: pk},
- {FieldName: []byte("name"), Value: name},
- {FieldName: []byte("v"), Value: int64(i)},
- }); err != nil {
- txn.Rollback()
- t.Fatalf("InsertRowWithID(%d): %v", i, err)
- }
- }
- if err := txn.Commit(); err != nil {
- t.Fatalf("Commit: %v", err)
- }
- // baseline
- baselineN := 3
- var baseline time.Duration
- for i := 0; i < baselineN; i++ {
- start := time.Now()
- fullScanCount(tbl)
- baseline += time.Since(start)
- }
- baseline /= time.Duration(baselineN)
- t.Logf("单次 Scan baseline: %v", baseline)
- // 预热
- for i := 0; i < 3; i++ {
- fullScanCount(tbl)
- }
- for _, readers := range []int{1, 2, 4, 6, 8} {
- start := time.Now()
- var wg sync.WaitGroup
- wg.Add(readers)
- for i := 0; i < readers; i++ {
- go func() {
- defer wg.Done()
- fullScanCount(tbl)
- }()
- }
- wg.Wait()
- elapsed := time.Since(start)
- speedup := float64(baseline*time.Duration(readers)) / float64(elapsed)
- perReader := float64(elapsed) / float64(time.Duration(readers))
- baselineNs := float64(baseline) / 1e6
- t.Logf("%d readers: total %v, per-reader %.1fms (baseline %.1fms), speedup %.2fx",
- readers, elapsed, perReader, baselineNs, speedup)
- }
- }
|