github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/integration/fs_commitlog_mixed_mode_read_write_test.go (about) 1 // +build integration 2 3 // Copyright (c) 2017 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 "fmt" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/dbnode/integration/generate" 31 "github.com/m3db/m3/src/dbnode/namespace" 32 "github.com/m3db/m3/src/dbnode/retention" 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 xtime "github.com/m3db/m3/src/x/time" 37 38 "github.com/m3db/m3/src/dbnode/testdata/prototest" 39 "github.com/stretchr/testify/require" 40 ) 41 42 type annotationGenerator interface { 43 Next() []byte 44 } 45 46 func TestFsCommitLogMixedModeReadWrite(t *testing.T) { 47 testFsCommitLogMixedModeReadWrite(t, nil, nil) 48 } 49 50 func TestProtoFsCommitLogMixedModeReadWrite(t *testing.T) { 51 testFsCommitLogMixedModeReadWrite(t, setProtoTestOptions, prototest.NewProtoMessageIterator(testProtoMessages)) 52 } 53 54 func testFsCommitLogMixedModeReadWrite(t *testing.T, setTestOpts setTestOptions, annGen annotationGenerator) { 55 if testing.Short() { 56 t.SkipNow() // Just skip if we're doing a short run 57 } 58 // Test setup 59 var ( 60 ns1BlockSize = 1 * time.Hour 61 ns1ROpts = retention.NewOptions().SetRetentionPeriod(3 * time.Hour).SetBlockSize(ns1BlockSize) 62 nsID = testNamespaces[0] 63 ) 64 65 ns1Opts := namespace.NewOptions(). 66 SetRetentionOptions(ns1ROpts) 67 ns1, err := namespace.NewMetadata(nsID, ns1Opts) 68 require.NoError(t, err) 69 opts := NewTestOptions(t). 70 SetNamespaces([]namespace.Metadata{ns1}) 71 72 if setTestOpts != nil { 73 opts = setTestOpts(t, opts) 74 ns1 = opts.Namespaces()[0] 75 } 76 77 // Test setup 78 setup := newTestSetupWithCommitLogAndFilesystemBootstrapper(t, opts) 79 defer setup.Close() 80 81 log := setup.StorageOpts().InstrumentOptions().Logger() 82 log.Info("commit log & fileset files, write, read, and merge bootstrap test") 83 84 filePathPrefix := setup.StorageOpts().CommitLogOptions().FilesystemOptions().FilePathPrefix() 85 86 // setting time to 2017/02/13 15:30:10 87 fakeStart := xtime.ToUnixNano(time.Date(2017, time.February, 13, 15, 30, 10, 0, time.Local)) 88 blkStart15 := fakeStart.Truncate(ns1BlockSize) 89 blkStart16 := blkStart15.Add(ns1BlockSize) 90 blkStart17 := blkStart16.Add(ns1BlockSize) 91 blkStart18 := blkStart17.Add(ns1BlockSize) 92 setup.SetNowFn(fakeStart) 93 94 // startup server 95 log.Debug("starting server") 96 startServerWithNewInspection(t, opts, setup) 97 log.Debug("server is now up") 98 99 // Stop the server 100 defer func() { 101 log.Debug("stopping server") 102 require.NoError(t, setup.StopServer()) 103 log.Debug("server is now down") 104 }() 105 106 // mimic a run of 200 minutes, 107 // should flush data for hour 15, 16, 17 108 // should have 50 mins of data in hour 18 109 var ( 110 total = 200 111 ids = &idGen{longTestID} 112 db = setup.DB() 113 ctx = context.NewBackground() 114 ) 115 defer ctx.Close() 116 log.Info("writing datapoints") 117 datapoints := generateDatapoints(fakeStart, total, ids, annGen) 118 for _, dp := range datapoints { 119 ts := dp.time 120 setup.SetNowFn(ts) 121 require.NoError(t, db.Write(ctx, nsID, dp.series, ts, dp.value, xtime.Second, dp.ann)) 122 } 123 log.Info("wrote datapoints") 124 125 // verify in-memory data matches what we expect 126 expectedSeriesMap := datapoints.toSeriesMap(ns1BlockSize) 127 log.Info("verifying data in database equals expected data") 128 verifySeriesMaps(t, setup, nsID, expectedSeriesMap) 129 log.Info("verified data in database equals expected data") 130 131 // current time is 18:50, so we expect data for block starts [15, 18) to be written out 132 // to fileset files, and flushed. 133 expectedFlushedData := datapoints.toSeriesMap(ns1BlockSize) 134 delete(expectedFlushedData, blkStart18) 135 waitTimeout := 5 * time.Minute 136 137 log.Info("waiting till expected fileset files have been written") 138 require.NoError(t, waitUntilDataFilesFlushed(filePathPrefix, setup.ShardSet(), nsID, expectedFlushedData, waitTimeout)) 139 log.Info("expected fileset files have been written") 140 141 // stopping db 142 log.Info("stopping database") 143 require.NoError(t, setup.StopServer()) 144 log.Info("database stopped") 145 146 // the time now is 18:55 147 setup.SetNowFn(setup.NowFn()().Add(5 * time.Minute)) 148 149 // recreate the db from the data files and commit log 150 // should contain data from 15:30 - 17:59 on disk and 18:00 - 18:50 in mem 151 log.Info("re-opening database & bootstrapping") 152 startServerWithNewInspection(t, opts, setup) 153 log.Info("verifying data in database equals expected data") 154 verifySeriesMaps(t, setup, nsID, expectedSeriesMap) 155 log.Info("verified data in database equals expected data") 156 157 // the time now is 19:15 158 setup.SetNowFn(setup.NowFn()().Add(20 * time.Minute)) 159 // data from hour 15 is now outdated, ensure the file has been cleaned up 160 log.Info("waiting till expired fileset files have been cleanedup") 161 require.NoError(t, waitUntilFileSetFilesCleanedUp(setup, nsID, blkStart15, waitTimeout)) 162 log.Info("fileset files have been cleaned up") 163 164 // stopping db 165 log.Info("stopping database") 166 require.NoError(t, setup.StopServer()) 167 log.Info("database stopped") 168 169 // recreate the db from the data files and commit log 170 log.Info("re-opening database & bootstrapping") 171 startServerWithNewInspection(t, opts, setup) 172 173 // verify in-memory data matches what we expect 174 // should contain data from 16:00 - 17:59 on disk and 18:00 - 18:50 in mem 175 delete(expectedSeriesMap, blkStart15) 176 log.Info("verifying data in database equals expected data") 177 verifySeriesMaps(t, setup, nsID, expectedSeriesMap) 178 log.Info("verified data in database equals expected data") 179 } 180 181 // We use this helper method to start the server so that a new filesystem 182 // inspection and commitlog bootstrapper are generated each time. 183 func startServerWithNewInspection( 184 t *testing.T, 185 opts TestOptions, 186 setup TestSetup, 187 ) { 188 setCommitLogAndFilesystemBootstrapper(t, opts, setup) 189 require.NoError(t, setup.StartServer()) 190 } 191 192 func waitUntilFileSetFilesCleanedUp( 193 setup TestSetup, 194 namespace ident.ID, 195 toDelete xtime.UnixNano, 196 timeout time.Duration, 197 ) error { 198 var ( 199 shardSet = setup.ShardSet() 200 filesetFiles = []cleanupTimesFileSet{} 201 commitLogFiles = cleanupTimesCommitLog{ 202 clOpts: setup.StorageOpts().CommitLogOptions(), 203 } 204 ) 205 for _, id := range shardSet.AllIDs() { 206 filesetFiles = append(filesetFiles, cleanupTimesFileSet{ 207 filePathPrefix: setup.FilePathPrefix(), 208 namespace: namespace, 209 shard: id, 210 times: []xtime.UnixNano{toDelete}, 211 }) 212 } 213 return waitUntilDataCleanedUpExtended(filesetFiles, commitLogFiles, timeout) 214 } 215 216 func newTestSetupWithCommitLogAndFilesystemBootstrapper(t *testing.T, opts TestOptions) TestSetup { 217 setup, err := NewTestSetup(t, opts, nil) 218 require.NoError(t, err) 219 220 setCommitLogAndFilesystemBootstrapper(t, opts, setup) 221 222 return setup 223 } 224 225 func setCommitLogAndFilesystemBootstrapper(t *testing.T, opts TestOptions, setup TestSetup) TestSetup { 226 commitLogOpts := setup.StorageOpts().CommitLogOptions() 227 228 commitLogOpts = commitLogOpts. 229 SetFlushInterval(defaultIntegrationTestFlushInterval) 230 setup.SetStorageOpts(setup.StorageOpts().SetCommitLogOptions(commitLogOpts)) 231 232 require.NoError(t, setup.InitializeBootstrappers(InitializeBootstrappersOptions{ 233 CommitLogOptions: commitLogOpts, 234 WithCommitLog: true, 235 WithFileSystem: true, 236 })) 237 238 // Need to make sure we have an active m3dbAdminClient because the previous one 239 // may have been shutdown by StopServer(). 240 setup.MaybeResetClients() 241 242 return setup 243 } 244 245 func generateDatapoints( 246 start xtime.UnixNano, numPoints int, ig *idGen, annGen annotationGenerator, 247 ) dataPointsInTimeOrder { 248 var points dataPointsInTimeOrder 249 for i := 0; i < numPoints; i++ { 250 t := start.Add(time.Duration(i) * time.Minute) 251 if annGen == nil { 252 points = append(points, 253 seriesDatapoint{ 254 series: ig.base(), 255 time: t, 256 value: float64(i), 257 }, 258 seriesDatapoint{ 259 series: ig.nth(i), 260 time: t, 261 value: float64(i), 262 }, 263 ) 264 } else { 265 annBytes := annGen.Next() 266 points = append(points, 267 seriesDatapoint{ 268 series: ig.base(), 269 time: t, 270 ann: annBytes, 271 }, 272 seriesDatapoint{ 273 series: ig.nth(i), 274 time: t, 275 ann: annBytes, 276 }, 277 ) 278 } 279 } 280 return points 281 } 282 283 type dataPointsInTimeOrder []seriesDatapoint 284 285 type seriesDatapoint struct { 286 series ident.ID 287 time xtime.UnixNano 288 value float64 289 ann []byte 290 } 291 292 func (d dataPointsInTimeOrder) toSeriesMap(blockSize time.Duration) generate.SeriesBlocksByStart { 293 blockStartToSeriesMap := make(map[xtime.UnixNano]map[string]generate.Series) 294 for _, point := range d { 295 t := point.time 296 trunc := t.Truncate(blockSize) 297 seriesBlock, ok := blockStartToSeriesMap[trunc] 298 if !ok { 299 seriesBlock = make(map[string]generate.Series) 300 } 301 idString := point.series.String() 302 dp, ok := seriesBlock[idString] 303 if !ok { 304 dp = generate.Series{ID: point.series} 305 } 306 dp.Data = append(dp.Data, generate.TestValue{Datapoint: ts.Datapoint{ 307 TimestampNanos: t, 308 Value: point.value, 309 }, Annotation: point.ann}) 310 seriesBlock[idString] = dp 311 blockStartToSeriesMap[trunc] = seriesBlock 312 } 313 314 seriesMap := make(generate.SeriesBlocksByStart, len(blockStartToSeriesMap)) 315 for t, serieses := range blockStartToSeriesMap { 316 seriesSlice := make([]generate.Series, 0, len(serieses)) 317 for _, series := range serieses { 318 seriesSlice = append(seriesSlice, series) 319 } 320 seriesMap[t] = seriesSlice 321 } 322 return seriesMap 323 } 324 325 // before returns a slice of the dataPointsInTimeOrder that are before the 326 // specified time t. 327 func (d dataPointsInTimeOrder) before(t xtime.UnixNano) dataPointsInTimeOrder { 328 var i int 329 for i = range d { 330 if !d[i].time.Before(t) { 331 break 332 } 333 } 334 335 return d[:i] 336 } 337 338 type idGen struct { 339 baseID string 340 } 341 342 func (i *idGen) base() ident.ID { 343 return ident.StringID(i.baseID) 344 } 345 346 func (i *idGen) nth(n int) ident.ID { 347 return ident.StringID(fmt.Sprintf("%s%d", i.baseID, n)) 348 } 349 350 const ( 351 longTestID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 352 )