wal_recovery.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package core
  2. import (
  3. "TsDB2/DB/core/page"
  4. "encoding/binary"
  5. "hash/crc32"
  6. "unsafe"
  7. )
  8. // =============================================================================
  9. // WAL 崩溃恢复
  10. //
  11. // 在 DB.Open 时调用 RecoverWAL:
  12. // 1. 从 FirstWALPage 遍历 WAL 链
  13. // 2. 读首页: TxnID + TotalEntries + Committed
  14. // 3. 收集全部 Entry (按 TotalEntries 计数)
  15. // 4. 每条 Entry 验证 CRC32
  16. // 5. Committed==1 -> 重放所有页镜像到实际位置
  17. // 6. Committed==0 -> 丢弃 (事务未完成)
  18. // 7. 回收所有 WAL 页, 重置 HeaderPage
  19. // =============================================================================
  20. // RecoverWAL 恢复未完成的 WAL 事务
  21. func RecoverWAL(ff *File, header *HeaderPage) error {
  22. if header.FirstWALPage == 0 {
  23. return nil // 无待恢复 WAL
  24. }
  25. // 读取首页
  26. firstBuf := make([]byte, PageSize)
  27. if err := ff.ReadPage(header.FirstWALPage, firstBuf); err != nil {
  28. return err
  29. }
  30. // 解析首页元数据
  31. _ = binary.LittleEndian.Uint64(firstBuf[walPHDR:]) // TxnID (用于调试)
  32. totalEntries := binary.LittleEndian.Uint32(firstBuf[walPHDR+8:])
  33. committed := firstBuf[walPHDR+12]
  34. // 收集所有 entry
  35. type recoveryEntry struct {
  36. pageID uint32
  37. data []byte
  38. }
  39. var entries []recoveryEntry
  40. pid := header.FirstWALPage
  41. remaining := int(totalEntries)
  42. for pid != 0 && remaining > 0 {
  43. buf := make([]byte, PageSize)
  44. if err := ff.ReadPage(pid, buf); err != nil {
  45. break
  46. }
  47. // 验证页类型
  48. hdr := (*page.PageHdr)(unsafe.Pointer(&buf[0]))
  49. if hdr.Type != page.WALIndex {
  50. break
  51. }
  52. // 确定条目起始偏移
  53. var off uint32
  54. if pid == header.FirstWALPage {
  55. off = walFirstHdr // 首页
  56. } else {
  57. off = walContHdr // 续页
  58. }
  59. // 读取本页的 entry
  60. entryCount := readEntryCount(buf, pid == header.FirstWALPage)
  61. for i := uint16(0); i < entryCount && remaining > 0; i++ {
  62. if off+walEntryMeta > PageSize {
  63. break
  64. }
  65. targetPID := binary.LittleEndian.Uint32(buf[off:])
  66. dataLen := binary.LittleEndian.Uint32(buf[off+4:])
  67. checksum := binary.LittleEndian.Uint32(buf[off+8:])
  68. if off+walEntryMeta+dataLen > PageSize {
  69. break
  70. }
  71. data := make([]byte, PageSize) // 完整页缓冲区, 未用部分零填充
  72. copy(data, buf[off+12:off+12+dataLen]) // 只读 dataLen 字节
  73. // 验证 CRC32 (只校验实际存储的 dataLen 字节, 与写入时一致)
  74. if crc32.ChecksumIEEE(data[:dataLen]) != checksum {
  75. // 校验失败, 停止恢复 (数据可能损坏)
  76. remaining = 0
  77. break
  78. }
  79. entries = append(entries, recoveryEntry{targetPID, data})
  80. off += walEntryMeta + dataLen
  81. remaining--
  82. }
  83. // 下一个 WAL 页
  84. pid = readNextWalPage(buf, pid == header.FirstWALPage)
  85. }
  86. // 根据 Committed 标记决定是否重放
  87. if committed == 1 && len(entries) > 0 {
  88. // 事务已提交但实际页可能未全部写入 -> 重放补齐
  89. for _, e := range entries {
  90. if e.pageID == 0 {
  91. // HeaderPage 特殊处理: 直接写回
  92. if err := recoverHeader(ff, e.data); err != nil {
  93. return err
  94. }
  95. } else {
  96. if err := ff.WritePageDirect(e.pageID, e.data); err != nil {
  97. return err
  98. }
  99. }
  100. }
  101. }
  102. // committed == 0: 事务未完成, 丢弃所有 entry (不回写)
  103. // 回收所有 WAL 页
  104. walPID := header.FirstWALPage
  105. for walPID != 0 {
  106. buf := make([]byte, PageSize)
  107. if err := ff.ReadPage(walPID, buf); err != nil {
  108. break
  109. }
  110. isFirst := walPID == header.FirstWALPage
  111. nextPID := readNextWalPage(buf, isFirst)
  112. header.recyclePage(walPID, ff.WritePageDirect)
  113. walPID = nextPID
  114. }
  115. // 重置 WAL 指针
  116. header.FirstWALPage = 0
  117. header.LastWALPage = 0
  118. return ff.WriteHeader(header)
  119. }
  120. // readEntryCount 读取 WAL 页的 EntryCount
  121. func readEntryCount(buf []byte, isFirst bool) uint16 {
  122. if isFirst {
  123. return binary.LittleEndian.Uint16(buf[walPHDR+17:])
  124. }
  125. return binary.LittleEndian.Uint16(buf[walPHDR+4:])
  126. }
  127. // readNextWalPage 读取 WAL 页的 NextWalPage
  128. func readNextWalPage(buf []byte, isFirst bool) uint32 {
  129. if isFirst {
  130. return binary.LittleEndian.Uint32(buf[walPHDR+13:])
  131. }
  132. return binary.LittleEndian.Uint32(buf[walPHDR:])
  133. }
  134. // recoverHeader 恢复 HeaderPage
  135. func recoverHeader(ff *File, data []byte) error {
  136. // data 是完整的 16KB 页镜像, 但 HeaderPage 只有 4KB
  137. // 取前 4KB 写入文件头
  138. if len(data) < PageSize4K {
  139. _, err := ff.f.WriteAt(data, 0)
  140. return err
  141. }
  142. _, err := ff.f.WriteAt(data[:PageSize4K], 0)
  143. return err
  144. }