blob_writer.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package core
  2. import (
  3. "errors"
  4. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  5. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  6. )
  7. // blobChainWriter 把 io.Writer.Write 调用直接转为"写 blob 链尾", 自动跨页 NewPage。
  8. //
  9. // 约束:
  10. // - 一页 = 一个 blob chunk (与 HeapPage 那种"一页多块"完全不同);
  11. // - 链由 PageHdr.NextPage 串, 最后一页 NextPage = 0;
  12. // - 不维护 RefCount (单 owner, 删除时整链 FreeChain 一次 O(1) 还链)。
  13. //
  14. // 所有页头统一用 PageHdr (共 20 字节, 与 heap/index/schema-ext 一致):
  15. // PageHdr 占每页前 20 字节, 后续 Data 区就是 raw blob 字节, 无 chunk 头、无长度前缀。
  16. //
  17. // 错误:
  18. // - ErrBlobTooLarge: 单 blob 链长超过 BlobChainMax (uint32 上限);
  19. //
  20. // 调用方应中止 io.Copy 并 tx.Rollback(), 由事务模型自动还链。
  21. type blobChainWriter struct {
  22. db *DB
  23. firstPage uint32 // 链首
  24. tailPage uint32 // 链尾 (Write 时优先填这一页, 满了再 NewPage)
  25. tailUsed uint16 // tailPage.Data 区已用字节
  26. chainLen uint32 // 已分配的页数 (含第一页)
  27. totalBytes uint64 // 已写入字节数 (= TotalLen)
  28. }
  29. // newBlobChainWriter 预分配第一页并把 writer 初始化到该页。
  30. //
  31. // 之所以必须"先 NewPage 再 Write": snappy.Writer 在第一次 Write 之前会先写一个
  32. // stream header (几字节), 如果第一页还没 NewPage, snappy 的 Write 会落到 nil tailPage
  33. // 上 panic。预开第一页后, 写入路径与 snappy 不耦合。
  34. //
  35. // 参数是 *DB 而不是 *Transaction: 链页的分配 (NewPage) 与写盘 (file.WritePage) 都不需要
  36. // 事务参与 —— 事务只是在有 currentTxn 时顺带把新页登记进 allocated (回滚时归还)。
  37. // 于是"没有显式事务"的写入路径 (core 层 InsertRow 直接塞 []byte, 见 Table.blobFieldRef)
  38. // 也能复用同一个 writer。
  39. func newBlobChainWriter(db *DB) (*blobChainWriter, error) {
  40. pid := db.NewPage()
  41. buf := make([]byte, PageSize)
  42. hdr := page.HdrOf(buf)
  43. hdr.Init(page.BlobIndex, pid)
  44. hdr.NextPage = 0
  45. hdr.UseSize = page.PageHdrSize
  46. if err := db.file.WritePage(pid, buf); err != nil {
  47. return nil, err
  48. }
  49. return &blobChainWriter{
  50. db: db,
  51. firstPage: pid,
  52. tailPage: pid,
  53. tailUsed: 0,
  54. chainLen: 1,
  55. totalBytes: 0,
  56. }, nil
  57. }
  58. // Write 实现 io.Writer. 一次 Write 可能跨多页, 由内部 NewPage 衔接.
  59. //
  60. // 返回值语义: 总是返回 (len(p), nil) 或 (已写入字节, err).
  61. // 错误只可能是 ErrBlobTooLarge, 一旦发生, 链已被 NewPage 占用,
  62. // 调用方应中止 io.Copy 并 tx.Rollback() 走事务模型回收整链.
  63. func (w *blobChainWriter) Write(p []byte) (int, error) {
  64. const pageData = uint32(PageSize - page.PageHdrSize) // 16364
  65. written := 0
  66. for written < len(p) {
  67. // 1. 当前尾页已满? 开新页并挂链
  68. if uint32(w.tailUsed) >= pageData {
  69. if w.chainLen >= BlobChainMax {
  70. return written, ErrBlobTooLarge
  71. }
  72. newPID := w.db.NewPage()
  73. // 挂到老尾页的 NextPage
  74. tailBuf := make([]byte, PageSize)
  75. if err := w.db.file.ReadPage(w.tailPage, tailBuf); err != nil {
  76. return written, err
  77. }
  78. page.HdrOf(tailBuf).NextPage = newPID
  79. if err := w.db.file.WritePage(w.tailPage, tailBuf); err != nil {
  80. return written, err
  81. }
  82. // 初始化新页
  83. newBuf := make([]byte, PageSize)
  84. hdr := page.HdrOf(newBuf)
  85. hdr.Init(page.BlobIndex, newPID)
  86. hdr.NextPage = 0
  87. hdr.UseSize = page.PageHdrSize
  88. if err := w.db.file.WritePage(newPID, newBuf); err != nil {
  89. return written, err
  90. }
  91. // 推进尾指针
  92. w.tailPage = newPID
  93. w.tailUsed = 0
  94. w.chainLen++
  95. }
  96. // 2. 计算本批可写字节数 (min(本页剩余空间, p 剩余字节))
  97. space := uint32(pageData - uint32(w.tailUsed))
  98. remaining := uint32(len(p) - written)
  99. n := space
  100. if remaining < n {
  101. n = remaining
  102. }
  103. // 3. 读尾页 + 写本批
  104. tailBuf := make([]byte, PageSize)
  105. if err := w.db.file.ReadPage(w.tailPage, tailBuf); err != nil {
  106. return written, err
  107. }
  108. copy(tailBuf[page.PageHdrSize+uint32(w.tailUsed):], p[written:written+int(n)])
  109. page.HdrOf(tailBuf).UseSize = page.PageHdrSize + uint32(w.tailUsed) + n
  110. if err := w.db.file.WritePage(w.tailPage, tailBuf); err != nil {
  111. return written, err
  112. }
  113. w.tailUsed += uint16(n)
  114. written += int(n)
  115. w.totalBytes += uint64(n)
  116. }
  117. return written, nil
  118. }
  119. // ErrBlobTooLarge 单 blob 链长超过 BlobChainMax (256K 页 = ~4GB).
  120. var ErrBlobTooLarge = errors.New("blob chain exceeds maximum length")
  121. // BlobChainMax 单 blob 链页数上限 (uint32 计数).
  122. //
  123. // 上限由来: 链长用 uint32 计数 (避免 RowFieldRef 里的 2 字节截断隐患),
  124. // 单页 16KB, 256K 页 ≈ 4GB. 这个量级对绝大多数 binary 大对象够用;
  125. // 超大文件 (>4GB) 应改用对象存储而非单字段.
  126. const BlobChainMax uint32 = 256 * 1024
  127. // BlobRef 行内 8 字节引用. 类型权威定义见 types.BlobRef (本类型为 alias, 历史代码兼容)。
  128. //
  129. // [TotalLen 4B BE][FirstPageID 4B BE]
  130. //
  131. // TotalLen = 原始 (解压后) 字节数;
  132. // FirstPageID = 0 表示空 blob (与 TotalLen = 0 一同出现).
  133. //
  134. // 【为什么要 alias】types 包持有 BlobRef 是为了让 EncodeValue / DecodeValue 不必反向依赖
  135. // core 包 (core 已经依赖 types, 反过来会循环)。core 内的历史 API (BlobCommit / BlobRead
  136. // / BlobFree 等) 全部继续用这个名字, 直接 type 别名过去, 调用方代码零改动。
  137. //
  138. // types.BlobRef 已定义 Empty / SizeBytes 方法, alias 自动继承, 不在这里再写一次
  139. // (否则是 duplicate method)。
  140. type BlobRef = types.BlobRef