github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/streaming_write_test.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package fs
    22  
    23  import (
    24  	"os"
    25  	"path/filepath"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/encoding"
    30  	"github.com/m3db/m3/src/dbnode/encoding/m3tsz"
    31  	"github.com/m3db/m3/src/dbnode/namespace"
    32  	"github.com/m3db/m3/src/dbnode/persist"
    33  	"github.com/m3db/m3/src/dbnode/ts"
    34  	"github.com/m3db/m3/src/x/context"
    35  	"github.com/m3db/m3/src/x/ident"
    36  	"github.com/m3db/m3/src/x/pool"
    37  	"github.com/m3db/m3/src/x/serialize"
    38  	xtime "github.com/m3db/m3/src/x/time"
    39  
    40  	"github.com/golang/mock/gomock"
    41  	"github.com/stretchr/testify/require"
    42  )
    43  
    44  type testStreamingEntry struct {
    45  	testEntry
    46  	values []float64
    47  }
    48  
    49  func newTestStreamingWriter(t *testing.T, filePathPrefix string) StreamingWriter {
    50  	writer, err := NewStreamingWriter(testDefaultOpts.
    51  		SetFilePathPrefix(filePathPrefix).
    52  		SetWriterBufferSize(testWriterBufferSize))
    53  	require.NoError(t, err)
    54  	return writer
    55  }
    56  
    57  func newOpenTestStreamingWriter(
    58  	t *testing.T,
    59  	filePathPrefix string,
    60  	shard uint32,
    61  	timestamp xtime.UnixNano,
    62  	nextVersion int,
    63  	plannedEntries uint,
    64  ) StreamingWriter {
    65  	writer := newTestStreamingWriter(t, filePathPrefix)
    66  
    67  	writerOpenOpts := StreamingWriterOpenOptions{
    68  		NamespaceID: testNs1ID,
    69  		ShardID:     shard,
    70  		BlockStart:  timestamp,
    71  		BlockSize:   testBlockSize,
    72  
    73  		VolumeIndex:         nextVersion,
    74  		PlannedRecordsCount: plannedEntries,
    75  	}
    76  	err := writer.Open(writerOpenOpts)
    77  	require.NoError(t, err)
    78  
    79  	return writer
    80  }
    81  
    82  func TestIdsMustBeSorted(t *testing.T) {
    83  	dir := createTempDir(t)
    84  	filePathPrefix := filepath.Join(dir, "")
    85  	defer os.RemoveAll(dir)
    86  
    87  	entries := []testStreamingEntry{
    88  		{testEntry{"baz", nil, nil}, []float64{65536}},
    89  		{testEntry{"bar", nil, nil}, []float64{4.8, 5.2, 6}},
    90  	}
    91  
    92  	w := newOpenTestStreamingWriter(t, filePathPrefix, 0, testWriterStart, 0, 5)
    93  	defer w.Close()
    94  	err := streamingWriteTestData(t, w, testWriterStart, entries)
    95  	require.Error(t, err)
    96  	require.Equal(t, "ids must be written in lexicographic order, no duplicates, but got baz followed by bar",
    97  		err.Error())
    98  }
    99  
   100  func TestDoubleWritesAreNotAllowed(t *testing.T) {
   101  	dir := createTempDir(t)
   102  	filePathPrefix := filepath.Join(dir, "")
   103  	defer os.RemoveAll(dir)
   104  
   105  	entries := []testStreamingEntry{
   106  		{testEntry{"baz", nil, nil}, []float64{65536}},
   107  		{testEntry{"baz", nil, nil}, []float64{4.8, 5.2, 6}},
   108  	}
   109  
   110  	w := newOpenTestStreamingWriter(t, filePathPrefix, 0, testWriterStart, 0, 5)
   111  	defer w.Close()
   112  	err := streamingWriteTestData(t, w, testWriterStart, entries)
   113  	require.Error(t, err)
   114  	require.Equal(t, "ids must be written in lexicographic order, no duplicates, but got baz followed by baz",
   115  		err.Error())
   116  }
   117  
   118  func TestSimpleReadStreamingWrite(t *testing.T) {
   119  	dir := createTempDir(t)
   120  	filePathPrefix := filepath.Join(dir, "")
   121  	defer os.RemoveAll(dir)
   122  
   123  	entries := []testStreamingEntry{
   124  		{testEntry{"bar", nil, nil}, []float64{4.8, 5.2, 6}},
   125  		{testEntry{"baz", nil, nil}, []float64{65536}},
   126  		{testEntry{"cat", nil, nil}, []float64{100000}},
   127  		{testEntry{"foo", nil, nil}, []float64{1, 2, 3}},
   128  		{testEntry{"foo+bar=baz,qux=qaz", map[string]string{
   129  			"bar": "baz",
   130  			"qux": "qaz",
   131  		}, nil}, []float64{7, 8, 9}},
   132  	}
   133  
   134  	w := newOpenTestStreamingWriter(t, filePathPrefix, 0, testWriterStart, 0, 5)
   135  	err := streamingWriteTestData(t, w, testWriterStart, entries)
   136  	require.NoError(t, err)
   137  	err = w.Close()
   138  	require.NoError(t, err)
   139  
   140  	r := newTestReader(t, filePathPrefix)
   141  	readTestData(t, r, 0, testWriterStart, toTestEntries(entries))
   142  
   143  	verifyInfoFile(t, filePathPrefix, testNs1ID, 0, len(entries))
   144  }
   145  
   146  func TestReuseStreamingWriter(t *testing.T) {
   147  	dir := createTempDir(t)
   148  	filePathPrefix := filepath.Join(dir, "")
   149  	defer os.RemoveAll(dir)
   150  
   151  	entries1 := []testStreamingEntry{
   152  		{testEntry{"bar", nil, nil}, []float64{4.8, 5.2, 6}},
   153  		{testEntry{"baz", nil, nil}, []float64{65536}},
   154  		{testEntry{"cat", nil, nil}, []float64{100000}},
   155  		{testEntry{"foo", nil, nil}, []float64{1, 2, 3}},
   156  	}
   157  
   158  	entries2 := []testStreamingEntry{
   159  		{testEntry{"bar2", nil, nil}, []float64{24.8, 25.2, 26}},
   160  		{testEntry{"baz2", nil, nil}, []float64{265536}},
   161  		{testEntry{"cat2", nil, nil}, []float64{200000}},
   162  	}
   163  
   164  	w := newTestStreamingWriter(t, filePathPrefix)
   165  
   166  	writerOpenOpts1 := StreamingWriterOpenOptions{
   167  		NamespaceID:         testNs1ID,
   168  		ShardID:             1,
   169  		BlockStart:          testWriterStart,
   170  		BlockSize:           testBlockSize,
   171  		VolumeIndex:         0,
   172  		PlannedRecordsCount: uint(len(entries1)),
   173  	}
   174  	err := w.Open(writerOpenOpts1)
   175  	require.NoError(t, err)
   176  
   177  	err = streamingWriteTestData(t, w, testWriterStart, entries1)
   178  	require.NoError(t, err)
   179  	err = w.Close()
   180  	require.NoError(t, err)
   181  
   182  	verifyInfoFile(t, filePathPrefix, testNs1ID, 1, len(entries1))
   183  
   184  	r := newTestReader(t, filePathPrefix)
   185  	readTestData(t, r, 1, testWriterStart, toTestEntries(entries1))
   186  
   187  	writerOpenOpts2 := StreamingWriterOpenOptions{
   188  		NamespaceID:         testNs1ID,
   189  		ShardID:             2,
   190  		BlockStart:          testWriterStart,
   191  		BlockSize:           testBlockSize,
   192  		VolumeIndex:         0,
   193  		PlannedRecordsCount: uint(len(entries2)),
   194  	}
   195  	err = w.Open(writerOpenOpts2)
   196  	require.NoError(t, err)
   197  
   198  	err = streamingWriteTestData(t, w, testWriterStart, entries2)
   199  	require.NoError(t, err)
   200  	err = w.Close()
   201  	require.NoError(t, err)
   202  
   203  	verifyInfoFile(t, filePathPrefix, testNs1ID, 2, len(entries2))
   204  
   205  	readTestData(t, r, 2, testWriterStart, toTestEntries(entries2))
   206  }
   207  
   208  func TestReadStreamingWriteEmptyFileset(t *testing.T) {
   209  	dir := createTempDir(t)
   210  	filePathPrefix := filepath.Join(dir, "")
   211  	defer os.RemoveAll(dir)
   212  
   213  	w := newOpenTestStreamingWriter(t, filePathPrefix, 0, testWriterStart, 0, 1)
   214  	err := streamingWriteTestData(t, w, testWriterStart, nil)
   215  	require.NoError(t, err)
   216  	err = w.Close()
   217  	require.NoError(t, err)
   218  
   219  	r := newTestReader(t, filePathPrefix)
   220  	readTestData(t, r, 0, testWriterStart, nil)
   221  }
   222  
   223  func TestReadStreamingWriteReject0PlannedRecordsCount(t *testing.T) {
   224  	dir := createTempDir(t)
   225  	filePathPrefix := filepath.Join(dir, "")
   226  	defer os.RemoveAll(dir) // nolint: errcheck
   227  
   228  	writer, err := NewStreamingWriter(testDefaultOpts.
   229  		SetFilePathPrefix(filePathPrefix).
   230  		SetWriterBufferSize(testWriterBufferSize))
   231  	require.NoError(t, err)
   232  
   233  	writerOpenOpts := StreamingWriterOpenOptions{
   234  		NamespaceID:         testNs1ID,
   235  		BlockSize:           testBlockSize,
   236  		PlannedRecordsCount: 0,
   237  	}
   238  	err = writer.Open(writerOpenOpts)
   239  	require.EqualError(t, err, "PlannedRecordsCount must be positive, got 0")
   240  }
   241  
   242  func TestStreamingWriterAbort(t *testing.T) {
   243  	dir := createTempDir(t)
   244  	filePathPrefix := filepath.Join(dir, "")
   245  	defer os.RemoveAll(dir)
   246  
   247  	w := newOpenTestStreamingWriter(t, filePathPrefix, 0, testWriterStart, 0, 1)
   248  	err := streamingWriteTestData(t, w, testWriterStart, nil)
   249  	require.NoError(t, err)
   250  	err = w.Abort()
   251  	require.NoError(t, err)
   252  
   253  	r := newTestReader(t, filePathPrefix)
   254  	rOpenOpts := DataReaderOpenOptions{
   255  		Identifier: FileSetFileIdentifier{
   256  			Namespace:  testNs1ID,
   257  			Shard:      0,
   258  			BlockStart: testWriterStart,
   259  		},
   260  	}
   261  	err = r.Open(rOpenOpts)
   262  	require.Equal(t, ErrCheckpointFileNotFound, err)
   263  }
   264  
   265  func streamingWriteTestData(
   266  	t *testing.T,
   267  	w StreamingWriter,
   268  	blockStart xtime.UnixNano,
   269  	entries []testStreamingEntry,
   270  ) error {
   271  	return streamingWriteWithVolume(t, w, blockStart, entries)
   272  }
   273  
   274  func streamingWriteWithVolume(
   275  	t *testing.T,
   276  	w StreamingWriter,
   277  	blockStart xtime.UnixNano,
   278  	entries []testStreamingEntry,
   279  ) error {
   280  	ctx := context.NewBackground()
   281  
   282  	encoder := m3tsz.NewEncoder(blockStart, nil, true, encoding.NewOptions())
   283  	defer encoder.Close()
   284  	ctrl := gomock.NewController(t)
   285  	schema := namespace.NewMockSchemaDescr(ctrl)
   286  	encoder.SetSchema(schema)
   287  	var dp ts.Datapoint
   288  
   289  	tagEncodingPool := serialize.NewTagEncoderPool(serialize.NewTagEncoderOptions(), pool.NewObjectPoolOptions())
   290  	tagEncodingPool.Init()
   291  
   292  	for i := range entries {
   293  		encoder.Reset(blockStart, 0, schema)
   294  		dp.TimestampNanos = blockStart
   295  
   296  		for _, v := range entries[i].values {
   297  			dp.Value = v
   298  			if err := encoder.Encode(dp, xtime.Second, nil); err != nil {
   299  				return err
   300  			}
   301  
   302  			dp.TimestampNanos = dp.TimestampNanos.Add(10 * time.Minute)
   303  		}
   304  
   305  		stream, ok := encoder.Stream(ctx)
   306  		require.True(t, ok)
   307  		segment, err := stream.Segment()
   308  		if err != nil {
   309  			return err
   310  		}
   311  		entries[i].data = append(segment.Head.Bytes(), segment.Tail.Bytes()...)
   312  		dataChecksum := segment.CalculateChecksum()
   313  		stream.Finalize()
   314  
   315  		tagsIter := ident.NewTagsIterator(entries[i].Tags())
   316  		tagEncoder := tagEncodingPool.Get()
   317  		err = tagEncoder.Encode(tagsIter)
   318  		require.NoError(t, err)
   319  		encodedTags, _ := tagEncoder.Data()
   320  
   321  		data := [][]byte{entries[i].data}
   322  
   323  		if err := w.WriteAll(ident.BytesID(entries[i].id), encodedTags.Bytes(), data, dataChecksum); err != nil {
   324  			return err
   325  		}
   326  	}
   327  	return nil
   328  }
   329  
   330  func verifyInfoFile(t *testing.T, filePathPrefix string, namespace ident.ID, shard uint32, expectedEntries int) {
   331  	readInfoFileResults := ReadInfoFiles(filePathPrefix, namespace, shard, 16, nil, persist.FileSetFlushType)
   332  	require.Equal(t, 1, len(readInfoFileResults))
   333  	require.NoError(t, readInfoFileResults[0].Err.Error())
   334  
   335  	infoFile := readInfoFileResults[0].Info
   336  	require.Equal(t, int64(testWriterStart), infoFile.BlockStart)
   337  	require.Equal(t, testBlockSize, time.Duration(infoFile.BlockSize))
   338  	require.Equal(t, int64(expectedEntries), infoFile.Entries)
   339  }
   340  
   341  func toTestEntries(streamingEntries []testStreamingEntry) []testEntry {
   342  	testEntries := make([]testEntry, 0, len(streamingEntries))
   343  	for _, e := range streamingEntries {
   344  		testEntries = append(testEntries, e.testEntry)
   345  	}
   346  	return testEntries
   347  }