wal_recovery.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. // 支持两种行级格式:
  22. //
  23. // 新格式 (OpType != 0): 行级记录, 用 applyRowToPage 应用到页
  24. // 旧格式 (OpType == 0): 整页镜像或旧行级格式, 直接写回
  25. func RecoverWAL(ff *File, header *HeaderPage) error {
  26. if header.FirstWALPage == 0 {
  27. return nil // 无待恢复 WAL
  28. }
  29. // 读取首页
  30. firstBuf := make([]byte, PageSize)
  31. if err := ff.ReadPage(header.FirstWALPage, firstBuf); err != nil {
  32. return err
  33. }
  34. // 解析首页元数据
  35. _ = binary.LittleEndian.Uint64(firstBuf[walPHDR:]) // TxnID
  36. totalEntries := binary.LittleEndian.Uint32(firstBuf[walPHDR+8:])
  37. committed := firstBuf[walPHDR+12]
  38. // 收集所有 entry
  39. type recoveryEntry struct {
  40. pageID uint32
  41. data []byte // 整页镜像 (旧格式)
  42. isRow bool // 是否为行级记录 (新格式)
  43. opType byte // 行级操作类型
  44. storeRow []byte // 行级数据
  45. }
  46. var entries []recoveryEntry
  47. pid := header.FirstWALPage
  48. remaining := int(totalEntries)
  49. for pid != 0 && remaining > 0 {
  50. buf := make([]byte, PageSize)
  51. if err := ff.ReadPage(pid, buf); err != nil {
  52. break
  53. }
  54. hdr := (*page.PageHdr)(unsafe.Pointer(&buf[0]))
  55. if hdr.Type != page.WALIndex {
  56. break
  57. }
  58. var off uint32
  59. if pid == header.FirstWALPage {
  60. off = walFirstHdr
  61. } else {
  62. off = walContHdr
  63. }
  64. entryCount := readEntryCount(buf, pid == header.FirstWALPage)
  65. for i := uint16(0); i < entryCount && remaining > 0; i++ {
  66. if off+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 在 off+12, 值为 1/2/3
  73. potentialOpType := buf[off+12]
  74. isNewRowFormat := dataLen > 0 &&
  75. (potentialOpType == WALOpInsert || potentialOpType == WALOpDelete || potentialOpType == WALOpUpdate) &&
  76. targetPID != 0 // HeaderPage (pageID=0) 不会是行级记录
  77. if isNewRowFormat {
  78. // === 新行级格式: [PageID][DataLen][Checksum][OpType(1B)][Data] ===
  79. if off+walEntryMeta+1 > PageSize {
  80. break
  81. }
  82. opType := buf[off+12]
  83. rowDataLen := dataLen // dataLen 只是 StoreRow 的长度
  84. if off+13+rowDataLen > PageSize {
  85. break
  86. }
  87. // CRC32 覆盖 OpType + Data
  88. csBuf := make([]byte, 1+rowDataLen)
  89. csBuf[0] = opType
  90. copy(csBuf[1:], buf[off+13:off+13+rowDataLen])
  91. if crc32.ChecksumIEEE(csBuf) != checksum {
  92. remaining = 0
  93. break
  94. }
  95. storeRow := make([]byte, rowDataLen)
  96. copy(storeRow, buf[off+13:off+13+rowDataLen])
  97. entries = append(entries, recoveryEntry{
  98. pageID: targetPID,
  99. isRow: true,
  100. opType: opType,
  101. storeRow: storeRow,
  102. })
  103. off += 13 + rowDataLen
  104. } else {
  105. // === 旧格式: 整页镜像或旧行级格式 ===
  106. if off+walEntryMeta+dataLen > PageSize {
  107. break
  108. }
  109. data := make([]byte, PageSize)
  110. copy(data, buf[off+12:off+12+dataLen])
  111. if crc32.ChecksumIEEE(data[:dataLen]) != checksum {
  112. remaining = 0
  113. break
  114. }
  115. entries = append(entries, recoveryEntry{
  116. pageID: targetPID,
  117. data: data,
  118. })
  119. off += walEntryMeta + dataLen
  120. }
  121. remaining--
  122. }
  123. pid = readNextWalPage(buf, pid == header.FirstWALPage)
  124. }
  125. // 根据 Committed 标记决定是否重放
  126. if committed == WALStateCommitted && len(entries) > 0 {
  127. // Phase 1: 先处理整页条目 (恢复页结构: B+树节点, IndexMeta, HeaderPage 等)
  128. for _, e := range entries {
  129. if e.isRow {
  130. continue
  131. }
  132. if e.pageID == 0 {
  133. if err := recoverHeader(ff, e.data); err != nil {
  134. return err
  135. }
  136. } else {
  137. if err := ff.WritePageDirect(e.pageID, e.data); err != nil {
  138. return err
  139. }
  140. }
  141. }
  142. // Phase 2: 处理行级条目 (用 applyRowToPage 应用到 DataPage)
  143. for _, e := range entries {
  144. if !e.isRow {
  145. continue
  146. }
  147. pageBuf := make([]byte, PageSize)
  148. if err := ff.readPageDisk(e.pageID, pageBuf); err != nil {
  149. continue // 页不存在, 跳过
  150. }
  151. applyRowToPage(pageBuf, e.opType, e.storeRow)
  152. if err := ff.WritePageDirect(e.pageID, pageBuf); err != nil {
  153. return err
  154. }
  155. }
  156. }
  157. // committed != Committed: 事务未完成, 丢弃所有 entry
  158. // 回收所有 WAL 页
  159. walPID := header.FirstWALPage
  160. for walPID != 0 {
  161. buf := make([]byte, PageSize)
  162. if err := ff.ReadPage(walPID, buf); err != nil {
  163. break
  164. }
  165. isFirst := walPID == header.FirstWALPage
  166. nextPID := readNextWalPage(buf, isFirst)
  167. header.recyclePage(walPID, ff.WritePageDirect)
  168. walPID = nextPID
  169. }
  170. header.FirstWALPage = 0
  171. header.LastWALPage = 0
  172. return ff.WriteHeader(header)
  173. }
  174. // readEntryCount 读取 WAL 页的 EntryCount
  175. func readEntryCount(buf []byte, isFirst bool) uint16 {
  176. if isFirst {
  177. return binary.LittleEndian.Uint16(buf[walPHDR+17:])
  178. }
  179. return binary.LittleEndian.Uint16(buf[walPHDR+4:])
  180. }
  181. // readNextWalPage 读取 WAL 页的 NextWalPage
  182. func readNextWalPage(buf []byte, isFirst bool) uint32 {
  183. if isFirst {
  184. return binary.LittleEndian.Uint32(buf[walPHDR+13:])
  185. }
  186. return binary.LittleEndian.Uint32(buf[walPHDR:])
  187. }
  188. // recoverHeader 恢复 HeaderPage
  189. func recoverHeader(ff *File, data []byte) error {
  190. // data 是完整的 16KB 页镜像, 但 HeaderPage 只有 4KB
  191. // 取前 4KB 写入文件头
  192. if len(data) < PageSize4K {
  193. _, err := ff.f.WriteAt(data, 0)
  194. return err
  195. }
  196. _, err := ff.f.WriteAt(data[:PageSize4K], 0)
  197. return err
  198. }