main.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package main
  2. // =============================================================================
  3. // bench_ts — 时序表基准: 时序索引 (IndexTypeTime) 行存储的写入/范围扫描/点查
  4. //
  5. // 场景仿照典型时序负载: 递增时间戳 + 浮点数值 (+设备标签), 时间非递减写入。
  6. //
  7. // TSDB_BENCH_TS_ROWS 行数 (默认 100 万)
  8. // TSDB_BENCH_TS_BATCH 每事务行数 (默认 1000, 控制 fsync 频率)
  9. // TSDB_BENCH_TS_QUERIES 查询采样数 (默认 200)
  10. // TSDB_BENCH_TS_SPAN 范围窗口大小(默认 1 万行)
  11. //
  12. // 输出: 写入行/秒、写入 MB/s、落盘文件大小、范围扫描行/秒、点查 QPS。
  13. // 最后做一轮数据校验 (TotalRows 与行级抽查), 防止"测了个错的"。
  14. // =============================================================================
  15. import (
  16. "fmt"
  17. "math/rand"
  18. "os"
  19. "runtime/pprof"
  20. "time"
  21. "git.ycl.pub/yl32940/TsDB2/DB/core"
  22. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  23. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  24. )
  25. const dbPath = "bench_ts.db"
  26. func envInt(key string, def int) int {
  27. if v := os.Getenv(key); v != "" {
  28. var n int
  29. if _, err := fmt.Sscanf(v, "%d", &n); err == nil && n > 0 {
  30. return n
  31. }
  32. }
  33. return def
  34. }
  35. func main() {
  36. rows := envInt("TSDB_BENCH_TS_ROWS", 1_000_000)
  37. batch := envInt("TSDB_BENCH_TS_BATCH", 1000)
  38. queries := envInt("TSDB_BENCH_TS_QUERIES", 200)
  39. span := int64(envInt("TSDB_BENCH_TS_SPAN", 10000))
  40. _ = os.Remove(dbPath)
  41. db, err := core.Open(dbPath)
  42. if err != nil {
  43. fmt.Println("open:", err)
  44. os.Exit(1)
  45. }
  46. db.SetAutoCommitDelay(0)
  47. tbl, err := db.CreateTable("ticks", []types.FieldInfo{
  48. {FieldType: types.FieldType_Uint64, FieldName: []byte("ts")},
  49. {FieldType: types.FieldType_Float64, FieldName: []byte("price")},
  50. {FieldType: types.FieldType_String, Flag: types.WidthCodeFor(16), FieldName: []byte("dev")},
  51. })
  52. if err != nil {
  53. fmt.Println("create table:", err)
  54. os.Exit(1)
  55. }
  56. if _, err := tbl.CreateIndex("ts", page.IndexTypeTime, []byte{0}); err != nil {
  57. fmt.Println("create time index:", err)
  58. os.Exit(1)
  59. }
  60. const baseTS = 1_700_000_000_000 // 固定起点, 便于复现
  61. devs := []string{"alpha", "bravo", "charlie", "delta"}
  62. // ============================ 写入 ============================
  63. if pf := os.Getenv("TSDB_BENCH_TS_CPUPROFILE"); pf != "" {
  64. f, err := os.Create(pf)
  65. if err == nil {
  66. _ = pprof.StartCPUProfile(f)
  67. defer func() { pprof.StopCPUProfile(); _ = f.Close() }()
  68. }
  69. }
  70. fmt.Printf("写入阶段: %d 行, 每 %d 行一个事务...\n", rows, batch)
  71. start := time.Now()
  72. for i := int64(0); i < int64(rows); i++ {
  73. if _, err := tbl.InsertRowWithID(uint64(i+1), []types.FieldValue{
  74. {FieldName: []byte("ts"), Value: uint64(baseTS + i)},
  75. {FieldName: []byte("price"), Value: float64(i) * 1.5},
  76. {FieldName: []byte("dev"), Value: devs[i%4]},
  77. }); err != nil {
  78. fmt.Println("insert", i, ":", err)
  79. os.Exit(1)
  80. }
  81. if (i+1)%int64(batch) == 0 {
  82. if err := db.Commit(); err != nil {
  83. fmt.Println("commit:", err)
  84. os.Exit(1)
  85. }
  86. }
  87. }
  88. if err := db.Commit(); err != nil {
  89. fmt.Println("final commit:", err)
  90. os.Exit(1)
  91. }
  92. writeDur := time.Since(start)
  93. st, _ := os.Stat(dbPath)
  94. fileMB := float64(st.Size()) / 1024 / 1024
  95. fmt.Printf(" 写入: %d 行 / %v = %s 行/秒, %.1f MB (%.2f MB/秒)\n",
  96. rows, writeDur.Round(time.Millisecond), comma(int(float64(rows)/writeDur.Seconds())), fileMB, fileMB/writeDur.Seconds())
  97. if total := tbl.TotalRows(); total != uint64(rows) {
  98. fmt.Printf(" ❌ TotalRows=%d, 想要 %d —— 数据校验失败\n", total, rows)
  99. os.Exit(1)
  100. }
  101. // ============================ 范围扫描 ============================
  102. rng := rand.New(rand.NewSource(42))
  103. warm := time.Now()
  104. scanOnce := func(from int64) (int, time.Duration) {
  105. n := 0
  106. s := time.Now()
  107. err := tbl.Query().Where(core.And(
  108. core.Gt("ts", uint64(from), types.FieldType_Uint64),
  109. core.Lt("ts", uint64(from+span), types.FieldType_Uint64),
  110. )).ScanWithPKID(func(pkID uint64, row []types.FieldValue) bool {
  111. n++
  112. return true
  113. })
  114. if err != nil {
  115. fmt.Println("range scan:", err)
  116. os.Exit(1)
  117. }
  118. return n, time.Since(s)
  119. }
  120. // 预热一次 (页缓存)
  121. if n, _ := scanOnce(baseTS); n != int(span)-1 {
  122. fmt.Printf(" ⚠️ 预热范围扫描返回 %d 行, 期望 %d\n", n, span-1)
  123. }
  124. _ = warm
  125. var scanRows int
  126. var scanDur time.Duration
  127. for q := 0; q < queries; q++ {
  128. from := baseTS + rng.Int63n(int64(rows)-span)
  129. n, d := scanOnce(from)
  130. scanRows += n
  131. scanDur += d
  132. }
  133. fmt.Printf(" 范围扫描 (%d 次 × ~%s 行窗口): %s 行/秒, 平均 %v/次\n",
  134. queries, comma(int(span)), comma(int(float64(scanRows)/scanDur.Seconds())), (scanDur / time.Duration(queries)).Round(time.Microsecond))
  135. // ============================ 点查 ============================
  136. hit := 0
  137. ptStart := time.Now()
  138. for q := 0; q < queries; q++ {
  139. ts := uint64(baseTS + rng.Int63n(int64(rows)))
  140. vals, err := tbl.Query().Where(core.Eq("ts", ts, types.FieldType_Uint64)).Find()
  141. if err != nil {
  142. fmt.Println("point query:", err)
  143. os.Exit(1)
  144. }
  145. if len(vals) == 1 {
  146. hit++
  147. }
  148. }
  149. ptDur := time.Since(ptStart)
  150. fmt.Printf(" 点查 (ts=随机): %d/%d 命中, QPS=%s, 平均 %v/次\n",
  151. hit, queries, comma(int(float64(queries)/ptDur.Seconds())), (ptDur / time.Duration(queries)).Round(time.Microsecond))
  152. fmt.Println("✅ 时序基准完成 (含 TotalRows 与范围行数校验)")
  153. }
  154. func comma(n int) string {
  155. s := fmt.Sprintf("%d", n)
  156. out := make([]byte, 0, len(s)+len(s)/3)
  157. for i, c := range s {
  158. if i > 0 && (len(s)-i)%3 == 0 {
  159. out = append(out, ',')
  160. }
  161. out = append(out, byte(c))
  162. }
  163. return string(out)
  164. }