wal_recovery.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package core
  2. import (
  3. "TsDB2/DB/core/page"
  4. "encoding/binary"
  5. "hash/crc32"
  6. )
  7. // =============================================================================
  8. // WAL 崩溃恢复
  9. //
  10. // 在 DB.Open 时调用 RecoverWAL:
  11. // 1. 从 FirstWALPage 遍历 WAL 链
  12. // 2. 读首页: TxnID + TotalEntries + Committed
  13. // 3. 收集全部 Entry (按 TotalEntries 计数)
  14. // 4. 每条 Entry 验证 CRC32
  15. // 5. Committed==1 -> 重放所有页镜像到实际位置
  16. // 6. Committed==0 -> 丢弃 (事务未完成)
  17. // 7. 回收所有 WAL 页, 重置 HeaderPage
  18. // =============================================================================
  19. // RecoverWAL 恢复未完成的 WAL 事务
  20. // 每个条目都显式带 OpType:
  21. //
  22. // WALOpFullPage (0): 整页镜像, 直接写回实际页
  23. // Insert/Delete/Update: 行级记录, 用 applyRowToPage 应用到页
  24. func RecoverWAL(ff *File, header *HeaderPage) error {
  25. if header.FirstWALPage == 0 {
  26. return nil // 无待恢复 WAL
  27. }
  28. // 读取首页 (直接读盘: 避免读到 BufferPool 里的陈旧缓存)
  29. firstBuf := make([]byte, PageSize)
  30. if err := ff.readPageDisk(header.FirstWALPage, firstBuf); err != nil {
  31. return err
  32. }
  33. // 解析首页元数据
  34. firstHdr := page.WalHdrOf(firstBuf)
  35. totalEntries := firstHdr.TotalEntries
  36. committed := firstHdr.State
  37. // 收集所有 entry
  38. type recoveryEntry struct {
  39. pageID uint32
  40. data []byte // 整页镜像 (旧格式)
  41. isRow bool // 是否为行级记录 (新格式)
  42. opType byte // 行级操作类型
  43. storeRow []byte // 行级数据
  44. }
  45. var entries []recoveryEntry
  46. pid := header.FirstWALPage
  47. remaining := int(totalEntries)
  48. walTail := uint32(0) // 已遍历到的最后一页 (链正常读完时即真实链尾)
  49. walPages := uint32(0)
  50. for pid != 0 && remaining > 0 {
  51. buf := make([]byte, PageSize)
  52. if err := ff.readPageDisk(pid, buf); err != nil {
  53. break
  54. }
  55. hdr := page.HdrOf(buf)
  56. if hdr.Type != page.WALIndex {
  57. break
  58. }
  59. walTail = pid
  60. walPages++
  61. // 首页与续页头部等长 —— 条目区起点与 EntryCount 位置一致, 无需按 isFirst 分叉
  62. walHdr := page.WalHdrOf(buf)
  63. off := page.WALPageHdrSize
  64. entryCount := walHdr.EntryCount
  65. for i := uint16(0); i < entryCount && remaining > 0; i++ {
  66. if off+page.WALEntryMeta > PageSize {
  67. break
  68. }
  69. targetPID := binary.LittleEndian.Uint32(buf[off:])
  70. dataLen := binary.LittleEndian.Uint32(buf[off+4:])
  71. checksum := binary.LittleEndian.Uint32(buf[off+8:])
  72. opType := buf[off+12]
  73. if off+page.WALEntryMeta+dataLen > PageSize {
  74. break
  75. }
  76. payload := buf[off+page.WALEntryMeta : off+page.WALEntryMeta+dataLen]
  77. // CRC32 覆盖 OpType + Data
  78. csBuf := make([]byte, 1+dataLen)
  79. csBuf[0] = opType
  80. copy(csBuf[1:], payload)
  81. if crc32.ChecksumIEEE(csBuf) != checksum {
  82. // 校验失败: 后续内容不可信, 停止收集
  83. remaining = 0
  84. break
  85. }
  86. if opType == WALOpFullPage {
  87. // 整页镜像: 零填充到整页长度, 保证写回时文件偏移一致
  88. data := make([]byte, PageSize)
  89. copy(data, payload)
  90. entries = append(entries, recoveryEntry{pageID: targetPID, data: data})
  91. } else {
  92. storeRow := make([]byte, dataLen)
  93. copy(storeRow, payload)
  94. entries = append(entries, recoveryEntry{
  95. pageID: targetPID,
  96. isRow: true,
  97. opType: opType,
  98. storeRow: storeRow,
  99. })
  100. }
  101. off += page.WALEntryMeta + dataLen
  102. remaining--
  103. }
  104. pid = readNextWalPage(buf)
  105. }
  106. // 根据 Committed 标记决定是否重放
  107. if committed == WALStateCommitted && len(entries) > 0 {
  108. // Phase 1: 先处理整页条目 (恢复页结构: B+树节点, IndexMeta, HeaderPage 等)
  109. for _, e := range entries {
  110. if e.isRow {
  111. continue
  112. }
  113. if e.pageID == 0 {
  114. if err := recoverHeader(ff, e.data); err != nil {
  115. return err
  116. }
  117. } else {
  118. if err := ff.WritePageDirect(e.pageID, e.data); err != nil {
  119. return err
  120. }
  121. }
  122. }
  123. // Phase 2: 处理行级条目 (用 applyRowToPage 应用到 DataPage)
  124. //
  125. // 去重: 若同一页已有整页镜像 (Phase 1 已完整恢复), 则跳过它的行级条目。
  126. // 否则整页镜像里的新行 + 行级 INSERT 会被应用两次, 造成行重复。
  127. fullPageApplied := make(map[uint32]bool, len(entries))
  128. for _, e := range entries {
  129. if !e.isRow {
  130. fullPageApplied[e.pageID] = true
  131. }
  132. }
  133. for _, e := range entries {
  134. if !e.isRow || fullPageApplied[e.pageID] {
  135. continue
  136. }
  137. pageBuf := make([]byte, PageSize)
  138. if err := ff.readPageDisk(e.pageID, pageBuf); err != nil {
  139. continue // 页不存在, 跳过
  140. }
  141. applyRowToPage(pageBuf, e.opType, e.storeRow)
  142. if err := ff.WritePageDirect(e.pageID, pageBuf); err != nil {
  143. return err
  144. }
  145. }
  146. }
  147. // committed != Committed: 事务未完成, 丢弃所有 entry
  148. // 回收整条 WAL 链
  149. //
  150. // 正常路径: 上面的收集循环正好把链读完 (pid == 0), 此时 walTail / walPages 即真实
  151. // 链尾 / 链长 —— 一次拼接 O(1) 还链, 与链长无关。
  152. // 异常路径 (CRC 失败或页类型不符而提前 break, pid != 0): 真实链尾未知, 退化为逐页回收,
  153. // 既不会漏页, 也避免把"半截链"拼进空闲链 —— 那截链尾的 NextPage 仍指向后继页, 会让
  154. // 空闲链遍历窜进未回收的 WAL 页, 造成链错乱。
  155. if pid == 0 && walPages > 0 {
  156. if err := recycleChainWithCache(ff, header, header.FirstWALPage, walTail, walPages); err != nil {
  157. return err
  158. }
  159. } else {
  160. walPID := header.FirstWALPage
  161. for walPID != 0 {
  162. buf := make([]byte, PageSize)
  163. if err := ff.readPageDisk(walPID, buf); err != nil {
  164. break
  165. }
  166. nextPID := readNextWalPage(buf)
  167. header.recyclePage(walPID, ff.WritePageDirect)
  168. walPID = nextPID
  169. }
  170. }
  171. ff.withHeader(func() {
  172. header.FirstWALPage = 0
  173. header.LastWALPage = 0
  174. })
  175. if err := ff.WriteHeader(header); err != nil {
  176. return err
  177. }
  178. // 恢复收尾 fsync: 保证已重放的真实页与"WAL 已清空"的 Header 一起落盘,
  179. // 否则恢复过程本身再断电会重复重放
  180. return ff.sync()
  181. }
  182. // readNextWalPage 读取 WAL 页的链指针 (统一存放在 PageHdr.NextPage, 与空闲链同字段)
  183. func readNextWalPage(buf []byte) uint32 {
  184. return page.HdrOf(buf).NextPage
  185. }
  186. // recoverHeader 恢复 HeaderPage
  187. func recoverHeader(ff *File, data []byte) error {
  188. // data 是完整的 16KB 页镜像, 但 HeaderPage 只有 4KB
  189. // 取前 4KB 写入文件头
  190. if len(data) < PageSize4K {
  191. _, err := ff.f.WriteAt(data, 0)
  192. return err
  193. }
  194. _, err := ff.f.WriteAt(data[:PageSize4K], 0)
  195. return err
  196. }