blob_read.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package core
  2. import (
  3. "encoding/binary"
  4. "errors"
  5. "io"
  6. "github.com/golang/snappy"
  7. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  8. )
  9. // ReadBlob types.BlobReader 接口实现 — 给 ORM 在读回 Blob 后拿 io.ReadCloser.
  10. //
  11. // 行为: 与 BlobRead 内部走同一份链页 / 解码路径, 区别只在"按流 / 按块":
  12. // - BlobRead 返 []byte, 适合一次性读完的小内容;
  13. // - ReadBlob 返 io.ReadCloser, 适合"调方按需读、不全拉到内存"的大内容与流式对接 (用户
  14. // 可在 io.Copy 中途断流)。
  15. //
  16. // chainReader 已经在跨页时按 PageSize 缓冲, 不需要再包一层 bufio。
  17. // 关链逻辑: chainReader 不持有 fd / 内存分配, 直接 NopCloser 即可。
  18. func (t *Table) ReadBlob(ref types.BlobRef, compress types.FieldCompress) (io.ReadCloser, error) {
  19. if t == nil || t.db == nil {
  20. return nil, errors.New("ReadBlob: table is nil")
  21. }
  22. if !compress.IsValid() {
  23. return nil, errors.New("ReadBlob: unknown compress option")
  24. }
  25. if ref.Empty() {
  26. return nil, nil
  27. }
  28. chainR, err := newBlobChainReader(t.db, ref.FirstPageID)
  29. if err != nil {
  30. return nil, err
  31. }
  32. if compress == types.CompressSnappy {
  33. return io.NopCloser(snappy.NewReader(chainR)), nil
  34. }
  35. return io.NopCloser(chainR), nil
  36. }
  37. // 静态断言: *Table 实现了 types.BlobReader 接口, 防止接口签名漂移。
  38. var _ types.BlobReader = (*Table)(nil)
  39. // BlobRead 把 BlobRef 引用的整条链读回, 自动按 compress 解压.
  40. //
  41. // 返回的是原始字节 (解压后), 长度 == ref.TotalLen, 任何不一致都返 ErrBlobCorrupted.
  42. //
  43. // 内存: 全读到内存 (io.ReadAll). Snappy 是块解压, 需要看到全部数据才能解, 无法流式;
  44. // 对极大 blob (>1GB) 请改用 Seek+部分读 API (v2 TODO).
  45. func (t *Table) BlobRead(ref BlobRef, compress types.FieldCompress) ([]byte, error) {
  46. if t == nil || t.db == nil {
  47. return nil, errors.New("BlobRead: table is nil")
  48. }
  49. if !compress.IsValid() {
  50. return nil, errors.New("BlobRead: unknown compress option")
  51. }
  52. if ref.Empty() {
  53. return nil, nil
  54. }
  55. chainR, err := newBlobChainReader(t.db, ref.FirstPageID)
  56. if err != nil {
  57. return nil, err
  58. }
  59. var data []byte
  60. if compress == types.CompressSnappy {
  61. // snappy.NewReader 是流式 API: 内部缓冲, 但 Read 返 EOF 后才能确认解压完整.
  62. // io.ReadAll 一次性读完, 与解码要求一致 (Snappy 帧必须见尾才知道解压完).
  63. sr := snappy.NewReader(chainR)
  64. data, err = io.ReadAll(sr)
  65. } else {
  66. data, err = io.ReadAll(chainR)
  67. }
  68. if err != nil {
  69. return nil, err
  70. }
  71. if uint32(len(data)) != ref.TotalLen {
  72. return nil, ErrBlobCorrupted
  73. }
  74. return data, nil
  75. }
  76. // BlobFree 把整条 blob 链归还空闲链表.
  77. //
  78. // 行删除/更新/DropTable 都需要释放旧 blob; 否则链页成为永久泄漏.
  79. //
  80. // 时序: 在事务里调用, 走 DeferFreeChain 路径, Commit 成功才真正还链, Rollback 丢弃.
  81. // 无事务上下文时直写空闲链 (与 DeferFreePage 在 db==nil 时的行为一致).
  82. func (t *Table) BlobFree(ref BlobRef) error {
  83. if ref.Empty() {
  84. return nil
  85. }
  86. if t == nil || t.db == nil {
  87. return errors.New("BlobFree: table is nil")
  88. }
  89. // 1. 走链, 找到尾页 + 计链长.
  90. cur := ref.FirstPageID
  91. tail := cur
  92. count := uint32(0)
  93. buf := make([]byte, PageSize)
  94. for cur != 0 {
  95. if err := t.db.file.ReadPage(cur, buf); err != nil {
  96. return err
  97. }
  98. tail = cur
  99. count++
  100. if count > BlobChainMax {
  101. return ErrBlobCorrupted // 链长不可能这么长, 拒绝继续走
  102. }
  103. cur = pageHdrNextPage(buf)
  104. }
  105. // 2. 整链回收 (与现有 FreeChain 同一接口).
  106. t.db.deferFreeBlobChain(ref.FirstPageID, tail, count)
  107. return nil
  108. }
  109. // pageHdrNextPage 读 PageHdr.NextPage, 避免依赖 page 包类型 (本文件独立可读).
  110. //
  111. // ★ 必须按小端解: PageHdr 是 unsafe 结构体直映射落盘 (page/hdr.go), NextPage 字段
  112. // 按本机字节序 (小端) 序列化; 写侧 blob_writer.go 与读侧 blob_reader.go 也都走
  113. // page.HdrOf 直读写。这里曾按大端解 → 多页链的第二页起 NextPage 被读成 0x0N000000,
  114. // BlobFree 走链找尾页时 ReadPage 越界报错, 而错误被 releaseRowBlobRefs 的
  115. // "放任不中断"语义吞掉 —— 行删了、链页却永不归还 (单页链 NextPage=0 不受影响,
  116. // 所以小 blob 测试全绿, 唯独跨页大 blob 泄漏)。
  117. func pageHdrNextPage(buf []byte) uint32 {
  118. const off = 12 // PageHdr.NextPage 在头内偏移
  119. return uint32(buf[off]) | uint32(buf[off+1])<<8 |
  120. uint32(buf[off+2])<<16 | uint32(buf[off+3])<<24
  121. }
  122. // deferFreeBlobChain 走事务的 pendingFreeChain 路径 (Commit 后真正还链).
  123. func (db *DB) deferFreeBlobChain(head, tail, count uint32) {
  124. if head == 0 || count == 0 {
  125. return
  126. }
  127. if txn := db.currentTxn.Load(); txn != nil {
  128. txn.pendingFreeChain = append(txn.pendingFreeChain,
  129. pendingChain{head: head, tail: tail, count: count})
  130. return
  131. }
  132. db.pendingMu.Lock()
  133. db.pendingFreeChain = append(db.pendingFreeChain,
  134. pendingChain{head: head, tail: tail, count: count})
  135. db.pendingMu.Unlock()
  136. }
  137. // rowBlobRefs 扫描行内 blob 列, 收集非空 BlobRef.
  138. //
  139. // 引擎层在 DeleteRow / UpdateRow / DropTable 时需要把旧行的 blob 链还掉.
  140. // 行内 blob 列的 8B BlobRef 没有 RowVarTag (内联内容), 必须按列类型定位, 不像堆引
  141. // 用可以走 RowFieldRefTagged.
  142. //
  143. // 返回 []BlobRef (空切片表示"无 blob 列"或"全 NULL"); 行布局解析失败时返 nil.
  144. //
  145. // 列级顺序与 FieldIndices 一致, 调用方按位置 / master 索引映射.
  146. func (table *Table) rowBlobRefs(rowData []byte) []BlobRef {
  147. if len(rowData) < 1 {
  148. return nil
  149. }
  150. sv := table.getVersion(rowData[0])
  151. if sv == nil || len(sv.FieldIndices) == 0 {
  152. return nil
  153. }
  154. n := len(sv.FieldIndices)
  155. col0Width := table.svCol0Width(sv)
  156. ends, ok := types.RowParseOffsets(rowData, n, col0Width)
  157. if !ok {
  158. return nil
  159. }
  160. bitmap, ok := types.RowNullBitmap(rowData, ends, n)
  161. if !ok {
  162. return nil
  163. }
  164. var refs []BlobRef
  165. for i, idx := range sv.FieldIndices {
  166. f := table.Field[idx]
  167. if f.IsDropped() || !types.IsBlob(f.FieldType) {
  168. continue
  169. }
  170. if types.NullBitSet(bitmap, i) {
  171. continue
  172. }
  173. start := int(types.RowFieldStart(ends, i))
  174. end := int(types.RowFieldEnd(ends, i, col0Width))
  175. if end-start < types.BlobRefSize || start+types.BlobRefSize > len(rowData) {
  176. continue
  177. }
  178. seg := rowData[start : start+types.BlobRefSize]
  179. ref := BlobRef{
  180. TotalLen: binary.BigEndian.Uint32(seg[:4]),
  181. FirstPageID: binary.BigEndian.Uint32(seg[4:8]),
  182. }
  183. if ref.Empty() {
  184. continue
  185. }
  186. refs = append(refs, ref)
  187. }
  188. return refs
  189. }
  190. // releaseRowBlobRefs 把 row 中所有 blob 链归还空闲链表 (DeleteRow / DropTable /
  191. // Update 替换旧值时调用).
  192. //
  193. // 单独走这条路径而不是合并进 heapReleaseRow: blob 链在文件上是"链页"序列, 释放走
  194. // deferFreeBlobChain, 与 heap 页 (PageID + RefCount) 不是同一条释放路径.
  195. //
  196. // 错误采用"放任单列失败不影响后续列"语义: DropTable 要把所有 blob 都还掉, 一列
  197. // 失败而中断会把后面的全漏, 与"全部回收"的承诺背离. 调用方最后会再走 defrag/
  198. // 复检; 单条失败可被日志/计数器观察, 但不会留下半回收状态.
  199. //
  200. // 行布局不可信 / 表结构已变时返错误但仍继续归还本轮已扫到的 blob.
  201. func (table *Table) releaseRowBlobRefs(rowData []byte) error {
  202. refs := table.rowBlobRefs(rowData)
  203. if refs == nil {
  204. if len(rowData) == 0 || rowData[0] == 0 {
  205. return nil // 空行 / 零版本 → 跳过
  206. }
  207. // 布局解析失败, 但仍可能是合法的"无 blob 列" — 行/表不匹配时 rowBlobRefs 返 nil
  208. // (其实有合法返回) 还是错乱都让这条路径容错过去, 不让一个坏行毁掉整批回收.
  209. }
  210. var firstErr error
  211. for _, ref := range refs {
  212. if err := table.BlobFree(ref); err != nil && firstErr == nil {
  213. firstErr = err
  214. }
  215. }
  216. return firstErr
  217. }
  218. // oldBlobRefsByMaster 与 rowBlobRefs 类似, 但按 masterIdx (字段 master 注册表下标)
  219. // 索引 ——
  220. //
  221. // Update 路径 (serializeRowInternal) 按 masterIdx 决定是否释放旧 ref, 这是按列访问,
  222. // 与 rowBlobRefs 返回的"按 FieldIndices 下标"的扁平切片不匹配, 单独提供这条.
  223. //
  224. // 行内 NULL / 空 blob 列不出现在返回 map 中 ——
  225. //
  226. // "old == empty ref"在行里语义上等价于"该列没有旧 ref", 不需要释放.
  227. func (table *Table) oldBlobRefsByMaster(rowData []byte) map[int]BlobRef {
  228. if len(rowData) < 1 {
  229. return nil
  230. }
  231. sv := table.getVersion(rowData[0])
  232. if sv == nil || len(sv.FieldIndices) == 0 {
  233. return nil
  234. }
  235. n := len(sv.FieldIndices)
  236. col0Width := table.svCol0Width(sv)
  237. ends, ok := types.RowParseOffsets(rowData, n, col0Width)
  238. if !ok {
  239. return nil
  240. }
  241. bitmap, ok := types.RowNullBitmap(rowData, ends, n)
  242. if !ok {
  243. return nil
  244. }
  245. var refs map[int]BlobRef
  246. for i, idx := range sv.FieldIndices {
  247. f := table.Field[idx]
  248. if f.IsDropped() || !types.IsBlob(f.FieldType) {
  249. continue
  250. }
  251. if types.NullBitSet(bitmap, i) {
  252. continue
  253. }
  254. start := int(types.RowFieldStart(ends, i))
  255. end := int(types.RowFieldEnd(ends, i, col0Width))
  256. if end-start < types.BlobRefSize || start+types.BlobRefSize > len(rowData) {
  257. continue
  258. }
  259. seg := rowData[start : start+types.BlobRefSize]
  260. ref := BlobRef{
  261. TotalLen: binary.BigEndian.Uint32(seg[:4]),
  262. FirstPageID: binary.BigEndian.Uint32(seg[4:8]),
  263. }
  264. if ref.Empty() {
  265. continue
  266. }
  267. if refs == nil {
  268. refs = make(map[int]BlobRef, n)
  269. }
  270. refs[int(idx)] = ref
  271. }
  272. return refs
  273. }