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  }