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  }