github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/lsmkv/red_black_tree_test.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package lsmkv 13 14 import ( 15 "crypto/rand" 16 "fmt" 17 "math" 18 "math/big" 19 "reflect" 20 "testing" 21 22 "github.com/stretchr/testify/require" 23 "github.com/weaviate/weaviate/adapters/repos/db/lsmkv/rbtree" 24 ) 25 26 const ( 27 R = true 28 B = false 29 ) 30 31 // This test adds keys to the RB tree. Afterwards the same nodes are added in the expected order, eg in the way 32 // the RB tree is expected to re-order the nodes 33 var rbTests = []struct { 34 name string 35 keys []uint 36 ReorderedKeys []uint 37 expectedColors []bool // with respect to the original keys 38 }{ 39 { 40 "Requires recoloring but no reordering", 41 []uint{61, 52, 83, 93}, 42 []uint{61, 52, 83, 93}, 43 []bool{B, B, B, R}, 44 }, 45 { 46 "Requires left rotate around root", 47 []uint{61, 83, 99}, 48 []uint{83, 61, 99}, 49 []bool{R, B, R}, 50 }, 51 { 52 "Requires left rotate with more nodes", 53 []uint{61, 52, 85, 93, 99}, 54 []uint{61, 52, 93, 85, 99}, 55 []bool{B, B, R, B, R}, 56 }, 57 { 58 "Requires right and then left rotate", 59 []uint{61, 52, 85, 93, 87}, 60 []uint{61, 52, 87, 85, 93}, 61 []bool{B, B, R, R, B}, 62 }, 63 { 64 "Requires right rotate around root", 65 []uint{61, 30, 10}, 66 []uint{30, 10, 61}, 67 []bool{R, B, R}, 68 }, 69 { 70 "Requires right rotate with more nodes", 71 []uint{61, 52, 85, 21, 10}, 72 []uint{61, 85, 21, 10, 52}, 73 []bool{B, R, B, B, R}, 74 }, 75 { 76 "Requires left and then right rotate", 77 []uint{61, 52, 85, 21, 36}, 78 []uint{61, 85, 36, 21, 52}, 79 []bool{B, R, B, R, B}, 80 }, 81 { 82 "Require reordering for two nodes", 83 []uint{61, 52, 40, 85, 105, 110}, 84 []uint{52, 40, 85, 61, 105, 110}, 85 []bool{B, B, B, R, B, R}, 86 }, 87 { 88 "Ordered nodes increasing", 89 []uint{1, 2, 3, 4, 5, 6, 7, 8}, 90 []uint{4, 2, 6, 1, 3, 5, 7, 8}, 91 []bool{B, R, B, B, B, R, B, R}, 92 }, 93 { 94 "Ordered nodes decreasing", 95 []uint{8, 7, 6, 5, 4, 3, 2, 1}, 96 []uint{5, 3, 7, 2, 4, 6, 8, 1}, 97 []bool{B, R, B, B, B, R, B, R}, 98 }, 99 { 100 "Multiple rotations along the tree and colour changes", 101 []uint{166, 92, 33, 133, 227, 236, 71, 183, 18, 139, 245, 161}, 102 []uint{166, 92, 227, 33, 139, 183, 236, 18, 71, 133, 161, 245}, 103 []bool{B, R, B, R, R, B, R, B, R, B, R, R}, 104 }, 105 } 106 107 func TestRBTree(t *testing.T) { 108 for _, tt := range rbTests { 109 t.Run(tt.name, func(t *testing.T) { 110 tree := &binarySearchTree{} 111 for _, key := range tt.keys { 112 iByte := []byte{uint8(key)} 113 tree.insert(iByte, iByte, nil) 114 require.Empty(t, tree.root.parent) 115 } 116 validateRBTree(t, tree.root) 117 118 flattenTree := tree.flattenInOrder() 119 require.Equal(t, len(tt.keys), len(flattenTree)) // no entries got lost 120 121 // add tree with the same nodes in the "optimal" order to be able to compare their order afterwards 122 treeCorrectOrder := &binarySearchTree{} 123 for _, key := range tt.ReorderedKeys { 124 iByte := []byte{uint8(key)} 125 treeCorrectOrder.insert(iByte, iByte, nil) 126 } 127 128 flattenTreeInput := treeCorrectOrder.flattenInOrder() 129 for i := range flattenTree { 130 byteKey := flattenTree[i].key 131 originalIndex := getIndexInSlice(tt.keys, byteKey) 132 require.Equal(t, byteKey, flattenTreeInput[i].key) 133 require.Equal(t, flattenTree[i].colourIsRed, tt.expectedColors[originalIndex]) 134 } 135 }) 136 } 137 } 138 139 func TestRBTreeMap(t *testing.T) { 140 for _, tt := range rbTests { 141 t.Run(tt.name, func(t *testing.T) { 142 tree := &binarySearchTreeMap{} 143 for _, key := range tt.keys { 144 tree.insert([]byte{uint8(key)}, MapPair{ 145 Key: []byte("map-key-1"), 146 Value: []byte("map-value-1"), 147 }) 148 require.Empty(t, tree.root.parent) 149 } 150 validateRBTree(t, tree.root) 151 152 flatten_tree := tree.flattenInOrder() 153 require.Equal(t, len(tt.keys), len(flatten_tree)) // no entries got lost 154 155 // add tree with the same nodes in the "optimal" order to be able to compare their order afterwards 156 treeCorrectOrder := &binarySearchTreeMap{} 157 for _, key := range tt.ReorderedKeys { 158 treeCorrectOrder.insert([]byte{uint8(key)}, MapPair{ 159 Key: []byte("map-key-1"), 160 Value: []byte("map-value-1"), 161 }) 162 } 163 164 flatten_tree_input := treeCorrectOrder.flattenInOrder() 165 for i := range flatten_tree { 166 byte_key := flatten_tree[i].key 167 originalIndex := getIndexInSlice(tt.keys, byte_key) 168 require.Equal(t, byte_key, flatten_tree_input[i].key) 169 require.Equal(t, flatten_tree[i].colourIsRed, tt.expectedColors[originalIndex]) 170 } 171 }) 172 } 173 } 174 175 func TestRBTreeMulti(t *testing.T) { 176 for _, tt := range rbTests { 177 t.Run(tt.name, func(t *testing.T) { 178 tree := &binarySearchTreeMulti{} 179 for _, key := range tt.keys { 180 values := []value{} 181 for j := uint(0); j < 5; j++ { 182 values = append(values, value{value: []byte{uint8(key * j)}, tombstone: false}) 183 } 184 tree.insert([]byte{uint8(key)}, values) 185 require.Empty(t, tree.root.parent) 186 } 187 validateRBTree(t, tree.root) 188 189 flatten_tree := tree.flattenInOrder() 190 require.Equal(t, len(tt.keys), len(flatten_tree)) // no entries got lost 191 192 // add tree with the same nodes in the "optimal" order to be able to compare their order afterwards 193 treeCorrectOrder := &binarySearchTreeMulti{} 194 for _, key := range tt.ReorderedKeys { 195 values := []value{} 196 for j := uint(0); j < 5; j++ { 197 values = append(values, value{value: []byte{uint8(key * j)}, tombstone: false}) 198 } 199 treeCorrectOrder.insert([]byte{uint8(key)}, values) 200 } 201 202 flatten_tree_input := treeCorrectOrder.flattenInOrder() 203 for i := range flatten_tree { 204 byte_key := flatten_tree[i].key 205 originalIndex := getIndexInSlice(tt.keys, byte_key) 206 require.Equal(t, byte_key, flatten_tree_input[i].key) 207 require.Equal(t, flatten_tree[i].colourIsRed, tt.expectedColors[originalIndex]) 208 } 209 }) 210 } 211 } 212 213 // add keys as a) normal keys b) tombstone keys and c) half tombstone, half normal. 214 // The resulting (rebalanced) trees must have the same order and colors 215 var tombstoneTests = []struct { 216 name string 217 keys []uint 218 }{ 219 {"Rotate left around root", []uint{61, 83, 99}}, 220 {"Rotate right around root", []uint{61, 30, 10}}, 221 {"Multiple rotations along the tree and colour changes", []uint{166, 92, 33, 133, 227, 236, 71, 183, 18, 139, 245, 161}}, 222 {"Ordered nodes increasing", []uint{1, 2, 3, 4, 5, 6, 7, 8}}, 223 {"Ordered nodes decreasing", []uint{8, 7, 6, 5, 4, 3, 2, 1}}, 224 } 225 226 func TestRBTrees_Tombstones(t *testing.T) { 227 for _, tt := range tombstoneTests { 228 t.Run(tt.name, func(t *testing.T) { 229 treeNormal := &binarySearchTree{} 230 treeTombstone := &binarySearchTree{} 231 treeHalfHalf := &binarySearchTree{} 232 for i, key := range tt.keys { 233 iByte := []byte{uint8(key)} 234 treeNormal.insert(iByte, iByte, nil) 235 treeTombstone.setTombstone(iByte, nil) 236 if i%2 == 0 { 237 treeHalfHalf.insert(iByte, iByte, nil) 238 } else { 239 treeHalfHalf.setTombstone(iByte, nil) 240 } 241 } 242 validateRBTree(t, treeNormal.root) 243 validateRBTree(t, treeTombstone.root) 244 validateRBTree(t, treeHalfHalf.root) 245 246 treeNormalFlatten := treeNormal.flattenInOrder() 247 treeTombstoneFlatten := treeTombstone.flattenInOrder() 248 treeHalfHalfFlatten := treeHalfHalf.flattenInOrder() 249 require.Equal(t, len(tt.keys), len(treeNormalFlatten)) 250 require.Equal(t, len(tt.keys), len(treeTombstoneFlatten)) 251 require.Equal(t, len(tt.keys), len(treeHalfHalfFlatten)) 252 253 for i := range treeNormalFlatten { 254 require.Equal(t, treeNormalFlatten[i].key, treeTombstoneFlatten[i].key) 255 require.Equal(t, treeNormalFlatten[i].key, treeHalfHalfFlatten[i].key) 256 require.Equal(t, treeNormalFlatten[i].colourIsRed, treeTombstoneFlatten[i].colourIsRed) 257 require.Equal(t, treeNormalFlatten[i].colourIsRed, treeHalfHalfFlatten[i].colourIsRed) 258 } 259 }) 260 } 261 } 262 263 type void struct{} 264 265 var member void 266 267 func mustRandIntn(max int64) int { 268 randInt, err := rand.Int(rand.Reader, big.NewInt(max)) 269 if err != nil { 270 panic(fmt.Sprintf("mustRandIntn error: %v", err)) 271 } 272 return int(randInt.Int64()) 273 } 274 275 func TestRBTrees_Random(t *testing.T) { 276 tree := &binarySearchTree{} 277 amount := mustRandIntn(100000) 278 keySize := mustRandIntn(100) 279 uniqueKeys := make(map[string]void) 280 for i := 0; i < amount; i++ { 281 key := make([]byte, keySize) 282 rand.Read(key) 283 uniqueKeys[fmt.Sprint(key)] = member 284 if mustRandIntn(5) == 1 { // add 20% of all entries as tombstone 285 tree.setTombstone(key, nil) 286 } else { 287 tree.insert(key, key, nil) 288 } 289 } 290 291 // all added keys are still part of the tree 292 treeFlattened := tree.flattenInOrder() 293 require.Equal(t, len(uniqueKeys), len(treeFlattened)) 294 for _, entry := range treeFlattened { 295 _, ok := uniqueKeys[fmt.Sprint(entry.key)] 296 require.True(t, ok) 297 } 298 validateRBTree(t, tree.root) 299 } 300 301 func TestRBTreesMap_Random(t *testing.T) { 302 tree := &binarySearchTreeMap{} 303 amount := mustRandIntn(100000) 304 keySize := mustRandIntn(100) 305 uniqueKeys := make(map[string]void) 306 for i := 0; i < amount; i++ { 307 key := make([]byte, keySize) 308 rand.Read(key) 309 uniqueKeys[fmt.Sprint(key)] = member 310 tree.insert(key, MapPair{ 311 Key: []byte("map-key-1"), 312 Value: []byte("map-value-1"), 313 }) 314 } 315 316 // all added keys are still part of the tree 317 treeFlattened := tree.flattenInOrder() 318 require.Equal(t, len(uniqueKeys), len(treeFlattened)) 319 for _, entry := range treeFlattened { 320 _, ok := uniqueKeys[fmt.Sprint(entry.key)] 321 require.True(t, ok) 322 } 323 validateRBTree(t, tree.root) 324 } 325 326 func TestRBTreesMulti_Random(t *testing.T) { 327 tree := &binarySearchTreeMulti{} 328 amount := mustRandIntn(100000) 329 keySize := mustRandIntn(100) 330 uniqueKeys := make(map[string]void) 331 for i := 0; i < amount; i++ { 332 key := make([]byte, keySize) 333 rand.Read(key) 334 uniqueKeys[fmt.Sprint(key)] = member 335 values := []value{} 336 for j := 0; j < 5; j++ { 337 values = append(values, value{value: []byte{uint8(i * j)}, tombstone: false}) 338 } 339 tree.insert(key, values) 340 } 341 342 // all added keys are still part of the tree 343 treeFlattened := tree.flattenInOrder() 344 require.Equal(t, len(uniqueKeys), len(treeFlattened)) 345 for _, entry := range treeFlattened { 346 _, ok := uniqueKeys[fmt.Sprint(entry.key)] 347 require.True(t, ok) 348 } 349 validateRBTree(t, tree.root) 350 } 351 352 func getIndexInSlice(reorderedKeys []uint, key []byte) int { 353 for i, v := range reorderedKeys { 354 if v == uint(key[0]) { 355 return i 356 } 357 } 358 return -1 359 } 360 361 // Checks if a tree is a RB tree 362 // 363 // There are several properties that valid RB trees follow: 364 // 1) The root node is always black 365 // 2) The max depth of a tree is 2* Log2(N+1), where N is the number of nodes 366 // 3) Every path from root to leave has the same number of _black_ nodes 367 // 4) Red nodes only have black (or nil) children 368 // 369 // In addition this also validates some general tree properties: 370 // - root has no parent 371 // - if node A is a child of B, B must be the parent of A) 372 func validateRBTree(t *testing.T, rootNode rbtree.Node) { 373 require.False(t, rootNode.IsRed()) 374 require.True(t, rootNode.Parent().IsNil()) 375 376 treeDepth, nodeCount, _ := walkTree(t, rootNode) 377 maxDepth := 2 * math.Log2(float64(nodeCount)+1) 378 require.True(t, treeDepth <= int(maxDepth)) 379 } 380 381 // Walks through the tree and counts the depth, number of nodes and number of black nodes 382 func walkTree(t *testing.T, node rbtree.Node) (int, int, int) { 383 if reflect.ValueOf(node).IsNil() { 384 return 0, 0, 0 385 } 386 leftNode := node.Left() 387 leftNodeIsNil := reflect.ValueOf(leftNode).IsNil() 388 rightNode := node.Right() 389 rightNodeIsNil := reflect.ValueOf(rightNode).IsNil() 390 391 // validate parent/child connections 392 if !rightNodeIsNil { 393 require.Equal(t, rightNode.Parent(), node) 394 } 395 if !leftNodeIsNil { 396 require.Equal(t, leftNode.Parent(), node) 397 } 398 399 // red nodes need black (or nil) children 400 if node.IsRed() { 401 require.True(t, leftNodeIsNil || !node.Left().IsRed()) 402 require.True(t, rightNodeIsNil || !node.Left().IsRed()) 403 } 404 405 blackNode := int(1) 406 if node.IsRed() { 407 blackNode = 0 408 } 409 410 if node.Right().IsNil() && node.Left().IsNil() { 411 return 1, 1, blackNode 412 } 413 414 depthRight, nodeCountRight, blackNodesDepthRight := walkTree(t, node.Right()) 415 depthLeft, nodeCountLeft, blackNodesDepthLeft := walkTree(t, node.Left()) 416 require.Equal(t, blackNodesDepthRight, blackNodesDepthLeft) 417 418 nodeCount := nodeCountLeft + nodeCountRight + 1 419 if depthRight > depthLeft { 420 return depthRight + 1, nodeCount, blackNodesDepthRight + blackNode 421 } else { 422 return depthLeft + 1, nodeCount, blackNodesDepthRight + blackNode 423 } 424 }