github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/lsmkv/bucket_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 "context" 16 "os" 17 "testing" 18 19 "github.com/sirupsen/logrus/hooks/test" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 "github.com/weaviate/weaviate/entities/cyclemanager" 23 ) 24 25 type bucketTest struct { 26 name string 27 f func(context.Context, *testing.T, []BucketOption) 28 opts []BucketOption 29 } 30 31 type bucketTests []bucketTest 32 33 func (tests bucketTests) run(ctx context.Context, t *testing.T) { 34 for _, test := range tests { 35 t.Run(test.name, func(t *testing.T) { 36 t.Run("mmap", func(t *testing.T) { 37 test.f(ctx, t, test.opts) 38 }) 39 t.Run("pread", func(t *testing.T) { 40 test.f(ctx, t, append([]BucketOption{WithPread(true)}, test.opts...)) 41 }) 42 }) 43 } 44 } 45 46 func TestBucket(t *testing.T) { 47 ctx := context.Background() 48 tests := bucketTests{ 49 { 50 name: "bucket_WasDeleted_KeepTombstones", 51 f: bucket_WasDeleted_KeepTombstones, 52 opts: []BucketOption{ 53 WithStrategy(StrategyReplace), 54 WithKeepTombstones(true), 55 }, 56 }, 57 { 58 name: "bucket_WasDeleted_CleanupTombstones", 59 f: bucket_WasDeleted_CleanupTombstones, 60 opts: []BucketOption{ 61 WithStrategy(StrategyReplace), 62 }, 63 }, 64 { 65 name: "bucketReadsIntoMemory", 66 f: bucketReadsIntoMemory, 67 opts: []BucketOption{ 68 WithStrategy(StrategyReplace), 69 WithSecondaryIndices(1), 70 }, 71 }, 72 } 73 tests.run(ctx, t) 74 } 75 76 func bucket_WasDeleted_KeepTombstones(ctx context.Context, t *testing.T, opts []BucketOption) { 77 tmpDir := t.TempDir() 78 logger, _ := test.NewNullLogger() 79 80 b, err := NewBucket(ctx, tmpDir, "", logger, nil, 81 cyclemanager.NewCallbackGroupNoop(), cyclemanager.NewCallbackGroupNoop(), opts...) 82 require.Nil(t, err) 83 t.Cleanup(func() { 84 require.Nil(t, b.Shutdown(context.Background())) 85 }) 86 87 var ( 88 key = []byte("key") 89 val = []byte("value") 90 ) 91 92 t.Run("insert object", func(t *testing.T) { 93 err = b.Put(key, val) 94 require.Nil(t, err) 95 }) 96 97 t.Run("assert object was not deleted yet", func(t *testing.T) { 98 deleted, err := b.WasDeleted(key) 99 require.Nil(t, err) 100 assert.False(t, deleted) 101 }) 102 103 t.Run("delete object", func(t *testing.T) { 104 err = b.Delete(key) 105 require.Nil(t, err) 106 }) 107 108 t.Run("assert object was deleted", func(t *testing.T) { 109 deleted, err := b.WasDeleted(key) 110 require.Nil(t, err) 111 assert.True(t, deleted) 112 }) 113 114 t.Run("assert a nonexistent object is not detected as deleted", func(t *testing.T) { 115 deleted, err := b.WasDeleted([]byte("DNE")) 116 require.Nil(t, err) 117 assert.False(t, deleted) 118 }) 119 } 120 121 func bucket_WasDeleted_CleanupTombstones(ctx context.Context, t *testing.T, opts []BucketOption) { 122 tmpDir := t.TempDir() 123 logger, _ := test.NewNullLogger() 124 125 b, err := NewBucket(ctx, tmpDir, "", logger, nil, 126 cyclemanager.NewCallbackGroupNoop(), cyclemanager.NewCallbackGroupNoop(), opts...) 127 require.NoError(t, err) 128 t.Cleanup(func() { 129 require.NoError(t, b.Shutdown(context.Background())) 130 }) 131 132 var ( 133 key = []byte("key") 134 val = []byte("value") 135 ) 136 137 t.Run("insert object", func(t *testing.T) { 138 err = b.Put(key, val) 139 require.Nil(t, err) 140 }) 141 142 t.Run("fails on WasDeleted without keepTombstones set (before delete)", func(t *testing.T) { 143 deleted, err := b.WasDeleted(key) 144 require.ErrorContains(t, err, "keepTombstones") 145 require.False(t, deleted) 146 }) 147 148 t.Run("delete object", func(t *testing.T) { 149 err = b.Delete(key) 150 require.Nil(t, err) 151 }) 152 153 t.Run("fails on WasDeleted without keepTombstones set (after delete)", func(t *testing.T) { 154 deleted, err := b.WasDeleted(key) 155 require.ErrorContains(t, err, "keepTombstones") 156 require.False(t, deleted) 157 }) 158 159 t.Run("fails on WasDeleted without keepTombstones set (non-existent key)", func(t *testing.T) { 160 deleted, err := b.WasDeleted([]byte("DNE")) 161 require.ErrorContains(t, err, "keepTombstones") 162 require.False(t, deleted) 163 }) 164 } 165 166 func bucketReadsIntoMemory(ctx context.Context, t *testing.T, opts []BucketOption) { 167 dirName := t.TempDir() 168 logger, _ := test.NewNullLogger() 169 170 b, err := NewBucket(ctx, dirName, "", logger, nil, 171 cyclemanager.NewCallbackGroupNoop(), cyclemanager.NewCallbackGroupNoop(), opts...) 172 require.Nil(t, err) 173 defer b.Shutdown(ctx) 174 175 require.Nil(t, b.Put([]byte("hello"), []byte("world"), 176 WithSecondaryKey(0, []byte("bonjour")))) 177 require.Nil(t, b.FlushMemtable()) 178 179 files, err := os.ReadDir(b.dir) 180 require.Nil(t, err) 181 182 _, ok := findFileWithExt(files, ".bloom") 183 assert.True(t, ok) 184 185 _, ok = findFileWithExt(files, "secondary.0.bloom") 186 assert.True(t, ok) 187 188 b2, err := NewBucket(ctx, b.dir, "", logger, nil, 189 cyclemanager.NewCallbackGroupNoop(), cyclemanager.NewCallbackGroupNoop(), opts...) 190 require.Nil(t, err) 191 defer b2.Shutdown(ctx) 192 193 valuePrimary, err := b2.Get([]byte("hello")) 194 require.Nil(t, err) 195 valueSecondary := make([]byte, 5) 196 valueSecondary, _, err = b2.GetBySecondaryIntoMemory(0, []byte("bonjour"), valueSecondary) 197 require.Nil(t, err) 198 199 assert.Equal(t, []byte("world"), valuePrimary) 200 assert.Equal(t, []byte("world"), valueSecondary) 201 } 202 203 func TestBucket_MemtableCountWithFlushing(t *testing.T) { 204 b := Bucket{ 205 // by using an empty segment group for the disk portion, we can test the 206 // memtable portion in isolation 207 disk: &SegmentGroup{}, 208 } 209 210 tests := []struct { 211 name string 212 current *countStats 213 previous *countStats 214 expectedNetActive int 215 expectedNetPrevious int 216 expectedNetTotal int 217 }{ 218 { 219 name: "only active, only additions", 220 current: &countStats{ 221 upsertKeys: [][]byte{[]byte("key-1")}, 222 }, 223 expectedNetActive: 1, 224 }, 225 { 226 name: "only active, both additions and deletions", 227 current: &countStats{ 228 upsertKeys: [][]byte{[]byte("key-1")}, 229 // no key with key-2 ever existed, so this does not alter the net count 230 tombstonedKeys: [][]byte{[]byte("key-2")}, 231 }, 232 expectedNetActive: 1, 233 }, 234 { 235 name: "an deletion that was previously added", 236 current: &countStats{ 237 tombstonedKeys: [][]byte{[]byte("key-a")}, 238 }, 239 previous: &countStats{ 240 upsertKeys: [][]byte{[]byte("key-a")}, 241 }, 242 expectedNetActive: -1, 243 expectedNetPrevious: 1, 244 expectedNetTotal: 0, 245 }, 246 } 247 248 for _, tt := range tests { 249 t.Run(tt.name, func(t *testing.T) { 250 actualActive := b.memtableNetCount(tt.current, tt.previous) 251 assert.Equal(t, tt.expectedNetActive, actualActive) 252 253 if tt.previous != nil { 254 actualPrevious := b.memtableNetCount(tt.previous, nil) 255 assert.Equal(t, tt.expectedNetPrevious, actualPrevious) 256 257 assert.Equal(t, tt.expectedNetTotal, actualPrevious+actualActive) 258 } 259 }) 260 } 261 }