main.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package main
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB"
  4. "git.ycl.pub/yl32940/TsDB2/DB/core"
  5. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  6. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  7. "fmt"
  8. "os"
  9. "time"
  10. )
  11. const dbPath = "vacuum_demo.db"
  12. func main() {
  13. os.Remove(dbPath)
  14. // ========================================================================
  15. // Step 1: 创建一个测试库, 写 100,000 行
  16. // ========================================================================
  17. fmt.Println("=== Step 1: 建库 + 写 100,000 行 ===")
  18. db, err := DB.Open(dbPath)
  19. if err != nil {
  20. panic(err)
  21. }
  22. db.SetAutoCommitDelay(0)
  23. tbl, err := db.CoreDB().CreateTable("events", []types.FieldInfo{
  24. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  25. {FieldType: types.FieldType_Uint64, FieldName: []byte("ts")},
  26. {FieldType: types.FieldType_Uint64, FieldName: []byte("value")},
  27. })
  28. if err != nil {
  29. panic(err)
  30. }
  31. // 主键
  32. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  33. panic(err)
  34. }
  35. // 时序二级索引 (按 ts)
  36. if _, err := tbl.CreateIndex("ts_idx", page.IndexTypeBTree, []byte{1}, false); err != nil {
  37. panic(err)
  38. }
  39. start := time.Now()
  40. for i := 1; i <= 100_000; i++ {
  41. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  42. {FieldName: []byte("id"), Value: uint64(i)},
  43. {FieldName: []byte("ts"), Value: uint64(1_700_000_000 + i)},
  44. {FieldName: []byte("value"), Value: uint64(i % 1000)},
  45. }); err != nil {
  46. panic(fmt.Errorf("插入第 %d 行: %w", i, err))
  47. }
  48. if i%10_000 == 0 {
  49. fmt.Printf(" 写入进度: %d / 100,000 (%.1fs)\n", i, time.Since(start).Seconds())
  50. }
  51. }
  52. if err := db.Commit(); err != nil {
  53. panic(err)
  54. }
  55. writeDur := time.Since(start)
  56. beforeSize, _ := os.Stat(dbPath)
  57. fmt.Printf("写完 100,000 行 + 2 索引, 耗时 %.1fs, 文件大小 %s\n\n",
  58. writeDur.Seconds(), humanSize(beforeSize.Size()))
  59. // ========================================================================
  60. // Step 2: 删掉 70% 数据, 制造大量墓碑
  61. // ========================================================================
  62. fmt.Println("=== Step 2: 删除 70% 数据 (70,000 行) ===")
  63. delStart := time.Now()
  64. for i := uint64(1); i <= 70_000; i++ {
  65. if err := tbl.DeleteRowByPK(i); err != nil {
  66. panic(err)
  67. }
  68. if i%10_000 == 0 {
  69. fmt.Printf(" 删除进度: %d / 70,000 (%.1fs)\n", i, time.Since(delStart).Seconds())
  70. }
  71. }
  72. if err := db.Commit(); err != nil {
  73. panic(err)
  74. }
  75. delDur := time.Since(delStart)
  76. afterDeleteSize, _ := os.Stat(dbPath)
  77. fmt.Printf("删完 70,000 行, 耗时 %.1fs, 文件大小 %s (只打墓碑, 不太缩)\n\n",
  78. delDur.Seconds(), humanSize(afterDeleteSize.Size()))
  79. c, _ := tbl.Query().Count()
  80. fmt.Printf("剩余 %d 行 (含活行 + 墓碑)\n\n", c)
  81. // ========================================================================
  82. // Step 3: VACUUM 整库重写
  83. // ========================================================================
  84. fmt.Println("=== Step 3: VACUUM 整库重写 ===")
  85. vacStart := time.Now()
  86. var lastPhase string
  87. res, err := db.VACUUM(core.VACUUMOptions{
  88. Progress: func(phase string, copied, total int64) {
  89. // 只在阶段切换时打, 避免刷屏
  90. if phase != lastPhase {
  91. fmt.Printf(" [phase=%s] 已复制 %d / %d\n", phase, copied, total)
  92. lastPhase = phase
  93. }
  94. },
  95. })
  96. if err != nil {
  97. panic(err)
  98. }
  99. vacDur := time.Since(vacStart)
  100. fmt.Println()
  101. fmt.Println("=== VACUUM 结果 ===")
  102. fmt.Printf(" 旧文件大小: %s\n", humanSize(res.OldFileSize))
  103. fmt.Printf(" 新文件大小: %s\n", humanSize(res.NewFileSize))
  104. fmt.Printf(" 节省空间: %s (%.1f%%)\n",
  105. humanSize(res.SavedBytes), res.SavedPercent)
  106. fmt.Printf(" 重建表数: %d\n", res.Tables)
  107. fmt.Printf(" 复制行数: %d\n", res.RowsCopied)
  108. fmt.Printf(" VACUUM 耗时: %.1fs\n", vacDur.Seconds())
  109. fmt.Println()
  110. // ========================================================================
  111. // Step 4: 验证
  112. // ========================================================================
  113. fmt.Println("=== Step 4: 验证 VACUUM 后数据正确 ===")
  114. c2, _ := tbl.Query().Count()
  115. fmt.Printf(" VACUUM 后剩余行数: %d (应为 30,000)\n", c2)
  116. if c2 != 30000 {
  117. fmt.Printf(" ✗ 错误! 应为 30,000, 实际 %d\n", c2)
  118. } else {
  119. fmt.Println(" ✓ 行数正确")
  120. }
  121. // 抽样校验: id=70001 应在 (前 70000 已删), id=100000 应在
  122. for _, id := range []uint64{70001, 85000, 99999, 100000} {
  123. row, err := tbl.FindByPK(id)
  124. if err != nil {
  125. fmt.Printf(" ✗ 查 id=%d 失败: %v\n", id, err)
  126. continue
  127. }
  128. fmt.Printf(" ✓ id=%d 存在: ts=%v value=%v\n",
  129. id, row[1].Value, row[2].Value)
  130. }
  131. // 验证已删的 id 不存在
  132. for _, id := range []uint64{1, 35000, 70000} {
  133. _, err := tbl.FindByPK(id)
  134. if err != nil {
  135. fmt.Printf(" ✓ id=%d 已删: %v\n", id, err)
  136. } else {
  137. fmt.Printf(" ✗ id=%d 应已删, 但仍能查到\n", id)
  138. }
  139. }
  140. // 验证时序索引 (二级索引在 VACUUM 后应可查)
  141. row, err := tbl.SearchByIndex("ts_idx", encodeUint64BE(1_700_000_000+80000))
  142. if err != nil {
  143. fmt.Printf(" ✗ 二级索引 ts_idx 查 id=80000 失败: %v\n", err)
  144. } else {
  145. fmt.Printf(" ✓ 二级索引 ts_idx 工作: id=%v ts=%v value=%v\n",
  146. row[0].Value, row[1].Value, row[2].Value)
  147. }
  148. // ========================================================================
  149. // Step 5: 自动 VACUUM 演示
  150. // ========================================================================
  151. fmt.Println()
  152. fmt.Println("=== Step 5: SetAutoVACUUM 自动调度演示 ===")
  153. // 设 5s 间隔, demo 期间不等待触发 (timer 在后台运行, 生产可设 1h+)
  154. db.SetAutoVACUUM(5 * time.Second)
  155. fmt.Println(" 已设置自动 VACUUM 间隔 = 5s (timer 在后台运行, 生产可设 1h+)")
  156. fmt.Printf(" 当前 LastVACUUM: %v\n", db.LastVACUUM().Format("15:04:05.000"))
  157. _ = db.LastVACUUM // 占位, 防 lint 报 unused
  158. // 检查文件最终大小
  159. finalSize, _ := os.Stat(dbPath)
  160. fmt.Printf(" 最终文件大小: %s\n\n", humanSize(finalSize.Size()))
  161. fmt.Println(" 准备 Close db...")
  162. if err := db.Close(); err != nil {
  163. fmt.Printf(" Close 报错: %v\n", err)
  164. fmt.Println("=== 完成 (Close 出错但不影响 VACUUM 演示) ===")
  165. return
  166. }
  167. fmt.Println(" Close 成功")
  168. fmt.Println("=== 完成 ===")
  169. }
  170. func humanSize(n int64) string {
  171. const (
  172. KB = 1024
  173. MB = 1024 * 1024
  174. GB = 1024 * 1024 * 1024
  175. )
  176. switch {
  177. case n >= GB:
  178. return fmt.Sprintf("%.2f GB", float64(n)/GB)
  179. case n >= MB:
  180. return fmt.Sprintf("%.2f MB", float64(n)/MB)
  181. case n >= KB:
  182. return fmt.Sprintf("%.1f KB", float64(n)/KB)
  183. default:
  184. return fmt.Sprintf("%d B", n)
  185. }
  186. }
  187. func encodeUint64BE(v uint64) []byte {
  188. b := make([]byte, 8)
  189. for i := 7; i >= 0; i-- {
  190. b[i] = byte(v)
  191. v >>= 8
  192. }
  193. return b
  194. }