github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/integration/bootstrap_after_buffer_rotation_regression_test.go (about)

     1  // +build integration
     2  
     3  // Copyright (c) 2018 Uber Technologies, Inc.
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  package integration
    24  
    25  import (
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/integration/generate"
    30  	"github.com/m3db/m3/src/dbnode/namespace"
    31  	"github.com/m3db/m3/src/dbnode/retention"
    32  	"github.com/m3db/m3/src/dbnode/runtime"
    33  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    34  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper"
    35  	bcl "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/commitlog"
    36  	"github.com/m3db/m3/src/dbnode/ts"
    37  	"github.com/m3db/m3/src/x/context"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	xtime "github.com/m3db/m3/src/x/time"
    40  
    41  	"github.com/stretchr/testify/require"
    42  )
    43  
    44  // TestBootstrapAfterBufferRotation was added as a regression test after we identified
    45  // an issue where blocks that were bootstrapped would sometimes be placed into series
    46  // buffers that had already been drained. Since we never drain a series buffer more than
    47  // once, the bootstrapped blocks would either be lost, or over-write the data that was
    48  // already in-memory instead of merging.
    49  // This integration test ensures that we handle that situation properly by writing out
    50  // a commitlog file that has a single write in the active block, then it starts the node
    51  // and writes a single datapoint into the active block, and then triggers a buffer drain for
    52  // that block by modifying the time to be after blockStart.Add(blockSize).Add(bufferPast).
    53  // Once the buffer has been drained/rotated, we allow the bootstrap process to complete
    54  // (controlled via a custom bootstrapper which can be delayed) and verify that the data
    55  // from the commitlog and the data in-memory are properly merged together and neither is
    56  // lost.
    57  func TestBootstrapAfterBufferRotation(t *testing.T) {
    58  	if testing.Short() {
    59  		t.SkipNow() // Just skip if we're doing a short run
    60  	}
    61  
    62  	// Test setup
    63  	var (
    64  		ropts = retention.NewOptions().
    65  			SetRetentionPeriod(12 * time.Hour).
    66  			SetBufferPast(5 * time.Minute).
    67  			SetBufferFuture(5 * time.Minute)
    68  		blockSize = ropts.BlockSize()
    69  	)
    70  	ns1, err := namespace.NewMetadata(testNamespaces[0], namespace.NewOptions().SetRetentionOptions(ropts))
    71  	require.NoError(t, err)
    72  	opts := NewTestOptions(t).
    73  		SetNamespaces([]namespace.Metadata{ns1})
    74  
    75  	setup, err := NewTestSetup(t, opts, nil)
    76  	require.NoError(t, err)
    77  	defer setup.Close()
    78  
    79  	setup.MustSetTickMinimumInterval(100 * time.Millisecond)
    80  
    81  	// Setup the commitlog and write a single datapoint into it one second into the
    82  	// active block.
    83  	commitLogOpts := setup.StorageOpts().CommitLogOptions().
    84  		SetFlushInterval(defaultIntegrationTestFlushInterval)
    85  	setup.SetStorageOpts(setup.StorageOpts().SetCommitLogOptions(commitLogOpts))
    86  
    87  	testID := ident.StringID("foo")
    88  	now := setup.NowFn()().Truncate(blockSize)
    89  	setup.SetNowFn(now)
    90  	startTime := now
    91  	commitlogWrite := ts.Datapoint{
    92  		TimestampNanos: startTime.Add(time.Second),
    93  		Value:          1,
    94  	}
    95  	seriesMaps := map[xtime.UnixNano]generate.SeriesBlock{
    96  		startTime: {
    97  			generate.Series{
    98  				ID:   testID,
    99  				Data: []generate.TestValue{{Datapoint: commitlogWrite}},
   100  			},
   101  		},
   102  	}
   103  	writeCommitLogData(t, setup, commitLogOpts, seriesMaps, ns1, false)
   104  
   105  	// Setup bootstrappers - We order them such that the custom test bootstrapper runs first
   106  	// which does not bootstrap any data, but simply waits until it is signaled, allowing us
   107  	// to delay bootstrap completion until after series buffer drain/rotation. After the custom
   108  	// test bootstrapper completes, the commitlog bootstrapper will run.
   109  	bootstrapOpts := newDefaulTestResultOptions(setup.StorageOpts())
   110  	bootstrapCommitlogOpts := bcl.NewOptions().
   111  		SetResultOptions(bootstrapOpts).
   112  		SetCommitLogOptions(commitLogOpts).
   113  		SetRuntimeOptionsManager(runtime.NewOptionsManager())
   114  	fsOpts := setup.StorageOpts().CommitLogOptions().FilesystemOptions()
   115  	commitlogBootstrapperProvider, err := bcl.NewCommitLogBootstrapperProvider(
   116  		bootstrapCommitlogOpts, mustInspectFilesystem(fsOpts), nil)
   117  	require.NoError(t, err)
   118  
   119  	// Setup the test bootstrapper to only return success when a signal is sent.
   120  	signalCh := make(chan struct{})
   121  	bs, err := commitlogBootstrapperProvider.Provide()
   122  	require.NoError(t, err)
   123  
   124  	test := newTestBootstrapperSource(testBootstrapperSourceOptions{
   125  		read: func(
   126  			ctx context.Context,
   127  			namespaces bootstrap.Namespaces,
   128  			cache bootstrap.Cache,
   129  		) (bootstrap.NamespaceResults, error) {
   130  			<-signalCh
   131  			// Mark all as unfulfilled so the commitlog bootstrapper will be called after
   132  			noopNone := bootstrapper.NewNoOpNoneBootstrapperProvider()
   133  			bs, err := noopNone.Provide()
   134  			if err != nil {
   135  				return bootstrap.NamespaceResults{}, err
   136  			}
   137  			return bs.Bootstrap(ctx, namespaces, cache)
   138  		},
   139  	}, bootstrapOpts, bs)
   140  
   141  	processOpts := bootstrap.NewProcessOptions().
   142  		SetTopologyMapProvider(setup).
   143  		SetOrigin(setup.Origin())
   144  
   145  	processProvider, err := bootstrap.NewProcessProvider(
   146  		test, processOpts, bootstrapOpts, fsOpts)
   147  	require.NoError(t, err)
   148  	setup.SetStorageOpts(setup.StorageOpts().SetBootstrapProcessProvider(processProvider))
   149  
   150  	// Start a background goroutine which will wait until the server is started,
   151  	// issue a single write into the active block, change the time to be far enough
   152  	// into the *next* block that the series buffer for the previously active block
   153  	// can be rotated, and then signals the test bootstrapper that it can proceed.
   154  	var memoryWrite ts.Datapoint
   155  	go func() {
   156  		// Wait for server to start
   157  		setup.WaitUntilServerIsUp()
   158  		now = now.Add(blockSize)
   159  		setup.SetNowFn(now)
   160  		memoryWrite = ts.Datapoint{
   161  			TimestampNanos: now.Add(-10 * time.Second),
   162  			Value:          2,
   163  		}
   164  
   165  		// Issue the write (still in the same block as the commitlog write).
   166  		err := setup.WriteBatch(ns1.ID(), generate.SeriesBlock{
   167  			generate.Series{
   168  				ID:   ident.StringID("foo"),
   169  				Data: []generate.TestValue{{Datapoint: memoryWrite}},
   170  			}})
   171  		if err != nil {
   172  			panic(err)
   173  		}
   174  
   175  		// Change the time far enough into the next block that a series buffer
   176  		// rotation will occur for the previously active block.
   177  		now = now.Add(ropts.BufferPast()).Add(time.Second)
   178  		setup.SetNowFn(now)
   179  		setup.SleepFor10xTickMinimumInterval()
   180  
   181  		// Close signalCh to unblock bootstrapper and run the bootstrap till the end
   182  		close(signalCh)
   183  	}()
   184  	require.NoError(t, setup.StartServer()) // Blocks until bootstrap is complete
   185  
   186  	defer func() {
   187  		require.NoError(t, setup.StopServer())
   188  	}()
   189  
   190  	// Verify in-memory data match what we expect - both commitlog and memory write
   191  	// should be present.
   192  	expectedSeriesMaps := map[xtime.UnixNano]generate.SeriesBlock{
   193  		commitlogWrite.TimestampNanos.Truncate(blockSize): {
   194  			generate.Series{
   195  				ID:   testID,
   196  				Data: []generate.TestValue{{Datapoint: commitlogWrite}, {Datapoint: memoryWrite}},
   197  			},
   198  		},
   199  	}
   200  
   201  	metadatasByShard := testSetupMetadatas(t, setup, testNamespaces[0], startTime, startTime.Add(blockSize))
   202  	observedSeriesMaps := testSetupToSeriesMaps(t, setup, ns1, metadatasByShard)
   203  	verifySeriesMapsEqual(t, expectedSeriesMaps, observedSeriesMaps)
   204  }