mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-26 09:54:47 +00:00
CP10-3 Tier 1 optimizations (T1-T4): - TCP_NODELAY + 256KB socket buffers on NVMe/TCP connections - Response batching: all C2H data chunks + CapsuleResp in single flush - Tiered buffer pool (4KB/64KB/256KB sync.Pool) for write payloads - Configurable MaxH2CDataLength wiring through controller/IC/chunking BUG-CP103-1: NVMe write retry with jittered backoff for transient WAL pressure - writeWithRetry() with bounded backoff [50/200/800ms] - throttleOnWALPressure() pre-write delay above 90% WAL usage - WALPressureProvider interface + NVMeAdapter.WALPressure() BUG-CP103-2: Volume-level WAL admission control - WALAdmission with counting semaphore (max concurrent writers) - Soft watermark (0.7): small delay to desynchronize herd - Hard watermark (0.9): block until flusher drains - Single-deadline budget shared across watermark wait + semaphore - Close-aware during both watermark and semaphore waits - Wired into BlockVol.WriteLBA() and Trim() Benchmark platform enhancements: - NVMe benchmark actions and scenarios (A/B, CW sweep, IOQ sweep) - Database benchmark actions (SQLite, pgbench) - K8s operator QA reconciler tests - New testrunner scenarios for HA, fault injection, CSI lifecycle Test counts: 213 NVMe + 625 engine + operator + testrunner tests, all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
81 lines
2.2 KiB
Go
81 lines
2.2 KiB
Go
package nvme
|
|
|
|
import (
|
|
"errors"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
|
|
)
|
|
|
|
// WALPressureProvider extends BlockDevice with WAL pressure reporting.
|
|
type WALPressureProvider interface {
|
|
WALPressure() float64 // 0.0 = empty, 1.0 = full
|
|
}
|
|
|
|
// isRetryableWALPressure returns true if the error represents transient
|
|
// WAL pressure that may clear with a short retry.
|
|
func isRetryableWALPressure(err error) bool {
|
|
return err != nil && errors.Is(err, blockvol.ErrWALFull)
|
|
}
|
|
|
|
// writeRetryBackoffs defines the backoff schedule for writeWithRetry.
|
|
var writeRetryBackoffs = [3]time.Duration{
|
|
50 * time.Millisecond,
|
|
200 * time.Millisecond,
|
|
800 * time.Millisecond,
|
|
}
|
|
|
|
// sleepFn is the sleep function used by retry/throttle helpers.
|
|
// Replaced in tests for deterministic behavior.
|
|
var sleepFn = time.Sleep
|
|
|
|
// jitterFn returns a jitter duration given a max value.
|
|
// Replaced in tests for deterministic behavior.
|
|
var jitterFn = func(max time.Duration) time.Duration {
|
|
if max <= 0 {
|
|
return 0
|
|
}
|
|
return time.Duration(rand.Int63n(int64(max)))
|
|
}
|
|
|
|
// writeWithRetry wraps dev.WriteAt with target-side retry on WAL pressure.
|
|
// Non-WAL errors return immediately. On WAL pressure, retries with jittered
|
|
// backoff before giving up. Returns the last error unchanged so mapBlockError
|
|
// preserves DNR=0 semantics.
|
|
func writeWithRetry(dev BlockDevice, lba uint64, data []byte) error {
|
|
err := dev.WriteAt(lba, data)
|
|
if err == nil || !isRetryableWALPressure(err) {
|
|
return err
|
|
}
|
|
|
|
for _, backoff := range writeRetryBackoffs {
|
|
jitter := jitterFn(backoff / 4)
|
|
sleepFn(backoff + jitter)
|
|
err = dev.WriteAt(lba, data)
|
|
if err == nil || !isRetryableWALPressure(err) {
|
|
return err
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// throttleOnWALPressure inserts a small delay when WAL pressure is high,
|
|
// desynchronizing concurrent writers to reduce thundering-herd retry storms.
|
|
// No-op if the device does not implement WALPressureProvider.
|
|
func throttleOnWALPressure(dev BlockDevice) {
|
|
prov, ok := dev.(WALPressureProvider)
|
|
if !ok {
|
|
return
|
|
}
|
|
p := prov.WALPressure()
|
|
if p < 0.9 {
|
|
return
|
|
}
|
|
// Scale: 0.9→1ms, 0.95→3ms, 1.0→5ms
|
|
ms := (p - 0.9) * 50
|
|
if ms > 0 {
|
|
sleepFn(time.Duration(ms * float64(time.Millisecond)))
|
|
}
|
|
}
|