datapage.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package timeindex
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB/core/index/datapage"
  4. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  5. "encoding/binary"
  6. "errors"
  7. )
  8. // Row 时序数据行
  9. type Row struct {
  10. TS int64 // 时间戳
  11. Flag byte // 标记/状态
  12. Data []byte // 真实数据
  13. }
  14. type TimeEntry struct {
  15. Hdr page.PageHdr
  16. BeginTime int64 // 本页最小时间
  17. EndTime int64 // 本页最大时间
  18. Count uint32 // 本页记录总数
  19. }
  20. type TimeDataPage struct {
  21. TimeEntry TimeEntry
  22. Rows []Row
  23. }
  24. // ======================== IDateEntry 接口实现 ========================
  25. func (t *TimeDataPage) Insert(handle page.HandleDb, indexEntries []byte, entryLen int, count int, key []byte, pkID uint64, rowData []byte) error {
  26. dataPageID, entryIdx := findDataPageByTime(indexEntries, entryLen, count, key)
  27. var dp *datapage.DataPage
  28. if dataPageID == 0 {
  29. dp = datapage.New(handle)
  30. } else {
  31. var err error
  32. dp, err = datapage.Load(handle, dataPageID)
  33. if err != nil {
  34. return err
  35. }
  36. }
  37. storeRow := make([]byte, 8+len(rowData))
  38. binary.BigEndian.PutUint64(storeRow[:8], pkID)
  39. copy(storeRow[8:], rowData)
  40. // 用"按 pkID 有序插入"而不是追加: FindRowByPK 依赖页内行按 pkID 升序, 一旦遇到
  41. // 比目标更大的行就提前判定"不存在"。正常写入时 pkID 单增, 有序插入等价于追加;
  42. // 但"改时间键把行搬到别的时间段"时, 搬过去的行号可能小于目标页里已有的行号 ——
  43. // 追加会打乱这页的顺序, 之后整页的行都按行号查不到 (二级索引回表也跟着失效)。
  44. if !dp.InsertRowSorted(storeRow) {
  45. // 开新页之前先按配置就地整理一次: 墓碑占比达到 CompactDeadRatio 时把墓碑空间还给
  46. // 活行, 常常就够放下这一行 —— 少开一页, 也少一个时间段分段。
  47. // 整理成功且重试写入成功 → 留在本页 (末尾统一 Save, 页 ID 不变即"追加进已有分段");
  48. // 否则 (没墓碑可回收 / 整理了也装不下) 新开一页, 由调用方为它新增一个时间段条目。
  49. need := uint32(len(storeRow)) + 4
  50. if !dp.CompactForInsert(need) || !dp.InsertRowSorted(storeRow) {
  51. dp.Save()
  52. dp = datapage.New(handle)
  53. if !dp.InsertRowSorted(storeRow) {
  54. return ErrRowTooLarge
  55. }
  56. }
  57. }
  58. dp.Save()
  59. if dp.PageID() != dataPageID {
  60. if count*entryLen+4 <= len(indexEntries) {
  61. binary.BigEndian.PutUint32(indexEntries[count*entryLen:], dp.PageID())
  62. }
  63. return ErrNeedNewEntry
  64. }
  65. // 追加进已有分段: 按需扩展该条目的 LastTime, 使时间范围扫描不遗漏本分段。
  66. if entryIdx >= 0 && entryLen >= 20 {
  67. lastOff := entryIdx*entryLen + 8
  68. if lastOff+8 <= len(indexEntries) {
  69. newLast := int64(binary.BigEndian.Uint64(key[:8]))
  70. if newLast > int64(binary.BigEndian.Uint64(indexEntries[lastOff:lastOff+8])) {
  71. binary.BigEndian.PutUint64(indexEntries[lastOff:lastOff+8], uint64(newLast))
  72. }
  73. }
  74. }
  75. return nil
  76. }
  77. func (t *TimeDataPage) Search(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) ([]byte, bool) {
  78. n := len(indexEntries) / entryLen
  79. dataPageID, _ := findDataPageByTime(indexEntries, entryLen, n, key)
  80. if dataPageID == 0 {
  81. return nil, false
  82. }
  83. dp, err := datapage.Load(handle, dataPageID)
  84. if err != nil {
  85. return nil, false
  86. }
  87. return dp.FindRowByIndexKey(key, 9)
  88. }
  89. // CreateDataPage 为新的时间段建第一个 DataPage, 并把首行写入。
  90. //
  91. // 旧实现把 rowData[:8] 当成行号写进了页首 —— rowData 是 [schemaVer + 字段...],
  92. // 前 8 字节是"schemaVer + 时间戳的高 7 字节", 根本不是行号。于是新建分段的
  93. // 第一行带了一个极大的假行号, FindRowByPK 一比较就提前结束, 整页行号全部不可达
  94. // (按行号读/改/删、二级索引回表统统失效)。这里直接用调用方给的真实 pkID。
  95. func (t *TimeDataPage) CreateDataPage(handle page.HandleDb, key []byte, pkID uint64, rowData []byte) (uint32, error) {
  96. dp := datapage.New(handle)
  97. storeRow := make([]byte, 8+len(rowData))
  98. binary.BigEndian.PutUint64(storeRow[:8], pkID)
  99. copy(storeRow[8:], rowData)
  100. dp.AppendRow(storeRow)
  101. dp.Save()
  102. return dp.PageID(), nil
  103. }
  104. func (t *TimeDataPage) Update(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte, pkID uint64, rowData []byte) error {
  105. n := len(indexEntries) / entryLen
  106. dataPageID, _ := findDataPageByTime(indexEntries, entryLen, n, key)
  107. if dataPageID == 0 {
  108. return page.ErrNotFound
  109. }
  110. dp, err := datapage.Load(handle, dataPageID)
  111. if err != nil {
  112. return err
  113. }
  114. storeRow := make([]byte, 8+len(rowData))
  115. binary.BigEndian.PutUint64(storeRow[:8], pkID)
  116. copy(storeRow[8:], rowData)
  117. // 时序行内布局: [pkID(8B)] + [schemaVer(1B)] + 字段..., 时间键位于偏移 9
  118. if err := dp.FindAndUpdateByIndexKey(key, 9, storeRow); err != nil {
  119. // schema 演进 (AddField) 后旧行变长, 原地覆盖装不下 → 页内重建支持变长
  120. return dp.RebuildWithIndexKey(key, 9, storeRow)
  121. }
  122. return nil
  123. }
  124. func (t *TimeDataPage) Delete(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) error {
  125. n := len(indexEntries) / entryLen
  126. dataPageID, _ := findDataPageByTime(indexEntries, entryLen, n, key)
  127. if dataPageID == 0 {
  128. return page.ErrNotFound
  129. }
  130. dp, err := datapage.Load(handle, dataPageID)
  131. if err != nil {
  132. return err
  133. }
  134. // 时序行内布局: [pkID(8B)] + [schemaVer(1B)] + 字段..., 时间键位于偏移 9
  135. deleted := dp.DeleteByIndexKey(key, 9)
  136. if !deleted {
  137. return page.ErrNotFound
  138. }
  139. if dp.AllDeleted() {
  140. // 延迟还链: 时序条目仍映射该时间段, 立即还链会让复用后的页被
  141. // 扫描/回表读到 (与 time_handler.go 按 pkID 删除路径的约定对齐)。
  142. handle.DeferFreeDataPage(dp.PageID())
  143. } else {
  144. dp.Save()
  145. }
  146. return nil
  147. }
  148. // ======================== 内部辅助 ========================
  149. // ErrNeedNewEntry 当前分段页已满, 调用方需要为这个 DataPage 新增一个时间段条目。
  150. // 导出为哨兵错误: 上层用 errors.Is 判定, 不再靠匹配错误字符串。
  151. var ErrNeedNewEntry = errors.New("need new index entry")
  152. // ErrRowTooLarge 行数据太大,需要堆页溢出处理
  153. var ErrRowTooLarge = errors.New("row too large, need heap overflow")
  154. // findDataPageByTime 二分查找 BeginTime <= key 的最后一个分段,
  155. // 返回 (DataPageID, 条目下标)。未命中时返回 (0, -1)。
  156. func findDataPageByTime(entries []byte, entryLen int, n int, key []byte) (uint32, int) {
  157. if n == 0 {
  158. return 0, -1
  159. }
  160. // 时间索引条目格式: BeginTime(8) + LastTime(8) + PageID(4)
  161. result := -1
  162. left, right := 0, n-1
  163. keyTime := int64(binary.BigEndian.Uint64(key[:8]))
  164. for left <= right {
  165. mid := (left + right) / 2
  166. entryBegin := int64(binary.BigEndian.Uint64(entries[mid*entryLen:]))
  167. if entryBegin <= keyTime {
  168. result = mid
  169. left = mid + 1
  170. } else {
  171. right = mid - 1
  172. }
  173. }
  174. if result < 0 {
  175. return 0, -1
  176. }
  177. // PageID 在条目最后 4 字节
  178. off := result*entryLen + entryLen - 4
  179. return binary.BigEndian.Uint32(entries[off:]), result
  180. }