github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/lsmkv/store_backup_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 "fmt" 17 "testing" 18 "time" 19 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus/hooks/test" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 "github.com/weaviate/weaviate/entities/cyclemanager" 25 "github.com/weaviate/weaviate/entities/errorcompounder" 26 "github.com/weaviate/weaviate/entities/storagestate" 27 ) 28 29 func TestStoreBackup(t *testing.T) { 30 ctx := context.Background() 31 tests := bucketTests{ 32 { 33 name: "pauseCompaction", 34 f: pauseCompaction, 35 }, 36 { 37 name: "resumeCompaction", 38 f: resumeCompaction, 39 }, 40 { 41 name: "flushMemtable", 42 f: flushMemtable, 43 }, 44 } 45 tests.run(ctx, t) 46 } 47 48 func pauseCompaction(ctx context.Context, t *testing.T, opts []BucketOption) { 49 logger, _ := test.NewNullLogger() 50 51 t.Run("assert that context timeout works for long compactions", func(t *testing.T) { 52 for _, buckets := range [][]string{ 53 {"test_bucket"}, 54 {"test_bucket1", "test_bucket2"}, 55 {"test_bucket1", "test_bucket2", "test_bucket3", "test_bucket4", "test_bucket5"}, 56 } { 57 t.Run(fmt.Sprintf("with %d buckets", len(buckets)), func(t *testing.T) { 58 dirName := t.TempDir() 59 60 shardCompactionCallbacks := cyclemanager.NewCallbackGroup("classCompaction", logger, 1) 61 shardFlushCallbacks := cyclemanager.NewCallbackGroupNoop() 62 63 store, err := New(dirName, dirName, logger, nil, shardCompactionCallbacks, shardFlushCallbacks) 64 require.Nil(t, err) 65 66 for _, bucket := range buckets { 67 err = store.CreateOrLoadBucket(ctx, bucket, opts...) 68 require.Nil(t, err) 69 } 70 71 expiredCtx, cancel := context.WithDeadline(ctx, time.Now()) 72 defer cancel() 73 74 err = store.PauseCompaction(expiredCtx) 75 require.NotNil(t, err) 76 assert.Equal(t, "long-running compaction in progress:"+ 77 " deactivating callback 'store/compaction/.' of 'classCompaction' failed:"+ 78 " context deadline exceeded", err.Error()) 79 80 err = store.Shutdown(ctx) 81 require.Nil(t, err) 82 }) 83 } 84 }) 85 86 t.Run("assert compaction is successfully paused", func(t *testing.T) { 87 for _, buckets := range [][]string{ 88 {"test_bucket"}, 89 {"test_bucket1", "test_bucket2"}, 90 {"test_bucket1", "test_bucket2", "test_bucket3", "test_bucket4", "test_bucket5"}, 91 } { 92 t.Run(fmt.Sprintf("with %d buckets", len(buckets)), func(t *testing.T) { 93 dirName := t.TempDir() 94 95 shardCompactionCallbacks := cyclemanager.NewCallbackGroup("classCompaction", logger, 1) 96 shardFlushCallbacks := cyclemanager.NewCallbackGroupNoop() 97 98 store, err := New(dirName, dirName, logger, nil, shardCompactionCallbacks, shardFlushCallbacks) 99 require.Nil(t, err) 100 101 for _, bucket := range buckets { 102 err = store.CreateOrLoadBucket(ctx, bucket, opts...) 103 require.Nil(t, err) 104 105 t.Run("insert contents into bucket", func(t *testing.T) { 106 bucket := store.Bucket(bucket) 107 for i := 0; i < 10; i++ { 108 err := bucket.Put([]byte(fmt.Sprint(i)), []byte(fmt.Sprint(i))) 109 require.Nil(t, err) 110 } 111 }) 112 } 113 114 expirableCtx, cancel := context.WithTimeout(ctx, 3*time.Second) 115 defer cancel() 116 117 err = store.PauseCompaction(expirableCtx) 118 assert.Nil(t, err) 119 120 err = store.Shutdown(context.Background()) 121 require.Nil(t, err) 122 }) 123 } 124 }) 125 } 126 127 func resumeCompaction(ctx context.Context, t *testing.T, opts []BucketOption) { 128 logger, _ := test.NewNullLogger() 129 130 t.Run("assert compaction restarts after pausing", func(t *testing.T) { 131 for _, buckets := range [][]string{ 132 {"test_bucket"}, 133 {"test_bucket1", "test_bucket2"}, 134 {"test_bucket1", "test_bucket2", "test_bucket3", "test_bucket4", "test_bucket5"}, 135 } { 136 t.Run(fmt.Sprintf("with %d buckets", len(buckets)), func(t *testing.T) { 137 dirName := t.TempDir() 138 139 shardCompactionCallbacks := cyclemanager.NewCallbackGroup("classCompaction", logger, 1) 140 shardFlushCallbacks := cyclemanager.NewCallbackGroupNoop() 141 142 store, err := New(dirName, dirName, logger, nil, shardCompactionCallbacks, shardFlushCallbacks) 143 require.Nil(t, err) 144 145 for _, bucket := range buckets { 146 err = store.CreateOrLoadBucket(ctx, bucket, opts...) 147 require.Nil(t, err) 148 149 t.Run("insert contents into bucket", func(t *testing.T) { 150 bucket := store.Bucket(bucket) 151 for i := 0; i < 10; i++ { 152 err := bucket.Put([]byte(fmt.Sprint(i)), []byte(fmt.Sprint(i))) 153 require.Nil(t, err) 154 } 155 }) 156 } 157 158 expirableCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 159 defer cancel() 160 161 err = store.PauseCompaction(expirableCtx) 162 require.Nil(t, err) 163 164 err = store.ResumeCompaction(expirableCtx) 165 require.Nil(t, err) 166 167 assert.True(t, store.cycleCallbacks.compactionCallbacksCtrl.IsActive()) 168 169 err = store.Shutdown(ctx) 170 require.Nil(t, err) 171 }) 172 } 173 }) 174 } 175 176 func flushMemtable(ctx context.Context, t *testing.T, opts []BucketOption) { 177 logger, _ := test.NewNullLogger() 178 179 t.Run("assert that context timeout works for long flushes", func(t *testing.T) { 180 for _, buckets := range [][]string{ 181 {"test_bucket"}, 182 {"test_bucket1", "test_bucket2"}, 183 {"test_bucket1", "test_bucket2", "test_bucket3", "test_bucket4", "test_bucket5"}, 184 } { 185 t.Run(fmt.Sprintf("with %d buckets", len(buckets)), func(t *testing.T) { 186 dirName := t.TempDir() 187 188 shardCompactionCallbacks := cyclemanager.NewCallbackGroupNoop() 189 shardFlushCallbacks := cyclemanager.NewCallbackGroup("classFlush", logger, 1) 190 191 store, err := New(dirName, dirName, logger, nil, shardCompactionCallbacks, shardFlushCallbacks) 192 require.Nil(t, err) 193 194 for _, bucket := range buckets { 195 err = store.CreateOrLoadBucket(ctx, bucket, opts...) 196 require.Nil(t, err) 197 } 198 199 expiredCtx, cancel := context.WithDeadline(ctx, time.Now()) 200 defer cancel() 201 202 err = store.FlushMemtables(expiredCtx) 203 require.NotNil(t, err) 204 assert.Equal(t, "long-running memtable flush in progress:"+ 205 " deactivating callback 'store/flush/.' of 'classFlush' failed:"+ 206 " context deadline exceeded", err.Error()) 207 208 err = store.Shutdown(ctx) 209 require.Nil(t, err) 210 }) 211 } 212 }) 213 214 t.Run("assert that flushes run successfully", func(t *testing.T) { 215 for _, buckets := range [][]string{ 216 {"test_bucket"}, 217 {"test_bucket1", "test_bucket2"}, 218 {"test_bucket1", "test_bucket2", "test_bucket3", "test_bucket4", "test_bucket5"}, 219 } { 220 t.Run(fmt.Sprintf("with %d buckets", len(buckets)), func(t *testing.T) { 221 dirName := t.TempDir() 222 223 shardCompactionCallbacks := cyclemanager.NewCallbackGroupNoop() 224 shardFlushCallbacks := cyclemanager.NewCallbackGroup("classFlush", logger, 1) 225 226 store, err := New(dirName, dirName, logger, nil, shardCompactionCallbacks, shardFlushCallbacks) 227 require.Nil(t, err) 228 229 err = store.CreateOrLoadBucket(ctx, "test_bucket", opts...) 230 require.Nil(t, err) 231 232 for _, bucket := range buckets { 233 err = store.CreateOrLoadBucket(ctx, bucket, opts...) 234 require.Nil(t, err) 235 236 t.Run("insert contents into bucket", func(t *testing.T) { 237 bucket := store.Bucket(bucket) 238 for i := 0; i < 10; i++ { 239 err := bucket.Put([]byte(fmt.Sprint(i)), []byte(fmt.Sprint(i))) 240 require.Nil(t, err) 241 } 242 }) 243 } 244 245 expirableCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 246 defer cancel() 247 248 err = store.FlushMemtables(expirableCtx) 249 assert.Nil(t, err) 250 251 err = store.Shutdown(ctx) 252 require.Nil(t, err) 253 }) 254 } 255 }) 256 257 t.Run("assert that readonly bucket fails to flush", func(t *testing.T) { 258 singleErr := errors.Wrap(storagestate.ErrStatusReadOnly, "flush memtable") 259 expectedErr := func(bucketsCount int) error { 260 ec := &errorcompounder.ErrorCompounder{} 261 for i := 0; i < bucketsCount; i++ { 262 ec.Add(singleErr) 263 } 264 return ec.ToError() 265 } 266 267 for _, buckets := range [][]string{ 268 {"test_bucket"}, 269 {"test_bucket1", "test_bucket2"}, 270 {"test_bucket1", "test_bucket2", "test_bucket3", "test_bucket4", "test_bucket5"}, 271 } { 272 t.Run(fmt.Sprintf("with %d buckets", len(buckets)), func(t *testing.T) { 273 dirName := t.TempDir() 274 275 shardCompactionCallbacks := cyclemanager.NewCallbackGroupNoop() 276 shardFlushCallbacks := cyclemanager.NewCallbackGroup("classFlush", logger, 1) 277 278 store, err := New(dirName, dirName, logger, nil, shardCompactionCallbacks, shardFlushCallbacks) 279 require.Nil(t, err) 280 281 for _, bucket := range buckets { 282 err = store.CreateOrLoadBucket(ctx, bucket, opts...) 283 require.Nil(t, err) 284 } 285 286 store.UpdateBucketsStatus(storagestate.StatusReadOnly) 287 288 expirableCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 289 defer cancel() 290 291 err = store.FlushMemtables(expirableCtx) 292 require.NotNil(t, err) 293 assert.EqualError(t, expectedErr(len(buckets)), err.Error()) 294 295 err = store.Shutdown(ctx) 296 require.Nil(t, err) 297 }) 298 } 299 }) 300 }