wal.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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. // =============================================================================
  40. // 行级 WAL 操作类型
  41. //
  42. // 行级记录新格式 (在 walEntryMeta 之后加 1B OpType):
  43. // [TargetPageID(4B)] [DataLen(4B)] [Checksum(4B)] [OpType(1B)] [Data...]
  44. //
  45. // INSERT: Data = storeRow [pkID(8B BE) + rowData]
  46. // DELETE: Data = pkID(8B BE)
  47. // UPDATE: Data = storeRow [pkID(8B BE) + rowData] (新行数据)
  48. // =============================================================================
  49. const (
  50. WALOpInsert = 1 // 插入行
  51. WALOpDelete = 2 // 删除行
  52. WALOpUpdate = 3 // 更新行
  53. )
  54. // RowEntry 行级 WAL 记录
  55. type RowEntry struct {
  56. PageID uint32 // 目标 DataPage ID
  57. OpType byte // WALOpInsert / WALOpDelete / WALOpUpdate
  58. StoreRow []byte // INSERT/UPDATE: [pkID(8B BE) + rowData]; DELETE: pkID(8B BE)
  59. }
  60. // WALWriter 事务 WAL 写入器
  61. type WALWriter struct {
  62. txnID uint64
  63. entries []walEntry
  64. file *File
  65. header *HeaderPage
  66. readPage func(uint32, []byte) error // 用于分配页时读取空闲链表
  67. state byte // 当前 WAL 状态 (Mem/Written/Persisted/Committed)
  68. }
  69. type walEntry struct {
  70. pageID uint32
  71. data []byte // 完整页镜像(原有逻辑,用于 Commit)
  72. originalData []byte // 原始行数据(事务回滚用)← 新增
  73. newData []byte // 新行数据(事务提交用)← 新增
  74. isRowLevel bool // 是否为行级记录(true=存数据,false=存整页)← 新增
  75. opType byte // 行级操作类型 (WALOpInsert/WALOpDelete/WALOpUpdate)
  76. rowEntry *RowEntry // 行级记录 (新格式, 用于恢复)
  77. }
  78. // BeginTxn 开始新事务
  79. func (db *DB) BeginTxn() *WALWriter {
  80. txnID := db.header.NextTxnID
  81. db.header.NextTxnID++
  82. return &WALWriter{
  83. txnID: txnID,
  84. entries: make([]walEntry, 0, 16),
  85. file: db.file,
  86. header: db.header,
  87. readPage: db.file.readPageDisk, // 必须直接读盘! 走 BufferPool 会被旧缓存干扰空闲链表
  88. state: WALStateMem, // 初始状态: Mem (内存中)
  89. }
  90. }
  91. // RecordPage 记录一个被修改的页 (同一 pageID 只保留最新镜像)
  92. func (w *WALWriter) RecordPage(pageID uint32, data []byte) {
  93. d := make([]byte, len(data))
  94. copy(d, data)
  95. // 同一 pageID 只保留最新镜像
  96. for i := range w.entries {
  97. if w.entries[i].pageID == pageID {
  98. w.entries[i].data = d
  99. return
  100. }
  101. }
  102. w.entries = append(w.entries, walEntry{
  103. pageID: pageID,
  104. data: d,
  105. isRowLevel: false, // 原有逻辑:存整页
  106. })
  107. }
  108. // RecordRowLevel 记录行级数据(用于事务支持)
  109. // 存储原始数据和新数据,而非整页,节省空间
  110. func (w *WALWriter) RecordRowLevel(pageID uint32, originalData []byte, newData []byte) {
  111. orig := make([]byte, len(originalData))
  112. copy(orig, originalData)
  113. newD := make([]byte, len(newData))
  114. copy(newD, newData)
  115. // 同一 pageID 只保留最新
  116. for i := range w.entries {
  117. if w.entries[i].pageID == pageID && w.entries[i].isRowLevel {
  118. w.entries[i].originalData = orig
  119. w.entries[i].newData = newD
  120. return
  121. }
  122. }
  123. w.entries = append(w.entries, walEntry{
  124. pageID: pageID,
  125. originalData: orig,
  126. newData: newD,
  127. isRowLevel: true, // 行级记录
  128. })
  129. }
  130. // RecordRowChange 记录行级变更 (新格式, 带 OpType, 用于崩溃恢复)
  131. // pageID: 目标 DataPage ID
  132. // opType: WALOpInsert / WALOpDelete / WALOpUpdate
  133. // storeRow: INSERT/UPDATE = [pkID(8B BE) + rowData], DELETE = pkID(8B BE)
  134. func (w *WALWriter) RecordRowChange(pageID uint32, opType byte, storeRow []byte) {
  135. data := make([]byte, len(storeRow))
  136. copy(data, storeRow)
  137. w.entries = append(w.entries, walEntry{
  138. pageID: pageID,
  139. isRowLevel: true,
  140. opType: opType,
  141. rowEntry: &RowEntry{
  142. PageID: pageID,
  143. OpType: opType,
  144. StoreRow: data,
  145. },
  146. })
  147. }
  148. // Rollback 回滚: 丢弃所有条目
  149. func (w *WALWriter) Rollback() {
  150. w.entries = nil
  151. w.state = WALStateMem // 重置状态
  152. }
  153. // Commit 原子提交流程:
  154. // 1. 将 entries 写入 WAL 页链 (State: Mem → Written)
  155. // 2. fsync WAL 页 (State: Written → Persisted)
  156. // 3. 回写实际页 (从 WAL 拷贝到真实位置)
  157. // 4. 标记 WAL 首页 Committed = 1 (State: Persisted → Committed)
  158. // 5. 回收所有 WAL 页
  159. func (w *WALWriter) Commit() error {
  160. if len(w.entries) == 0 {
  161. return nil
  162. }
  163. // === 步骤 1: 写入 WAL 页链 (State: Mem → Written) ===
  164. walPageIDs, err := w.writeWALPages()
  165. if err != nil {
  166. return err
  167. }
  168. // === 步骤 2: fsync WAL 页 (State: Written → Persisted) ===
  169. if err := w.file.sync(); err != nil {
  170. return err
  171. }
  172. // 状态转换: Written → Persisted (已落盘)
  173. w.state = WALStatePersisted
  174. // === 步骤 3: 回写实际页 (直接写盘, 绕过 BufferPool) ===
  175. for _, e := range w.entries {
  176. if e.pageID == 0 {
  177. continue
  178. }
  179. if err := w.file.WritePageDirect(e.pageID, e.data); err != nil {
  180. return err
  181. }
  182. }
  183. w.file.ClearCache()
  184. // === 步骤 4: 标记 WAL 首页为已提交 (State: Persisted → Committed) ===
  185. if err := w.markCommitted(walPageIDs[0]); err != nil {
  186. return err
  187. }
  188. if err := w.file.sync(); err != nil {
  189. return err
  190. }
  191. // === 步骤 5: 回收所有 WAL 页 (直接写盘 + 同步 BufferPool 缓存) ===
  192. for _, pid := range walPageIDs {
  193. w.header.recyclePage(pid, w.file.WritePageDirect)
  194. // 同步 BufferPool 缓存: recyclePage 通过 WritePageDirect 写盘, 绕过了 BufferPool
  195. // 必须从磁盘读回并更新缓存, 否则 newPageWithReader 从缓存读到旧数据导致页面无法回收
  196. syncBuf := make([]byte, PageSize)
  197. _ = w.file.readPageDisk(pid, syncBuf)
  198. w.file.SyncCache(pid, syncBuf)
  199. }
  200. w.header.FirstWALPage = 0
  201. w.header.LastWALPage = 0
  202. if err := w.file.WriteHeader(w.header); err != nil {
  203. return err
  204. }
  205. w.entries = nil
  206. return nil
  207. }
  208. // writeWALPages 将所有 entry 写入 WAL 页链, 返回分配的页 ID 列表
  209. // 状态转换: Mem → Written
  210. func (w *WALWriter) writeWALPages() ([]uint32, error) {
  211. var pageIDs []uint32
  212. entryIdx := 0
  213. for entryIdx < len(w.entries) {
  214. // 分配新 WAL 页 (必须用 newPageWithReader 推进空闲链表)
  215. pid := w.header.newPageWithReader(w.readPage)
  216. pageIDs = append(pageIDs, pid)
  217. buf := make([]byte, PageSize)
  218. hdr := (*page.PageHdr)(unsafe.Pointer(&buf[0]))
  219. hdr.Type = page.WALIndex
  220. hdr.PageID = pid
  221. if len(pageIDs) > 1 {
  222. hdr.PrePage = pageIDs[len(pageIDs)-2]
  223. }
  224. off := uint32(0)
  225. if entryIdx == 0 {
  226. // === 首页: 写入 TxnID + TotalEntries + State + NextWalPage + EntryCount ===
  227. binary.LittleEndian.PutUint64(buf[walPHDR:], w.txnID)
  228. binary.LittleEndian.PutUint32(buf[walPHDR+8:], uint32(len(w.entries)))
  229. buf[walPHDR+12] = WALStateWritten // State = Written (已写盘,未 fsync)
  230. binary.LittleEndian.PutUint32(buf[walPHDR+13:], 0) // NextWalPage (稍后填)
  231. binary.LittleEndian.PutUint16(buf[walPHDR+17:], 0) // EntryCount (稍后填)
  232. off = walFirstHdr
  233. } else {
  234. // === 续页: 写入 NextWalPage + EntryCount ===
  235. binary.LittleEndian.PutUint32(buf[walPHDR:], 0) // NextWalPage (稍后填)
  236. binary.LittleEndian.PutUint16(buf[walPHDR+4:], 0) // EntryCount (稍后填)
  237. off = walContHdr
  238. }
  239. // 写入当前 entry
  240. e := w.entries[entryIdx]
  241. if e.isRowLevel && e.rowEntry != nil {
  242. // === 新行级格式: [PageID][DataLen][Checksum][OpType(1B)][Data] ===
  243. dataLen := uint32(len(e.rowEntry.StoreRow))
  244. // Checksum 覆盖 OpType + Data
  245. csBuf := make([]byte, 1+dataLen)
  246. csBuf[0] = e.rowEntry.OpType
  247. copy(csBuf[1:], e.rowEntry.StoreRow)
  248. checksum := crc32.ChecksumIEEE(csBuf)
  249. binary.LittleEndian.PutUint32(buf[off:], e.rowEntry.PageID)
  250. binary.LittleEndian.PutUint32(buf[off+4:], dataLen)
  251. binary.LittleEndian.PutUint32(buf[off+8:], checksum)
  252. buf[off+12] = e.rowEntry.OpType
  253. off += 13 // PageID(4) + DataLen(4) + Checksum(4) + OpType(1)
  254. copy(buf[off:], e.rowEntry.StoreRow)
  255. off += dataLen
  256. } else if e.isRowLevel {
  257. // === 旧行级格式: [PageID][OrigLen][NewLen][Checksum][OriginalData][NewData] ===
  258. origLen := uint32(len(e.originalData))
  259. newLen := uint32(len(e.newData))
  260. totalLen := origLen + newLen
  261. combined := make([]byte, 0, totalLen)
  262. combined = append(combined, e.originalData...)
  263. combined = append(combined, e.newData...)
  264. checksum := crc32.ChecksumIEEE(combined)
  265. binary.LittleEndian.PutUint32(buf[off:], e.pageID)
  266. binary.LittleEndian.PutUint32(buf[off+4:], origLen)
  267. binary.LittleEndian.PutUint32(buf[off+8:], newLen)
  268. binary.LittleEndian.PutUint32(buf[off+12:], checksum)
  269. off += 16
  270. copy(buf[off:], e.originalData)
  271. off += origLen
  272. copy(buf[off:], e.newData)
  273. off += newLen
  274. } else {
  275. // === 整页记录: [PageID][DataLen][Checksum][Data] (原有逻辑) ===
  276. storeLen := uint32(len(e.data)) // 默认存全部
  277. if len(e.data) >= 20 {
  278. useSize := binary.LittleEndian.Uint32(e.data[16:20])
  279. if useSize > 0 && useSize < uint32(len(e.data)) {
  280. storeLen = useSize
  281. }
  282. // HeaderPage (pageID=0): 至少存 PageSize4K
  283. if e.pageID == 0 && storeLen < PageSize4K {
  284. storeLen = PageSize4K
  285. }
  286. }
  287. checksum := crc32.ChecksumIEEE(e.data[:storeLen])
  288. binary.LittleEndian.PutUint32(buf[off:], e.pageID)
  289. binary.LittleEndian.PutUint32(buf[off+4:], storeLen)
  290. binary.LittleEndian.PutUint32(buf[off+8:], checksum)
  291. copy(buf[off+12:], e.data[:storeLen])
  292. // storeLen 可能 < len(e.data), 剩余字节保持零填充
  293. off += walEntryMeta + storeLen
  294. }
  295. hdr.UseSize = off
  296. entryIdx++
  297. // 立即设置当前页的 EntryCount = 1 (不依赖后续链接代码)
  298. if entryIdx == 1 {
  299. // 首页: EntryCount 在 walPHDR+17
  300. binary.LittleEndian.PutUint16(buf[walPHDR+17:], 1)
  301. } else {
  302. // 续页: EntryCount 在 walPHDR+4
  303. binary.LittleEndian.PutUint16(buf[walPHDR+4:], 1)
  304. }
  305. // 链接上一页的 NextWalPage
  306. if len(pageIDs) > 1 {
  307. prevPID := pageIDs[len(pageIDs)-2]
  308. prevBuf := make([]byte, PageSize)
  309. if err := w.file.ReadPage(prevPID, prevBuf); err == nil {
  310. if isFirstWALPage(prevBuf) {
  311. binary.LittleEndian.PutUint32(prevBuf[walPHDR+13:], pid)
  312. binary.LittleEndian.PutUint16(prevBuf[walPHDR+17:], 1)
  313. } else {
  314. binary.LittleEndian.PutUint32(prevBuf[walPHDR:], pid)
  315. binary.LittleEndian.PutUint16(prevBuf[walPHDR+4:], 1)
  316. }
  317. _ = w.file.WritePageDirect(prevPID, prevBuf)
  318. }
  319. }
  320. // 写入当前页
  321. if err := w.file.WritePageDirect(pid, buf); err != nil {
  322. return nil, err
  323. }
  324. }
  325. // 更新 HeaderPage 的 WAL 链指针
  326. if len(pageIDs) > 0 {
  327. if w.header.FirstWALPage == 0 {
  328. w.header.FirstWALPage = pageIDs[0]
  329. }
  330. w.header.LastWALPage = pageIDs[len(pageIDs)-1]
  331. _ = w.file.WriteHeader(w.header)
  332. }
  333. // 状态转换: Mem → Written (已写盘,未 fsync)
  334. w.state = WALStateWritten
  335. return pageIDs, nil
  336. }
  337. // markCommitted 标记 WAL 首页为已提交 (State: Persisted → Committed)
  338. func (w *WALWriter) markCommitted(firstPageID uint32) error {
  339. buf := make([]byte, PageSize)
  340. if err := w.file.ReadPage(firstPageID, buf); err != nil {
  341. return err
  342. }
  343. buf[walPHDR+12] = WALStateCommitted // State = Committed
  344. if err := w.file.WritePageDirect(firstPageID, buf); err != nil {
  345. return err
  346. }
  347. // 状态转换: Persisted → Committed
  348. w.state = WALStateCommitted
  349. return nil
  350. }
  351. // isFirstWALPage 判断 WAL 页是否为事务首页
  352. func isFirstWALPage(buf []byte) bool {
  353. // 首页在 walPHDR 处有 TxnID (非零), 续页没有
  354. txnID := binary.LittleEndian.Uint64(buf[walPHDR:])
  355. return txnID != 0
  356. }
  357. // sync 刷盘 (fsync)
  358. func (ff *File) sync() error {
  359. return ff.f.Sync()
  360. }
  361. // =============================================================================
  362. // applyRowToPage 将行级 WAL 记录应用到页缓冲区 (用于崩溃恢复)
  363. //
  364. // 操作类型:
  365. //
  366. // WALOpInsert: 在 DataPage 中追加一行
  367. // WALOpDelete: 在 DataPage 中软删除指定 pkID 的行
  368. // WALOpUpdate: 在 DataPage 中按 pkID 原地更新行数据
  369. //
  370. // storeRow 格式: [pkID(8B BE) + rowData] (INSERT/UPDATE)
  371. // storeRow 格式: pkID(8B BE) (DELETE)
  372. // =============================================================================
  373. func applyRowToPage(buf []byte, opType byte, storeRow []byte) {
  374. const dpHdrSize = uint32(unsafe.Sizeof(page.PageHdr{})) + 4 + 8 + 8 // 38B
  375. const delFlag = uint32(0x80000000)
  376. // 读取 DataPage 元数据
  377. count := *(*uint32)(unsafe.Pointer(&buf[unsafe.Sizeof(page.PageHdr{})]))
  378. dataEnd := *(*uint32)(unsafe.Pointer(&buf[0])) // PageHdr.UseSize
  379. switch opType {
  380. case WALOpInsert:
  381. // 追加行: [len(4B)] [storeRow]
  382. off := dataEnd
  383. binary.LittleEndian.PutUint32(buf[off:], uint32(len(storeRow)))
  384. off += 4
  385. copy(buf[off:], storeRow)
  386. off += uint32(len(storeRow))
  387. // 更新 count 和 dataEnd (UseSize)
  388. *(*uint32)(unsafe.Pointer(&buf[unsafe.Sizeof(page.PageHdr{})])) = count + 1
  389. *(*uint32)(unsafe.Pointer(&buf[0])) = off
  390. case WALOpDelete:
  391. // storeRow = pkID(8B BE)
  392. if len(storeRow) < 8 {
  393. return
  394. }
  395. keyPK := binary.BigEndian.Uint64(storeRow[:8])
  396. off := dpHdrSize
  397. for i := uint32(0); i < count; i++ {
  398. if off+4 > dataEnd {
  399. break
  400. }
  401. rawLen := binary.LittleEndian.Uint32(buf[off:])
  402. deleted := rawLen&delFlag != 0
  403. rowLen := rawLen & ^delFlag
  404. rowStart := off + 4
  405. if rowStart+rowLen > dataEnd {
  406. break
  407. }
  408. if !deleted && rowLen >= 8 {
  409. rowPK := binary.BigEndian.Uint64(buf[rowStart : rowStart+8])
  410. if rowPK == keyPK {
  411. // 设置软删除标志
  412. binary.LittleEndian.PutUint32(buf[off:], rowLen|delFlag)
  413. return
  414. }
  415. }
  416. off = rowStart + rowLen
  417. }
  418. case WALOpUpdate:
  419. // storeRow = [pkID(8B BE) + rowData]
  420. if len(storeRow) < 8 {
  421. return
  422. }
  423. keyPK := binary.BigEndian.Uint64(storeRow[:8])
  424. off := dpHdrSize
  425. for i := uint32(0); i < count; i++ {
  426. if off+4 > dataEnd {
  427. break
  428. }
  429. rawLen := binary.LittleEndian.Uint32(buf[off:])
  430. deleted := rawLen&delFlag != 0
  431. rowLen := rawLen & ^delFlag
  432. rowStart := off + 4
  433. if rowStart+rowLen > dataEnd {
  434. break
  435. }
  436. if !deleted && rowLen >= 8 {
  437. rowPK := binary.BigEndian.Uint64(buf[rowStart : rowStart+8])
  438. if rowPK == keyPK {
  439. // 原地覆盖行数据
  440. copy(buf[rowStart:], storeRow)
  441. // 剩余字节置零
  442. for j := uint32(len(storeRow)); j < rowLen; j++ {
  443. buf[rowStart+j] = 0
  444. }
  445. return
  446. }
  447. }
  448. off = rowStart + rowLen
  449. }
  450. }
  451. }