secondary_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. package btree
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "math/rand"
  6. "sort"
  7. "testing"
  8. "TsDB2/DB/core/page"
  9. )
  10. // ============================================================================
  11. // 复合分隔键 (key, pkID) 回归测试。
  12. //
  13. // 二级树的全局不变量: 所有叶条目按复合键 (key, pkID) 有序; 内部条目的前
  14. // EntryLen-4 字节 = 其子树的最小复合键。一旦破坏:
  15. // - sbtChildIdxPK 的点路由会落到错误子树 → (key,pkID) 精确删除漏删;
  16. // - sbtLeafLowerBoundPK 的叶内二分会漏条目 → SBTSearchAll 返回不全。
  17. // 本测试用「乱序插入 → 乱序删除 → 重插入 → 删空」全流程 + 全树不变量校验钉死它。
  18. // ============================================================================
  19. // checkSubtree 递归校验以 pid 为根的子树:
  20. // - 叶内/内部条目按复合键严格递增;
  21. // - 内部条目复合键 == 其子树的最小复合键;
  22. // - 不存在 "空的非根子树" (删除路径的下溢处理应消掉它们);
  23. // - 叶子链 (PrePage/NextPage) 与 DFS 顺序一致。
  24. // 返回该子树的最小复合键; 空树返回 nil。
  25. func checkSubtree(t *testing.T, h page.HandleDb, pid uint32) []byte {
  26. t.Helper()
  27. n := sbtLoad(h, pid)
  28. if n == nil {
  29. t.Fatalf("checkSubtree: 读不出节点 pid=%d", pid)
  30. }
  31. cnt := int(n.info.Count)
  32. if n.isLeaf() {
  33. for i := 1; i < cnt; i++ {
  34. a, b := sbtEntry(n, i-1), sbtEntry(n, i)
  35. if sbtCmpKeyPK(sbtLeafKey(a), sbtPKID(a), sbtLeafKey(b), sbtPKID(b)) >= 0 {
  36. t.Fatalf("叶 %d 内复合键乱序 @%d", pid, i)
  37. }
  38. }
  39. if cnt == 0 {
  40. return nil
  41. }
  42. return sbtEntry(n, 0) // 叶条目本身就是 [key|pkID]
  43. }
  44. if cnt == 0 {
  45. return nil // 仅允许根为空内部节点 (上层归一化会换掉它)
  46. }
  47. var first []byte
  48. for i := 0; i < cnt; i++ {
  49. e := sbtEntry(n, i)
  50. if i > 0 {
  51. p := sbtEntry(n, i-1)
  52. if sbtCmpKeyPK(sbtIntKey(p), sbtIntPKID(p), sbtIntKey(e), sbtIntPKID(e)) >= 0 {
  53. t.Fatalf("内部节点 %d 复合键乱序 @%d", pid, i)
  54. }
  55. }
  56. childPID := sbtChildPID(e)
  57. child := sbtLoad(h, childPID)
  58. if child == nil {
  59. t.Fatalf("内部节点 %d 条目 %d 指向读不出的子页 %d", pid, i, childPID)
  60. }
  61. if child.info.Count == 0 {
  62. t.Fatalf("存在空的非根子树: 父 %d 条目 %d → 子 %d (下溢处理漏了它)", pid, i, childPID)
  63. }
  64. cm := checkSubtree(t, h, childPID)
  65. if !bytes.Equal(cm, e[:len(e)-4]) {
  66. t.Fatalf("父条目复合键 != 子树最小复合键: 父 %d 条目 %d → 子 %d\n父=%v 子=%v",
  67. pid, i, childPID, e[:len(e)-4], cm)
  68. }
  69. if i == 0 {
  70. first = append([]byte(nil), cm...)
  71. }
  72. }
  73. return first
  74. }
  75. // dfsLeafOrder DFS 收集叶页号 (复合键序)。
  76. func dfsLeafOrder(h page.HandleDb, pid uint32) []uint32 {
  77. n := sbtLoad(h, pid)
  78. if n == nil {
  79. return nil
  80. }
  81. if n.isLeaf() {
  82. return []uint32{pid}
  83. }
  84. var out []uint32
  85. for i := 0; i < int(n.info.Count); i++ {
  86. out = append(out, dfsLeafOrder(h, sbtChildPID(sbtEntry(n, i)))...)
  87. }
  88. return out
  89. }
  90. func checkLeafChain(t *testing.T, h page.HandleDb, root uint32) {
  91. t.Helper()
  92. leaves := dfsLeafOrder(h, root)
  93. if len(leaves) == 0 {
  94. return
  95. }
  96. for i, pid := range leaves {
  97. n := sbtLoad(h, pid)
  98. if n == nil || !n.isLeaf() {
  99. t.Fatalf("DFS 叶序里出现非叶/读不出的页 %d", pid)
  100. }
  101. wantPre, wantNext := uint32(0), uint32(0)
  102. if i > 0 {
  103. wantPre = leaves[i-1]
  104. }
  105. if i+1 < len(leaves) {
  106. wantNext = leaves[i+1]
  107. }
  108. if n.info.Hdr.PrePage != wantPre || n.info.Hdr.NextPage != wantNext {
  109. t.Fatalf("叶子链断裂: 叶[%d]=%d want(pre=%d,next=%d) got(pre=%d,next=%d)",
  110. i, pid, wantPre, wantNext, n.info.Hdr.PrePage, n.info.Hdr.NextPage)
  111. }
  112. }
  113. }
  114. // TestSecondaryCompositeOrderStress 乱序插入/删除/重插入全程校验复合键不变量。
  115. func TestSecondaryCompositeOrderStress(t *testing.T) {
  116. const (
  117. keyLen = 8
  118. keys = 50 // 每个 key 400 条重复 → 单 key 区段 6400B, 必然跨多个叶
  119. perKey = 400
  120. )
  121. total := keys * perKey
  122. m := newMemHandle()
  123. h := m.handle()
  124. type pair struct {
  125. key uint64
  126. pk uint64
  127. }
  128. all := make([]pair, 0, total)
  129. for k := uint64(1); k <= keys; k++ {
  130. for p := uint64(1); p <= perKey; p++ {
  131. all = append(all, pair{key: k, pk: p*k + p%97}) // pkID 乱序于插入序列
  132. }
  133. }
  134. rng := rand.New(rand.NewSource(20260914))
  135. rng.Shuffle(len(all), func(i, j int) { all[i], all[j] = all[j], all[i] })
  136. mkEntry := func(p pair) []byte {
  137. e := make([]byte, keyLen+8)
  138. binary.BigEndian.PutUint64(e[:keyLen], p.key)
  139. binary.BigEndian.PutUint64(e[keyLen:], p.pk)
  140. return e
  141. }
  142. mkKey := func(k uint64) []byte {
  143. b := make([]byte, keyLen)
  144. binary.BigEndian.PutUint64(b, k)
  145. return b
  146. }
  147. // 活跃集合 (期望状态): key → 有序 pkID 列表
  148. live := make(map[uint64][]uint64)
  149. var root uint32
  150. // ---- 阶段 1: 乱序全量插入 ----
  151. for _, p := range all {
  152. var err error
  153. root, err = SBTInsert(root, mkEntry(p), h)
  154. if err != nil {
  155. t.Fatalf("插入 (%d,%d) 失败: %v", p.key, p.pk, err)
  156. }
  157. live[p.key] = append(live[p.key], p.pk)
  158. }
  159. checkSubtree(t, h, root)
  160. checkLeafChain(t, h, root)
  161. // live[k] 保持有序, 供 sort.Search 精确移除
  162. for k := range live {
  163. sort.Slice(live[k], func(a, b int) bool { return live[k][a] < live[k][b] })
  164. }
  165. // ---- 阶段 2: 乱序删除一半 ----
  166. rng.Shuffle(len(all), func(i, j int) { all[i], all[j] = all[j], all[i] })
  167. deleted := make(map[pair]bool)
  168. for i, p := range all[:total/2] {
  169. newRoot, err := SBTDeleteWithPKID(root, mkKey(p.key), p.pk, h)
  170. if err != nil {
  171. t.Fatalf("删除 (%d,%d) 失败: %v", p.key, p.pk, err)
  172. }
  173. root = newRoot
  174. pks := live[p.key]
  175. idx := sort.Search(len(pks), func(x int) bool { return pks[x] >= p.pk })
  176. live[p.key] = append(pks[:idx], pks[idx+1:]...)
  177. deleted[p] = true
  178. if i%500 == 499 {
  179. checkSubtree(t, h, root)
  180. checkLeafChain(t, h, root)
  181. // 抽查被删 key 与随机 key 的 SBTSearchAll
  182. for _, k := range []uint64{p.key, uint64(rng.Intn(keys) + 1), 1, uint64(keys)} {
  183. want := append([]uint64(nil), live[k]...)
  184. sort.Slice(want, func(a, b int) bool { return want[a] < want[b] })
  185. got, ok := SBTSearchAll(root, mkKey(k), h)
  186. if len(want) == 0 {
  187. if ok {
  188. t.Fatalf("SBTSearchAll(key=%d) 应为空, 返回 %v", k, got)
  189. }
  190. continue
  191. }
  192. if !ok || !equalU64(got, want) {
  193. t.Fatalf("SBTSearchAll(key=%d) 不符: want %d 条, got %v (ok=%v)", k, len(want), got, ok)
  194. }
  195. }
  196. }
  197. }
  198. checkSubtree(t, h, root)
  199. // ---- 阶段 3: 重插入部分已删条目 (混合分裂/合并后的结构) ----
  200. reins := 0
  201. for _, p := range all[:total/2] {
  202. if reins >= total/8 {
  203. break
  204. }
  205. if !deleted[p] {
  206. continue
  207. }
  208. var err error
  209. root, err = SBTInsert(root, mkEntry(p), h)
  210. if err != nil {
  211. t.Fatalf("重插入 (%d,%d) 失败: %v", p.key, p.pk, err)
  212. }
  213. live[p.key] = append(live[p.key], p.pk)
  214. sort.Slice(live[p.key], func(a, b int) bool { return live[p.key][a] < live[p.key][b] })
  215. delete(deleted, p)
  216. reins++
  217. }
  218. checkSubtree(t, h, root)
  219. checkLeafChain(t, h, root)
  220. // ---- 阶段 4: 全量删除至空 ----
  221. remaining := make([]pair, 0, total)
  222. for k := uint64(1); k <= keys; k++ {
  223. for _, pk := range live[k] {
  224. remaining = append(remaining, pair{key: k, pk: pk})
  225. }
  226. }
  227. rng.Shuffle(len(remaining), func(i, j int) { remaining[i], remaining[j] = remaining[j], remaining[i] })
  228. for _, p := range remaining {
  229. newRoot, err := SBTDeleteWithPKID(root, mkKey(p.key), p.pk, h)
  230. if err != nil {
  231. t.Fatalf("清空阶段删除 (%d,%d) 失败: %v", p.key, p.pk, err)
  232. }
  233. root = newRoot
  234. }
  235. // 删空后: 根必须是 (空) 叶, 全树只剩 1 页
  236. n := sbtLoad(h, root)
  237. if n == nil || !n.isLeaf() || n.info.Count != 0 {
  238. t.Fatalf("删空后根不是空叶: %+v", n.info)
  239. }
  240. // 页收支平衡: 曾分配的每一页, 要么是最终根, 要么在下溢/合并中已归还
  241. freedSoFar := sortedSet(m.freed)
  242. if len(freedSoFar)+1 != len(m.allocated()) {
  243. t.Fatalf("页收支不平衡: 分配 %d 页, 已归还 %d, 最终根 1 页",
  244. len(m.allocated()), len(freedSoFar))
  245. }
  246. // 释放单页空根应恰好只归还根本身 (其余 34 页已在途中归还, 不得二次归还)
  247. m.freed = nil
  248. btReleaseSubtree(h, root)
  249. if freed := sortedSet(m.freed); !sameSet(freed, []uint32{root}) {
  250. t.Fatalf("释放单页空根应只归还根 %d, 实际归还 %v", root, freed)
  251. }
  252. }
  253. func equalU64(a, b []uint64) bool {
  254. if len(a) != len(b) {
  255. return false
  256. }
  257. for i := range a {
  258. if a[i] != b[i] {
  259. return false
  260. }
  261. }
  262. return true
  263. }
  264. // TestSecondaryBuildFromSortedComposite 批量建树要求条目按复合键 (key, pkID) 有序;
  265. // 同 key 条目 pkID 无序时, 复合路由/二分都会静默漏条目 —— 这里验证有序输入的正确性,
  266. // 并抽查精确删除落在叶中部 (非区段端点) 的场景。
  267. func TestSecondaryBuildFromSortedComposite(t *testing.T) {
  268. const keyLen = 8
  269. m := newMemHandle()
  270. h := m.handle()
  271. // 3 个 key, 每个 800 条, 按 (key, pkID) 排序
  272. var entries [][]byte
  273. for k := uint64(1); k <= 3; k++ {
  274. for p := uint64(1); p <= 800; p++ {
  275. e := make([]byte, keyLen+8)
  276. binary.BigEndian.PutUint64(e[:keyLen], k)
  277. binary.BigEndian.PutUint64(e[keyLen:], p)
  278. entries = append(entries, e)
  279. }
  280. }
  281. root := SBTBuildFromSorted(entries, h)
  282. if root == 0 {
  283. t.Fatal("SBTBuildFromSorted 返回 0")
  284. }
  285. checkSubtree(t, h, root)
  286. // SBTSearchAll 应按 pkID 升序返回完整区段
  287. for k := uint64(1); k <= 3; k++ {
  288. b := make([]byte, keyLen)
  289. binary.BigEndian.PutUint64(b, k)
  290. got, ok := SBTSearchAll(root, b, h)
  291. if !ok || len(got) != 800 {
  292. t.Fatalf("key=%d: want 800 条, got %d (ok=%v)", k, len(got), ok)
  293. }
  294. for i := 1; i < len(got); i++ {
  295. if got[i-1] >= got[i] {
  296. t.Fatalf("key=%d: 返回未按 pkID 有序", k)
  297. }
  298. }
  299. }
  300. // 删掉中间一段 pkID, 区段两端不受影响
  301. for p := uint64(300); p <= 500; p++ {
  302. b := make([]byte, keyLen)
  303. binary.BigEndian.PutUint64(b, 2)
  304. var err error
  305. root, err = SBTDeleteWithPKID(root, b, p, h)
  306. if err != nil {
  307. t.Fatalf("删除 (2,%d) 失败: %v", p, err)
  308. }
  309. }
  310. checkSubtree(t, h, root)
  311. b2 := make([]byte, keyLen)
  312. binary.BigEndian.PutUint64(b2, 2)
  313. got, ok := SBTSearchAll(root, b2, h)
  314. if !ok || len(got) != 599 {
  315. t.Fatalf("删除中段 (300..500 共 201 条) 后 key=2 应剩 599 条, got %d", len(got))
  316. }
  317. }
  318. // TestSecondaryDeleteRoutingExact 点删除不再向左试探兄弟子树:
  319. // 构造 "同 key 区段横跨多个叶" 后, 删除区段最左侧的条目必须一次命中。
  320. // (旧实现从 key 路由落点向左扫, 删左端条目要试完整段; 若复合路由错位, 这里会漏删报
  321. // ErrNotFound 或删错条目。)
  322. func TestSecondaryDeleteRoutingExact(t *testing.T) {
  323. const keyLen = 8
  324. m := newMemHandle()
  325. h := m.handle()
  326. // key=7 的 3000 条 + 两侧各 1 条异键哨兵
  327. var entries [][]byte
  328. add := func(k, p uint64) {
  329. e := make([]byte, keyLen+8)
  330. binary.BigEndian.PutUint64(e[:keyLen], k)
  331. binary.BigEndian.PutUint64(e[keyLen:], p)
  332. entries = append(entries, e)
  333. }
  334. add(6, 1)
  335. for p := uint64(1); p <= 3000; p++ {
  336. add(7, p)
  337. }
  338. add(8, 1)
  339. sort.Slice(entries, func(i, j int) bool {
  340. c := bytes.Compare(entries[i][:keyLen], entries[j][:keyLen])
  341. if c != 0 {
  342. return c < 0
  343. }
  344. return binary.BigEndian.Uint64(entries[i][keyLen:]) < binary.BigEndian.Uint64(entries[j][keyLen:])
  345. })
  346. root := SBTBuildFromSorted(entries, h)
  347. // 从区段最左 (pkID=1) 删到最右 (pkID=3000): 任何一步 ErrNotFound 都是路由错位
  348. for p := uint64(1); p <= 3000; p++ {
  349. b := make([]byte, keyLen)
  350. binary.BigEndian.PutUint64(b, 7)
  351. var err error
  352. root, err = SBTDeleteWithPKID(root, b, p, h)
  353. if err != nil {
  354. t.Fatalf("顺序删除 (7,%d) 失败: %v", p, err)
  355. }
  356. }
  357. // 区段删空, 哨兵仍在
  358. for k, want := range map[uint64]int{6: 1, 7: 0, 8: 1} {
  359. b := make([]byte, keyLen)
  360. binary.BigEndian.PutUint64(b, k)
  361. got, ok := SBTSearchAll(root, b, h)
  362. if want == 0 && ok {
  363. t.Fatalf("key=%d 应已删空, got %v", k, got)
  364. }
  365. if want > 0 && (!ok || len(got) != want) {
  366. t.Fatalf("key=%d want %d 条, got %v (ok=%v)", k, want, got, ok)
  367. }
  368. }
  369. checkSubtree(t, h, root)
  370. }