Files
rustfs/docs/issue-4003-listobjects-v2-performance-optimization-plan-en.md
T
houseme 22c7cb1923 feat(ecstore): refresh issue-787 phase-3 validation track (#4106)
* feat: isolate list objects quorum config

* perf: add list gather observability for issue-785

* feat: add list-path observability for storage layers

* chore: add issue 785 list observability acceptance runner

* fix: avoid moved fields in list_path logging

* test: add marker replay idempotence checks for list pagination

* feat(ecstore): add list marker v2 cache id

* Revert "feat(ecstore): add list marker v2 cache id"

This reverts commit 980c364e02.

* Reapply "feat(ecstore): add list marker v2 cache id"

This reverts commit 766d2cee65.

* feat(ecstore): propagate list cache cursor in list layers

* test: add versioned continuation fallback tests

* test: add live pagination continuity smoke mode

* chore: run cargo fmt on issue-4003 branch

* build: fix bash3 compatibility in live smoke

* test(ecstore): simplify bool marker test assertion

* refactor(ecstore): unify list pagination state machine

* test: add issue 787 quorum validation runner

* build: support bash32 in issue-787 runner

* docs: add issue-787 benchmark evidence
2026-06-30 11:37:44 +08:00

14 KiB

Issue #4003 Pragmatic Execution Plan for ListObjectsV2 Performance

Date: 2026-06-29

Reference:

1) Problem statement and observed failure mode

ListObjectsV2 becomes very slow on very large buckets (~745k objects) under recursive listing workloads (rclone size, rclone lsf --recursive).

Current implementation characteristics observed in code path:

  • List operations still inherit a conservative default quorum in many paths.
  • Listing for each page can trigger repeated full/near-full scan + merge behavior.
  • Continuation semantics are marker-based and can create high per-page amplification.

2) Root-cause hypothesis (five-perspective review)

2.1 Infrastructure / SRE view

  • Per-page scan work is multiplicative for deep namespaces.
  • No explicit low-overhead listing profile mode exists for listing-only workloads.
  • Operational rollback is possible, but currently not documented as a one-liner.

2.2 Storage engine view

  • Quorum configuration currently defaults to strict behavior (RUSTFS_API_LIST_QUORUM=strict) which is safe but can be expensive under read-heavy list workloads.
  • Merge and resolution path is correct but not optimized for pagination throughput.

2.3 API contract view

  • List API correctness requirements (no duplicates / no skips / stable order behavior) must stay unchanged.
  • Any cursor optimization must preserve existing token compatibility first.

2.4 Performance view

  • Listing is dominated by scan and merge fan-out before response assembly under large directories.
  • Early-stop / reduced ask set tuning can yield immediate wins without touching on-wire protocol.

2.5 Release risk view

  • Quick changes should be isolated behind an environment switch and default to a safer path in case of behavior concern.
  • Test/verification surface should track both correctness and latency delta.

3) Fixes completed in current patch (Phase-1 scope)

3.1 Introduce ListObjects-only quorum switch

File: crates/ecstore/src/store/list_objects.rs

  • Added constants:
    • RUSTFS_LIST_OBJECTS_QUORUM
    • default optimal
  • Added helper:
    • list_objects_quorum_from_env()
  • Switched ask disks for listing operations to use this helper in 6 call sites:
    • ECStore::list_objects
    • ECStore::list_object_versions
    • Sets::list_objects
    • Sets::list_object_versions
    • SetDisks::list_objects
    • SetDisks::list_object_versions

3.2 Unit tests added

In #[cfg(test)] for list_objects.rs:

  • list_objects_quorum_from_env_defaults_to_optimal
  • list_objects_quorum_from_env_honors_supported_value

These verify default and explicit env behavior.

3.3 Why this is safe

  • API behavior does not change directly; only quorum selection for listing I/O path changes.
  • Existing RUSTFS_API_LIST_QUORUM remains untouched for non-listing paths.
  • Invalid value behavior remains deterministic via existing normalize_list_quorum() logic.

4) Phase plan (practical and staged)

Phase 1 (Now): Observable list performance tuning without protocol change

Scope:

  • Ship listing-specific quorum knob and default.
  • Add metrics/logging for per-page scan count and early-stop ratio.
  • Run baseline and post-change micro-bench on recursive list.

Acceptance:

  • No correctness regression in list responses.
  • Reduction in list CPU + elapsed time under large recursive reads.
  • Rollback confirmed by setting RUSTFS_LIST_OBJECTS_QUORUM=strict.

Phase 2 (Next): Resume semantics refactor

Scope:

  • Introduce versioned continuation token for ListObjectsV2.
  • Keep legacy token compatibility in parsing path.
  • Add validation tests for deterministic pagination (no duplicate/missing entries).

Acceptance:

  • Idempotent pagination under repeated same token requests.
  • Zero regression in API contract and clients.
  • Graceful fallback for unknown token format.

Phase 3 (Long): Index-backed listing path

Scope:

  • Evaluate/introduce index assist path for walker-heavy environments.
  • Fallback to walker on index inconsistency.
  • Define rebuild + repair strategy and runbooks.

Acceptance:

  • Sustained throughput gains on very large buckets.
  • Operational risk under consistency faults bounded.
  • Rollback and recovery steps documented and tested.

5) Validation checklist (before merge)

  • cargo test -p rustfs-ecstore list_objects -- --nocapture (focused on list code)
  • cargo test -p rustfs-ecstore list_objects_quorum_from_env (if supported selector)
  • scripts/validate_issue_787_list_quorum.sh (offline) and scripts/validate_issue_787_list_quorum.sh --full (live, if environment ready)
  • Add/verify benchmark baseline:
    • recursive rclone lsf --recursive on staging bucket
    • rclone size style pass
  • Compare:
    • p99 listing latency
    • request count per page
    • duplicated/missing keys between consecutive pages
    • CPU and disk read amplification

6) #787 live benchmark and rollout acceptance (in progress)

6.1 live benchmark plan (execution contract)

  • Workload baseline:

    • Bucket: pre-populated production-equivalent bucket (>= 700k objects, mixed prefixes, deep tree)
    • Tool: rclone lsf --recursive + rclone size
    • Endpoint: RUSTFS_ADDRESS=... on fresh RustFS node with RUSTFS_ACCESS_KEY, RUSTFS_SECRET_KEY
    • Pages: --max-keys at 1000 / 5000 to measure p95/p99 page behavior
  • Comparative configurations (at least two runs):

    • Baseline: RUSTFS_LIST_OBJECTS_QUORUM=strict
    • Candidate: RUSTFS_LIST_OBJECTS_QUORUM=optimal (or reduced if strict is unavailable in workload)
  • Required outputs for each run:

    • p95/p99 page latency
    • wall-clock time for complete recursive listing pass
    • duplicates/missing count by token replay check
    • per-page key count and continuation token progression
  • scripts/validate_issue_787_list_quorum.sh --full

    • set MODE_LIST to the candidate matrix, for example:
      • MODE_LIST=strict,optimal,reduced
    • export SERVER_RESTART_CMD and SERVER_WAIT_SECONDS=20 to produce mode-scoped logs under one run output directory.
  • Acceptance thresholds (proposal):

    • Candidate p99 page latency delta ≤ +10% and ≥ +10% wall-clock gain against baseline on same bucket class
    • Zero duplicates/missing objects across page transitions
    • No listing correctness regressions

6.2 completed local benchmark package (2026-06-30)

Benchmark environment used to close the missing evidence gap:

  • single-node local RustFS with the same data directory reused across mode restarts
  • bucket: issue787-bench
  • object layout: 6,000 zero-byte objects across mixed recursive prefixes:
    • alpha/obj-*
    • beta/deep/obj-*
    • gamma/nested/path/obj-*
  • request shape: full bucket recursive ListObjectsV2
  • pagination: max-keys=1000
  • sample size: 15 full passes per mode, 6 pages per pass, 90 page samples per mode

Results summary:

Mode Page p95 (ms) Page p99 (ms) Wall avg (ms) p99 delta vs strict Wall avg delta vs strict Continuity
strict 255.413 275.438 1469.938 baseline baseline pass
optimal 259.146 267.342 1485.912 -2.939% +1.087% pass
reduced 255.937 272.557 1477.955 -1.046% +0.545% pass

Decision from the benchmark package:

  • Both candidate modes stayed within the strict-baseline p99 guardrail in this local harness.
  • Neither candidate achieved the required >= 10% page-latency improvement or wall-clock gain on the same bucket/workload.
  • Therefore the current evidence does not justify quorum-only tuning as a production-safe standalone mitigation for issue #4003.
  • Recommendation: keep quorum tuning as an operator-controlled diagnostic/experimental gate and treat index-backed listing as the real Phase-3 mitigation candidate.

6.3 execution attempt log

  • Attempt start/run status:

    • scripts/restart_local_single_node_multidisk_rustfs.sh and direct target/debug/rustfs server launch path
    • Endpoint bind error observed: Operation not permitted (os error 1) on both 9000 and 19000
    • Artifact directory:
      • target/issue-787-live-benchmark-20260630-031521/
  • Current status:

    • Live benchmark for #787 is blocked by environment bind permission in this runner.
    • Offline/contract checks for #785/Phase-1 behavior remain passed and can be attached in #787 as pre-condition.

6.4 rollout documentation package (required to close #787)

  • Add rollout control:

    • Config default: RUSTFS_LIST_OBJECTS_QUORUM=optimal (current patch)
    • Safety fallback: RUSTFS_LIST_OBJECTS_QUORUM=strict
    • Error budget guardrails for rollout (phase plan):
      • p99 latency +20% ceiling
      • error-rate +10% ceiling
      • continuity test zero duplicate/missing
  • Release sequencing:

    • Stage A: shadow traffic for large-recursive listing buckets (same object layout + namespace)
    • Stage B: partial bucket/range enablement with continuity probes
    • Stage C: scale to wider prefix families only after two consecutive acceptance windows pass

6.5 index-backed engineering scaffold (next milestone)

  • Scope: introduce an optional index-assisted listing mode with walk-based fallback.
  • Minimal state machine design:
    1. read-path planner reads request metadata from marker/continuation context and picks one of two strategies: walk or index
    2. if index is selected, enforce version+layout checksum check before scan
    3. when index cursor miss/validate failure, fallback immediately to walk and emit a marker_fallback metric
    4. emit cursor and source tags in NextContinuationToken only when index strategy was used
  • Acceptance for first pass:
    • zero functional regressions (duplication=0, missing=0) in offline smoke and token replay tests
    • fallback event rate below threshold in steady-state run (target: < 1%)
    • rollback via existing RUSTFS_LIST_OBJECTS_QUORUM control remains unchanged
  • Close-out action: keep this section as the live action list in the follow-up issue/subtask and bind each item to a dedicated PR before closure.

6.6 explicit index-backed design artifact

Continuation-token compatibility matrix:

Token family Current/Planned producer Parser action Preferred read path Fallback behavior
legacy marker-only legacy clients / pre-v2 servers parse as legacy continuation walker fail closed only on malformed XML/query
v2 cache-tag token current Phase-2 implementation parse marker + cache tag, preserve replay semantics walker with metacache resume force cache refresh on corrupt/replay mismatch
proposed v3 index-cursor token future index-assisted planner parse marker + index cursor metadata + manifest epoch index immediate downgrade to walker on token parse / epoch / checksum mismatch

Proposed index schema (design only, not implemented in this PR):

  • bucket
  • prefix_scope
  • object_key
  • version_id (optional for versioned buckets)
  • delete_marker
  • etag
  • size
  • last_modified
  • cursor_seq
  • manifest_epoch
  • record_checksum

Proposed write-path hooks:

  • PutObject, CopyObject, CompleteMultipartUpload: emit index upsert records
  • DeleteObject, DeleteObjectVersion, lifecycle expiry: emit tombstone records
  • replication/heal/rebuild workflows: emit replayable repair events into the same mutation stream

Consistency, backfill, and rebuild plan:

  • initial build trigger: bucket/range enrollment into index-assisted listing
  • backfill model: prefix-ordered walker snapshot with persisted watermark per bucket/range
  • drift detection: periodic sampled compare between index page output and walker page output using the same continuation boundary
  • rebuild trigger:
    • manifest epoch mismatch
    • record checksum mismatch
    • fallback rate above threshold
    • operator-requested rebuild after repair/heal incidents
  • proposed operator tooling surface (design-only names):
    • list-index build
    • list-index verify
    • list-index rebuild
    • list-index repair

Fallback state machine requirements:

  • token parse failure -> walker
  • manifest epoch mismatch -> walker + marker_fallback metric
  • sampled drift mismatch -> walker + alert
  • high fallback/error budget burn -> disable index path for affected bucket/range

6.7 rollout / rollback closure package

Rollout phases:

Phase Scope Success condition Abort condition
0 local/offline validation continuity pass, unit/static pass duplicate/missing in local smoke
A shadow benchmark on production-shaped bucket two consecutive windows with continuity pass and stable latency any continuity failure
B canary bucket or prefix range error budget within guardrail, fallback rate below threshold p99 +20%, error +10%, fallback spike
C broadened prefix families same as canary for two windows any regression repeats in two windows
D wider rollout observability remains stable operator rollback trigger

Explicit rollback conditions:

  • duplicate objects across pages
  • missing objects after truncation / continuation transition
  • p99 latency regression > +20%
  • error-rate regression > +10%
  • fallback rate above the planned threshold
  • sampled index drift mismatch

Rollback actions:

  • quorum-only experiment: set RUSTFS_LIST_OBJECTS_QUORUM=strict
  • future index-assisted planner: disable index path and force walker
  • token-family regression: stop emitting proposed v3 index tokens and continue accepting legacy / v2 parser branches only

Closure interpretation for #787:

  • the benchmark evidence is now attached and complete;
  • the outcome is negative for quorum-only rollout;
  • the design/rollout package now supports moving the real mitigation focus to index-backed listing.

7) Risk register

  • Wrong env value fallback behavior: normalized by existing helper to safe default.
  • Quorum drift across environments: explicit rollout control via env variable in deployment chart.
  • Unexpected compatibility concerns: no client-facing protocol change in this phase.

8) Rollback plan

  • Set RUSTFS_LIST_OBJECTS_QUORUM=strict at runtime/restart.
  • If needed, revert this commit only.

9) Deliverables expected

  • Code: this Phase-1 patch in worktree branch houseme/issue-4003-listobjects-v2-performance.
  • Docs: English and Chinese documents (this plan and Chinese counterpart).
  • Tracking: backlog issues #785, #786, #787 under parent #788.