Skip to content

Commit 4a69899

Browse files
authored
Merge pull request #11117 from dolthub/aaron/read-only-doesnt-load-db
go/store/nbs: Lazily load chunk journal database resources when constructing a NomsBlockStore.
2 parents 213eff6 + fd9284e commit 4a69899

31 files changed

Lines changed: 615 additions & 166 deletions

go/cmd/dolt/commands/fsck.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ func (cmd FsckCmd) Exec(ctx context.Context, commandStr string, args []string, d
133133
ddb, _, _, err := dbFact.CreateDbNoCache(ctx, types.Format_DOLT, u, params, func(vErr error) {
134134
report.ScanErrs.AppendE(vErr)
135135
})
136+
if err == nil {
137+
// Creating NomsBlockStore can lazily load the actual database resources. We force them here by performing
138+
// an actual read against the database.
139+
_, err = datas.ChunkStoreFromDatabase(ddb).Root(ctx)
140+
}
136141
if err != nil {
137142
if errors.Is(err, nbs.ErrJournalDataLoss) {
138143
cli.PrintErrln("WARNING: Chunk journal is corrupted and some data may be lost.")
@@ -491,11 +496,11 @@ type roundTripper struct {
491496
}
492497

493498
func newRoundTripper(ctx context.Context, gs *nbs.GenerationalNBS, progress chan FsckProgressMessage, errs *Errs, fileErrCounts map[string]int) (*roundTripper, error) {
494-
chunkCount, err := gs.OldGen().Count()
499+
chunkCount, err := gs.OldGen().Count(ctx)
495500
if err != nil {
496501
return nil, err
497502
}
498-
chunkCount2, err := gs.NewGen().Count()
503+
chunkCount2, err := gs.NewGen().Count(ctx)
499504
if err != nil {
500505
return nil, err
501506
}
@@ -517,11 +522,11 @@ func newRoundTripper(ctx context.Context, gs *nbs.GenerationalNBS, progress chan
517522
}
518523

519524
func (rt *roundTripper) scanAll(ctx context.Context) error {
520-
rt.gs.TolerantIterateAllChunks(ctx, rt.roundTripAndCategorizeChunk, func(sourceFile string, err error) {
525+
err := rt.gs.TolerantIterateAllChunks(ctx, rt.roundTripAndCategorizeChunk, func(sourceFile string, err error) {
521526
rt.errs.AppendE(err)
522527
rt.fileErrCounts[sourceFile]++
523528
})
524-
return ctx.Err()
529+
return errors.Join(err, ctx.Err())
525530
}
526531

527532
// roundTripAndCategorizeChunk verifies the chunk's hash matches its content, categorizes it by type. This method is

go/libraries/doltcore/dconfig/envvars.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,9 @@ const (
5757
// Used for tests. If set, Dolt will error if it would rebuild a table's row data.
5858
EnvAssertNoTableRewrite = "DOLT_TEST_ASSERT_NO_TABLE_REWRITE"
5959
EnvAssertNoInMemoryArchiveIndex = "DOLT_TEST_ASSERT_NO_IN_MEMORY_ARCHIVE_INDEX"
60+
61+
// Used for tests. Make Dolt fail if it loads table file data
62+
// from disk, such as bootstraping the journal file or loading
63+
// a table file with tableFilePersister.Open.
64+
EnvAssertNoTableFilesRead = "DOLT_TEST_ASSERT_NO_TABLE_FILES_READ"
6065
)

go/libraries/doltcore/doltdb/doltdb.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,7 +2035,15 @@ func pullHash(
20352035
destCS := datas.ChunkStoreFromDatabase(destDB)
20362036
waf := types.WalkAddrsForNBF(srcDB.Format(), skipHashes)
20372037

2038-
if datas.CanUsePuller(srcDB) && datas.CanUsePuller(destDB) {
2038+
srcCanUsePuller, err := datas.CanUsePuller(ctx, srcDB)
2039+
if err != nil {
2040+
return err
2041+
}
2042+
destCanUsePuller, err := datas.CanUsePuller(ctx, destDB)
2043+
if err != nil {
2044+
return err
2045+
}
2046+
if srcCanUsePuller && destCanUsePuller {
20392047
puller, err := pull.NewPuller(ctx, tempDir, defaultTargetFileSize, srcCS, destCS, waf, targetHashes, statsCh)
20402048
if err == pull.ErrDBUpToDate {
20412049
return nil
@@ -2107,15 +2115,15 @@ func (ddb *DoltDB) restoreDefaultConjoinBehavior() {
21072115
// NomsBlockStore instance which exposes its roots through
21082116
// IterateRoots. Otherwise returns |nil| without visiting any roots,
21092117
// including the current one.
2110-
func (ddb *DoltDB) IterateRoots(cb func(root string, timestamp *time.Time) error) error {
2118+
func (ddb *DoltDB) IterateRoots(ctx context.Context, cb func(root string, timestamp *time.Time) error) error {
21112119
cs := datas.ChunkStoreFromDatabase(ddb.db)
21122120

21132121
if generationalNBS, ok := cs.(*nbs.GenerationalNBS); ok {
21142122
cs = generationalNBS.NewGen()
21152123
}
21162124

21172125
if nbsStore, ok := cs.(*nbs.NomsBlockStore); ok {
2118-
return nbsStore.IterateRoots(cb)
2126+
return nbsStore.IterateRoots(ctx, cb)
21192127
} else {
21202128
return nil
21212129
}
@@ -2172,7 +2180,10 @@ func (ddb *DoltDB) StoreSizes(ctx context.Context) (StoreSizes, error) {
21722180
if err != nil {
21732181
return StoreSizes{}, err
21742182
}
2175-
journalSz, ok := newGenNBS.ChunkJournalSize()
2183+
journalSz, ok, err := newGenNBS.ChunkJournalSize(ctx)
2184+
if err != nil {
2185+
return StoreSizes{}, err
2186+
}
21762187
if ok {
21772188
return StoreSizes{
21782189
JournalBytes: uint64(journalSz),

go/libraries/doltcore/env/environment.go

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -247,45 +247,48 @@ func LoadDoltDB(ctx context.Context, dEnv *DoltEnv) {
247247
dEnv.doltDB = ddb
248248
dEnv.DBLoadError = dbLoadErr
249249

250-
if dbLoadErr == nil && dEnv.HasDoltDir() {
251-
if !dEnv.HasDoltTempTableDir() {
252-
tmpDir, err := dEnv.TempTableFilesDir()
253-
if err != nil {
254-
dEnv.DBLoadError = err
255-
}
256-
err = dEnv.FS.MkDirs(tmpDir)
257-
dEnv.DBLoadError = err
258-
} else {
259-
// fire and forget cleanup routine. Will delete as many old temp files as it can during the main commands execution.
260-
// The process will not wait for this to finish so this may not always complete.
261-
go func() {
262-
// TODO dEnv.HasDoltTempTableDir() true but dEnv.TempTableFileDir() panics
263-
tmpTableDir, err := dEnv.FS.Abs(filepath.Join(dEnv.urlStr, dbfactory.DoltDir, tempTablesDir))
250+
if ddb != nil && ddb.AccessMode() != chunks.ExclusiveAccessMode_ReadOnly {
251+
// Only do the following when we have write access to the database.
252+
if dbLoadErr == nil && dEnv.HasDoltDir() {
253+
if !dEnv.HasDoltTempTableDir() {
254+
tmpDir, err := dEnv.TempTableFilesDir()
264255
if err != nil {
265-
return
256+
dEnv.DBLoadError = err
266257
}
267-
_ = dEnv.FS.Iter(tmpTableDir, true, func(path string, size int64, isDir bool) (stop bool) {
268-
if !isDir {
269-
lm, exists := dEnv.FS.LastModified(path)
258+
err = dEnv.FS.MkDirs(tmpDir)
259+
dEnv.DBLoadError = err
260+
} else {
261+
// fire and forget cleanup routine. Will delete as many old temp files as it can during the main commands execution.
262+
// The process will not wait for this to finish so this may not always complete.
263+
go func() {
264+
// TODO dEnv.HasDoltTempTableDir() true but dEnv.TempTableFileDir() panics
265+
tmpTableDir, err := dEnv.FS.Abs(filepath.Join(dEnv.urlStr, dbfactory.DoltDir, tempTablesDir))
266+
if err != nil {
267+
return
268+
}
269+
_ = dEnv.FS.Iter(tmpTableDir, true, func(path string, size int64, isDir bool) (stop bool) {
270+
if !isDir {
271+
lm, exists := dEnv.FS.LastModified(path)
270272

271-
if exists && time.Now().Sub(lm) > (time.Hour*24) {
272-
_ = dEnv.FS.DeleteFile(path)
273+
if exists && time.Now().Sub(lm) > (time.Hour*24) {
274+
_ = dEnv.FS.DeleteFile(path)
275+
}
273276
}
274-
}
275277

276-
return false
277-
})
278-
}()
278+
return false
279+
})
280+
}()
281+
}
279282
}
280-
}
281283

282-
if dEnv.RSLoadErr == nil && dbLoadErr == nil {
283-
// If the working set isn't present in the DB, create it from the repo state. This step can be removed post 1.0.
284-
_, err := dEnv.WorkingSet(ctx)
285-
if errors.Is(err, doltdb.ErrWorkingSetNotFound) {
286-
_ = dEnv.initWorkingSetFromRepoState(ctx)
287-
} else if err != nil {
288-
dEnv.RSLoadErr = err
284+
if dEnv.RSLoadErr == nil && dbLoadErr == nil {
285+
// If the working set isn't present in the DB, create it from the repo state. This step can be removed post 1.0.
286+
_, err := dEnv.WorkingSet(ctx)
287+
if errors.Is(err, doltdb.ErrWorkingSetNotFound) {
288+
_ = dEnv.initWorkingSetFromRepoState(ctx)
289+
} else if err != nil {
290+
dEnv.RSLoadErr = err
291+
}
289292
}
290293
}
291294
})

go/libraries/doltcore/env/multi_repo_env.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ func multiEnvForConfigDirectoryEnv(ctx context.Context, config config.ReadWriteC
193193
if dbErr != nil {
194194
if errors.Is(dbErr, nbs.ErrJournalDataLoss) {
195195
logrus.Errorf("failed to load database %s with error: %s", dbName, dbErr.Error())
196-
logrus.Errorf("please run 'dolt fsck' to assess the damage and attempt repairs")
197196
} else if !errors.Is(dbErr, doltdb.ErrMissingDoltDataDir) {
198197
logrus.Warnf("failed to load database with error: %s", dbErr.Error())
199198
}

go/libraries/doltcore/remotesrv/dbcache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type RemoteSrvStore interface {
3030
chunks.ChunkStore
3131
chunks.TableFileStore
3232

33-
Path() (string, bool)
33+
Path(ctx context.Context) (string, bool, error)
3434
GetChunkLocationsWithPaths(ctx context.Context, hashes hash.HashSet) (map[string]map[hash.Hash]nbs.Range, error)
3535
}
3636

go/libraries/doltcore/remotesrv/grpc.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,11 @@ func (rs *RemoteChunkStore) HasChunks(ctx context.Context, req *remotesapi.HasCh
143143
return resp, nil
144144
}
145145

146-
func (rs *RemoteChunkStore) getRelativeStorePath(cs RemoteSrvStore) (string, error) {
147-
cspath, ok := cs.Path()
146+
func (rs *RemoteChunkStore) getRelativeStorePath(ctx context.Context, cs RemoteSrvStore) (string, error) {
147+
cspath, ok, err := cs.Path(ctx)
148+
if err != nil {
149+
return "", err
150+
}
148151
if !ok {
149152
return "", status.Error(codes.Internal, "chunkstore misconfigured; cannot generate HTTP paths")
150153
}
@@ -175,7 +178,7 @@ func (rs *RemoteChunkStore) GetDownloadLocations(ctx context.Context, req *remot
175178

176179
hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
177180

178-
prefix, err := rs.getRelativeStorePath(cs)
181+
prefix, err := rs.getRelativeStorePath(ctx, cs)
179182
if err != nil {
180183
logger.WithError(err).Error("error getting file store path for chunk store")
181184
return nil, err
@@ -280,7 +283,7 @@ func (rs *RemoteChunkStore) StreamDownloadLocations(stream remotesapi.ChunkStore
280283
if err != nil {
281284
return err
282285
}
283-
prefix, err = rs.getRelativeStorePath(cs)
286+
prefix, err = rs.getRelativeStorePath(stream.Context(), cs)
284287
if err != nil {
285288
logger.WithError(err).Error("error getting file store path for chunk store")
286289
return err
@@ -396,7 +399,7 @@ func (rs *RemoteChunkStore) StreamChunkLocations(stream remotesapi.ChunkStoreSer
396399
if err != nil {
397400
return err
398401
}
399-
prefix, err = rs.getRelativeStorePath(cs)
402+
prefix, err = rs.getRelativeStorePath(stream.Context(), cs)
400403
if err != nil {
401404
logger.WithError(err).Error("error getting file store path for chunk store")
402405
return err
@@ -765,13 +768,13 @@ func (rs *RemoteChunkStore) ListTableFiles(ctx context.Context, req *remotesapi.
765768

766769
md, _ := metadata.FromIncomingContext(ctx)
767770

768-
tableFileInfo, err := getTableFileInfo(logger, md, rs, tables, req, cs)
771+
tableFileInfo, err := getTableFileInfo(ctx, logger, md, rs, tables, req, cs)
769772
if err != nil {
770773
logger.WithError(err).Error("error getting table file info")
771774
return nil, err
772775
}
773776

774-
appendixTableFileInfo, err := getTableFileInfo(logger, md, rs, appendixTables, req, cs)
777+
appendixTableFileInfo, err := getTableFileInfo(ctx, logger, md, rs, appendixTables, req, cs)
775778
if err != nil {
776779
logger.WithError(err).Error("error getting appendix table file info")
777780
return nil, err
@@ -792,14 +795,15 @@ func (rs *RemoteChunkStore) ListTableFiles(ctx context.Context, req *remotesapi.
792795
}
793796

794797
func getTableFileInfo(
798+
ctx context.Context,
795799
logger *logrus.Entry,
796800
md metadata.MD,
797801
rs *RemoteChunkStore,
798802
tableList []chunks.TableFile,
799803
req *remotesapi.ListTableFilesRequest,
800804
cs RemoteSrvStore,
801805
) ([]*remotesapi.TableFileInfo, error) {
802-
prefix, err := rs.getRelativeStorePath(cs)
806+
prefix, err := rs.getRelativeStorePath(ctx, cs)
803807
if err != nil {
804808
return nil, err
805809
}

go/libraries/doltcore/remotestorage/chunk_store.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,13 +1082,13 @@ const (
10821082
chunkAggDistance = 8 * 1024
10831083
)
10841084

1085-
func (dcs *DoltChunkStore) SupportedOperations() chunks.TableFileStoreOps {
1085+
func (dcs *DoltChunkStore) SupportedOperations(_ context.Context) (chunks.TableFileStoreOps, error) {
10861086
return chunks.TableFileStoreOps{
10871087
CanRead: true,
10881088
CanWrite: true,
10891089
CanPrune: false,
10901090
CanGC: false,
1091-
}
1091+
}, nil
10921092
}
10931093

10941094
// WriteTableFile reads a table file from the provided reader and writes it to the chunk store.

go/libraries/doltcore/sqle/dtablefunctions/dolt_reflog.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func (rltf *ReflogTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.Row
9595

9696
previousCommitsByRef := make(map[string]string)
9797
rows := make([]sql.Row, 0)
98-
err := ddb.IterateRoots(func(root string, timestamp *time.Time) error {
98+
err := ddb.IterateRoots(ctx, func(root string, timestamp *time.Time) error {
9999
hashof := hash.Parse(root)
100100
datasets, err := ddb.DatasetsByRootHash(ctx, hashof)
101101
if err != nil {

go/store/chunks/chunk_store.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ type ChunkStoreGarbageCollector interface {
258258
//
259259
// This function should not block indefinitely and should return an
260260
// error if a GC is already in progress.
261-
BeginGC(addChunk func(hash.Hash) bool, mode GCMode) error
261+
BeginGC(ctx context.Context, addChunk func(hash.Hash) bool, mode GCMode) error
262262

263263
// EndGC indicates that the GC is over. The previously provided
264264
// addChunk function must not be called after this function.
@@ -273,7 +273,7 @@ type ChunkStoreGarbageCollector interface {
273273
MarkAndSweepChunks(ctx context.Context, getAddrs GetAddrs, filter HasManyFunc, dest ChunkStore, config GCConfig, incrementalUpdateManifest bool) (MarkAndSweeper, error)
274274

275275
// Count returns the number of chunks in the store.
276-
Count() (uint32, error)
276+
Count(ctx context.Context) (uint32, error)
277277

278278
// IterateAllChunks iterates over all chunks in the store, calling the provided callback for each chunk. This is
279279
// a wrapper over the internal chunkSource.iterateAllChunks() method.

0 commit comments

Comments
 (0)