discard.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /*
  2. * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package badger
  6. import (
  7. "encoding/binary"
  8. "os"
  9. "path/filepath"
  10. "sort"
  11. "sync"
  12. "github.com/dgraph-io/badger/v4/y"
  13. "github.com/dgraph-io/ristretto/v2/z"
  14. )
  15. // discardStats keeps track of the amount of data that could be discarded for
  16. // a given logfile.
  17. type discardStats struct {
  18. sync.Mutex
  19. *z.MmapFile
  20. opt Options
  21. nextEmptySlot int
  22. }
  23. const discardFname string = "DISCARD"
  24. func InitDiscardStats(opt Options) (*discardStats, error) {
  25. fname := filepath.Join(opt.ValueDir, discardFname)
  26. // 1MB file can store 65.536 discard entries. Each entry is 16 bytes.
  27. mf, err := z.OpenMmapFile(fname, os.O_CREATE|os.O_RDWR, 1<<20)
  28. lf := &discardStats{
  29. MmapFile: mf,
  30. opt: opt,
  31. }
  32. if err == z.NewFile {
  33. // We don't need to zero out the entire 1MB.
  34. lf.zeroOut()
  35. } else if err != nil {
  36. return nil, y.Wrapf(err, "while opening file: %s\n", discardFname)
  37. }
  38. for slot := 0; slot < lf.maxSlot(); slot++ {
  39. if lf.get(16*slot) == 0 {
  40. lf.nextEmptySlot = slot
  41. break
  42. }
  43. }
  44. sort.Sort(lf)
  45. opt.Infof("Discard stats nextEmptySlot: %d\n", lf.nextEmptySlot)
  46. return lf, nil
  47. }
  48. func (lf *discardStats) Len() int {
  49. return lf.nextEmptySlot
  50. }
  51. func (lf *discardStats) Less(i, j int) bool {
  52. return lf.get(16*i) < lf.get(16*j)
  53. }
  54. func (lf *discardStats) Swap(i, j int) {
  55. left := lf.Data[16*i : 16*i+16]
  56. right := lf.Data[16*j : 16*j+16]
  57. var tmp [16]byte
  58. copy(tmp[:], left)
  59. copy(left, right)
  60. copy(right, tmp[:])
  61. }
  62. // offset is not slot.
  63. func (lf *discardStats) get(offset int) uint64 {
  64. return binary.BigEndian.Uint64(lf.Data[offset : offset+8])
  65. }
  66. func (lf *discardStats) set(offset int, val uint64) {
  67. binary.BigEndian.PutUint64(lf.Data[offset:offset+8], val)
  68. }
  69. // zeroOut would zero out the next slot.
  70. func (lf *discardStats) zeroOut() {
  71. lf.set(lf.nextEmptySlot*16, 0)
  72. lf.set(lf.nextEmptySlot*16+8, 0)
  73. }
  74. func (lf *discardStats) maxSlot() int {
  75. return len(lf.Data) / 16
  76. }
  77. // Update would update the discard stats for the given file id. If discard is
  78. // 0, it would return the current value of discard for the file. If discard is
  79. // < 0, it would set the current value of discard to zero for the file.
  80. func (lf *discardStats) Update(fidu uint32, discard int64) int64 {
  81. fid := uint64(fidu)
  82. lf.Lock()
  83. defer lf.Unlock()
  84. idx := sort.Search(lf.nextEmptySlot, func(slot int) bool {
  85. return lf.get(slot*16) >= fid
  86. })
  87. if idx < lf.nextEmptySlot && lf.get(idx*16) == fid {
  88. off := idx*16 + 8
  89. curDisc := lf.get(off)
  90. if discard == 0 {
  91. return int64(curDisc)
  92. }
  93. if discard < 0 {
  94. lf.set(off, 0)
  95. return 0
  96. }
  97. lf.set(off, curDisc+uint64(discard))
  98. return int64(curDisc + uint64(discard))
  99. }
  100. if discard <= 0 {
  101. // No need to add a new entry.
  102. return 0
  103. }
  104. // Could not find the fid. Add the entry.
  105. idx = lf.nextEmptySlot
  106. lf.set(idx*16, fid)
  107. lf.set(idx*16+8, uint64(discard))
  108. // Move to next slot.
  109. lf.nextEmptySlot++
  110. for lf.nextEmptySlot >= lf.maxSlot() {
  111. y.Check(lf.Truncate(2 * int64(len(lf.Data))))
  112. }
  113. lf.zeroOut()
  114. sort.Sort(lf)
  115. return discard
  116. }
  117. func (lf *discardStats) Iterate(f func(fid, stats uint64)) {
  118. for slot := 0; slot < lf.nextEmptySlot; slot++ {
  119. idx := 16 * slot
  120. f(lf.get(idx), lf.get(idx+8))
  121. }
  122. }
  123. // MaxDiscard returns the file id with maximum discard bytes.
  124. func (lf *discardStats) MaxDiscard() (uint32, int64) {
  125. lf.Lock()
  126. defer lf.Unlock()
  127. var maxFid, maxVal uint64
  128. lf.Iterate(func(fid, val uint64) {
  129. if maxVal < val {
  130. maxVal = val
  131. maxFid = fid
  132. }
  133. })
  134. return uint32(maxFid), int64(maxVal)
  135. }