feat: expose local scheduler admission snapshots (#3603)

This commit is contained in:
安正超
2026-06-19 09:14:50 +08:00
committed by GitHub
parent 53ff19dd2d
commit b106b628c1
4 changed files with 175 additions and 20 deletions
+39 -15
View File
@@ -5,19 +5,19 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
## Current Context ## Current Context
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660) - Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
- Branch: `overtrue/arch-scheduler-workload-contracts` - Branch: `overtrue/arch-set-local-scheduler-snapshots`
- Baseline: stacked on `origin/overtrue/arch-test-harness-compat-aliases` - Baseline: `origin/main` after `rustfs/rustfs#3602`
after `rustfs/rustfs#3601` (`56c3cf50ae93d6705cf9f615e49b66787cb99954`).
(`dc099f415670d94c0f79a7f9015653a08cdf458f`). - PR type for this branch: `api-extraction`
- PR type for this branch: `test-only` plus `contract`
- Runtime behavior changes: none. - Runtime behavior changes: none.
- Rust code changes: add scheduler preservation tests and workload admission - Rust code changes: expose local foreground-read admission snapshots from
contract types to `rustfs-concurrency`. RustFS storage concurrency through the `rustfs-concurrency` workload contract.
- CI/script changes: extend migration re-export guard coverage for workload - CI/script changes: extend migration guard coverage for the storage
admission contract exports. `WorkloadAdmissionSnapshotProvider` implementation.
- Docs changes: add - Docs changes: add
set-local snapshot extraction notes to
[`workload-admission-contracts.md`](workload-admission-contracts.md) and [`workload-admission-contracts.md`](workload-admission-contracts.md) and
record the combined PR-05/TEST-SCH-001 plus PR-07/R-015 slice. record the API-055/SCH-001 extraction slice.
## Phase 0 Tasks ## Phase 0 Tasks
@@ -144,6 +144,19 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
scheduling, placement, membership, or business call-site migration. scheduling, placement, membership, or business call-site migration.
- Verification: workload contract unit tests, focused concurrency check, - Verification: workload contract unit tests, focused concurrency check,
migration guard, formatting, diff hygiene, and three-expert review. migration guard, formatting, diff hygiene, and three-expert review.
- [x] `API-055/SCH-001` Expose set-local scheduler admission snapshot.
- Completed slice: implement `WorkloadAdmissionSnapshotProvider` for the
RustFS storage `ConcurrencyManager` and expose foreground-read disk-read
permit usage through a local read-only workload registry snapshot.
- Acceptance: local foreground read admission reports active permit usage,
configured limit, and open/saturated/disabled state without ECStore,
admin-route, cluster, or scheduler mutation dependencies.
- Must preserve: disk-read semaphore acquisition, priority assignment,
buffer sizing, storage media detection, request guards, and queue behavior.
- Verification: storage concurrency tests, focused RustFS library check,
migration guard, formatting, diff hygiene, and three-expert review.
- [x] `TEST-PRTYPE-001` Check PR type enum consistency. - [x] `TEST-PRTYPE-001` Check PR type enum consistency.
- Acceptance: `./scripts/check_architecture_migration_rules.sh` parses the - Acceptance: `./scripts/check_architecture_migration_rules.sh` parses the
allowed PR types from [`crate-boundaries.md`](crate-boundaries.md) and fails allowed PR types from [`crate-boundaries.md`](crate-boundaries.md) and fails
@@ -1729,8 +1742,9 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
1. `contract`/`consumer-migration`: wire read-only observability and topology 1. `contract`/`consumer-migration`: wire read-only observability and topology
snapshots to implementation owners without changing runtime, profiling, snapshots to implementation owners without changing runtime, profiling,
placement, or admin route behavior. placement, or admin route behavior.
2. `api-extraction`: expose the set-local scheduler snapshot after the 2. `consumer-migration`: connect additional scanner, repair, replication, and
TEST-SCH-001 and R-015 protection/contract slice. metadata admission owners to the workload registry only after each owner has
dedicated preservation coverage.
3. `pure-move`/`consumer-migration`: continue larger cleanup slices with the 3. `pure-move`/`consumer-migration`: continue larger cleanup slices with the
loss-prevention guards active for remaining ECStore compatibility contracts loss-prevention guards active for remaining ECStore compatibility contracts
now that broad compatibility passthroughs are fully closed. now that broad compatibility passthroughs are fully closed.
@@ -1739,14 +1753,24 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| Expert | Status | Notes | | Expert | Status | Notes |
|---|---|---| |---|---|---|
| Quality/architecture | passed | S-015 removes obsolete KMS admin policy action variants after the handler fallback cleanup; API-042/API-043/API-044/API-045/API-046/API-047/API-048/API-049/API-050/API-051/API-052/API-053/API-054 narrow notify, S3 Select, OBS, IAM, Swift, heal, scanner, RustFS runtime, test, fuzz, lifecycle helper, harness, and RustFS runtime compatibility contracts without moving ECStore storage metadata ownership; G-011/G-012/G-013 add docs-only baselines for scheduler, placement/repair, and profiling/NUMA work; Issue #660 PR-08/PR-09 add read-only observability and topology contracts in storage-api only; current PR-05/PR-07 slice adds scheduler preservation tests and a small workload admission contract in concurrency only. | | Quality/architecture | passed | S-015 removes obsolete KMS admin policy action variants after the handler fallback cleanup; API-042/API-043/API-044/API-045/API-046/API-047/API-048/API-049/API-050/API-051/API-052/API-053/API-054 narrow notify, S3 Select, OBS, IAM, Swift, heal, scanner, RustFS runtime, test, fuzz, lifecycle helper, harness, and RustFS runtime compatibility contracts without moving ECStore storage metadata ownership; G-011/G-012/G-013 add docs-only baselines for scheduler, placement/repair, and profiling/NUMA work; Issue #660 PR-08/PR-09 add read-only observability and topology contracts in storage-api only; PR-05/PR-07 add scheduler preservation tests and workload contracts; current API-055/SCH-001 adds a local storage concurrency provider only. |
| Migration preservation | passed | KMS endpoint URLs, query aliases, request bodies, response contracts, and dedicated `kms:*` authorization behavior are preserved; event builder call sites, ECStore event bridge conversion, restore event data, version IDs, metadata filtering, config read/save semantics, S3 Select store/error/buffer semantics, OBS metrics state reads, IAM config/notification/error semantics, Swift bucket metadata access, heal disk/resume/task behavior, scanner lifecycle/replication/data-usage behavior, RustFS startup/admin/app/storage runtime access, e2e/test/fuzz import behavior, lifecycle expiration/transition helper DTO field contracts, flattened harness and RustFS runtime scalar/secondary alias behavior, unchanged no-op handling, remove-event behavior, scheduler/readiness/placement/profiling runtime behavior, platform gates, missing/unknown capability states, placement/topology labels, scheduler thresholds, queue snapshot semantics, and admission behavior are preserved. | | Migration preservation | passed | KMS endpoint URLs, query aliases, request bodies, response contracts, and dedicated `kms:*` authorization behavior are preserved; event builder call sites, ECStore event bridge conversion, restore event data, version IDs, metadata filtering, config read/save semantics, S3 Select store/error/buffer semantics, OBS metrics state reads, IAM config/notification/error semantics, Swift bucket metadata access, heal disk/resume/task behavior, scanner lifecycle/replication/data-usage behavior, RustFS startup/admin/app/storage runtime access, e2e/test/fuzz import behavior, lifecycle expiration/transition helper DTO field contracts, flattened harness and RustFS runtime scalar/secondary alias behavior, unchanged no-op handling, remove-event behavior, scheduler/readiness/placement/profiling runtime behavior, platform gates, missing/unknown capability states, placement/topology labels, scheduler thresholds, queue snapshot semantics, disk-read semaphore behavior, and admission behavior are preserved. |
| Testing/verification | passed | Focused compiles/tests, fuzz target compile, guards, formatting, diff hygiene, risk scan, and full `make pre-commit` passed for prior code slices; current Issue #660 PR-05/PR-07 slice uses concurrency tests/checks, migration guard, formatting, diff hygiene, and three-expert review. | | Testing/verification | passed | Focused compiles/tests, fuzz target compile, guards, formatting, diff hygiene, risk scan, and full `make pre-commit` passed for prior code slices; current Issue #660 API-055/SCH-001 slice uses storage concurrency tests, focused RustFS library check, migration guard, formatting, diff hygiene, and three-expert review. |
## Verification Notes ## Verification Notes
Passed before push: Passed before push:
- Issue #660 API-055/SCH-001 current slice:
- `cargo test -p rustfs --lib storage::concurrency::manager::integration_tests -- --nocapture`: passed.
- `cargo check -p rustfs --lib`: passed.
- `./scripts/check_architecture_migration_rules.sh`: passed.
- `./scripts/check_layer_dependencies.sh`: passed.
- `cargo fmt --all --check`: passed.
- `git diff --check`: passed.
- `make pre-commit`: passed.
- Three-expert review: passed.
- Issue #660 PR-05/PR-07 current slice: - Issue #660 PR-05/PR-07 current slice:
- `cargo test -p rustfs-concurrency --no-fail-fast`: passed. - `cargo test -p rustfs-concurrency --no-fail-fast`: passed.
- `cargo check -p rustfs-concurrency`: passed. - `cargo check -p rustfs-concurrency`: passed.
@@ -41,3 +41,21 @@ scanner, heal, replication, or ECStore placement behavior.
- No scheduler decision logic, queue capacity, Tokio runtime default, scanner - No scheduler decision logic, queue capacity, Tokio runtime default, scanner
admission, heal admission, replication admission, placement, membership, or admission, heal admission, replication admission, placement, membership, or
NUMA behavior changes are part of this slice. NUMA behavior changes are part of this slice.
## Set-Local Snapshot Extraction
The RustFS storage `ConcurrencyManager` now implements
`WorkloadAdmissionSnapshotProvider` for local foreground-read admission:
- `ForegroundRead` reports local disk-read permit usage through
`GetObjectQueueSnapshot`.
- `active` is the number of disk-read permits currently in use.
- `limit` is the configured maximum concurrent disk reads.
- `queued` remains `None` because the current semaphore does not expose waiter
counts.
- Scanner, repair, replication, foreground write, and metadata entries remain
`Unknown` until their owning runtime components expose read-only status.
This is an observation surface only. Permit acquisition, priority assignment,
buffer sizing, storage media detection, request guards, and queue behavior are
unchanged.
+106 -5
View File
@@ -19,7 +19,10 @@ use super::io_schedule::{
get_advanced_buffer_size, get_advanced_buffer_size,
}; };
use super::request_guard::GetObjectGuard; use super::request_guard::GetObjectGuard;
use rustfs_concurrency::GetObjectQueueSnapshot; use rustfs_concurrency::{
AdmissionState, GetObjectQueueSnapshot, WorkloadAdmissionRegistrySnapshot, WorkloadAdmissionSnapshot,
WorkloadAdmissionSnapshotProvider, WorkloadClass,
};
use rustfs_config::{KI_B, MI_B}; use rustfs_config::{KI_B, MI_B};
use rustfs_io_core::BytesPool; use rustfs_io_core::BytesPool;
use rustfs_io_core::io_profile::{AccessPattern, IoPatternDetector, StorageMedia, detect_storage_media}; use rustfs_io_core::io_profile::{AccessPattern, IoPatternDetector, StorageMedia, detect_storage_media};
@@ -521,10 +524,7 @@ impl ConcurrencyManager {
/// ///
/// Returns information about permit usage and waiting requests. /// Returns information about permit usage and waiting requests.
pub fn io_queue_status(&self) -> IoQueueStatus { pub fn io_queue_status(&self) -> IoQueueStatus {
let snapshot = GetObjectQueueSnapshot::from_available_permits( let snapshot = self.get_object_queue_snapshot();
self.scheduler_config.max_concurrent_reads,
self.disk_read_semaphore.available_permits(),
);
IoQueueStatus { IoQueueStatus {
total_permits: snapshot.total_permits, total_permits: snapshot.total_permits,
@@ -539,6 +539,53 @@ impl ConcurrencyManager {
} }
} }
/// Get a read-only snapshot of local GetObject disk-read admission.
pub fn get_object_queue_snapshot(&self) -> GetObjectQueueSnapshot {
GetObjectQueueSnapshot::from_available_permits(
self.scheduler_config.max_concurrent_reads,
self.disk_read_semaphore.available_permits(),
)
}
/// Get a read-only workload admission snapshot for foreground reads.
pub fn get_object_admission_snapshot(&self) -> WorkloadAdmissionSnapshot {
let snapshot = self.get_object_queue_snapshot();
let state = if snapshot.total_permits == 0 {
AdmissionState::Disabled
} else if snapshot.permits_available() == 0 {
AdmissionState::Saturated
} else {
AdmissionState::Open
};
let admission = WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundRead, state).with_counts(
Some(snapshot.permits_in_use),
None,
Some(snapshot.total_permits),
);
match state {
AdmissionState::Disabled => admission.with_reason("disk read permits disabled"),
AdmissionState::Saturated => admission.with_reason("all disk read permits are in use"),
_ => admission,
}
}
/// Get a read-only workload admission registry snapshot for local storage concurrency.
pub fn workload_admission_registry_snapshot(&self) -> WorkloadAdmissionRegistrySnapshot {
let entries = WorkloadClass::REQUIRED
.iter()
.copied()
.map(|class| match class {
WorkloadClass::ForegroundRead => self.get_object_admission_snapshot(),
class => WorkloadAdmissionSnapshot::new(class, AdmissionState::Unknown)
.with_reason("not exposed by storage concurrency manager"),
})
.collect();
WorkloadAdmissionRegistrySnapshot::new(entries)
}
/// Acquire a disk read permit with priority awareness. /// Acquire a disk read permit with priority awareness.
/// ///
/// When priority scheduling is enabled, this method logs the priority /// When priority scheduling is enabled, this method logs the priority
@@ -573,6 +620,12 @@ impl ConcurrencyManager {
} }
} }
impl WorkloadAdmissionSnapshotProvider for ConcurrencyManager {
fn workload_admission_snapshot(&self) -> WorkloadAdmissionRegistrySnapshot {
self.workload_admission_registry_snapshot()
}
}
impl Default for ConcurrencyManager { impl Default for ConcurrencyManager {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@@ -617,6 +670,54 @@ mod integration_tests {
assert_eq!(status.low_priority_waiting, 0); assert_eq!(status.low_priority_waiting, 0);
} }
#[tokio::test]
#[serial]
async fn test_concurrency_manager_workload_admission_snapshot_tracks_disk_read_permits() {
let manager = ConcurrencyManager::new();
let initial = manager.get_object_admission_snapshot();
assert_eq!(initial.class, WorkloadClass::ForegroundRead);
assert_eq!(initial.state, AdmissionState::Open);
assert_eq!(initial.active, Some(0));
assert_eq!(initial.queued, None);
assert_eq!(initial.limit, Some(manager.scheduler_config().max_concurrent_reads));
let permit = manager.acquire_disk_read_permit().await;
assert!(permit.is_ok());
let _permit = match permit {
Ok(permit) => permit,
Err(error) => panic!("disk read permit acquisition failed: {error}"),
};
let snapshot = manager.get_object_admission_snapshot();
assert_eq!(snapshot.active, Some(1));
assert_eq!(snapshot.limit, Some(manager.scheduler_config().max_concurrent_reads));
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_workload_admission_registry_covers_required_classes() {
let manager = ConcurrencyManager::new();
let registry = manager.workload_admission_registry_snapshot();
let provider: &dyn WorkloadAdmissionSnapshotProvider = &manager;
let trait_registry = provider.workload_admission_snapshot();
assert_eq!(registry.entries(), trait_registry.entries());
assert_eq!(registry.entries().len(), WorkloadClass::REQUIRED.len());
for class in WorkloadClass::REQUIRED {
assert!(registry.get(class).is_some(), "missing workload class {class}");
}
assert_eq!(
registry.get(WorkloadClass::ForegroundRead).map(|snapshot| snapshot.state),
Some(AdmissionState::Open)
);
assert_eq!(
registry.get(WorkloadClass::Scanner).map(|snapshot| snapshot.state),
Some(AdmissionState::Unknown)
);
}
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn test_concurrency_manager_disk_read_permit() { async fn test_concurrency_manager_disk_read_permit() {
@@ -224,6 +224,18 @@ require_source_contains \
"crates/concurrency/src/lib.rs" \ "crates/concurrency/src/lib.rs" \
"WorkloadClass," \ "WorkloadClass," \
"concurrency public workload class re-export" "concurrency public workload class re-export"
require_source_contains \
"rustfs/src/storage/concurrency/manager.rs" \
"impl WorkloadAdmissionSnapshotProvider for ConcurrencyManager" \
"storage concurrency workload admission snapshot provider"
require_source_contains \
"rustfs/src/storage/concurrency/manager.rs" \
"pub fn workload_admission_registry_snapshot(&self) -> WorkloadAdmissionRegistrySnapshot" \
"storage concurrency workload admission registry snapshot"
require_source_contains \
"rustfs/src/storage/concurrency/manager.rs" \
"WorkloadClass::ForegroundRead => self.get_object_admission_snapshot()" \
"storage concurrency foreground read admission snapshot"
require_source_line \ require_source_line \
"crates/storage-api/src/lib.rs" \ "crates/storage-api/src/lib.rs" \
"pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};" \ "pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};" \