github.com/koko1123/flow-go-1@v0.29.6/module/executiondatasync/pruner/pruner.go (about) 1 package pruner 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/rs/zerolog" 9 10 "github.com/koko1123/flow-go-1/module" 11 "github.com/koko1123/flow-go-1/module/component" 12 "github.com/koko1123/flow-go-1/module/executiondatasync/tracker" 13 "github.com/koko1123/flow-go-1/module/irrecoverable" 14 "github.com/koko1123/flow-go-1/module/util" 15 ) 16 17 const ( 18 defaultHeightRangeTarget = uint64(400_000) 19 defaultThreshold = uint64(100_000) 20 ) 21 22 // Pruner is a component responsible for pruning data from 23 // execution data storage. It is configured with the following 24 // parameters: 25 // - Height range target: The target number of most recent blocks 26 // to store data for. This controls the total amount of data 27 // stored on disk. 28 // - Threshold: The number of block heights that we can exceed 29 // the height range target by before pruning is triggered. This 30 // controls the frequency of pruning. 31 // 32 // The Pruner consumes a stream of tracked height notifications, 33 // and triggers pruning once the difference between the tracked 34 // height and the last pruned height reaches the height range 35 // target + threshold. 36 // A height is considered fulfilled once it has both been executed, 37 // tracked, and sealed. 38 type Pruner struct { 39 storage tracker.Storage 40 pruneCallback func(ctx context.Context) error 41 42 // channels used to send new fulfilled heights and config changes to the worker thread 43 fulfilledHeights chan uint64 44 thresholdChan chan uint64 45 heightRangeTargetChan chan uint64 46 47 lastFulfilledHeight uint64 48 lastPrunedHeight uint64 49 50 // the height range is the range of heights between the last pruned and last fulfilled 51 // heightRangeTarget is the target minimum value for this range, so that after pruning 52 // the height range is equal to the target. 53 heightRangeTarget uint64 54 55 // threshold defines the maximum height range and how frequently pruning is performed. 56 // once the height range reaches `heightRangeTarget+threshold`, `threshold` many blocks 57 // are pruned 58 threshold uint64 59 60 logger zerolog.Logger 61 metrics module.ExecutionDataPrunerMetrics 62 63 component.Component 64 cm *component.ComponentManager 65 } 66 67 type PrunerOption func(*Pruner) 68 69 // WithHeightRangeTarget is used to configure the pruner with a custom 70 // height range target. 71 func WithHeightRangeTarget(heightRangeTarget uint64) PrunerOption { 72 return func(p *Pruner) { 73 p.heightRangeTarget = heightRangeTarget 74 } 75 } 76 77 // WithThreshold is used to configure the pruner with a custom threshold. 78 func WithThreshold(threshold uint64) PrunerOption { 79 return func(p *Pruner) { 80 p.threshold = threshold 81 } 82 } 83 84 func WithPruneCallback(callback func(context.Context) error) PrunerOption { 85 return func(p *Pruner) { 86 p.pruneCallback = callback 87 } 88 } 89 90 // NewPruner creates a new Pruner. 91 func NewPruner(logger zerolog.Logger, metrics module.ExecutionDataPrunerMetrics, storage tracker.Storage, opts ...PrunerOption) (*Pruner, error) { 92 lastPrunedHeight, err := storage.GetPrunedHeight() 93 if err != nil { 94 return nil, fmt.Errorf("failed to get pruned height: %w", err) 95 } 96 97 fulfilledHeight, err := storage.GetFulfilledHeight() 98 if err != nil { 99 return nil, fmt.Errorf("failed to get fulfilled height: %w", err) 100 } 101 102 fulfilledHeights := make(chan uint64, 32) 103 fulfilledHeights <- fulfilledHeight 104 105 p := &Pruner{ 106 logger: logger.With().Str("component", "execution_data_pruner").Logger(), 107 storage: storage, 108 pruneCallback: func(ctx context.Context) error { return nil }, 109 fulfilledHeights: fulfilledHeights, 110 thresholdChan: make(chan uint64), 111 heightRangeTargetChan: make(chan uint64), 112 lastFulfilledHeight: fulfilledHeight, 113 lastPrunedHeight: lastPrunedHeight, 114 heightRangeTarget: defaultHeightRangeTarget, 115 threshold: defaultThreshold, 116 metrics: metrics, 117 } 118 p.cm = component.NewComponentManagerBuilder(). 119 AddWorker(p.loop). 120 Build() 121 p.Component = p.cm 122 123 for _, opt := range opts { 124 opt(p) 125 } 126 127 return p, nil 128 } 129 130 // NotifyFulfilledHeight notifies the Pruner of the latest fulfilled height. 131 func (p *Pruner) NotifyFulfilledHeight(height uint64) { 132 if util.CheckClosed(p.cm.ShutdownSignal()) { 133 return 134 } 135 136 select { 137 case p.fulfilledHeights <- height: 138 default: 139 } 140 141 } 142 143 // SetHeightRangeTarget updates the Pruner's height range target. 144 // This may block for the duration of a pruning operation. 145 func (p *Pruner) SetHeightRangeTarget(heightRangeTarget uint64) error { 146 select { 147 case p.heightRangeTargetChan <- heightRangeTarget: 148 return nil 149 case <-p.cm.ShutdownSignal(): 150 return component.ErrComponentShutdown 151 } 152 } 153 154 // SetThreshold update's the Pruner's threshold. 155 // This may block for the duration of a pruning operation. 156 func (p *Pruner) SetThreshold(threshold uint64) error { 157 select { 158 case p.thresholdChan <- threshold: 159 return nil 160 case <-p.cm.ShutdownSignal(): 161 return component.ErrComponentShutdown 162 } 163 } 164 165 func (p *Pruner) loop(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 166 ready() 167 168 for { 169 select { 170 case <-ctx.Done(): 171 return 172 case height := <-p.fulfilledHeights: 173 if height > p.lastFulfilledHeight { 174 p.lastFulfilledHeight = height 175 } 176 p.checkPrune(ctx) 177 case heightRangeTarget := <-p.heightRangeTargetChan: 178 p.heightRangeTarget = heightRangeTarget 179 p.checkPrune(ctx) 180 case threshold := <-p.thresholdChan: 181 p.threshold = threshold 182 p.checkPrune(ctx) 183 } 184 } 185 } 186 187 func (p *Pruner) checkPrune(ctx irrecoverable.SignalerContext) { 188 if p.lastFulfilledHeight > p.heightRangeTarget+p.threshold+p.lastPrunedHeight { 189 pruneHeight := p.lastFulfilledHeight - p.heightRangeTarget 190 191 p.logger.Info().Uint64("prune_height", pruneHeight).Msg("pruning storage") 192 start := time.Now() 193 194 if err := p.storage.PruneUpToHeight(pruneHeight); err != nil { 195 ctx.Throw(fmt.Errorf("failed to prune: %w", err)) 196 } 197 198 if err := p.pruneCallback(ctx); err != nil { 199 ctx.Throw(err) 200 } 201 202 duration := time.Since(start) 203 p.logger.Info().Dur("duration", duration).Msg("pruned storage") 204 205 p.metrics.Pruned(pruneHeight, duration) 206 207 p.lastPrunedHeight = pruneHeight 208 } 209 }