Skip to content
This repository has been archived by the owner on Mar 9, 2019. It is now read-only.

introduce initialMmapSize #432

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion db.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
}

// Memory map the data file.
if err := db.mmap(0); err != nil {
if err := db.mmap(options.InitialMmapSize); err != nil {
_ = db.close()
return nil, err
}
Expand Down Expand Up @@ -406,6 +406,10 @@ func (db *DB) close() error {
// writer to deadlock because the database periodically needs to re-mmap itself
// as it grows and it cannot do that while a read transaction is open.
//
// If a long running read transaction (for example, a snapshot transaction) is
// needed, you might want to set DB.InitialMmapSize to a large enough value
// to avoid potential blocking of write transaction.
//
// IMPORTANT: You must close read-only transactions after you are finished or
// else the database will not reclaim old pages.
func (db *DB) Begin(writable bool) (*Tx, error) {
Expand Down Expand Up @@ -672,6 +676,18 @@ type Options struct {
// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
// grab a shared lock (UNIX).
ReadOnly bool

// InitialMmapSize is the initial mmap size of the database
// in bytes.
//
// Read transaction is guaranteed not to block write transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Read transactions are guaranteed not to block write transactions ..."

// if the mmap size required by the database is smaller than
// the initial mmap. (See DB.Begin for more information)
//
// If <=0, the initial map size is 0.
// If initialMmapSize is smaller than the previous database size,
// it takes no effect.
InitialMmapSize int
}

// DefaultOptions represent the options used if nil options are passed into Open().
Expand Down
41 changes: 41 additions & 0 deletions db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,47 @@ func TestOpen_ReadOnly(t *testing.T) {
}
}

// TestDB_Open_InitialMmapSize tests the passed in InitialMmapSize takes
// effect. This is a very hacky test since the mmap size is not exposed.
//
// This test indirecly ensures the behavior by trying to block the re-mmap
// by a reader and aks a writer to write a large value that will trigger
// a mmap if the initial mmap size is small.
func TestDB_Open_InitialMmapSize(t *testing.T) {
path := tempfile()
defer os.Remove(path)

isz := 1 << 31 // 2GB
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think zero size is better, so we don't need to commit a large write :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*indirectly?

db, err := bolt.Open(path, 0666, &bolt.Options{InitialMmapSize: isz})
assert(t, err == nil, "")

// a reader tx
rtx, err := db.Begin(false)
assert(t, err == nil, "")
defer rtx.Rollback()

// create a write tx and commit a large write
wtx, err := db.Begin(true)
assert(t, err == nil, "")
b, err := wtx.CreateBucket([]byte("test"))
assert(t, err == nil, "")
err = b.Put([]byte("foo"), make([]byte, 1<<23))
assert(t, err == nil, "")

done := make(chan struct{})

go func() {
wtx.Commit()
done <- struct{}{}
}()

select {
case <-time.After(5 * time.Second):
t.Errorf("unexpected that the reader blocks writer")
case <-done:
}
}

// TODO(benbjohnson): Test corruption at every byte of the first two pages.

// Ensure that a database cannot open a transaction when it's not open.
Expand Down