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  }