mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-07-20 23:12:36 +00:00
7e13340dcb
* filer: tolerance-window read pattern detection for concurrent readahead ReaderPattern detected sequential access via strict contiguity (lastReadStopOffset == offset). Under concurrent/reordered ReadAt (parallel readahead, which ReadAt already supports via RLock + atomics), that exact match frequently misses even for a genuinely sequential stream, drifting toward random mode and disabling prefetch. Track a read frontier (max offset+size, advanced with a lock-free CAS loop) and treat a read as sequential when its start is within SeqTolerance (8 MiB) of the frontier. The window absorbs reordered/concurrent readahead while still rejecting far random jumps; the existing ModeChangeLimit hysteresis is unchanged. API (NewReaderPattern/MonitorReadAt/IsRandomMode) is unchanged, so reader_at is unaffected. Adds reader_pattern_test.go. * filer: read the read frontier inside the CAS loop Capture the frontier from the CAS loop's own load rather than a separate up-front snapshot, so the sequentiality diff is judged against the freshest pre-image even if a concurrent readahead advances the frontier while we loop. Also drops the now-redundant load. Mirrors the same fix on the write-side ReaderPattern twin (review feedback on the companion PR). * filer: add frontier/boundary/recovery guard tests for ReaderPattern Mirror the regression guards added to the write-side WriterPattern: assert the max-frontier never regresses on a backward read (the CAS invariant), pin both sides of the inclusive SeqTolerance boundary, and verify sustained near reads recover sequential mode out of the negative floor. Each guards an invariant the original four tests left uncovered.
67 lines
2.0 KiB
Go
67 lines
2.0 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 > -ModeChangeLimit {
|
|
atomic.AddInt64(&rp.isSequentialCounter, -1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rp *ReaderPattern) IsRandomMode() bool {
|
|
return atomic.LoadInt64(&rp.isSequentialCounter) < 0
|
|
}
|