Skip to content

Commit 2cc37e5

Browse files
committed
metadata: set NoFreelistSync on bolt databases to avoid write amplification
bbolt rewrites the entire freelist on every transaction commit. Once a metadata database accumulates many free pages (for example after GC frees most of a large database), each small metadata write amplifies into a multi-MB disk write. With a high commit rate this can saturate disk write bandwidth and, because every commit holds bbolt's single writer lock while fsyncing, stall otherwise-cheap build operations. Set NoFreelistSync on the bolt databases that buildkit opens itself (containerdmeta.db, metadata_v2.db and cache.db). The freelist is not data; it is reconstructed by scanning the database on open, so durability is unaffected. containerd applies the same option to its own metadata database for the same reason: containerd/containerd#6761 Signed-off-by: ZRHann <zrhann@foxmail.com>
1 parent e7b395c commit 2cc37e5

3 files changed

Lines changed: 15 additions & 2 deletions

File tree

cache/metadata/metadata.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ func NewStore(dbPath string) (*Store, error) {
4141
)
4242
}
4343
}
44-
db, err := boltutil.Open(dbPath, 0600, nil)
44+
// Disable freelist syncing to avoid rewriting the whole freelist on every
45+
// commit (write amplification on a fragmented database). See the comment in
46+
// worker/runc and https://github.com/containerd/containerd/pull/6761
47+
db, err := boltutil.Open(dbPath, 0600, &bolt.Options{NoFreelistSync: true})
4548
if err != nil {
4649
return nil, errors.Wrapf(err, "failed to open database file %s", dbPath)
4750
}

solver/bboltcachestorage/storage.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ type Store struct {
2727
func NewStore(dbPath string) (*Store, error) {
2828
db, err := boltutil.SafeOpen(dbPath, 0600, &bolt.Options{
2929
NoSync: true,
30+
// Avoid rewriting the whole freelist on every commit (write
31+
// amplification). See https://github.com/containerd/containerd/pull/6761
32+
NoFreelistSync: true,
3033
})
3134
if err != nil {
3235
return nil, err

worker/runc/runc.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,14 @@ func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, proc
9595
return opt, err
9696
}
9797

98-
db, err := bolt.Open(filepath.Join(root, "containerdmeta.db"), 0644, nil)
98+
// Disable freelist syncing. bbolt rewrites the whole freelist on every
99+
// commit, so once the database accumulates many free pages a single small
100+
// metadata write amplifies into a multi-MB write, which can saturate disk
101+
// write bandwidth. containerd disables it on its own metadata database for
102+
// the same reason: https://github.com/containerd/containerd/pull/6761
103+
db, err := bolt.Open(filepath.Join(root, "containerdmeta.db"), 0644, &bolt.Options{
104+
NoFreelistSync: true,
105+
})
99106
if err != nil {
100107
return opt, err
101108
}

0 commit comments

Comments
 (0)