s3: pin small ranged GETs to range reads

A ranged GET whose first read lands within SeqTolerance of offset 0 is
judged sequential immediately, and even a far-starting range could flip
back mid-request; either way readChunkSliceAt downloads each covered
chunk in full, multiplying disk reads for small ranged reads (measured
~7x). Pin random mode for ranged requests no larger than SeqTolerance so
all of the request's buffer reads stay range fetches. Larger ranges keep
the dynamic pattern, where whole-chunk fetches amortize.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Chris LuandDevin committed 2026-10-02 09:33:33 -07:00
1 parent 8227ae3f49
commit 58aa9acfb0
4 files changed
+34 -1

No files matched your search

+6
View File
@@ -180,6 +180,12 @@ func (c *ChunkReadAt) ReleaseStream() {
c.readerCache.releaseStream(&c.stream)
}
// PinRandomMode pins this reader to range fetches; used by callers that
// already know the request is a partial read, e.g. a small ranged GET.
func (c *ChunkReadAt) PinRandomMode() {
c.readerPattern.PinRandomMode()
}
func (c *ChunkReadAt) ReadAt(p []byte, offset int64) (n int, err error) {
c.readerPattern.MonitorReadAt(offset, len(p))
+9 -1
View File
@@ -7,6 +7,7 @@ import (
type ReaderPattern struct {
isSequentialCounter int64
readFrontier int64 // highest (offset+size) observed across reads
pinnedRandom int64 // set when the caller already knows the reads are a partial range
}
const ModeChangeLimit = 3
@@ -65,5 +66,12 @@ func (rp *ReaderPattern) MonitorReadAt(offset int64, size int) {
}
func (rp *ReaderPattern) IsRandomMode() bool {
return atomic.LoadInt64(&rp.isSequentialCounter) < 0
return atomic.LoadInt64(&rp.pinnedRandom) != 0 || atomic.LoadInt64(&rp.isSequentialCounter) < 0
}
// PinRandomMode pins the pattern to random for readers that already know
// they serve a partial range, e.g. a small ranged GET: contiguous buffer
// reads within the request must not escalate into whole-chunk fetches.
func (rp *ReaderPattern) PinRandomMode() {
atomic.StoreInt64(&rp.pinnedRandom, 1)
}
+13
View File
@@ -123,3 +123,16 @@ func TestReaderPatternRangedReadStaysRandom(t *testing.T) {
}
}
// A reader pinned random (a known small ranged request) must stay random
// for every buffer of the request, near or far.
func TestReaderPatternPinnedRandom(t *testing.T) {
rp := NewReaderPattern()
rp.PinRandomMode()
for i := int64(0); i < 10; i++ {
rp.MonitorReadAt(i*256*1024, 256*1024) // contiguous from the start
if !rp.IsRandomMode() {
t.Fatalf("pinned reader flipped to sequential at read %d", i+1)
}
}
}
+6
View File
@@ -1104,6 +1104,12 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
chunkViews := filer.ViewFromVisibleIntervals(visibleIntervals, offset, size)
reader := filer.NewChunkReaderAtFromClient(ctx, s3a.readerCache, chunkViews, totalSize, filer.DefaultPrefetchCount)
defer reader.ReleaseStream()
// A small ranged request reads back less than a whole chunk; pin random
// mode so every buffer fetch stays a range read instead of downloading
// each covered chunk in full.
if isRangeRequest && size <= filer.SeqTolerance {
reader.PinRandomMode()
}
streamPrepTime = time.Since(tStreamPrep)
// A cached chunk whose volume server is down, or whose needle was evicted and