wal.go 11 KB

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