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 }