From 8227ae3f496034ab55047930f1fa3c2f354b281f Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 2 Oct 2026 09:32:40 -0700 Subject: [PATCH] filer: keep a ranged read in random mode through its contiguous tail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A far ReadAt on a fresh ReaderPattern left the sequential counter at -1, so the next buffer of the same ranged request landed on the frontier and flipped the verdict straight back to sequential — readChunkSliceAt then paid a whole-chunk fetch for the remainder of the range. Drop the counter to -ModeChangeLimit when random mode is entered so the verdict needs sustained sequential evidence to undo, matching the hysteresis an established sequential stream already gets. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- weed/filer/reader_pattern.go | 9 ++++++--- weed/filer/reader_pattern_test.go | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/weed/filer/reader_pattern.go b/weed/filer/reader_pattern.go index 6a9a95f837..65184722fc 100644 --- a/weed/filer/reader_pattern.go +++ b/weed/filer/reader_pattern.go @@ -54,10 +54,13 @@ func (rp *ReaderPattern) MonitorReadAt(offset int64, size int) { if counter < ModeChangeLimit { atomic.AddInt64(&rp.isSequentialCounter, 1) } + } else if counter <= 0 { + // Entering random mode is a strong verdict: drop to the bottom of + // the window so the contiguous tail of one ranged request cannot + // flip it back on the next buffer read and pay a whole-chunk fetch. + atomic.StoreInt64(&rp.isSequentialCounter, -ModeChangeLimit) } else { - if counter > -ModeChangeLimit { - atomic.AddInt64(&rp.isSequentialCounter, -1) - } + atomic.AddInt64(&rp.isSequentialCounter, -1) } } diff --git a/weed/filer/reader_pattern_test.go b/weed/filer/reader_pattern_test.go index 54b896ab4e..0b4bea5c46 100644 --- a/weed/filer/reader_pattern_test.go +++ b/weed/filer/reader_pattern_test.go @@ -102,3 +102,24 @@ func TestReaderPatternRecoversFromRandom(t *testing.T) { t.Fatal("sustained near reads must recover sequential mode") } } + +// A ranged request's first read lands far from the frontier, but its +// remaining buffer reads are contiguous. The random verdict must stick for +// them — otherwise the tail of every range >256KiB pays a whole-chunk fetch. +func TestReaderPatternRangedReadStaysRandom(t *testing.T) { + rp := NewReaderPattern() + rp.MonitorReadAt(500*mb, 256*1024) // far first read -> -ModeChangeLimit + for i := 1; i <= 2; i++ { + rp.MonitorReadAt(500*mb+int64(i)*256*1024, 256*1024) + if !rp.IsRandomMode() { + t.Fatalf("contiguous read %d of a ranged request flipped back to sequential", i+1) + } + } + for i := 3; i < 10; i++ { + rp.MonitorReadAt(500*mb+int64(i)*256*1024, 256*1024) + } + if rp.IsRandomMode() { + t.Fatal("sustained sequential reads should restore sequential mode") + } +} +