func (db *DB) incGCSizeInBatch(batch *leveldb.Batch, change int64) (err error) {
	if change == 0 {
		return nil
	}
	gcSize, err := db.gcSize.Get()
	if err != nil && !errors.Is(err, leveldb.ErrNotFound) {
		return err
	}
	var newSize uint64
	if change > 0 {
		newSize = gcSize + uint64(change)
	} else {
		c := uint64(-change)
		if c > gcSize {
			return nil
		}
		newSize = gcSize - c
	}
	db.gcSize.PutInBatch(batch, newSize)
	db.metrics.GCSize.Set(float64(newSize))
	if newSize >= db.cacheCapacity {
		db.triggerGarbageCollection()
	}
	return nil
}
incGCSizeInBatch changes gcSize field value
by change which can be negative. This function
must be called under batchMu lock.
package localstore
import (
	"errors"
	"time"
	"github.com/ethsana/sana/pkg/shed"
	"github.com/ethsana/sana/pkg/swarm"
	"github.com/syndtr/goleveldb/leveldb"
)
var (
	gcTargetRatio = 0.9
	gcBatchSize uint64 = 2000
	reserveCollectionRatio = 0.5
	reserveEvictionBatch uint64 = 200
	maxPurgeablePercentageOfReserve = 0.1
)
func (db *DB) collectGarbageWorker() {
	defer close(db.collectGarbageWorkerDone)
	for {
		select {
		case <-db.collectGarbageTrigger:
			collectedCount, done, err := db.collectGarbage()
			if err != nil {
				db.logger.Errorf("localstore: collect garbage: %v", err)
			}
			if !done {
				db.triggerGarbageCollection()
			}
			if testHookCollectGarbage != nil {
				testHookCollectGarbage(collectedCount)
			}
		case <-db.close:
			return
		}
	}
}
func (db *DB) collectGarbage() (collectedCount uint64, done bool, err error) {
	db.metrics.GCCounter.Inc()
	defer func(start time.Time) {
		if err != nil {
			db.metrics.GCErrorCounter.Inc()
		}
		totalTimeMetric(db.metrics.TotalTimeCollectGarbage, start)
	}(time.Now())
	batch := new(leveldb.Batch)
	target := db.gcTarget()
	db.batchMu.Lock()
	db.gcRunning = true
	db.batchMu.Unlock()
	defer func() {
		db.batchMu.Lock()
		db.gcRunning = false
		db.dirtyAddresses = nil
		db.batchMu.Unlock()
	}()
	gcSize, err := db.gcSize.Get()
	if err != nil {
		return 0, true, err
	}
	if gcSize == target {
		return 0, true, nil
	}
	db.metrics.GCSize.Set(float64(gcSize))
	done = true
	first := true
	start := time.Now()
	candidates := make([]shed.Item, 0)
	err = db.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) {
		if first {
			totalTimeMetric(db.metrics.TotalTimeGCFirstItem, start)
			first = false
		}
		if gcSize-collectedCount <= target {
			return true, nil
		}
		candidates = append(candidates, item)
		collectedCount++
		if collectedCount >= gcBatchSize {
			return true, nil
		}
		return false, nil
	}, nil)
	if err != nil {
		return 0, false, err
	}
	db.metrics.GCCollectedCounter.Add(float64(collectedCount))
	if testHookGCIteratorDone != nil {
		testHookGCIteratorDone()
	}
	db.batchMu.Lock()
	defer totalTimeMetric(db.metrics.TotalTimeGCLock, time.Now())
	defer db.batchMu.Unlock()
	gcSize, err = db.gcSize.Get()
	if err != nil {
		return 0, false, err
	}
	for _, item := range candidates {
		if swarm.NewAddress(item.Address).MemberOf(db.dirtyAddresses) {
			collectedCount--
			continue
		}
		db.metrics.GCStoreTimeStamps.Set(float64(item.StoreTimestamp))
		db.metrics.GCStoreAccessTimeStamps.Set(float64(item.AccessTimestamp))
		err = db.retrievalDataIndex.DeleteInBatch(batch, item)
		if err != nil {
			return 0, false, err
		}
		err = db.retrievalAccessIndex.DeleteInBatch(batch, item)
		if err != nil {
			return 0, false, err
		}
		err = db.pullIndex.DeleteInBatch(batch, item)
		if err != nil {
			return 0, false, err
		}
		err = db.gcIndex.DeleteInBatch(batch, item)
		if err != nil {
			return 0, false, err
		}
		err = db.postageChunksIndex.DeleteInBatch(batch, item)
		if err != nil {
			return 0, false, err
		}
		err = db.postageIndexIndex.DeleteInBatch(batch, item)
		if err != nil {
			return 0, false, err
		}
	}
	if gcSize-collectedCount > target {
		done = false
	}
	db.metrics.GCCommittedCounter.Add(float64(collectedCount))
	db.gcSize.PutInBatch(batch, gcSize-collectedCount)
	err = db.shed.WriteBatch(batch)
	if err != nil {
		db.metrics.GCErrorCounter.Inc()
		return 0, false, err
	}
	return collectedCount, done, nil
}
func (db *DB) gcTarget() (target uint64) {
	return uint64(float64(db.cacheCapacity) * gcTargetRatio)
}
func (db *DB) reserveEvictionTarget() (target uint64) {
	targetCache := db.reserveCapacity - uint64(float64(db.cacheCapacity)*reserveCollectionRatio)
	targetCeiling := db.reserveCapacity - uint64(float64(db.reserveCapacity)*maxPurgeablePercentageOfReserve)
	if targetCeiling > targetCache {
		return targetCeiling
	}
	return targetCache
}
func (db *DB) triggerGarbageCollection() {
	select {
	case db.collectGarbageTrigger <- struct{}{}:
	case <-db.close:
	default:
	}
}
func (db *DB) triggerReserveEviction() {
	select {
	case db.reserveEvictionTrigger <- struct{}{}:
	case <-db.close:
	default:
	}
}