@@ -2,9 +2,16 @@ package bucket
22
33import (
44 "context"
5+ "fmt"
56 "io"
7+ "time"
68
9+ "github.com/dustin/go-humanize"
10+ "github.com/go-kit/log"
11+ "github.com/go-kit/log/level"
712 "golang.org/x/time/rate"
13+
14+ util_log "github.com/grafana/loki/v3/pkg/util/log"
815)
916
1017// minReadSize is the minimum chunk size for reading data.
@@ -57,6 +64,16 @@ type rateLimitedReader struct {
5764 io.ReadCloser
5865 limiter * rate.Limiter
5966 ctx context.Context
67+ logger log.Logger
68+ }
69+
70+ func newRateLimitedReader (ctx context.Context , readCloser io.ReadCloser , logger log.Logger ) * rateLimitedReader {
71+ return & rateLimitedReader {
72+ ReadCloser : readCloser ,
73+ limiter : getQueryRateLimiter (ctx ),
74+ ctx : ctx ,
75+ logger : logger ,
76+ }
6077}
6178
6279// Read reads data from the underlying reader while respecting the rate limit.
@@ -74,26 +91,68 @@ func (r *rateLimitedReader) Read(p []byte) (n int, err error) {
7491 return r .ReadCloser .Read (p )
7592 }
7693
77- // Read in batches, waiting for rate limiter approval before each batch
94+ // Cap the read size to the minimum read size and the burst
95+ minReadSize := min (minReadSize , burst )
7896 totalRead := 0
79- for totalRead < len (p ) {
80- remaining := len (p ) - totalRead
81- // Use minReadSize as the read size, but don't exceed burst or remaining buffer
82- readSize := minReadSize
83- if readSize > remaining {
84- readSize = remaining
85- }
86- if readSize > burst {
87- readSize = burst
97+
98+ // Other logging stats
99+ var (
100+ rateLimitedCount int
101+ totalWaitTime time.Duration
102+ maxWaitTime time.Duration
103+ )
104+
105+ // Defer logging to ensure it happens on all exit paths
106+ defer func () {
107+ if rateLimitedCount > 0 && r .logger != nil {
108+ logger := util_log .WithContext (r .ctx , r .logger )
109+ level .Debug (logger ).Log (
110+ "msg" , "query rate limited during bucket read operation" ,
111+ "rateLimitedCount" , rateLimitedCount ,
112+ "totalWaitTime" , totalWaitTime .String (),
113+ "maxWaitTime" , maxWaitTime .String (),
114+ "readBufferSize" , humanize .Bytes (uint64 (len (p ))),
115+ "readBytes" , humanize .Bytes (uint64 (totalRead )),
116+ "remainingBytes" , humanize .Bytes (uint64 (len (p )- totalRead )),
117+ "err" , err ,
118+ )
88119 }
120+ }()
89121
90- // Wait for rate limiter approval before reading this batch
91- if err := r .limiter .WaitN (r .ctx , readSize ); err != nil {
122+ for totalRead < len (p ) {
123+ remaining := len (p ) - totalRead
124+ // Use minReadSize but cap to the remaining
125+ readSize := min (minReadSize , remaining )
126+
127+ // Reserve rate limiter tokens for this batch read
128+ reservation := r .limiter .ReserveN (time .Now (), readSize )
129+ if ! reservation .OK () {
130+ // Reservation failed (e.g., readSize > burst), return error
131+ // This should not happen in practice since we cap readSize to burst
92132 if totalRead > 0 {
93- // Return what we've read so far
94133 return totalRead , nil
95134 }
96- return 0 , err
135+ return 0 , fmt .Errorf ("rate limited reader: reservation failed. readSize (%d) > burst: (%d)?" , readSize , burst )
136+ }
137+
138+ // If we need to wait, record the logging stats and wait for the delay
139+ if delay := reservation .Delay (); delay > 0 {
140+ rateLimitedCount ++
141+ totalWaitTime += delay
142+ maxWaitTime = max (maxWaitTime , delay )
143+
144+ timer := time .NewTimer (delay )
145+ select {
146+ case <- timer .C :
147+ // Delay completed, proceed
148+ case <- r .ctx .Done ():
149+ timer .Stop ()
150+ reservation .Cancel ()
151+ if totalRead > 0 {
152+ return totalRead , nil
153+ }
154+ return 0 , r .ctx .Err ()
155+ }
97156 }
98157
99158 // Read from underlying reader (up to the approved read size)
0 commit comments