buffer_pool.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package pool
  2. import (
  3. "sync"
  4. )
  5. // pageEntry 缓冲池中的页条目
  6. type pageEntry struct {
  7. data []byte // 页数据副本
  8. dirty bool // 是否被修改(需要写回磁盘)
  9. pageLen int // 页字节长度(用于计算文件偏移)
  10. }
  11. // DirtyPage 脏页快照
  12. type DirtyPage struct {
  13. PageID uint32
  14. Data []byte
  15. PageLen int
  16. }
  17. // BufferPool 页面缓冲池
  18. // 拦截 ReadPage/WritePage,将页缓存在内存中,减少磁盘 I/O
  19. // 写回策略:
  20. // - Commit() → 通过 CollectDirty() 收集脏页,走 WAL 流程写回
  21. // - 脏页超过阈值时自动触发 onThreshold 回调 (由 DB 层执行 Commit)
  22. type BufferPool struct {
  23. pages map[uint32]*pageEntry // pageID → 页条目
  24. mu sync.Mutex
  25. capacity int // 最大缓存页数
  26. dirtyThreshold int // 脏页阈值 (0=不限制)
  27. onThreshold func() // 脏页超阈值回调
  28. dirtyCount int // 当前脏页数 (实时追踪)
  29. diskRead func(pageID uint32, buf []byte) error // 底层磁盘读
  30. diskWrite func(pageID uint32, buf []byte) error // 底层磁盘写
  31. }
  32. // NewBufferPool 创建页面缓冲池
  33. // - capacity: 最大缓存页数
  34. // - diskRead/diskWrite: 底层磁盘读写函数
  35. func NewBufferPool(capacity int,
  36. diskRead func(uint32, []byte) error,
  37. diskWrite func(uint32, []byte) error,
  38. ) *BufferPool {
  39. bp := &BufferPool{
  40. pages: make(map[uint32]*pageEntry, capacity),
  41. capacity: capacity,
  42. diskRead: diskRead,
  43. diskWrite: diskWrite,
  44. }
  45. return bp
  46. }
  47. // SetDirtyThreshold 设置脏页阈值和回调
  48. // 当脏页数超过 threshold 时调用 onThreshold (由 DB 层触发 Commit)
  49. func (bp *BufferPool) SetDirtyThreshold(threshold int, onThreshold func()) {
  50. bp.mu.Lock()
  51. defer bp.mu.Unlock()
  52. bp.dirtyThreshold = threshold
  53. bp.onThreshold = onThreshold
  54. }
  55. // CollectDirty 取出所有脏页快照并清除脏标记
  56. // 由 DB.Commit() 调用,配合 WAL 流程统一写回
  57. // 同时清除干净页面缓存,释放内存并确保后续脏页计数准确
  58. func (bp *BufferPool) CollectDirty() []DirtyPage {
  59. bp.mu.Lock()
  60. defer bp.mu.Unlock()
  61. var dirty []DirtyPage
  62. for pid, entry := range bp.pages {
  63. if entry.dirty {
  64. data := make([]byte, len(entry.data))
  65. copy(data, entry.data)
  66. dirty = append(dirty, DirtyPage{pid, data, entry.pageLen})
  67. }
  68. // 无论脏净,都从缓存中移除
  69. // 脏页已被复制,干净页不需要缓存
  70. // 下次访问时会从磁盘重新加载
  71. delete(bp.pages, pid)
  72. }
  73. bp.dirtyCount = 0 // 重置脏页计数
  74. return dirty
  75. }
  76. // Flush 将所有脏页写入磁盘 (仅在 Close 时使用)
  77. func (bp *BufferPool) Flush() {
  78. dirty := bp.CollectDirty()
  79. for _, item := range dirty {
  80. _ = bp.diskWrite(item.PageID, item.Data)
  81. }
  82. }
  83. // Close 关闭缓冲池 (刷盘后停止)
  84. func (bp *BufferPool) Close() {
  85. bp.Flush()
  86. }
  87. // UpdatePage 更新缓存页 (WAL 提交后调用, 保持缓存与磁盘一致)
  88. func (bp *BufferPool) UpdatePage(pageID uint32, data []byte) {
  89. bp.mu.Lock()
  90. defer bp.mu.Unlock()
  91. cached := make([]byte, len(data))
  92. copy(cached, data)
  93. bp.pages[pageID] = &pageEntry{data: cached, dirty: false, pageLen: len(data)}
  94. }
  95. // ClearAll 清除所有脏标记 (WAL 提交后调用)
  96. // 保留干净页在缓存中, 避免下次 WritePage 重新缓存导致 BufferPool 反复膨胀
  97. func (bp *BufferPool) ClearAll() {
  98. bp.mu.Lock()
  99. defer bp.mu.Unlock()
  100. for _, entry := range bp.pages {
  101. entry.dirty = false
  102. }
  103. bp.dirtyCount = 0
  104. }
  105. // ReadPage 读页 — 先查缓存,未命中则读磁盘 (不缓存, 避免 BufferPool 膨胀)
  106. func (bp *BufferPool) ReadPage(pageID uint32, buf []byte) error {
  107. bp.mu.Lock()
  108. if entry, ok := bp.pages[pageID]; ok {
  109. copy(buf, entry.data)
  110. bp.mu.Unlock()
  111. return nil
  112. }
  113. bp.mu.Unlock()
  114. // 缓存未命中 → 直接从磁盘读取, 不写入缓存
  115. return bp.diskRead(pageID, buf)
  116. }
  117. // WritePage 写页 — 写入缓存并标记脏,不立即落盘
  118. func (bp *BufferPool) WritePage(pageID uint32, buf []byte) error {
  119. cached := make([]byte, len(buf))
  120. copy(cached, buf)
  121. var triggerThreshold bool
  122. bp.mu.Lock()
  123. existing, exists := bp.pages[pageID]
  124. if exists && existing.dirty {
  125. // 已是脏页, 直接覆盖, 不增加脏页计数
  126. existing.data = cached
  127. } else {
  128. // 新页或干净页, 增加脏页计数
  129. bp.pages[pageID] = &pageEntry{data: cached, dirty: true, pageLen: len(buf)}
  130. bp.dirtyCount++
  131. }
  132. // 检查是否超过阈值 (脏页或总页面数)
  133. // 使用总页面数作为第二道安全网,防止脏页计数增长缓慢时内存无限膨胀
  134. totalPages := len(bp.pages)
  135. if bp.dirtyThreshold > 0 && bp.onThreshold != nil {
  136. // 脏页阈值或总页面数阈值 (两者相同,确保及时刷盘)
  137. if bp.dirtyCount >= bp.dirtyThreshold || totalPages >= bp.dirtyThreshold {
  138. triggerThreshold = true
  139. }
  140. }
  141. bp.mu.Unlock()
  142. // 在锁外触发回调, 避免死锁
  143. if triggerThreshold {
  144. bp.onThreshold()
  145. }
  146. return nil
  147. }
  148. // Len 当前缓存页数 (调试用)
  149. func (bp *BufferPool) Len() int {
  150. bp.mu.Lock()
  151. defer bp.mu.Unlock()
  152. return len(bp.pages)
  153. }
  154. // DirtyCount 脏页数 (调试用)
  155. func (bp *BufferPool) DirtyCount() int {
  156. bp.mu.Lock()
  157. defer bp.mu.Unlock()
  158. return bp.dirtyCount
  159. }