add PreLoadFreelist to support loading free pages in readonly mode

Signed-off-by: Benjamin Wang <wachao@vmware.com>
pull/381/head
Benjamin Wang 2023-01-11 10:42:53 +08:00
parent 51c763c4e9
commit 6bc57389f0
4 changed files with 60 additions and 4 deletions

View File

@ -195,7 +195,10 @@ func (cmd *CheckCommand) Run(args ...string) error {
}
// Open database.
db, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
db, err := bolt.Open(path, 0666, &bolt.Options{
ReadOnly: true,
PreLoadFreelist: true,
})
if err != nil {
return err
}
@ -644,7 +647,10 @@ func (cmd *PagesCommand) Run(args ...string) error {
}
// Open database.
db, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
db, err := bolt.Open(path, 0666, &bolt.Options{
ReadOnly: true,
PreLoadFreelist: true,
})
if err != nil {
return err
}

View File

@ -255,6 +255,37 @@ func TestGetCommand_Run(t *testing.T) {
}
}
// Ensure the "pages" command neither panic, nor change the db file.
func TestPagesCommand_Run(t *testing.T) {
db := btesting.MustCreateDB(t)
err := db.Update(func(tx *bolt.Tx) error {
for _, name := range []string{"foo", "bar"} {
b, err := tx.CreateBucket([]byte(name))
if err != nil {
return err
}
for i := 0; i < 3; i++ {
key := fmt.Sprintf("%s-%d", name, i)
val := fmt.Sprintf("val-%s-%d", name, i)
if err := b.Put([]byte(key), []byte(val)); err != nil {
return err
}
}
}
return nil
})
require.NoError(t, err)
db.Close()
defer requireDBNoChange(t, dbData(t, db.Path()), db.Path())
// Run the command.
m := NewMain()
err = m.Run("pages", db.Path())
require.NoError(t, err)
}
// Main represents a test wrapper for main.Main that records output.
type Main struct {
*main.Main

20
db.go
View File

@ -95,6 +95,11 @@ type DB struct {
// https://github.com/boltdb/bolt/issues/284
NoGrowSync bool
// When `true`, bbolt will always load the free pages when opening the DB.
// When opening db in write mode, this flag will always automatically
// set to `true`.
PreLoadFreelist bool
// If you want to read the entire database fast, you can set MmapFlag to
// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead.
MmapFlags int
@ -196,6 +201,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
db.NoGrowSync = options.NoGrowSync
db.MmapFlags = options.MmapFlags
db.NoFreelistSync = options.NoFreelistSync
db.PreLoadFreelist = options.PreLoadFreelist
db.FreelistType = options.FreelistType
db.Mlock = options.Mlock
@ -208,6 +214,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
if options.ReadOnly {
flag = os.O_RDONLY
db.readOnly = true
} else {
// always load free pages in write mode
db.PreLoadFreelist = true
}
db.openFile = options.OpenFile
@ -277,12 +286,14 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
return nil, err
}
if db.PreLoadFreelist {
db.loadFreelist()
}
if db.readOnly {
return db, nil
}
db.loadFreelist()
// Flush freelist when transitioning from no sync to sync so
// NoFreelistSync unaware boltdb can open the db later.
if !db.NoFreelistSync && !db.hasSyncedFreelist() {
@ -1163,6 +1174,11 @@ type Options struct {
// under normal operation, but requires a full database re-sync during recovery.
NoFreelistSync bool
// PreLoadFreelist sets whether to load the free pages when opening
// the db file. Note when opening db in write mode, bbolt will always
// load the free pages.
PreLoadFreelist bool
// FreelistType sets the backend freelist type. There are two options. Array which is simple but endures
// dramatic performance degradation if database is large and framentation in freelist is common.
// The alternative one is using hashmap, it is faster in almost all circumstances

3
tx.go
View File

@ -652,6 +652,9 @@ func (tx *Tx) Page(id int) (*PageInfo, error) {
return nil, nil
}
// Force loading free list if opened in ReadOnly mode.
tx.db.loadFreelist()
// Build the page info.
p := tx.db.page(pgid(id))
info := &PageInfo{