wal.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. package core
  2. import (
  3. "TsDB2/DB/core/page"
  4. "encoding/binary"
  5. "hash/crc32"
  6. "unsafe"
  7. )
  8. // =============================================================================
  9. // WAL (Write-Ahead Log) 实现
  10. //
  11. // WAL 页布局 (16KB):
  12. // 首页: [PageHdr(20B)] [TxnID(8B)] [TotalEntries(4B)] [State(1B)]
  13. // [NextWalPage(4B)] [EntryCount(2B)] [WALEntry...]
  14. // 续页: [PageHdr(20B)] [NextWalPage(4B)] [EntryCount(2B)] [WALEntry...]
  15. //
  16. // WALEntry: [TargetPageID(4B)] [DataLen(4B)] [Checksum(4B)] [Data(DataLen)]
  17. //
  18. // WAL 状态机 (State 字段):
  19. // 0 = Mem (内存中,未写盘)
  20. // 1 = Written (已 write(),未 fsync())
  21. // 2 = Persisted (已 fsync(),但未标记提交)
  22. // 3 = Committed (已提交,可安全恢复)
  23. //
  24. // 每个 WAL 页存 1 条完整 16KB 页镜像 (因为 entry 12B + 16KB > 16KB - 39B)
  25. // =============================================================================
  26. const (
  27. walPHDR = uint32(unsafe.Sizeof(page.PageHdr{})) // 20
  28. walFirstHdr = walPHDR + 8 + 4 + 1 + 4 + 2 // 39: PageHdr + TxnID + TotalEntries + State + NextWalPage + EntryCount
  29. walContHdr = walPHDR + 4 + 2 // 26: PageHdr + NextWalPage + EntryCount
  30. walEntryMeta = 4 + 4 + 4 // 12: TargetPageID + DataLen + Checksum
  31. )
  32. // WAL 状态常量 (对应 State 字段)
  33. const (
  34. WALStateMem = 0 // 内存中,未写盘
  35. WALStateWritten = 1 // 已 write(),未 fsync()
  36. WALStatePersisted = 2 // 已 fsync(),但未标记提交
  37. WALStateCommitted = 3 // 已提交,可安全恢复
  38. )
  39. // WALWriter 事务 WAL 写入器
  40. type WALWriter struct {
  41. txnID uint64
  42. entries []walEntry
  43. file *File
  44. header *HeaderPage
  45. readPage func(uint32, []byte) error // 用于分配页时读取空闲链表
  46. }
  47. type walEntry struct {
  48. pageID uint32
  49. data []byte // 完整页镜像
  50. }
  51. // BeginTxn 开始新事务
  52. func (db *DB) BeginTxn() *WALWriter {
  53. txnID := db.header.NextTxnID
  54. db.header.NextTxnID++
  55. return &WALWriter{
  56. txnID: txnID,
  57. entries: make([]walEntry, 0, 16),
  58. file: db.file,
  59. header: db.header,
  60. readPage: db.file.readPageDisk, // 必须直接读盘! 走 BufferPool 会被旧缓存干扰空闲链表
  61. }
  62. }
  63. // RecordPage 记录一个被修改的页 (同一 pageID 只保留最新镜像)
  64. func (w *WALWriter) RecordPage(pageID uint32, data []byte) {
  65. d := make([]byte, len(data))
  66. copy(d, data)
  67. // 同一 pageID 只保留最新镜像
  68. for i := range w.entries {
  69. if w.entries[i].pageID == pageID {
  70. w.entries[i].data = d
  71. return
  72. }
  73. }
  74. w.entries = append(w.entries, walEntry{pageID, d})
  75. }
  76. // Rollback 回滚: 丢弃所有条目
  77. func (w *WALWriter) Rollback() {
  78. w.entries = nil
  79. }
  80. // Commit 原子提交流程:
  81. // 1. 将 entries 写入 WAL 页链 (State: Mem → Written)
  82. // 2. fsync WAL 页 (State: Written → Persisted)
  83. // 3. 回写实际页 (从 WAL 拷贝到真实位置)
  84. // 4. 标记 WAL 首页 Committed = 1 (State: Persisted → Committed)
  85. // 5. 回收所有 WAL 页
  86. func (w *WALWriter) Commit() error {
  87. if len(w.entries) == 0 {
  88. return nil
  89. }
  90. // === 步骤 1: 写入 WAL 页链 (State: Mem → Written) ===
  91. walPageIDs, err := w.writeWALPages()
  92. if err != nil {
  93. return err
  94. }
  95. // === 步骤 2: fsync WAL 页 (State: Written → Persisted) ===
  96. if err := w.file.sync(); err != nil {
  97. return err
  98. }
  99. // === 步骤 3: 回写实际页 (直接写盘, 绕过 BufferPool) ===
  100. for _, e := range w.entries {
  101. if e.pageID == 0 {
  102. continue
  103. }
  104. if err := w.file.WritePageDirect(e.pageID, e.data); err != nil {
  105. return err
  106. }
  107. }
  108. w.file.ClearCache()
  109. // === 步骤 4: 标记 WAL 首页为已提交 (State: Persisted → Committed) ===
  110. if err := w.markCommitted(walPageIDs[0]); err != nil {
  111. return err
  112. }
  113. if err := w.file.sync(); err != nil {
  114. return err
  115. }
  116. // === 步骤 5: 回收所有 WAL 页 (直接写盘 + 同步 BufferPool 缓存) ===
  117. for _, pid := range walPageIDs {
  118. w.header.recyclePage(pid, w.file.WritePageDirect)
  119. // 同步 BufferPool 缓存: recyclePage 通过 WritePageDirect 写盘, 绕过了 BufferPool
  120. // 必须从磁盘读回并更新缓存, 否则 newPageWithReader 从缓存读到旧数据导致页面无法回收
  121. syncBuf := make([]byte, PageSize)
  122. _ = w.file.readPageDisk(pid, syncBuf)
  123. w.file.SyncCache(pid, syncBuf)
  124. }
  125. w.header.FirstWALPage = 0
  126. w.header.LastWALPage = 0
  127. if err := w.file.WriteHeader(w.header); err != nil {
  128. return err
  129. }
  130. w.entries = nil
  131. return nil
  132. }
  133. // writeWALPages 将所有 entry 写入 WAL 页链, 返回分配的页 ID 列表
  134. func (w *WALWriter) writeWALPages() ([]uint32, error) {
  135. var pageIDs []uint32
  136. entryIdx := 0
  137. for entryIdx < len(w.entries) {
  138. // 分配新 WAL 页 (必须用 newPageWithReader 推进空闲链表)
  139. pid := w.header.newPageWithReader(w.readPage)
  140. pageIDs = append(pageIDs, pid)
  141. buf := make([]byte, PageSize)
  142. hdr := (*page.PageHdr)(unsafe.Pointer(&buf[0]))
  143. hdr.Type = page.WALIndex
  144. hdr.PageID = pid
  145. if len(pageIDs) > 1 {
  146. hdr.PrePage = pageIDs[len(pageIDs)-2]
  147. }
  148. off := uint32(0)
  149. if entryIdx == 0 {
  150. // === 首页: 写入 TxnID + TotalEntries + State + NextWalPage + EntryCount ===
  151. binary.LittleEndian.PutUint64(buf[walPHDR:], w.txnID)
  152. binary.LittleEndian.PutUint32(buf[walPHDR+8:], uint32(len(w.entries)))
  153. buf[walPHDR+12] = WALStateWritten // State = Written (已写盘,未 fsync)
  154. binary.LittleEndian.PutUint32(buf[walPHDR+13:], 0) // NextWalPage (稍后填)
  155. binary.LittleEndian.PutUint16(buf[walPHDR+17:], 0) // EntryCount (稍后填)
  156. off = walFirstHdr
  157. } else {
  158. // === 续页: 写入 NextWalPage + EntryCount ===
  159. binary.LittleEndian.PutUint32(buf[walPHDR:], 0) // NextWalPage (稍后填)
  160. binary.LittleEndian.PutUint16(buf[walPHDR+4:], 0) // EntryCount (稍后填)
  161. off = walContHdr
  162. }
  163. // 写入当前 entry (只存 UseSize 字节, 减少 WAL 空间)
  164. e := w.entries[entryIdx]
  165. storeLen := uint32(len(e.data)) // 默认存全部
  166. if len(e.data) >= 20 {
  167. useSize := binary.LittleEndian.Uint32(e.data[16:20])
  168. if useSize > 0 && useSize < uint32(len(e.data)) {
  169. storeLen = useSize
  170. }
  171. // HeaderPage (pageID=0): 至少存 PageSize4K
  172. if e.pageID == 0 && storeLen < PageSize4K {
  173. storeLen = PageSize4K
  174. }
  175. }
  176. checksum := crc32.ChecksumIEEE(e.data[:storeLen])
  177. binary.LittleEndian.PutUint32(buf[off:], e.pageID)
  178. binary.LittleEndian.PutUint32(buf[off+4:], storeLen)
  179. binary.LittleEndian.PutUint32(buf[off+8:], checksum)
  180. copy(buf[off+12:], e.data[:storeLen])
  181. // storeLen 可能 < len(e.data), 剩余字节保持零填充
  182. off += walEntryMeta + storeLen
  183. hdr.UseSize = off
  184. entryIdx++
  185. // 立即设置当前页的 EntryCount = 1 (不依赖后续链接代码)
  186. if entryIdx == 1 {
  187. // 首页: EntryCount 在 walPHDR+17
  188. binary.LittleEndian.PutUint16(buf[walPHDR+17:], 1)
  189. } else {
  190. // 续页: EntryCount 在 walPHDR+4
  191. binary.LittleEndian.PutUint16(buf[walPHDR+4:], 1)
  192. }
  193. // 链接上一页的 NextWalPage
  194. if len(pageIDs) > 1 {
  195. prevPID := pageIDs[len(pageIDs)-2]
  196. prevBuf := make([]byte, PageSize)
  197. if err := w.file.ReadPage(prevPID, prevBuf); err == nil {
  198. if isFirstWALPage(prevBuf) {
  199. binary.LittleEndian.PutUint32(prevBuf[walPHDR+13:], pid)
  200. binary.LittleEndian.PutUint16(prevBuf[walPHDR+17:], 1)
  201. } else {
  202. binary.LittleEndian.PutUint32(prevBuf[walPHDR:], pid)
  203. binary.LittleEndian.PutUint16(prevBuf[walPHDR+4:], 1)
  204. }
  205. _ = w.file.WritePageDirect(prevPID, prevBuf)
  206. }
  207. }
  208. // 写入当前页
  209. if err := w.file.WritePageDirect(pid, buf); err != nil {
  210. return nil, err
  211. }
  212. }
  213. // 更新 HeaderPage 的 WAL 链指针
  214. if len(pageIDs) > 0 {
  215. if w.header.FirstWALPage == 0 {
  216. w.header.FirstWALPage = pageIDs[0]
  217. }
  218. w.header.LastWALPage = pageIDs[len(pageIDs)-1]
  219. _ = w.file.WriteHeader(w.header)
  220. }
  221. return pageIDs, nil
  222. }
  223. // markCommitted 标记 WAL 首页为已提交 (State: Persisted → Committed)
  224. func (w *WALWriter) markCommitted(firstPageID uint32) error {
  225. buf := make([]byte, PageSize)
  226. if err := w.file.ReadPage(firstPageID, buf); err != nil {
  227. return err
  228. }
  229. buf[walPHDR+12] = WALStateCommitted // State = Committed
  230. return w.file.WritePageDirect(firstPageID, buf)
  231. }
  232. // isFirstWALPage 判断 WAL 页是否为事务首页
  233. func isFirstWALPage(buf []byte) bool {
  234. // 首页在 walPHDR 处有 TxnID (非零), 续页没有
  235. txnID := binary.LittleEndian.Uint64(buf[walPHDR:])
  236. return txnID != 0
  237. }
  238. // sync 刷盘 (fsync)
  239. func (ff *File) sync() error {
  240. return ff.f.Sync()
  241. }