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.pushIndex.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
}
collectGarbage removes chunks from retrieval and other
indexes if maximal number of chunks in database is reached.
This function returns the number of removed chunks. If done
is false, another call to this function is needed to collect
the rest of the garbage as the batch size limit is reached.
This function is called in collectGarbageWorker.
package localstore
import (
	"errors"
	"time"
	"github.com/ethersphere/bee/pkg/shed"
	"github.com/ethersphere/bee/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
		}
	}
}