@@ -8,13 +8,15 @@ package badger
88import (
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+
899966func (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