live_copy_heal_test.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. package migrate
  2. // 临时实验: 活拷文件自愈验证。用真实引擎打开(含 WAL 恢复) → Commit → Close,
  3. // 对比自愈前后的孤儿页口径。不会碰原件 (先拷到 t.TempDir)。
  4. // 用法: GO_LIVECOPY=<docker.db 路径> go test ./migrate -run TestLiveCopyHeal -v
  5. import (
  6. "fmt"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "testing"
  11. "git.ycl.pub/yl32940/TsDB2/DB/core"
  12. "git.ycl.pub/yl32940/TsDB2/internal/orphancheck"
  13. )
  14. func TestLiveCopyHeal(t *testing.T) {
  15. src := os.Getenv("GO_LIVECOPY")
  16. if src == "" {
  17. t.Skip("GO_LIVECOPY 未设置")
  18. }
  19. dst := filepath.Join(t.TempDir(), "docker.db")
  20. fin, err := os.Open(src)
  21. if err != nil {
  22. t.Fatal(err)
  23. }
  24. fout, err := os.Create(dst)
  25. if err != nil {
  26. t.Fatal(err)
  27. }
  28. if _, err := io.Copy(fout, fin); err != nil {
  29. t.Fatal(err)
  30. }
  31. fin.Close()
  32. fout.Close()
  33. before, err := orphancheck.Run(dst, orphancheck.Options{EngineBlobScan: true, SamplesPerType: 2})
  34. if err != nil {
  35. t.Fatal("自愈前取证:", err)
  36. }
  37. fmt.Printf("== 自愈前: analyzer 孤儿=%d (blob 感知=%d, 其中 WAL 残留 %d), 空闲链=%d (声明 %d), WALPending=%v\n",
  38. len(before.AnalyzerOrphans), len(before.Orphans), before.AnalyzerWalLeft, before.FreeCount, before.FreeDeclared, before.WALPending)
  39. db, err := core.OpenWithOptions(dst, core.OpenOptions{})
  40. if err != nil {
  41. t.Fatal("引擎打开:", err)
  42. }
  43. fmt.Printf("引擎打开成功 (WAL 恢复已执行)\n")
  44. if err := db.Commit(); err != nil {
  45. t.Fatal("Commit:", err)
  46. }
  47. if err := db.Close(); err != nil {
  48. t.Fatal("Close:", err)
  49. }
  50. fmt.Println("引擎 Commit+Close 完成")
  51. after, err := orphancheck.Run(dst, orphancheck.Options{EngineBlobScan: true, SamplesPerType: 2})
  52. if err != nil {
  53. t.Fatal("自愈后取证:", err)
  54. }
  55. fmt.Printf("== 自愈后: analyzer 孤儿=%d (blob 感知=%d, 其中 WAL 残留 %d), 空闲链=%d (声明 %d), WALPending=%v\n",
  56. len(after.AnalyzerOrphans), len(after.Orphans), after.AnalyzerWalLeft, after.FreeCount, after.FreeDeclared, after.WALPending)
  57. // 第二段: VACUUM 重建 — 行数据不动, 索引/空闲链/孤儿页全部重建到新文件。
  58. db2, err := core.OpenWithOptions(dst, core.OpenOptions{})
  59. if err != nil {
  60. t.Fatal("VACUUM 前打开:", err)
  61. }
  62. vres, err := db2.VACUUM()
  63. if err != nil {
  64. t.Fatal("VACUUM:", err)
  65. }
  66. fmt.Printf("VACUUM 完成: %d 字节 → %d 字节, 复制 %d 行\n", vres.OldFileSize, vres.NewFileSize, vres.RowsCopied)
  67. if err := db2.Close(); err != nil {
  68. t.Fatal("VACUUM 后 Close:", err)
  69. }
  70. final, err := orphancheck.Run(dst, orphancheck.Options{EngineBlobScan: true, SamplesPerType: 2})
  71. if err != nil {
  72. t.Fatal("VACUUM 后取证:", err)
  73. }
  74. fmt.Printf("== VACUUM 后: analyzer 孤儿=%d (blob 感知=%d, 其中 WAL 残留 %d), 空闲链=%d (声明 %d), WALPending=%v\n",
  75. len(final.AnalyzerOrphans), len(final.Orphans), final.AnalyzerWalLeft, final.FreeCount, final.FreeDeclared, final.WALPending)
  76. fmt.Println("最终文件保留在:", dst)
  77. if out := os.Getenv("GO_LIVEOUT"); out != "" {
  78. fin2, _ := os.Open(dst)
  79. fout2, err := os.Create(out)
  80. if err != nil {
  81. t.Fatal(err)
  82. }
  83. if _, err := io.Copy(fout2, fin2); err != nil {
  84. t.Fatal(err)
  85. }
  86. fin2.Close()
  87. fout2.Close()
  88. fmt.Println("已复制到:", out)
  89. }
  90. }