mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-31 09:20:50 +01:00
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:
1 parent
8227ae3f49
commit
58aa9acfb0
4 files changed
+34
-1
No files matched your search
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user