|
@@ -0,0 +1,85 @@
|
|
|
|
|
+package btree
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "encoding/binary"
|
|
|
|
|
+ "testing"
|
|
|
|
|
+
|
|
|
|
|
+ "git.ycl.pub/yl32940/TsDB2/DB/core/page"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// sbtFillStats 统计一棵 SBT 树所有节点的 (页数, 平均填充字节比)。
|
|
|
|
|
+// 直接沿最左叶链走页 (btree 包内白盒)。
|
|
|
|
|
+func sbtLeafFill(t *testing.T, h page.HandleDb, root uint32) (pages int, sumRatio float64) {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ cur := root
|
|
|
|
|
+ for {
|
|
|
|
|
+ n := sbtLoad(h, cur)
|
|
|
|
|
+ if n == nil {
|
|
|
|
|
+ t.Fatalf("加载节点 %d 失败", cur)
|
|
|
|
|
+ }
|
|
|
|
|
+ if n.isLeaf() {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ cur = sbtChildPID(sbtEntry(n, 0))
|
|
|
|
|
+ }
|
|
|
|
|
+ for cur != 0 {
|
|
|
|
|
+ n := sbtLoad(h, cur)
|
|
|
|
|
+ if n == nil {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ pages++
|
|
|
|
|
+ sumRatio += float64(n.info.Hdr.UseSize) / float64(page.IndexPageSize)
|
|
|
|
|
+ cur = n.info.Hdr.NextPage
|
|
|
|
|
+ }
|
|
|
|
|
+ return pages, sumRatio
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TestSBTSequentialFill 回归: 顺序键 (自增号/流水号) 插入二级 B+ 树,
|
|
|
|
|
+// 叶子平均填充必须 ≥85% —— 防 "50/50 分裂导致最右脊长期半空" 复发
|
|
|
|
|
+// (实测过: bench_10m 1000 万行 int64 二级索引 40,111 页中 38,290 页 ~50% 空,
|
|
|
|
|
+// 索引体积膨胀 1.8x; 修复 = SBTSplitCfg.SequentialRight + 分裂点传真实插入位置)。
|
|
|
|
|
+func TestSBTSequentialFill(t *testing.T) {
|
|
|
|
|
+ h := newMemHandle().handle()
|
|
|
|
|
+ root := uint32(0)
|
|
|
|
|
+ key := make([]byte, 8)
|
|
|
|
|
+ for i := uint64(1); i <= 30000; i++ {
|
|
|
|
|
+ binary.BigEndian.PutUint64(key, i)
|
|
|
|
|
+ entry := make([]byte, 16)
|
|
|
|
|
+ copy(entry[:8], key)
|
|
|
|
|
+ binary.BigEndian.PutUint64(entry[8:], i)
|
|
|
|
|
+ nr, err := SBTInsert(root, entry, h)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("插入 %d: %v", i, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ root = nr
|
|
|
|
|
+ }
|
|
|
|
|
+ pages, sum := sbtLeafFill(t, h, root)
|
|
|
|
|
+ avg := sum / float64(pages)
|
|
|
|
|
+ t.Logf("3 万顺序条目: %d 叶页, 平均填充 %.1f%%", pages, avg*100)
|
|
|
|
|
+ if avg < 0.85 {
|
|
|
|
|
+ t.Fatalf("顺序插入的二级 B+ 树叶平均填充 %.1f%% < 85%% —— 顺序分裂优化失效/回归", avg*100)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 随机键对照: 50/50 分裂下随机插入也应保持在合理填充 (>60%), 防优化误伤
|
|
|
|
|
+ h2 := newMemHandle().handle()
|
|
|
|
|
+ root2 := uint32(0)
|
|
|
|
|
+ seed := uint64(12345)
|
|
|
|
|
+ for i := 0; i < 30000; i++ {
|
|
|
|
|
+ seed = seed*6364136223846793005 + 1442695040888963407 // LCG 伪随机, 顺序无关
|
|
|
|
|
+ k := (seed >> 33) % 40000000
|
|
|
|
|
+ binary.BigEndian.PutUint64(key, k)
|
|
|
|
|
+ entry := make([]byte, 16)
|
|
|
|
|
+ copy(entry[:8], key)
|
|
|
|
|
+ binary.BigEndian.PutUint64(entry[8:], uint64(i+1))
|
|
|
|
|
+ nr, err := SBTInsert(root2, entry, h2)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("随机插入 %d: %v", i, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ root2 = nr
|
|
|
|
|
+ }
|
|
|
|
|
+ pages2, sum2 := sbtLeafFill(t, h2, root2)
|
|
|
|
|
+ avg2 := sum2 / float64(pages2)
|
|
|
|
|
+ t.Logf("3 万随机条目: %d 叶页, 平均填充 %.1f%%", pages2, avg2*100)
|
|
|
|
|
+ if avg2 < 0.60 {
|
|
|
|
|
+ t.Fatalf("随机插入填充 %.1f%% < 60%% —— 异常", avg2*100)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|