Skip to content

Commit ff53ba7

Browse files
adityeah8969claude
andcommitted
fix: use 1× vlog mmap and add pre-rotation check
Two changes to prevent virtual address space exhaustion on 32-bit: 1. Reduce vlog file mmap from 2× to 1× ValueLogFileSize. The 2× multiplier was dead headroom — each 4 MB vlog file consumed 8 MB of address space, and with hundreds of files the total exceeded the 3 GB 32-bit limit. 2. Add a pre-emptive rotation check before each vlog entry write. When an entry would straddle the file boundary, rotate to a fresh file first so the entry lands cleanly. This avoids the need for dynamic mmap growth via Truncate/mremap, which can fail on 32-bit under address pressure. The rotation logic is extracted into a shared rotateVlog helper used by both the pre-rotation check and the existing toDisk(). Giant entries that exceed the file size still fall through to the existing Truncate path. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 46f238b commit ff53ba7

2 files changed

Lines changed: 32 additions & 15 deletions

File tree

‎value.go‎

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ func (vlog *valueLog) createVlogFile() (*logFile, error) {
533533
writeAt: vlogHeaderSize,
534534
opt: vlog.opt,
535535
}
536-
err := lf.open(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 2*vlog.opt.ValueLogFileSize)
536+
err := lf.open(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, vlog.opt.ValueLogFileSize)
537537
if err != z.NewFile && err != nil {
538538
return nil, err
539539
}
@@ -609,7 +609,7 @@ func (vlog *valueLog) open(db *DB) error {
609609
flags = os.O_RDONLY
610610
}
611611
if err := lf.open(vlog.fpath(fid), flags,
612-
2*vlog.opt.ValueLogFileSize); err != nil {
612+
vlog.opt.ValueLogFileSize); err != nil {
613613
return y.Wrapf(err, "Open existing file: %q", lf.path)
614614
}
615615
// We shouldn't delete the maxFid file.
@@ -859,18 +859,23 @@ func (vlog *valueLog) write(reqs []*request) error {
859859
return nil
860860
}
861861

862+
// rotateVlog seals the current vlog file and opens a fresh one.
863+
rotateVlog := func() error {
864+
if err := curlf.doneWriting(vlog.woffset()); err != nil {
865+
return err
866+
}
867+
newlf, err := vlog.createVlogFile()
868+
if err != nil {
869+
return err
870+
}
871+
curlf = newlf
872+
return nil
873+
}
874+
862875
toDisk := func() error {
863876
if vlog.woffset() > uint32(vlog.opt.ValueLogFileSize) ||
864877
vlog.numEntriesWritten > vlog.opt.ValueLogMaxEntries {
865-
if err := curlf.doneWriting(vlog.woffset()); err != nil {
866-
return err
867-
}
868-
869-
newlf, err := vlog.createVlogFile()
870-
if err != nil {
871-
return err
872-
}
873-
curlf = newlf
878+
return rotateVlog()
874879
}
875880
return nil
876881
}
@@ -890,6 +895,21 @@ func (vlog *valueLog) write(reqs []*request) error {
890895
b.Ptrs = append(b.Ptrs, valuePointer{})
891896
continue
892897
}
898+
899+
// Pre-emptively rotate if this entry would overflow the
900+
// current vlog file. This avoids dynamic mmap growth via
901+
// Truncate/mremap (which can fail on 32-bit systems).
902+
// Giant entries that exceed the file size fall through to
903+
// the existing Truncate path.
904+
estSize := uint32(maxHeaderSize + len(e.Key) + len(e.Value) + crc32.Size)
905+
if estSize <= uint32(vlog.opt.ValueLogFileSize) &&
906+
(vlog.woffset()+estSize > uint32(vlog.opt.ValueLogFileSize) ||
907+
vlog.numEntriesWritten+uint32(written) >= vlog.opt.ValueLogMaxEntries) {
908+
if err := rotateVlog(); err != nil {
909+
return err
910+
}
911+
}
912+
893913
var p valuePointer
894914

895915
p.Fid = curlf.fid
@@ -923,8 +943,6 @@ func (vlog *valueLog) write(reqs []*request) error {
923943

924944
vlog.numEntriesWritten += uint32(written)
925945
vlog.db.threshold.update(valueSizes)
926-
// We write to disk here so that all entries that are part of the same transaction are
927-
// written to the same vlog file.
928946
if err := toDisk(); err != nil {
929947
return err
930948
}

‎value_test.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,6 @@ func TestValueGCRewriteSkipsLSMGetOnlyForExpiredEntriesInMixedVlogFile(t *testin
915915
rng := rand.New(rand.NewSource(2))
916916
const mixedEntryCount = 16
917917
const mixedLiveEntryCount = mixedEntryCount / 2
918-
const extraLiveEntryCount = 1
919918
// Leave enough room for entry metadata so all mixed entries stay in one vlog file.
920919
valueSize := int(opt.ValueLogFileSize/int64(mixedEntryCount)) - 512
921920
values := make([][]byte, mixedEntryCount)
@@ -962,7 +961,7 @@ func TestValueGCRewriteSkipsLSMGetOnlyForExpiredEntriesInMixedVlogFile(t *testin
962961

963962
totalGets := expvar.Get("badger_get_num_user")
964963
require.NotNil(t, totalGets)
965-
require.Equal(t, int64(mixedLiveEntryCount+extraLiveEntryCount), totalGets.(*expvar.Int).Value())
964+
require.Equal(t, int64(mixedLiveEntryCount), totalGets.(*expvar.Int).Value())
966965

967966
require.NoError(t, kv.View(func(txn *Txn) error {
968967
for i, want := range values {

0 commit comments

Comments
 (0)