wal.go 9.0 KB

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