github.com/thanos-io/thanos@v0.32.5/pkg/block/indexheader/reader_pool.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package indexheader
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/go-kit/log"
    12  	"github.com/go-kit/log/level"
    13  	"github.com/oklog/ulid"
    14  	"github.com/pkg/errors"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/thanos-io/objstore"
    17  )
    18  
    19  // ReaderPoolMetrics holds metrics tracked by ReaderPool.
    20  type ReaderPoolMetrics struct {
    21  	lazyReader *LazyBinaryReaderMetrics
    22  }
    23  
    24  // NewReaderPoolMetrics makes new ReaderPoolMetrics.
    25  func NewReaderPoolMetrics(reg prometheus.Registerer) *ReaderPoolMetrics {
    26  	return &ReaderPoolMetrics{
    27  		lazyReader: NewLazyBinaryReaderMetrics(reg),
    28  	}
    29  }
    30  
    31  // ReaderPool is used to istantiate new index-header readers and keep track of them.
    32  // When the lazy reader is enabled, the pool keeps track of all instantiated readers
    33  // and automatically close them once the idle timeout is reached. A closed lazy reader
    34  // will be automatically re-opened upon next usage.
    35  type ReaderPool struct {
    36  	lazyReaderEnabled     bool
    37  	lazyReaderIdleTimeout time.Duration
    38  	logger                log.Logger
    39  	metrics               *ReaderPoolMetrics
    40  
    41  	// Channel used to signal once the pool is closing.
    42  	close chan struct{}
    43  
    44  	// Keep track of all readers managed by the pool.
    45  	lazyReadersMx sync.Mutex
    46  	lazyReaders   map[*LazyBinaryReader]struct{}
    47  }
    48  
    49  // NewReaderPool makes a new ReaderPool.
    50  func NewReaderPool(logger log.Logger, lazyReaderEnabled bool, lazyReaderIdleTimeout time.Duration, metrics *ReaderPoolMetrics) *ReaderPool {
    51  	p := &ReaderPool{
    52  		logger:                logger,
    53  		metrics:               metrics,
    54  		lazyReaderEnabled:     lazyReaderEnabled,
    55  		lazyReaderIdleTimeout: lazyReaderIdleTimeout,
    56  		lazyReaders:           make(map[*LazyBinaryReader]struct{}),
    57  		close:                 make(chan struct{}),
    58  	}
    59  
    60  	// Start a goroutine to close idle readers (only if required).
    61  	if p.lazyReaderEnabled && p.lazyReaderIdleTimeout > 0 {
    62  		checkFreq := p.lazyReaderIdleTimeout / 10
    63  
    64  		go func() {
    65  			for {
    66  				select {
    67  				case <-p.close:
    68  					return
    69  				case <-time.After(checkFreq):
    70  					p.closeIdleReaders()
    71  				}
    72  			}
    73  		}()
    74  	}
    75  
    76  	return p
    77  }
    78  
    79  // NewBinaryReader creates and returns a new binary reader. If the pool has been configured
    80  // with lazy reader enabled, this function will return a lazy reader. The returned lazy reader
    81  // is tracked by the pool and automatically closed once the idle timeout expires.
    82  func (p *ReaderPool) NewBinaryReader(ctx context.Context, logger log.Logger, bkt objstore.BucketReader, dir string, id ulid.ULID, postingOffsetsInMemSampling int) (Reader, error) {
    83  	var reader Reader
    84  	var err error
    85  
    86  	if p.lazyReaderEnabled {
    87  		reader, err = NewLazyBinaryReader(ctx, logger, bkt, dir, id, postingOffsetsInMemSampling, p.metrics.lazyReader, p.onLazyReaderClosed)
    88  	} else {
    89  		reader, err = NewBinaryReader(ctx, logger, bkt, dir, id, postingOffsetsInMemSampling)
    90  	}
    91  
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// Keep track of lazy readers only if required.
    97  	if p.lazyReaderEnabled && p.lazyReaderIdleTimeout > 0 {
    98  		p.lazyReadersMx.Lock()
    99  		p.lazyReaders[reader.(*LazyBinaryReader)] = struct{}{}
   100  		p.lazyReadersMx.Unlock()
   101  	}
   102  
   103  	return reader, err
   104  }
   105  
   106  // Close the pool and stop checking for idle readers. No reader tracked by this pool
   107  // will be closed. It's the caller responsibility to close readers.
   108  func (p *ReaderPool) Close() {
   109  	close(p.close)
   110  }
   111  
   112  func (p *ReaderPool) closeIdleReaders() {
   113  	idleTimeoutAgo := time.Now().Add(-p.lazyReaderIdleTimeout).UnixNano()
   114  
   115  	for _, r := range p.getIdleReadersSince(idleTimeoutAgo) {
   116  		if err := r.unloadIfIdleSince(idleTimeoutAgo); err != nil && !errors.Is(err, errNotIdle) {
   117  			level.Warn(p.logger).Log("msg", "failed to close idle index-header reader", "err", err)
   118  		}
   119  	}
   120  }
   121  
   122  func (p *ReaderPool) getIdleReadersSince(ts int64) []*LazyBinaryReader {
   123  	p.lazyReadersMx.Lock()
   124  	defer p.lazyReadersMx.Unlock()
   125  
   126  	var idle []*LazyBinaryReader
   127  	for r := range p.lazyReaders {
   128  		if r.isIdleSince(ts) {
   129  			idle = append(idle, r)
   130  		}
   131  	}
   132  
   133  	return idle
   134  }
   135  
   136  func (p *ReaderPool) isTracking(r *LazyBinaryReader) bool {
   137  	p.lazyReadersMx.Lock()
   138  	defer p.lazyReadersMx.Unlock()
   139  
   140  	_, ok := p.lazyReaders[r]
   141  	return ok
   142  }
   143  
   144  func (p *ReaderPool) onLazyReaderClosed(r *LazyBinaryReader) {
   145  	p.lazyReadersMx.Lock()
   146  	defer p.lazyReadersMx.Unlock()
   147  
   148  	// When this function is called, it means the reader has been closed NOT because was idle
   149  	// but because the consumer closed it. By contract, a reader closed by the consumer can't
   150  	// be used anymore, so we can automatically remove it from the pool.
   151  	delete(p.lazyReaders, r)
   152  }