| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package main
- import (
- "git.ycl.pub/yl32940/TsDB2/DB"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- "fmt"
- "os"
- "time"
- )
- const dbPath = "vacuum_demo.db"
- func main() {
- os.Remove(dbPath)
- // ========================================================================
- // Step 1: 创建一个测试库, 写 100,000 行
- // ========================================================================
- fmt.Println("=== Step 1: 建库 + 写 100,000 行 ===")
- db, err := DB.Open(dbPath)
- if err != nil {
- panic(err)
- }
- db.SetAutoCommitDelay(0)
- tbl, err := db.CoreDB().CreateTable("events", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_Uint64, FieldName: []byte("ts")},
- {FieldType: types.FieldType_Uint64, FieldName: []byte("value")},
- })
- if err != nil {
- panic(err)
- }
- // 主键
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- panic(err)
- }
- // 时序二级索引 (按 ts)
- if _, err := tbl.CreateIndex("ts_idx", page.IndexTypeBTree, []byte{1}, false); err != nil {
- panic(err)
- }
- start := time.Now()
- for i := 1; i <= 100_000; i++ {
- if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(i)},
- {FieldName: []byte("ts"), Value: uint64(1_700_000_000 + i)},
- {FieldName: []byte("value"), Value: uint64(i % 1000)},
- }); err != nil {
- panic(fmt.Errorf("插入第 %d 行: %w", i, err))
- }
- if i%10_000 == 0 {
- fmt.Printf(" 写入进度: %d / 100,000 (%.1fs)\n", i, time.Since(start).Seconds())
- }
- }
- if err := db.Commit(); err != nil {
- panic(err)
- }
- writeDur := time.Since(start)
- beforeSize, _ := os.Stat(dbPath)
- fmt.Printf("写完 100,000 行 + 2 索引, 耗时 %.1fs, 文件大小 %s\n\n",
- writeDur.Seconds(), humanSize(beforeSize.Size()))
- // ========================================================================
- // Step 2: 删掉 70% 数据, 制造大量墓碑
- // ========================================================================
- fmt.Println("=== Step 2: 删除 70% 数据 (70,000 行) ===")
- delStart := time.Now()
- for i := uint64(1); i <= 70_000; i++ {
- if err := tbl.DeleteRowByPK(i); err != nil {
- panic(err)
- }
- if i%10_000 == 0 {
- fmt.Printf(" 删除进度: %d / 70,000 (%.1fs)\n", i, time.Since(delStart).Seconds())
- }
- }
- if err := db.Commit(); err != nil {
- panic(err)
- }
- delDur := time.Since(delStart)
- afterDeleteSize, _ := os.Stat(dbPath)
- fmt.Printf("删完 70,000 行, 耗时 %.1fs, 文件大小 %s (只打墓碑, 不太缩)\n\n",
- delDur.Seconds(), humanSize(afterDeleteSize.Size()))
- c, _ := tbl.Query().Count()
- fmt.Printf("剩余 %d 行 (含活行 + 墓碑)\n\n", c)
- // ========================================================================
- // Step 3: VACUUM 整库重写
- // ========================================================================
- fmt.Println("=== Step 3: VACUUM 整库重写 ===")
- vacStart := time.Now()
- var lastPhase string
- res, err := db.VACUUM(core.VACUUMOptions{
- Progress: func(phase string, copied, total int64) {
- // 只在阶段切换时打, 避免刷屏
- if phase != lastPhase {
- fmt.Printf(" [phase=%s] 已复制 %d / %d\n", phase, copied, total)
- lastPhase = phase
- }
- },
- })
- if err != nil {
- panic(err)
- }
- vacDur := time.Since(vacStart)
- fmt.Println()
- fmt.Println("=== VACUUM 结果 ===")
- fmt.Printf(" 旧文件大小: %s\n", humanSize(res.OldFileSize))
- fmt.Printf(" 新文件大小: %s\n", humanSize(res.NewFileSize))
- fmt.Printf(" 节省空间: %s (%.1f%%)\n",
- humanSize(res.SavedBytes), res.SavedPercent)
- fmt.Printf(" 重建表数: %d\n", res.Tables)
- fmt.Printf(" 复制行数: %d\n", res.RowsCopied)
- fmt.Printf(" VACUUM 耗时: %.1fs\n", vacDur.Seconds())
- fmt.Println()
- // ========================================================================
- // Step 4: 验证
- // ========================================================================
- fmt.Println("=== Step 4: 验证 VACUUM 后数据正确 ===")
- c2, _ := tbl.Query().Count()
- fmt.Printf(" VACUUM 后剩余行数: %d (应为 30,000)\n", c2)
- if c2 != 30000 {
- fmt.Printf(" ✗ 错误! 应为 30,000, 实际 %d\n", c2)
- } else {
- fmt.Println(" ✓ 行数正确")
- }
- // 抽样校验: id=70001 应在 (前 70000 已删), id=100000 应在
- for _, id := range []uint64{70001, 85000, 99999, 100000} {
- row, err := tbl.FindByPK(id)
- if err != nil {
- fmt.Printf(" ✗ 查 id=%d 失败: %v\n", id, err)
- continue
- }
- fmt.Printf(" ✓ id=%d 存在: ts=%v value=%v\n",
- id, row[1].Value, row[2].Value)
- }
- // 验证已删的 id 不存在
- for _, id := range []uint64{1, 35000, 70000} {
- _, err := tbl.FindByPK(id)
- if err != nil {
- fmt.Printf(" ✓ id=%d 已删: %v\n", id, err)
- } else {
- fmt.Printf(" ✗ id=%d 应已删, 但仍能查到\n", id)
- }
- }
- // 验证时序索引 (二级索引在 VACUUM 后应可查)
- row, err := tbl.SearchByIndex("ts_idx", encodeUint64BE(1_700_000_000+80000))
- if err != nil {
- fmt.Printf(" ✗ 二级索引 ts_idx 查 id=80000 失败: %v\n", err)
- } else {
- fmt.Printf(" ✓ 二级索引 ts_idx 工作: id=%v ts=%v value=%v\n",
- row[0].Value, row[1].Value, row[2].Value)
- }
- // ========================================================================
- // Step 5: 自动 VACUUM 演示
- // ========================================================================
- fmt.Println()
- fmt.Println("=== Step 5: SetAutoVACUUM 自动调度演示 ===")
- // 设 5s 间隔, demo 期间不等待触发 (timer 在后台运行, 生产可设 1h+)
- db.SetAutoVACUUM(5 * time.Second)
- fmt.Println(" 已设置自动 VACUUM 间隔 = 5s (timer 在后台运行, 生产可设 1h+)")
- fmt.Printf(" 当前 LastVACUUM: %v\n", db.LastVACUUM().Format("15:04:05.000"))
- _ = db.LastVACUUM // 占位, 防 lint 报 unused
- // 检查文件最终大小
- finalSize, _ := os.Stat(dbPath)
- fmt.Printf(" 最终文件大小: %s\n\n", humanSize(finalSize.Size()))
- fmt.Println(" 准备 Close db...")
- if err := db.Close(); err != nil {
- fmt.Printf(" Close 报错: %v\n", err)
- fmt.Println("=== 完成 (Close 出错但不影响 VACUUM 演示) ===")
- return
- }
- fmt.Println(" Close 成功")
- fmt.Println("=== 完成 ===")
- }
- func humanSize(n int64) string {
- const (
- KB = 1024
- MB = 1024 * 1024
- GB = 1024 * 1024 * 1024
- )
- switch {
- case n >= GB:
- return fmt.Sprintf("%.2f GB", float64(n)/GB)
- case n >= MB:
- return fmt.Sprintf("%.2f MB", float64(n)/MB)
- case n >= KB:
- return fmt.Sprintf("%.1f KB", float64(n)/KB)
- default:
- return fmt.Sprintf("%d B", n)
- }
- }
- func encodeUint64BE(v uint64) []byte {
- b := make([]byte, 8)
- for i := 7; i >= 0; i-- {
- b[i] = byte(v)
- v >>= 8
- }
- return b
- }
|