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  }