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 }