@@ -762,8 +762,10 @@ func (vlog *valueLog) dropAll() (int, error) {
762762// lfDiscardStats keeps track of the amount of data that could be discarded for
763763// a given logfile.
764764type lfDiscardStats struct {
765- sync.Mutex
765+ sync.RWMutex
766766 m map [uint32 ]int64
767+ flushChan chan map [uint32 ]int64
768+ closer * y.Closer
767769 updatesSinceFlush int
768770}
769771
@@ -838,6 +840,7 @@ func (lf *logFile) open(path string, flags uint32) error {
838840 if lf .fd , err = y .OpenExistingFile (path , flags ); err != nil {
839841 return y .Wrapf (err , "Error while opening file in logfile %s" , path )
840842 }
843+
841844 fi , err := lf .fd .Stat ()
842845 if err != nil {
843846 return errFile (err , lf .path , "Unable to run file.Stat" )
@@ -999,7 +1002,12 @@ func (vlog *valueLog) open(db *DB, ptr valuePointer, replayFn logEntry) error {
9991002 vlog .elog = trace .NewEventLog ("Badger" , "Valuelog" )
10001003 }
10011004 vlog .garbageCh = make (chan struct {}, 1 ) // Only allow one GC at a time.
1002- vlog .lfDiscardStats = & lfDiscardStats {m : make (map [uint32 ]int64 )}
1005+ vlog .lfDiscardStats = & lfDiscardStats {
1006+ m : make (map [uint32 ]int64 ),
1007+ closer : y .NewCloser (1 ),
1008+ flushChan : make (chan map [uint32 ]int64 , 16 ),
1009+ }
1010+ go vlog .flushDiscardStats ()
10031011 if err := vlog .populateFilesMap (); err != nil {
10041012 return err
10051013 }
@@ -1131,6 +1139,9 @@ func (lf *logFile) init() error {
11311139}
11321140
11331141func (vlog * valueLog ) Close () error {
1142+ // close flushDiscardStats.
1143+ vlog .lfDiscardStats .closer .SignalAndWait ()
1144+
11341145 vlog .elog .Printf ("Stopping garbage collection of values." )
11351146 defer vlog .elog .Finish ()
11361147
@@ -1217,7 +1228,7 @@ func (reqs requests) DecrRef() {
12171228// sync function syncs content of latest value log file to disk. Syncing of value log directory is
12181229// not required here as it happens every time a value log file rotation happens(check createVlogFile
12191230// function). During rotation, previous value log file also gets synced to disk. It only syncs file
1220- // if fid >= vlog.maxFid. In some cases such as replay(while openning db), it might be called with
1231+ // if fid >= vlog.maxFid. In some cases such as replay(while opening db), it might be called with
12211232// fid < vlog.maxFid. To sync irrespective of file id just call it with math.MaxUint32.
12221233func (vlog * valueLog ) sync (fid uint32 ) error {
12231234 if vlog .opt .SyncWrites {
@@ -1443,7 +1454,7 @@ func (vlog *valueLog) pickLog(head valuePointer, tr trace.Trace) (files []*logFi
14431454 fid uint32
14441455 discard int64
14451456 }{math .MaxUint32 , 0 }
1446- vlog .lfDiscardStats .Lock ()
1457+ vlog .lfDiscardStats .RLock ()
14471458 for _ , fid := range fids {
14481459 if fid >= head .Fid {
14491460 break
@@ -1453,7 +1464,7 @@ func (vlog *valueLog) pickLog(head valuePointer, tr trace.Trace) (files []*logFi
14531464 candidate .discard = vlog .lfDiscardStats .m [fid ]
14541465 }
14551466 }
1456- vlog .lfDiscardStats .Unlock ()
1467+ vlog .lfDiscardStats .RUnlock ()
14571468
14581469 if candidate .fid != math .MaxUint32 { // Found a candidate
14591470 tr .LazyPrintf ("Found candidate via discard stats: %v" , candidate )
@@ -1682,58 +1693,72 @@ func (vlog *valueLog) runGC(discardRatio float64, head valuePointer) error {
16821693 }
16831694}
16841695
1685- func (vlog * valueLog ) updateDiscardStats (stats map [uint32 ]int64 ) error {
1686- vlog .lfDiscardStats .Lock ()
1687-
1688- for fid , sz := range stats {
1689- vlog .lfDiscardStats .m [fid ] += sz
1690- vlog .lfDiscardStats .updatesSinceFlush ++
1691- }
1692- if vlog .lfDiscardStats .updatesSinceFlush > discardStatsFlushThreshold {
1693- vlog .lfDiscardStats .Unlock ()
1694- // flushDiscardStats also acquires lock. So, we need to unlock here.
1695- return vlog .flushDiscardStats ()
1696+ func (vlog * valueLog ) updateDiscardStats (stats map [uint32 ]int64 ) {
1697+ select {
1698+ case vlog .lfDiscardStats .flushChan <- stats :
1699+ default :
1700+ vlog .opt .Warningf ("updateDiscardStats called: discard stats flushChan full, " +
1701+ "returning without pushing to flushChan" )
16961702 }
1697- vlog .lfDiscardStats .Unlock ()
1698- return nil
16991703}
17001704
1701- // flushDiscardStats inserts discard stats into badger. Returns error on failure.
1702- func (vlog * valueLog ) flushDiscardStats () error {
1703- vlog .lfDiscardStats .Lock ()
1704- defer vlog .lfDiscardStats .Unlock ()
1705+ func (vlog * valueLog ) flushDiscardStats () {
1706+ defer vlog .lfDiscardStats .closer .Done ()
17051707
1706- if len (vlog .lfDiscardStats .m ) == 0 {
1707- return nil
1708+ mergeStats := func (stats map [uint32 ]int64 ) ([]byte , error ) {
1709+ vlog .lfDiscardStats .Lock ()
1710+ defer vlog .lfDiscardStats .Unlock ()
1711+ for fid , count := range stats {
1712+ vlog .lfDiscardStats .m [fid ] += count
1713+ vlog .lfDiscardStats .updatesSinceFlush ++
1714+ }
1715+
1716+ if vlog .lfDiscardStats .updatesSinceFlush > discardStatsFlushThreshold {
1717+ encodedDS , err := json .Marshal (vlog .lfDiscardStats .m )
1718+ if err != nil {
1719+ return nil , err
1720+ }
1721+ vlog .lfDiscardStats .updatesSinceFlush = 0
1722+ return encodedDS , nil
1723+ }
1724+ return nil , nil
17081725 }
1709- entries := []* Entry {{
1710- Key : y .KeyWithTs (lfDiscardStatsKey , 1 ),
1711- Value : vlog .encodedDiscardStats (),
1712- }}
1713- req , err := vlog .db .sendToWriteCh (entries )
1714- if err == ErrBlockedWrites {
1715- // We'll block write while closing db.
1716- // When L0 compaction in close may push discard stats.
1717- // So ignoring it.
1718- // https://github.com/dgraph-io/badger/issues/970
1719- return nil
1720- } else if err != nil {
1721- return errors .Wrapf (err , "failed to push discard stats to write channel" )
1726+
1727+ process := func (stats map [uint32 ]int64 ) error {
1728+ encodedDS , err := mergeStats (stats )
1729+ if err != nil || encodedDS == nil {
1730+ return err
1731+ }
1732+
1733+ entries := []* Entry {{
1734+ Key : y .KeyWithTs (lfDiscardStatsKey , 1 ),
1735+ Value : encodedDS ,
1736+ }}
1737+ req , err := vlog .db .sendToWriteCh (entries )
1738+ // No special handling of ErrBlockedWrites is required as err is just logged in
1739+ // for loop below.
1740+ if err != nil {
1741+ return errors .Wrapf (err , "failed to push discard stats to write channel" )
1742+ }
1743+ return req .Wait ()
17221744 }
1723- vlog .lfDiscardStats .updatesSinceFlush = 0
1724- return req .Wait ()
1725- }
17261745
1727- // encodedDiscardStats returns []byte representation of lfDiscardStats
1728- // This will be called while storing stats in BadgerDB
1729- // caller should acquire lock before encoding the stats.
1730- func (vlog * valueLog ) encodedDiscardStats () []byte {
1731- encodedStats , _ := json .Marshal (vlog .lfDiscardStats .m )
1732- return encodedStats
1746+ closer := vlog .lfDiscardStats .closer
1747+ for {
1748+ select {
1749+ case <- closer .HasBeenClosed ():
1750+ // For simplicity just return without processing already present in stats in flushChan.
1751+ return
1752+ case stats := <- vlog .lfDiscardStats .flushChan :
1753+ if err := process (stats ); err != nil {
1754+ vlog .opt .Errorf ("unable to process discardstats with error: %s" , err )
1755+ }
1756+ }
1757+ }
17331758}
17341759
1735- // populateDiscardStats populates vlog.lfDiscardStats
1736- // This function will be called while initializing valueLog
1760+ // populateDiscardStats populates vlog.lfDiscardStats.
1761+ // This function will be called while initializing valueLog.
17371762func (vlog * valueLog ) populateDiscardStats () error {
17381763 key := y .KeyWithTs (lfDiscardStatsKey , math .MaxUint64 )
17391764 var statsMap map [uint32 ]int64
@@ -1785,6 +1810,6 @@ func (vlog *valueLog) populateDiscardStats() error {
17851810 return errors .Wrapf (err , "failed to unmarshal discard stats" )
17861811 }
17871812 vlog .opt .Debugf ("Value Log Discard stats: %v" , statsMap )
1788- vlog .lfDiscardStats = & lfDiscardStats { m : statsMap }
1813+ vlog .lfDiscardStats . flushChan <- statsMap
17891814 return nil
17901815}
0 commit comments