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") + } +} +