Skip to content

Commit 2612620

Browse files
committed
db: support store-relative paths for WAL dirs
Relative WAL paths (including the actual WAL, the failover path, and the recovery paths) are (unfortunately) interpreted as relative to the current working directory. The cross-version metamorphic test copies a store from a previous run as the initial state for a new test. The options will fail the compatibility check since the path changes. This change adds support for using a special `{store_path}` prefix inside the path. Any such prefix is replaced with the store directory. We also improve the missing WAL recovery dir error to show what directories are actually configured.
1 parent 886faf4 commit 2612620

File tree

5 files changed

+67
-15
lines changed

5 files changed

+67
-15
lines changed

open.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,12 +377,17 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
377377
}
378378
if opts.WALFailover != nil {
379379
walOpts.Secondary = opts.WALFailover.Secondary
380+
walOpts.Secondary.Dirname = resolveStorePath(dirname, walOpts.Secondary.Dirname)
380381
walOpts.FailoverOptions = opts.WALFailover.FailoverOptions
381382
walOpts.FailoverWriteAndSyncLatency = prometheus.NewHistogram(prometheus.HistogramOpts{
382383
Buckets: FsyncLatencyBuckets,
383384
})
384385
}
385-
walDirs := append(walOpts.Dirs(), opts.WALRecoveryDirs...)
386+
walDirs := walOpts.Dirs()
387+
for _, dir := range opts.WALRecoveryDirs {
388+
dir.Dirname = resolveStorePath(dirname, dir.Dirname)
389+
walDirs = append(walDirs, dir)
390+
}
386391
wals, err := wal.Scan(walDirs...)
387392
if err != nil {
388393
return nil, err
@@ -663,9 +668,9 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
663668
func prepareAndOpenDirs(
664669
dirname string, opts *Options,
665670
) (walDirname string, dataDir vfs.File, err error) {
666-
walDirname = opts.WALDir
667-
if opts.WALDir == "" {
668-
walDirname = dirname
671+
walDirname = dirname
672+
if opts.WALDir != "" {
673+
walDirname = resolveStorePath(dirname, opts.WALDir)
669674
}
670675

671676
// Create directories if needed.
@@ -684,7 +689,7 @@ func prepareAndOpenDirs(
684689
}
685690
if opts.WALFailover != nil {
686691
secondary := opts.WALFailover.Secondary
687-
f, err := mkdirAllAndSyncParents(secondary.FS, secondary.Dirname)
692+
f, err := mkdirAllAndSyncParents(secondary.FS, resolveStorePath(dirname, secondary.Dirname))
688693
if err != nil {
689694
return "", nil, err
690695
}

open_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ func TestNewDBFilenames(t *testing.T) {
369369
func testOpenCloseOpenClose(t *testing.T, fs vfs.FS, root string) {
370370
opts := testingRandomized(t, &Options{FS: fs})
371371

372+
useStoreRelativeWALPath := rand.IntN(2) == 0
372373
for _, startFromEmpty := range []bool{false, true} {
373374
for _, walDirname := range []string{"", "wal"} {
374375
for _, length := range []int{-1, 0, 1, 1000, 10000, 100000} {
@@ -380,7 +381,11 @@ func testOpenCloseOpenClose(t *testing.T, fs vfs.FS, root string) {
380381
if walDirname == "" {
381382
opts.WALDir = ""
382383
} else {
383-
opts.WALDir = fs.PathJoin(dirname, walDirname)
384+
if useStoreRelativeWALPath {
385+
opts.WALDir = MakeStoreRelativePath(fs, walDirname)
386+
} else {
387+
opts.WALDir = fs.PathJoin(dirname, walDirname)
388+
}
384389
}
385390

386391
got, xxx := []byte(nil), ""

options.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,12 +2159,13 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
21592159
// opened without supplying a Options.WALRecoveryDir entry for a directory that
21602160
// may contain WALs required to recover a consistent database state.
21612161
type ErrMissingWALRecoveryDir struct {
2162-
Dir string
2162+
Dir string
2163+
ExtraInfo string
21632164
}
21642165

21652166
// Error implements error.
21662167
func (e ErrMissingWALRecoveryDir) Error() string {
2167-
return fmt.Sprintf("directory %q may contain relevant WALs", e.Dir)
2168+
return fmt.Sprintf("directory %q may contain relevant WALs%s", e.Dir, e.ExtraInfo)
21682169
}
21692170

21702171
// CheckCompatibility verifies the options are compatible with the previous options
@@ -2200,7 +2201,18 @@ func (o *Options) CheckCompatibility(previousOptions string) error {
22002201
return nil
22012202
}
22022203
}
2203-
return ErrMissingWALRecoveryDir{Dir: value}
2204+
var buf bytes.Buffer
2205+
fmt.Fprintf(&buf, "\n OPTIONS key: %s\n", section+"."+key)
2206+
if o.WALDir != "" {
2207+
fmt.Fprintf(&buf, " o.WALDir: %s\n", o.WALDir)
2208+
}
2209+
if o.WALFailover != nil {
2210+
fmt.Fprintf(&buf, " o.WALFailover.Secondary.Dirname: %s\n", o.WALFailover.Secondary.Dirname)
2211+
}
2212+
for _, d := range o.WALRecoveryDirs {
2213+
fmt.Fprintf(&buf, " WALRecoveryDir: %s\n", d)
2214+
}
2215+
return ErrMissingWALRecoveryDir{Dir: value, ExtraInfo: buf.String()}
22042216
}
22052217
}
22062218
return nil
@@ -2426,3 +2438,26 @@ func (kc *UserKeyCategories) CategorizeKeyRange(startUserKey, endUserKey []byte)
24262438
})
24272439
return kc.rangeNames[p][q]
24282440
}
2441+
2442+
const storePathIdentifier = "{store_path}"
2443+
2444+
// MakeStoreRelativePath takes a path that is relative to the store directory
2445+
// and creates a path that can be used for Options.WALDir and wal.Dir.Dirname.
2446+
//
2447+
// This is used in metamorphic tests, so that the test run directory can be
2448+
// copied or moved.
2449+
func MakeStoreRelativePath(fs vfs.FS, relativePath string) string {
2450+
if relativePath == "" {
2451+
return storePathIdentifier
2452+
}
2453+
return fs.PathJoin(storePathIdentifier, relativePath)
2454+
}
2455+
2456+
// resolveStorePath is the inverse of MakeStoreRelativePath(). It replaces any
2457+
// storePathIdentifier prefix with the store dir.
2458+
func resolveStorePath(storeDir, path string) string {
2459+
if remainder, ok := strings.CutPrefix(path, storePathIdentifier); ok {
2460+
return storeDir + remainder
2461+
}
2462+
return path
2463+
}

options_test.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,15 @@ func TestOptionsCheckCompatibility(t *testing.T) {
192192

193193
// Check that an OPTIONS file that configured an explicit WALDir that will
194194
// no longer be used errors if it's not also present in WALRecoveryDirs.
195-
require.Equal(t, ErrMissingWALRecoveryDir{Dir: "external-wal-dir"},
196-
DefaultOptions().CheckCompatibility(`
195+
//require.Equal(t, ErrMissingWALRecoveryDir{Dir: "external-wal-dir"},
196+
err := DefaultOptions().CheckCompatibility(`
197197
[Options]
198198
wal_dir=external-wal-dir
199-
`))
199+
`)
200+
var missingWALRecoveryDirErr ErrMissingWALRecoveryDir
201+
require.True(t, errors.As(err, &missingWALRecoveryDirErr))
202+
require.Equal(t, "external-wal-dir", missingWALRecoveryDirErr.Dir)
203+
200204
// But not if it's configured as a WALRecoveryDir or current WALDir.
201205
opts = &Options{WALRecoveryDirs: []wal.Dir{{Dirname: "external-wal-dir"}}}
202206
opts.EnsureDefaults()
@@ -214,13 +218,15 @@ func TestOptionsCheckCompatibility(t *testing.T) {
214218
// Check that an OPTIONS file that configured a secondary failover WAL dir
215219
// that will no longer be used errors if it's not also present in
216220
// WALRecoveryDirs.
217-
require.Equal(t, ErrMissingWALRecoveryDir{Dir: "failover-wal-dir"},
218-
DefaultOptions().CheckCompatibility(`
221+
err = DefaultOptions().CheckCompatibility(`
219222
[Options]
220223
221224
[WAL Failover]
222225
secondary_dir=failover-wal-dir
223-
`))
226+
`)
227+
require.True(t, errors.As(err, &missingWALRecoveryDirErr))
228+
require.Equal(t, "failover-wal-dir", missingWALRecoveryDirErr.Dir)
229+
224230
// But not if it's configured as a WALRecoveryDir or current failover
225231
// secondary dir.
226232
opts = &Options{WALRecoveryDirs: []wal.Dir{{Dirname: "failover-wal-dir"}}}

testdata/open_wal_failover

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ grep-between path=(a,data/OPTIONS-000007) start=(\[WAL Failover\]) end=^$
5959
open path=(a,data)
6060
----
6161
directory "secondary-wals" may contain relevant WALs
62+
OPTIONS key: WAL Failover.secondary_dir
6263

6364
# But opening the same directory while providing the secondary path as a WAL
6465
# recovery dir should succeed.

0 commit comments

Comments
 (0)