* fix(heal): gate auto replacement formatting Co-Authored-By: heihutu <[email protected]> * fix(heal): require replacement target outcomes Co-Authored-By: heihutu <[email protected]> * fix(heal): bind resumes to replacement targets Co-Authored-By: heihutu <[email protected]> * fix(heal): fence healing marker ownership Co-Authored-By: heihutu <[email protected]> * test(heal): cover replacement target completion Co-Authored-By: heihutu <[email protected]> * docs(heal): clarify replacement recovery status Co-Authored-By: heihutu <[email protected]> * fix(heal): canonicalize replacement target checks Co-Authored-By: heihutu <[email protected]> * fix(heal): satisfy marker test module lint Co-Authored-By: heihutu <[email protected]> * fix(heal): scope automatic replacement format Co-Authored-By: heihutu <[email protected]> * fix(heal): require a mounted replacement target Co-Authored-By: heihutu <[email protected]> * fix(ecstore): avoid cloned ref slice in test Co-Authored-By: heihutu <[email protected]> * fix(heal): revalidate replacement before scanning Co-Authored-By: heihutu <[email protected]> * fix(heal): reset stale resume checkpoints Co-Authored-By: heihutu <[email protected]> * fix(heal): release scanner disk map before probing Co-Authored-By: heihutu <[email protected]> * fix(heal): persist replacement intent before format Co-Authored-By: heihutu <[email protected]> * fix(heal): fail closed on mountinfo read errors Co-Authored-By: heihutu <[email protected]> * fix(heal): fence replacement target identity Co-Authored-By: heihutu <[email protected]> * fix(heal): order replacement completion cleanup Co-Authored-By: heihutu <[email protected]> * fix(heal): atomically seal replacement completion Co-Authored-By: heihutu <[email protected]> * test(heal): census replacement target shards Co-Authored-By: heihutu <[email protected]> * fix(heal): fence replacement recovery ownership Co-Authored-By: heihutu <[email protected]> * fix(heal): preserve replacement recovery anchors Co-Authored-By: heihutu <[email protected]> * fix(heal): satisfy replacement recovery lint gates Co-Authored-By: heihutu <[email protected]> * fix(ecstore): bind replacement identity to mount lease Co-Authored-By: heihutu <[email protected]> * test(heal): cover durable replacement recovery states Co-Authored-By: heihutu <[email protected]> * fix(heal): validate persisted resume task identifiers Co-Authored-By: heihutu <[email protected]> * fix(ecstore): avoid blocking replacement marker CAS Co-Authored-By: heihutu <[email protected]> * fix(heal): report failed marker rollback Co-Authored-By: heihutu <[email protected]> * test(heal): pin replacement resume schema compatibility Co-Authored-By: heihutu <[email protected]> * fix(heal): preserve durable recovery anchors Co-Authored-By: heihutu <[email protected]> * fix(ecstore): preserve public disk path semantics Co-Authored-By: heihutu <[email protected]> * test(heal): use canonical replacement task ids Co-Authored-By: heihutu <[email protected]> * test(heal): cover automatic replacement in 3x4 cluster Co-Authored-By: heihutu <[email protected]> * fix(heal): verify replacement target commits Co-Authored-By: heihutu <[email protected]> * fix(heal): persist replacement completion proof Co-Authored-By: heihutu <[email protected]> * feat(heal): expose durable replacement status Co-Authored-By: heihutu <[email protected]> * fix(heal): bound durable replacement discovery Co-Authored-By: heihutu <[email protected]> * fix(heal): remove replacement readiness bypass Co-Authored-By: heihutu <[email protected]> * fix(heal): retry terminal replacement cleanup Co-Authored-By: heihutu <[email protected]> * fix(heal): isolate replacement intents from legacy resume Co-Authored-By: heihutu <[email protected]> * fix(heal): migrate legacy replacement intents at startup Co-Authored-By: heihutu <[email protected]> * style(heal): apply strict clippy fix Co-Authored-By: heihutu <[email protected]> * fix(heal): prioritize active replacement recovery state Co-Authored-By: heihutu <[email protected]> * fix(heal): bind readiness to the admitted mount lease Co-Authored-By: heihutu <[email protected]> * fix(heal): atomically publish replacement intents Co-Authored-By: heihutu <[email protected]> * fix(heal): isolate replacement recovery directory Co-Authored-By: heihutu <[email protected]> * fix(heal): tolerate an empty recovery directory Co-Authored-By: heihutu <[email protected]> * style(heal): remove redundant disk bytes conversion Co-Authored-By: heihutu <[email protected]> * fix(heal): reconcile proof-first replacement recovery Co-Authored-By: heihutu <[email protected]> * fix(heal): fence torn intent recovery Co-Authored-By: heihutu <[email protected]> * test(heal): cover replacement migration conflicts Co-Authored-By: heihutu <[email protected]> * fix(ecstore): fence replacement lease mount identity Co-Authored-By: heihutu <[email protected]> * test(heal): cover missing replacement path admission Co-Authored-By: heihutu <[email protected]> * fix(heal): reject conflicting legacy completion proof Co-Authored-By: heihutu <[email protected]> * fix(ecstore): fall back to proc mount identity Co-Authored-By: heihutu <[email protected]> * feat(admin): expose replacement recovery status Surface the local durable replacement recovery snapshot in the background heal status response so operators can tell whether replacement cleanup is definitive or still pending. Co-Authored-By: heihutu <[email protected]> * fix(heal): keep replacement status compatible Keep the existing background heal status response wire-compatible while retaining the Linux mount lease cleanup needed for the replacement recovery branch. Co-Authored-By: heihutu <[email protected]> * style(ecstore): match linux mount lease formatting Keep Linux rustfmt output stable for the replacement mount lease comparison. Co-Authored-By: heihutu <[email protected]> * fix(ecstore): qualify mount lease test constant Use the disk module path for the format config constant in the Linux mount lease regression test. Co-Authored-By: heihutu <[email protected]> * fix(ecstore): keep procfd mount roots directory-safe Use a procfd path with an explicit directory component so Unix directory guards can open the replacement mount lease root with O_NOFOLLOW while preserving handle-relative I/O semantics. Co-Authored-By: heihutu <[email protected]> * fix(ecstore): delete empty leased buckets via dirfd Use the held mount lease fd as the parent for non-force empty bucket deletion on Linux so procfd-rooted paths do not get rejected as BucketNotEmpty. Also make the download-part OpenOptions truncate behavior explicit and keep fsync test recording stable across procfd canonicalization. Co-Authored-By: heihutu <[email protected]> * fix(ecstore): scan leased bucket paths for emptiness Use the local disk I/O root for bucket emptiness probes before non-force bucket deletion and table-bucket metadata checks. This keeps validation on the same mount instance as the subsequent local disk delete path. Co-Authored-By: heihutu <[email protected]> * test(ecstore): align lease path test probes Co-Authored-By: heihutu <[email protected]> * fix(heal): block unsafe replacement recovery restarts Co-Authored-By: heihutu <[email protected]> * fix(heal): defer blocked replacement candidates Co-Authored-By: heihutu <[email protected]> * fix(heal): retry transient replacement discovery Co-Authored-By: heihutu <[email protected]> * fix(heal): keep transient recovery errors retryable Co-Authored-By: heihutu <[email protected]> * fix(heal): block corrupt legacy replacement state Co-Authored-By: heihutu <[email protected]> * fix(heal): classify flat replacement intent corruption Co-Authored-By: heihutu <[email protected]> * fix(heal): keep transient resume loads retryable Classify malformed legacy replacement state as blocking corruption while preserving disk and transient load failures for retry. This avoids permanently blocking replacement recovery on temporary storage errors. Co-Authored-By: heihutu <[email protected]> * fix(heal): avoid latching transient legacy publishes Co-Authored-By: heihutu <[email protected]> * fix(heal): retry blocked legacy migrations Co-Authored-By: heihutu <[email protected]> * fix(heal): defer blocked startup recoveries Co-Authored-By: heihutu <[email protected]> * fix(ecstore): preserve disk sync limiter across lease roots Co-Authored-By: heihutu <[email protected]> --------- Co-authored-by: heihutu <[email protected]> Co-authored-by: zhi22915 <[email protected]>
429 lines
23 KiB
Markdown
429 lines
23 KiB
Markdown
# Scanner Runtime Controls
|
|
|
|
This document describes the runtime controls and status fields for the RustFS
|
|
data scanner. It is written for operators who need to reduce scanner pressure,
|
|
diagnose slow scan progress, or confirm that background lifecycle, replication,
|
|
heal, bitrot, and usage work is still moving.
|
|
|
|
For reproducible scanner-pressure validation and before/after evidence, see
|
|
[Scanner Benchmark Runbook](scanner-benchmark-runbook.md).
|
|
|
|
## What the scanner does
|
|
|
|
The scanner is the background maintenance loop that walks stored objects and
|
|
feeds several subsystems:
|
|
|
|
- usage accounting and data usage cache updates;
|
|
- lifecycle expiry and transition admission;
|
|
- bucket replication repair admission;
|
|
- scanner-originated heal and bitrot checks;
|
|
- namespace alerts for excessive versions, retained version size, and folder
|
|
fan-out.
|
|
|
|
Slowing the scanner can reduce idle CPU and disk pressure, but it also delays
|
|
the maintenance work above. Prefer using the status fields below before changing
|
|
cycle or pacing values.
|
|
|
|
## Configuration Sources
|
|
|
|
Scanner runtime config is resolved in this order:
|
|
|
|
1. Environment variables.
|
|
2. Persisted admin config for the `scanner` subsystem.
|
|
3. Built-in defaults or speed preset-derived values.
|
|
|
|
Bitrot cycle resolution is slightly different because the canonical persistent
|
|
key belongs to the `heal` subsystem:
|
|
|
|
1. `RUSTFS_SCANNER_BITROT_CYCLE_SECS`.
|
|
2. `heal.bitrot_cycle`.
|
|
3. Legacy compatibility key `scanner.bitrot_cycle`.
|
|
4. Built-in default.
|
|
|
|
The `/v3/scanner/status` response reports each effective runtime value with a
|
|
`source` of `env`, `config`, `scanner_compat_config`, or `default`.
|
|
|
|
## Runtime Controls
|
|
|
|
| Persistent key | Environment variable | Unit | Default | Effect |
|
|
|---|---|---:|---:|---|
|
|
| `scanner.speed` | `RUSTFS_SCANNER_SPEED` | preset | `default` | Selects the base pacing preset: `fastest`, `fast`, `default`, `slow`, or `slowest`. |
|
|
| `scanner.delay` | `RUSTFS_SCANNER_DELAY` | factor | preset-derived | Overrides the sleep multiplier. Valid range is `0` through `10000`. |
|
|
| `scanner.max_wait` | `RUSTFS_SCANNER_MAX_WAIT_SECS` | seconds | preset-derived | Caps one scanner sleep. |
|
|
| `scanner.cycle` | `RUSTFS_SCANNER_CYCLE` | seconds | preset-derived | Sets the interval between scanner cycles. |
|
|
| `scanner.start_delay` | `RUSTFS_SCANNER_START_DELAY_SECS` | seconds | unset | Sets startup delay and, for compatibility, the cycle interval when `scanner.cycle` is unset. |
|
|
| `scanner.cycle_max_duration` | `RUSTFS_SCANNER_CYCLE_MAX_DURATION_SECS` | seconds | `0` | Caps one cycle's runtime. `0` disables this budget. |
|
|
| `scanner.cycle_max_objects` | `RUSTFS_SCANNER_CYCLE_MAX_OBJECTS` | objects | `0` | Caps objects processed by one cycle. `0` disables this budget. |
|
|
| `scanner.cycle_max_directories` | `RUSTFS_SCANNER_CYCLE_MAX_DIRECTORIES` | directories | `0` | Caps directories entered by one cycle. `0` disables this budget. |
|
|
| `heal.bitrot_cycle` | `RUSTFS_SCANNER_BITROT_CYCLE_SECS` | seconds | `2592000` | Controls periodic deep bitrot scans. `false`, `off`, `no`, or `disabled` disables periodic deep scans; `0`, `true`, `on`, or `yes` runs deep mode every scanner cycle. |
|
|
| `scanner.idle_mode` | `RUSTFS_SCANNER_IDLE_MODE` | boolean | `true` | Enables scanner sleeps and cooperative throttling. |
|
|
| `scanner.cache_save_timeout` | `RUSTFS_SCANNER_CACHE_SAVE_TIMEOUT_SECS` | seconds | `30` | Timeout for saving scanner cache; runtime enforces a minimum of `1`. |
|
|
| `scanner.max_concurrent_set_scans` | `RUSTFS_SCANNER_MAX_CONCURRENT_SET_SCANS` | count | `4` | Caps concurrent set-level scanner tasks. `0` keeps topology-derived concurrency. |
|
|
| `scanner.max_concurrent_disk_scans` | `RUSTFS_SCANNER_MAX_CONCURRENT_DISK_SCANS` | count | `4` | Caps concurrent disk bucket walks per set. `0` keeps disk-count-derived concurrency. |
|
|
| `scanner.yield_every_n_objects` | `RUSTFS_SCANNER_YIELD_EVERY_N_OBJECTS` | objects | `128` | Controls how often object loops yield to the async runtime. `0` disables this extra yield. |
|
|
| `scanner.alert_excess_versions` | `RUSTFS_SCANNER_ALERT_EXCESS_VERSIONS` | versions | `100` | Version count threshold for scanner alerts. |
|
|
| `scanner.alert_excess_version_size` | `RUSTFS_SCANNER_ALERT_EXCESS_VERSION_SIZE` | bytes | `1099511627776` | Retained version byte threshold for scanner alerts. |
|
|
| `scanner.alert_excess_folders` | `RUSTFS_SCANNER_ALERT_EXCESS_FOLDERS` | folders | `65538` | Direct subfolder threshold for scanner alerts. |
|
|
|
|
The `fastest`, `fast`, `default`, `slow`, and `slowest` presets set the base
|
|
sleep multiplier, maximum wait, and cycle interval. Use `scanner.delay`,
|
|
`scanner.max_wait`, and `scanner.cycle` when the preset is close but one axis
|
|
needs a precise override.
|
|
|
|
## Single-disk clean-idle scheduling
|
|
|
|
An erasure single-disk deployment using the built-in cycle and bitrot defaults
|
|
automatically backs off repeated clean idle scans instead of walking the same
|
|
unchanged namespace every minute. Each successful timer-driven cycle that
|
|
finds no dirty usage or unresolved maintenance work doubles the next interval.
|
|
The status endpoint reports the effective interval and multiplier.
|
|
|
|
The backoff is reset to the base interval by object or bucket mutations,
|
|
lifecycle or replication configuration changes, partial or failed cycles,
|
|
usage persistence failures, and unresolved scanner-originated heal or bitrot
|
|
work. Active lifecycle or replication rules keep the base cadence. An explicit
|
|
cycle, a non-default persisted speed, any environment speed or start-delay
|
|
override, an environment bitrot override, or a non-default persisted active
|
|
bitrot cycle also keeps the configured cadence rather than applying the
|
|
automatic policy. Persisting `scanner.speed=default` or the default bitrot cycle
|
|
is normalized to the built-in default and therefore keeps automatic scheduling
|
|
enabled.
|
|
|
|
Lifecycle and replication configuration inspection is bounded so a slow
|
|
metadata read cannot stall scanner startup or scheduling. A failed or timed-out
|
|
inspection keeps the base cadence and is retried after 5 minutes, doubling up
|
|
to a maximum of 60 minutes while failures continue. A lifecycle or replication
|
|
configuration change wakes the scanner and retries inspection immediately.
|
|
|
|
With the default 30-day bitrot cycle, the clean-idle interval is capped at the
|
|
bitrot cycle divided by the object selection window. With the default selection
|
|
window this is about 42 minutes, which preserves the intended wall-clock bitrot
|
|
coverage. If periodic bitrot is disabled, the clean-idle policy cap is 24 hours.
|
|
The effective interval is jittered by up to 10 percent to avoid synchronized
|
|
scanner starts.
|
|
|
|
## Status Endpoint
|
|
|
|
The scanner status route is:
|
|
|
|
```text
|
|
GET /v3/scanner/status
|
|
```
|
|
|
|
The request must be authenticated with an admin identity that has
|
|
`ServerInfoAdminAction`. The JSON response has three scanner-specific top-level
|
|
objects:
|
|
|
|
- `runtime_config`: the effective runtime controls and their value sources.
|
|
- `cycle_schedule`: the current effective cycle interval and clean-idle
|
|
backoff state.
|
|
- `metrics`: scanner work, pressure, checkpoint, lifecycle, replication, heal,
|
|
bitrot, and alert counters.
|
|
|
|
Example fields to inspect:
|
|
|
|
```text
|
|
runtime_config.speed.value
|
|
runtime_config.delay.value
|
|
runtime_config.max_wait_seconds.value
|
|
runtime_config.cycle_interval_seconds.value
|
|
runtime_config.bitrot_cycle_seconds.value
|
|
cycle_schedule.effective_interval_seconds
|
|
cycle_schedule.clean_idle_backoff_enabled
|
|
cycle_schedule.clean_idle_backoff_multiplier
|
|
metrics.pacing_pressure.primary_pressure
|
|
metrics.pacing_pressure.last_cycle_budget_limited
|
|
metrics.lifecycle_transition.current_queued
|
|
metrics.lifecycle_transition.scanner_missed
|
|
metrics.maintenance_control.primary_control
|
|
metrics.source_work
|
|
metrics.replication_repair
|
|
metrics.scan_checkpoint
|
|
```
|
|
|
|
## Reading Pacing Pressure
|
|
|
|
`metrics.pacing_pressure.primary_pressure` summarizes the highest-priority
|
|
scanner pressure signal:
|
|
|
|
| Value | Meaning | Usual response |
|
|
|---|---|---|
|
|
| `queued_scans` | Set or disk scan queues are backing up. | Lower scanner concurrency or increase pacing delay if user traffic is affected. |
|
|
| `cycle_budget` | The last cycle stopped because a runtime/object/directory budget was reached. | Check `last_cycle_partial_reason` and `last_cycle_partial_source`; increase the specific budget if scans need to finish sooner. |
|
|
| `throttle_pause` | Scanner sleeps or cooperative yields were observed. | Expected when `idle_mode` is enabled; inspect pause ratios before tuning. |
|
|
| `active_scans` | Scanner work is active but not currently queued or budget-limited. | Usually healthy; correlate with CPU/disk metrics. |
|
|
| `none` | No current scanner pressure was observed. | No scanner pacing action needed. |
|
|
|
|
The ratio fields are fractions of the last cycle duration:
|
|
|
|
- `last_cycle_throttle_sleep_ratio`
|
|
- `last_cycle_yield_ratio`
|
|
- `last_cycle_total_pause_ratio`
|
|
|
|
If CPU is high but pause ratios are already high, increasing `scanner.delay` or
|
|
`scanner.max_wait` may have limited value. Check active paths, source work, and
|
|
disk activity before changing the cycle interval.
|
|
|
|
## Reading Source Work
|
|
|
|
`metrics.source_work`, `metrics.current_cycle_source_work`, and
|
|
`metrics.last_cycle_source_work` group scanner work by source:
|
|
|
|
- `usage`
|
|
- `lifecycle`
|
|
- `bucket_replication`
|
|
- `site_replication`
|
|
- `heal`
|
|
- `bitrot`
|
|
- `alerts`
|
|
|
|
Each source has `checked`, `queued`, `executed`, `failed`, `skipped`, and
|
|
`missed` counters. `missed` means the scanner found work but could not admit it
|
|
to the downstream queue. `skipped` means the work was intentionally merged or
|
|
deduplicated.
|
|
|
|
Use these counters to decide whether scan progress is limited by scanner pacing
|
|
or by a downstream subsystem such as lifecycle transition, replication repair,
|
|
or heal admission.
|
|
|
|
## Reading Heal Operations
|
|
|
|
The background heal status route is:
|
|
|
|
```text
|
|
POST /v3/background-heal/status
|
|
```
|
|
|
|
It reports scanner-driven bitrot state together with heal queue execution
|
|
state. `healQueueLength` and `healActiveTasks` keep the legacy totals.
|
|
`healOperations` adds the same totals split by request source and priority:
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `queueLength` | Total queued heal requests. |
|
|
| `activeTasks` | Total running heal tasks. |
|
|
| `queuedBySource` | Queued requests split into `scanner`, `admin`, `autoHeal`, and `internal`. |
|
|
| `activeBySource` | Running tasks split into `scanner`, `admin`, `autoHeal`, and `internal`. |
|
|
| `queuedByPriority` | Queued requests split into `low`, `normal`, `high`, and `urgent`. |
|
|
| `activeByPriority` | Running tasks split into `low`, `normal`, `high`, and `urgent`. |
|
|
|
|
Use this route when `metrics.source_work` shows `heal` or `bitrot` queued or
|
|
missed work. Scanner-originated object checks should appear under
|
|
`scanner/low` for opportunistic work, while manual admin heal should appear
|
|
under `admin/high`. If scanner work grows but admin work remains blocked, treat
|
|
that as heal queue pressure rather than scanner pacing pressure.
|
|
|
|
## Replacement Recovery Completion
|
|
|
|
`POST /v3/background-heal/status` is an execution-queue view. `state=idle`, zero queue and active counts, an online disk, a readable object, or acceptance of an Admin deep-heal request do not independently prove that a replacement disk contains every erasure shard.
|
|
|
|
Treat replacement recovery as verified only after the repair task has completed for the exact replacement instance and an operator has confirmed the target disk contains the expected `xl.meta` and data parts for every relevant object version. A replacement that is not mounted, is unsafe to format, loses its marker, or returns a partial target outcome must be treated as deferred or incomplete rather than complete.
|
|
|
|
The v3 route and its peer status protocol preserve their existing fields for mixed-version clusters. A new node must not infer replacement completion from an old or unavailable peer; regard that information as unknown or degraded until every required peer can report the same replacement instance and verified completion. Do not automate destructive replacement actions from an `idle` observation alone.
|
|
|
|
Replacement resume and checkpoint files use an independent on-disk schema. A newer reader rejects a future schema rather than continuing with data it cannot interpret, while an older binary cannot safely enforce the new generation fence because it may ignore fields it does not know. Do not roll a cluster back after a replacement generation has started. Complete that recovery with the current-or-newer release; if it cannot complete, keep that version for diagnosis rather than deleting its durable records or continuing with an older binary.
|
|
|
|
## Reading Replication Repair
|
|
|
|
`metrics.replication_repair`, `metrics.current_cycle_replication_repair`, and
|
|
`metrics.last_cycle_replication_repair` split scanner-discovered replication
|
|
repair work by source and repair kind.
|
|
|
|
Each entry has the same `checked`, `queued`, `executed`, `failed`, `skipped`,
|
|
and `missed` counters used by `source_work`, plus:
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `source` | `bucket_replication` for bucket replication repair, or `site_replication` for site replication boundary signals. |
|
|
| `kind` | Bucket repair kinds are `object`, `delete_marker`, `version_purge`, and `existing_object`. Site replication boundary kinds are `passive_requeue` and `active_resync`. |
|
|
| `scanner_role` | `repair_admission` means scanner found work and attempted to admit it to a worker queue. `boundary_signal` means scanner is reporting state owned by another runtime. |
|
|
| `execution_owner` | `bucket_replication_queue` for bucket replication repair execution, or `site_replication_runtime` for site replication resync execution. |
|
|
|
|
For bucket replication, `queued` means scanner-discovered repair was admitted
|
|
to the replication queue, `missed` means the queue or worker path could not
|
|
accept it, and `skipped` means the object did not require a new repair task.
|
|
|
|
The site replication kinds keep passive scanner discovery separate from active
|
|
resync. Scanner status may report site replication boundary counters, but the
|
|
scanner should not be treated as the active site replication resync controller.
|
|
|
|
Use this boundary when interpreting replication pressure:
|
|
|
|
| Scenario | Scanner source | Repair kind | Scanner role | Execution owner | Operational meaning |
|
|
|---|---|---|---|---|---|
|
|
| Bucket object, delete-marker, version-purge, or existing-object repair found during a scan | `bucket_replication` | `object`, `delete_marker`, `version_purge`, `existing_object` | `repair_admission` | `bucket_replication_queue` | Scanner found bucket replication repair work and attempted to admit it to the replication queue. |
|
|
| Peer-originated or passive site replication work is observed while scanning | `site_replication` | `passive_requeue` | `boundary_signal` | `site_replication_runtime` | Scanner is reporting a passive site-replication boundary signal; it is not taking ownership of active site resync. |
|
|
| Admin-triggered or runtime-owned site resync activity is visible in scanner metrics | `site_replication` | `active_resync` | `boundary_signal` | `site_replication_runtime` | Treat this as a boundary/status signal owned by the site replication runtime, not as scanner-controlled repair execution. |
|
|
|
|
If `site_replication` counters grow while bucket replication counters stay
|
|
flat, investigate site replication status and resync state before tuning
|
|
scanner pacing. If `bucket_replication` `missed` grows, investigate the bucket
|
|
replication worker queue or target health before changing scanner cycle
|
|
settings.
|
|
|
|
## Reading Maintenance Control
|
|
|
|
`metrics.maintenance_control` derives a source-level control snapshot from
|
|
scanner pacing, partial-cycle state, source work, and lifecycle transition
|
|
queue state. It does not change scanner scheduling by itself; it explains why a
|
|
source is moving, deferred, or blocked. When no scan cycle is currently active,
|
|
source-work controls use the last completed cycle so recently missed work stays
|
|
visible between scanner passes.
|
|
|
|
`metrics.maintenance_control.primary_control` summarizes the highest-priority
|
|
source state:
|
|
|
|
| Value | Meaning |
|
|
|---|---|
|
|
| `blocked_source` | At least one maintenance source found work that could not be admitted or is blocked by a downstream queue. |
|
|
| `deferred_source` | At least one source was deferred by a partial scanner cycle or budget-limited pass. |
|
|
| `active_source` | At least one source has current-cycle work or queued downstream work. |
|
|
| `pacing_pressure` | No source-specific state dominated, but scanner pacing pressure is still visible. |
|
|
| `none` | No source-level maintenance control pressure was observed. |
|
|
|
|
Each `metrics.maintenance_control.sources[]` entry has:
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `source` | Scanner source such as `usage`, `lifecycle`, `bucket_replication`, `site_replication`, `heal`, `bitrot`, or `alerts`. |
|
|
| `state` | `idle`, `active`, `deferred`, or `blocked`. |
|
|
| `reason` | Derived reason such as `active_work`, `queued_work`, `partial_cycle`, `missed_work`, `expiry_queue_backlog`, `transition_failed`, `transition_compensation_backlog`, `transition_queue_backlog`, or `transition_queue_full`. |
|
|
| `backlog` | Current source-level backlog estimate from queued or missed work. |
|
|
| `current_checked` | Current-cycle checked work for this source, or the last completed cycle when no scan cycle is active. |
|
|
| `current_queued` | Current-cycle queued work for this source, or the last completed cycle when no scan cycle is active. |
|
|
| `current_missed` | Current-cycle work that could not be admitted, or the last completed cycle when no scan cycle is active. |
|
|
| `lifetime_missed` | Lifetime missed work counter for context. |
|
|
| `partial_cycles` | Partial cycles attributed to this source. |
|
|
|
|
Use this snapshot before changing scanner controls. For example,
|
|
`blocked_source` with `lifecycle/missed_work` points at downstream lifecycle
|
|
admission, while `deferred_source` with `usage/partial_cycle` points at scanner
|
|
cycle budgets. `lifecycle/expiry_queue_backlog` means scanner-driven expiry or
|
|
delete work is still queued or active in the expiry worker pool.
|
|
`lifecycle/transition_failed` means transition worker execution failed during
|
|
the current or last completed scan cycle, while
|
|
`lifecycle/transition_compensation_backlog` means transition compensation is
|
|
still pending or running after queue backpressure.
|
|
|
|
`metrics.lifecycle_expiry` exposes the expiry/delete worker queue observed by
|
|
scanner-driven lifecycle work:
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `current_queue_capacity` | Effective expiry worker queue capacity for this node. |
|
|
| `current_queued` | Expiry/delete tasks currently waiting in the worker queue. |
|
|
| `current_active` | Expiry/delete tasks currently running in a worker. |
|
|
| `current_workers` | Configured expiry worker count. |
|
|
| `queue_missed` | Expiry/delete tasks that could not be queued because no worker channel was available or the queue was closed. |
|
|
| `scanner_queued` | Scanner-discovered expiry/delete object versions admitted to the expiry queue. |
|
|
| `scanner_missed` | Scanner-discovered expiry/delete object versions that could not be admitted. |
|
|
|
|
## Reading Distributed Metrics
|
|
|
|
`/rustfs/admin/v3/scanner/status` and `/rustfs/admin/v3/metrics` report the
|
|
node that handles the HTTP request. The metrics endpoint does not fan out to
|
|
peer nodes. In distributed deployments, query every node explicitly and keep
|
|
`by-host=true` enabled so each response includes that node's host view:
|
|
|
|
```bash
|
|
for endpoint in http://node-a:9000 http://node-b:9000 http://node-c:9000; do
|
|
node="${endpoint#http://}"
|
|
node="${node%%:*}"
|
|
awscurl \
|
|
--service s3 \
|
|
--region us-east-1 \
|
|
--access_key "$RUSTFS_ACCESS_KEY" \
|
|
--secret_key "$RUSTFS_SECRET_KEY" \
|
|
--request GET \
|
|
"${endpoint}/rustfs/admin/v3/metrics?types=1&by-host=true&n=1" \
|
|
> "artifacts/scanner-metrics.${node}.$(date -u +%Y%m%dT%H%M%SZ).ndjson"
|
|
done
|
|
```
|
|
|
|
The `aggregated.scanner` payload preserves the same scanner progress,
|
|
checkpoint, pacing, source work, maintenance control, lifecycle expiry, and
|
|
lifecycle transition fields used by the local scanner status, but only for the
|
|
node that returned the response. The `by_host.*.scanner` payload keeps that
|
|
node's host view.
|
|
Compare the per-node artifacts externally to find old active paths, partial
|
|
checkpoints, pacing pressure, source-level control pressure, or downstream
|
|
queue admission problems across the deployment.
|
|
|
|
## Reading Lifecycle Transition Status
|
|
|
|
`metrics.lifecycle_transition` focuses on scanner-driven lifecycle transition
|
|
work:
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `current_queue_capacity` | Current transition queue capacity. |
|
|
| `current_queued` | Transition tasks currently queued. |
|
|
| `current_active` | Transition tasks currently being processed. |
|
|
| `current_workers` | Transition worker count. |
|
|
| `queue_full` | Queue-full observations in the transition state. |
|
|
| `queue_send_timeout` | Send timeouts for transition queue admission. |
|
|
| `compensation_scheduled` | Buckets scheduled for transition compensation. |
|
|
| `compensation_pending` | Buckets with transition compensation still pending or running. |
|
|
| `compensation_running` | Transition compensation tasks currently running. |
|
|
| `scanner_queued` | Scanner transition tasks admitted to the queue. |
|
|
| `scanner_missed` | Scanner transition tasks that could not be admitted. |
|
|
| `completed` | Transition worker completions. |
|
|
| `failed` | Transition worker failures. |
|
|
|
|
When `scanner_missed` or `queue_full` rises, scanner lifecycle work is finding
|
|
transition candidates faster than the transition queue can accept them. That is
|
|
a downstream transition pressure signal, not just a scanner walk pressure signal.
|
|
|
|
## Tuning Workflow
|
|
|
|
For symptoms where a mostly idle single-node, single-disk deployment has
|
|
sustained CPU usage while the scanner is enabled:
|
|
|
|
1. Read `/v3/scanner/status`.
|
|
2. Check `metrics.pacing_pressure.primary_pressure`.
|
|
3. Check `metrics.maintenance_control.primary_control` and source entries
|
|
before changing runtime controls.
|
|
4. Check `runtime_config.delay`, `runtime_config.max_wait_seconds`, and
|
|
`runtime_config.cycle_interval_seconds` to confirm the active values and
|
|
their sources.
|
|
5. Check `metrics.current_cycle_objects_scanned`,
|
|
`metrics.current_cycle_directories_scanned`, and active paths to confirm the
|
|
scanner is the active work.
|
|
6. If `primary_pressure` is `throttle_pause` and pause ratios are low, raise
|
|
`scanner.delay` first.
|
|
7. If individual sleeps are too short, raise `scanner.max_wait`.
|
|
8. If each scan cycle finishes but starts too often, raise `scanner.cycle`.
|
|
9. If scans must be broken into bounded chunks, set one of the cycle budgets:
|
|
`scanner.cycle_max_duration`, `scanner.cycle_max_objects`, or
|
|
`scanner.cycle_max_directories`.
|
|
10. Recheck `pacing_pressure`, `maintenance_control`, source work, and
|
|
lifecycle transition status after one or more scanner cycles.
|
|
|
|
Do not rely only on a longer cycle interval if lifecycle, replication, heal, or
|
|
bitrot work must keep moving. Use source work and transition status to confirm
|
|
that background maintenance is still making progress.
|
|
|
|
## Helm
|
|
|
|
The Helm chart exposes the scanner environment variables under
|
|
`config.rustfs.scanner`. Example:
|
|
|
|
```yaml
|
|
config:
|
|
rustfs:
|
|
scanner:
|
|
speed: "slow"
|
|
delay: "30"
|
|
max_wait_secs: "15"
|
|
cycle_secs: "3600"
|
|
cycle_max_duration_secs: "1800"
|
|
cycle_max_objects: "1000000"
|
|
cycle_max_directories: "100000"
|
|
idle_mode: "true"
|
|
yield_every_n_objects: "128"
|
|
bitrot_cycle_secs: "2592000"
|
|
```
|
|
|
|
Use `extraEnv` for experimental or unrelated environment variables that are not
|
|
represented by chart values.
|