mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-22 14:32:35 +00:00
Replaces the PinRandomMode size heuristic with a per-chunk coverage rule. ViewFromVisibleIntervals already clips chunk views to the request window, so a view that is not IsFullChunk() is one the request only partially needs; fetch it as a range regardless of the detected read pattern. This closes the holes a request-size pin left open: ranges larger than SeqTolerance no longer revert to whole-chunk downloads once their buffers look sequential, and ranges that fully cover a chunk keep the shared whole-chunk path instead of fetching 256KiB slices piecemeal. Prefetch (MaybeCache) skips clipped views so it cannot amplify a range read either. PinRandomMode is dropped: no caller needs it once coverage drives the fetch choice. Range fetches route through fetchChunkDataFn so tests observe them the same way as whole-chunk downloads.
70 lines
2.3 KiB
Go
70 lines
2.3 KiB
Go
package filer
|
|
|
|
import (
|
|
"sync/atomic"
|
|
)
|
|
|
|
type ReaderPattern struct {
|
|
isSequentialCounter int64
|
|
readFrontier int64 // highest (offset+size) observed across reads
|
|
}
|
|
|
|
const ModeChangeLimit = 3
|
|
|
|
// SeqTolerance: a read whose start is within this many bytes of the current read
|
|
// frontier still counts as sequential. Using a tolerance window rather than
|
|
// strict contiguity absorbs reordered/concurrent readahead (multiple ReadAt can
|
|
// be in flight at once) while still rejecting far random jumps.
|
|
const SeqTolerance = 8 << 20 // 8 MiB
|
|
|
|
// For streaming read: only cache the first chunk
|
|
// For random read: only fetch the requested range, instead of the whole chunk
|
|
|
|
func NewReaderPattern() *ReaderPattern {
|
|
return &ReaderPattern{
|
|
isSequentialCounter: 0,
|
|
readFrontier: 0,
|
|
}
|
|
}
|
|
|
|
func (rp *ReaderPattern) MonitorReadAt(offset int64, size int) {
|
|
// Advance the frontier to max(frontier, offset+size) and capture, in the same
|
|
// CAS loop, the pre-image this read is judged against. Reading the frontier
|
|
// inside the loop (rather than once up front) keeps `diff` below comparing
|
|
// against the freshest value even if a concurrent readahead advances the
|
|
// frontier while we loop. Lock-free, consistent with the rest of this type.
|
|
end := offset + int64(size)
|
|
var frontier int64
|
|
for {
|
|
frontier = atomic.LoadInt64(&rp.readFrontier)
|
|
if end <= frontier || atomic.CompareAndSwapInt64(&rp.readFrontier, frontier, end) {
|
|
break
|
|
}
|
|
}
|
|
|
|
// near = this read starts within SeqTolerance of where reads had reached.
|
|
// Hysteresis (the ±ModeChangeLimit counter) keeps a single outlier read from
|
|
// flipping the mode.
|
|
diff := offset - frontier
|
|
if diff < 0 {
|
|
diff = -diff
|
|
}
|
|
counter := atomic.LoadInt64(&rp.isSequentialCounter)
|
|
if diff <= SeqTolerance {
|
|
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 {
|
|
atomic.AddInt64(&rp.isSequentialCounter, -1)
|
|
}
|
|
}
|
|
|
|
func (rp *ReaderPattern) IsRandomMode() bool {
|
|
return atomic.LoadInt64(&rp.isSequentialCounter) < 0
|
|
}
|