Skip to content

Commit 541b2d1

Browse files
committed
optimize value GC rewrite for expired vlog entries
1 parent 796cb85 commit 541b2d1

2 files changed

Lines changed: 71 additions & 3 deletions

File tree

value.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ func (vlog *valueLog) rewrite(f *logFile) error {
183183
vlog.opt.Debugf("Processing entry %d", count)
184184
}
185185

186+
if isDeletedOrExpired(e.meta, e.ExpiresAt) {
187+
return nil
188+
}
189+
186190
vs, err := vlog.db.get(e.Key)
187191
if err != nil {
188192
return err
@@ -1037,9 +1041,6 @@ func discardEntry(e Entry, vs y.ValueStruct, db *DB) bool {
10371041
// Version not found. Discard.
10381042
return true
10391043
}
1040-
if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) {
1041-
return true
1042-
}
10431044
if (vs.Meta & bitValuePointer) == 0 {
10441045
// Key also stores the value in LSM. Discard.
10451046
return true

value_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ package badger
88
import (
99
"bytes"
1010
"errors"
11+
"expvar"
1112
"fmt"
1213
"math"
1314
"math/rand"
1415
"os"
1516
"reflect"
1617
"sync"
1718
"testing"
19+
"time"
1820

1921
humanize "github.com/dustin/go-humanize"
2022
"github.com/stretchr/testify/require"
@@ -896,6 +898,71 @@ func (th *testHelper) writeRange(from, to int) {
896898
}
897899
}
898900

901+
func TestValueGCRewriteSkipsLSMGetForExpiredVlogEntry(t *testing.T) {
902+
dir := t.TempDir()
903+
904+
opt := getTestOptions(dir)
905+
opt.ValueLogFileSize = 1 << 20
906+
opt.BaseTableSize = 1 << 15
907+
opt.ValueThreshold = 1 << 10
908+
909+
kv, err := Open(opt)
910+
require.NoError(t, err)
911+
defer kv.Close()
912+
913+
rng := rand.New(rand.NewSource(1))
914+
expiredVal1 := make([]byte, 600<<10)
915+
expiredVal2 := make([]byte, 600<<10)
916+
liveVal := make([]byte, 32<<10)
917+
_, err = rng.Read(expiredVal1)
918+
require.NoError(t, err)
919+
_, err = rng.Read(expiredVal2)
920+
require.NoError(t, err)
921+
_, err = rng.Read(liveVal)
922+
require.NoError(t, err)
923+
924+
// Write enough expired data to make the first vlog file contain only expired entries.
925+
require.NoError(t, kv.Update(func(txn *Txn) error {
926+
if err := txn.SetEntry(NewEntry([]byte("expired-1"), expiredVal1).WithTTL(-time.Hour)); err != nil {
927+
return err
928+
}
929+
return txn.SetEntry(NewEntry([]byte("expired-2"), expiredVal2).WithTTL(-time.Hour))
930+
}))
931+
require.NoError(t, kv.Update(func(txn *Txn) error {
932+
return txn.SetEntry(NewEntry([]byte("live"), liveVal))
933+
}))
934+
935+
fids := kv.vlog.sortedFids()
936+
require.Len(t, fids, 2)
937+
938+
kv.vlog.filesLock.RLock()
939+
lf := kv.vlog.filesMap[fids[0]]
940+
kv.vlog.filesLock.RUnlock()
941+
942+
// Rewriting an expired-only vlog file should not trigger an LSM lookup.
943+
clearAllMetrics()
944+
require.NoError(t, kv.vlog.rewrite(lf))
945+
946+
totalGets := expvar.Get("badger_get_num_user")
947+
require.NotNil(t, totalGets)
948+
require.Equal(t, int64(0), totalGets.(*expvar.Int).Value())
949+
950+
// Expired keys must stay invisible and live keys must remain readable.
951+
require.NoError(t, kv.View(func(txn *Txn) error {
952+
_, err := txn.Get([]byte("expired-1"))
953+
require.Equal(t, ErrKeyNotFound, err)
954+
955+
_, err = txn.Get([]byte("expired-2"))
956+
require.Equal(t, ErrKeyNotFound, err)
957+
958+
item, err := txn.Get([]byte("live"))
959+
require.NoError(t, err)
960+
val := getItemValue(t, item)
961+
require.Equal(t, liveVal, val)
962+
return nil
963+
}))
964+
}
965+
899966
func (th *testHelper) readRange(from, to int) {
900967
for i := from; i <= to; i++ {
901968
err := th.db.View(func(txn *Txn) error {

0 commit comments

Comments
 (0)