Commit Graph
313 Commits
Author SHA1 Message Date
39274fc37c feat(ecstore): default bounded metadata fanout (#6156)
Co-authored-by: heihutu <[email protected]>
2026-08-17 00:56:20 +08:00
33eff4c3c4 test(ecstore): add metadata slow-tail fault hook (#6150)
Add a diagnostic metadata-only read_version delay hook for GET data-read fanout so bounded/default behavior can be compared under controlled slow-tail metadata responses.

Co-authored-by: heihutu <[email protected]>
2026-08-16 23:36:46 +08:00
Zhengchao AnandGitHub a2f16aa066 test(tier): pin the compressed transitioned read against its stored bytes (#6151)
#6107 routed the transitioned read through the object's own ReadPlan so a tiered SSE object stops serving ciphertext. Compression rides that same plan and got fixed with it, but nothing pins it: revert the routing and a compressed object that ILM moved to a warm tier returns its stored (compressed) bytes under the compressed size, with every existing test still green.

The gap is easy to reopen because transition genuinely uploads the stored representation — the upload side is correct and the read side is the only place that can decode it. These tests state that contract at the boundary where it broke.

Four cases, all through SetDisks::get_object_reader against a mock warm tier:

- a full GET of a transitioned compressed object returns the plaintext and publishes the plaintext size (the test also asserts the remote copy holds the compressed bytes, so it fails loudly if the upload side ever changes instead);
- a ranged GET returns that plaintext slice, with the range deliberately starting past the compressed size so a range still measured in stored coordinates cannot produce it;
- a restore read still receives the stored bytes under the stored size — restore_request_active holds it on the Plain branch, and decompressing there would write plaintext under compressed metadata;
- a plain transitioned object still reads back byte-identical, full and ranged.

Verified as guards, not decoration: forcing the tiered read back onto the Plain branch turns the two compressed tests red and leaves the plain and restore tests green.

What these do not pin, so the gap stays recorded rather than implied covered: the fixture carries no compression index, so part.index stays None and the plan's storage offset is always 0 — the compressed-offset translation itself is still untested, as are multipart compressed objects, partNumber reads, and the encrypted tiered read that #6107 targeted.
2026-08-16 15:27:59 +00:00
Zhengchao AnandGitHub 3272730c13 fix(ecstore): silence two dead_code warnings left on main (#6153) 2026-08-16 22:42:45 +08:00
Zhengchao AnandGitHub f1f86ee9d0 chore(ecstore): drop the set_disk dead_code blanket (#6141)
* chore(ecstore): drop the set_disk dead_code blanket

Removing the blanket exposes 39 items; exactly one is deleted. The low share is a finding, not caution: unlike the disk root, where platform gating made local adjudication impossible, here the items were checked and nearly all of them are live.

Deleted: HealEntryResult, the only item with no reference anywhere.

What the checks turned up, in the order the warnings suggest deleting them:

SetDisks::rename_data looked like the head of a dead chain feeding into_legacy_tuple and RenameDataLegacyTuple. It is not: production goes through rename_data_owned, and rename_data itself has test callers at mod.rs:5809 and 5880. The chain below it is therefore live through the tests, and inferring "this is dead, so its callee is dead" would have removed three working items.

create_bitrot_readers_until_quorum, read_multiple_files and map_cleanup_join_result all have callers inside their files' test modules, so they only look dead in the lib target.

TransitionCommitBarrier and TransitionUploadedSaveProbe, with their install/wait_until_paused/release surfaces, are installed by tests behind #[cfg(all(test, feature = "test-util"))].

ctx.rs's SetDisksCtx accessors are the split seam left by the SetDisks god-object break-up (backlog#815).

heal_object_dir's two apparent references are comments, and they document an index-alignment contract that live code maintains for it, so they stay as they are.

Worth a maintainer decision: the metadata early-stop switch has a complete percentage-rollout facet — ENV_RUSTFS_GET_METADATA_EARLY_STOP_ROLLOUT_PCT, get_metadata_early_stop_rollout_pct and should_use_metadata_early_stop — with no caller, no test and no documentation, while its sibling enable flag is live. It is kept with an allow that says so rather than removed, since a rollout knob is a product call.

One placement note for anyone adding allows near heal code: check_logging_guardrails.sh requires #[instrument(level = "trace")] to sit immediately before async fn heal_object_dir, so the allow goes above the instrument attribute. Putting it between the two drops the guard's match count and fails the check.

Verification, four lanes warning-free: default, --tests, --features rio-v2 --tests, --features test-util --tests. cargo nextest run -p rustfs-ecstore 4096 passed; clippy --lib --tests -D warnings clean; make pre-commit exit 0.

Ref rustfs/backlog#1823 (step 2).

* chore(ecstore): fix duplicated and inaccurate dead_code reasons in set_disk

format_lock_error carried the same #[allow] twice. Five items in the
locking/heal roots were labelled 'asserted by this file's tests' while
having no reference at all - heal_object_dir's only two references are
comments, as this branch's own notes point out. Say what each item
actually is instead, so the next reader does not assume test coverage
that is not there.

Ref rustfs/backlog#1823.

* chore(ecstore): correct the bounded_spare_disk_index dead_code reason

The mod.rs copy is an unused test fixture, not something this module's
tests assert; the namesake that is exercised lives in the io_primitives
test module.

Ref rustfs/backlog#1823.
2026-08-16 21:38:46 +08:00
a118d7e4fd perf(ecstore): enable inline data read early-stop by default (#6140)
* perf(ecstore): enable inline data read early-stop by default

Co-Authored-By: heihutu <[email protected]>

* test(scanner): box large ILM transition flow future

Co-Authored-By: heihutu <[email protected]>

* test(ecstore): align internal meta early-stop miss

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
2026-08-16 06:38:51 +00:00
Zhengchao AnandGitHub 0d86c50760 fix(ecstore): classify remote inline early-stop misses (#6136) 2026-08-16 09:50:16 +08:00
housemeandGitHub 526d6f667e perf(ecstore): defer pending inline data shards (#6137) 2026-08-16 09:44:00 +08:00
7f23a1ba91 feat(ecstore): report inline early-stop miss reasons (#6134)
Co-authored-by: heihutu <[email protected]>
2026-08-15 17:18:26 +00:00
cxymdsandGitHub eca6bc1600 fix(ecstore): preserve CopyObject producer errors (#6090)
* fix(ecstore): preserve CopyObject producer errors

* fix(app): resume preserved relocation I/O errors

* fix(copy): preserve transformed source errors
2026-08-14 14:12:42 +00:00
cxymdsandGitHub 85be26b3c1 test(ecstore): cover cancelled PUT tmp cleanup (#6105) 2026-08-14 14:07:44 +00:00
Zhengchao AnandGitHub ebbcfa3ac2 fix(tier): decrypt transitioned objects instead of serving their ciphertext (#6107)
* fix(tier): decrypt transitioned objects instead of serving their ciphertext

A GET on a managed-SSE object that lifecycle had transitioned to a remote tier returned the ciphertext with the plaintext's Content-Length and no error: silent corruption on read-through, and worse than a failed request because nothing signals it. Restore of the same object failed server-side with IncompleteBody while POST ?restore still answered 200, so the object simply never came back and HEAD never showed an x-amz-restore marker.

Both symptoms are one cause. The transitioned read path built its fetch through new_getobjectreader, which decides nothing about encryption: it derived the range from the parts table — whose sizes are PLAINTEXT sizes — then used that range to fetch the object's STORED bytes from the tier, and handed the stream to the caller without any decrypt transform. The GET therefore served the first plaintext-length bytes of ciphertext; the restore copy-back, which validates against the stored size, came up short by exactly the encryption overhead.

The path now builds the same ReadPlan the local read path uses, so a single place decides how stored bytes map to requested bytes. ReadPlan gains a two-phase API — build_for_request to learn the storage coordinates before issuing the tier fetch, into_object_reader to wrap the returned stream — because the tier fetch has to be positioned before a stream exists. The encryption resolver reaches the path from InstanceContext, the same source the local read uses.

A restore read additionally stops synthesizing a range from the part number. A restore serves the stored representation (restore_request_active already forces the Plain branch), so a plaintext-coordinate range would be reinterpreted as a storage range and truncate the payload by its encoding overhead. An explicit caller range is already in storage coordinates on that path and is still honored, which two existing tests pin.

crates/e2e_test/src/kms/kms_ilm_sse_kms_test.rs drops its #[ignore]: the transition test now runs and asserts the plaintext round-trips byte-identically through transition, read-through and restore. The same file had its enforcement switch stuck at false from a control experiment; it is back to true, so the test again exercises what its name and module docs claim.

Fixes #6025. Refs rustfs/backlog#1582, rustfs/backlog#1637.

* test(tier): pass resolver to transitioned reader tests
2026-08-14 21:59:38 +08:00
cxymdsandGitHub 67a19021b5 fix(ecstore): allow migrated unknown part sizes (#6112) 2026-08-14 21:00:01 +08:00
0ff3d4cbf4 perf(ecstore): borrow rename metadata during commit fanout (#6104)
* perf(ecstore): borrow rename metadata during commit fanout

Co-Authored-By: heihutu <[email protected]>

* fix(ecstore): preserve rename_data API compatibility

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
Co-authored-by: Zhengchao An <[email protected]>
2026-08-14 11:56:36 +00:00
cxymdsandGitHub 6f29431a65 test(ecstore): isolate rename publication hooks (#6106) 2026-08-14 11:07:34 +00:00
cxymdsandGitHub d60a77b750 fix(quota): enforce durable hard quota reservations (#6058)
* fix(quota): enforce durable hard quota reservations

* fix(quota): close reservation bypasses

* fix(quota): isolate tests and box object futures

* fix(quota): close legacy and deferred settlement bypasses

* fix(app): keep object futures off caller stacks

* fix(metrics): preserve object operation labels

* fix(logging): retain GET trace guard contract
2026-08-14 06:26:00 +00:00
068a0c2b8c perf(ecstore): shorten multipart commit lock tail (#6080)
Co-authored-by: heihutu <[email protected]>
2026-08-14 04:48:05 +08:00
5b54c4303d fix(ecstore): reconcile object cleanup receipts (#6077)
* fix(s3): keep multipart completion publication owned

Co-Authored-By: heihutu <[email protected]>

* fix(s3): keep put publication owned

Co-Authored-By: heihutu <[email protected]>

* chore(app): route multipart context through facade

Co-Authored-By: heihutu <[email protected]>

* fix(ecstore): gate object transaction fencing

Co-Authored-By: heihutu <[email protected]>

* fix(ecstore): fence object transaction epochs

Co-Authored-By: heihutu <[email protected]>

* fix(ecstore): reconcile old data cleanup receipts

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
2026-08-13 18:08:50 +00:00
6178083985 perf(ecstore): reuse erasure codecs on GET paths (#6074)
* perf(ecstore): share legacy SIMD workspaces

Reuse legacy Reed-Solomon encoder and decoder workspaces across Erasure instances with the same shard layout while keeping active codecs request-exclusive.

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): reuse GET erasure shells and scratch buffers

Co-Authored-By: heihutu <[email protected]>

* test(ecstore): satisfy concurrent codec lint

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): bound cached legacy workspaces

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): cap retained legacy codec memory

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
2026-08-13 17:51:42 +00:00
e16c07b9cd perf(ecstore): scale inline threshold by EC layout (#6075)
* perf(ecstore): scale inline threshold by EC layout

Co-Authored-By: heihutu <[email protected]>

* test(ecstore): preserve inline budget semantics

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
2026-08-13 17:00:31 +00:00
aa4d3317ed perf(ecstore): guard inline data-read metadata early-stop (#6069)
Add a default-off inline-only data-read metadata early-stop gate that verifies inline plaintext before cancelling pending metadata tasks.

Keep non-inline, prepared, and request-shape-sensitive reads on full fanout, and record scheduled/completed/cancelled ReadVersion lifecycle metrics for normal fanout completion.

Co-authored-by: heihutu <[email protected]>
2026-08-13 21:40:14 +08:00
housemeandGitHub 6b86d44cac fix(ecstore): retain commit owners across cancellation (#6068) 2026-08-13 18:08:58 +08:00
d2b1003612 perf(storage): converge Wave 2 hot-path optimizations (#6065)
* perf(get): share inline shards and lock clients

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): converge PUT encoding on contiguous blocks

Co-Authored-By: heihutu <[email protected]>

* perf(get): cache codec streaming gate config

Co-Authored-By: heihutu <[email protected]>

* fix(sse): redact projected customer headers

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): collapse GET metadata snapshots

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): reuse decode stripe scratch

Co-Authored-By: heihutu <[email protected]>

* refactor(ecstore): trim decode scratch adapters

Co-Authored-By: heihutu <[email protected]>

* test(ecstore): adapt transition checks to metadata snapshots

Co-Authored-By: heihutu <[email protected]>

* perf(get): release metadata snapshots at ownership boundary

Co-Authored-By: heihutu <[email protected]>

* refactor(ecstore): close cumulative fast-path findings

Co-Authored-By: heihutu <[email protected]>

* fix(storage): preserve lock and header invariants

Co-Authored-By: heihutu <[email protected]>

* test(ecstore): adapt cumulative paths after rebase

Co-Authored-By: heihutu <[email protected]>

* fix(rio-v2): adapt generated metadata fixture

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
2026-08-13 16:34:28 +08:00
cxymdsandGitHub e11fcfbd08 fix(rebalance): converge multipart data movement retries (#6057)
* fix(rebalance): converge multipart data movement retries

* fix(rebalance): harden multipart retry replacement

* fix(rebalance): isolate internal multipart uploads

* test(ecstore): adapt metadata mutation fixtures

* fix(rebalance): preserve transition metadata semantics

* refactor(ecstore): reuse internal metadata matcher

* Revert "refactor(ecstore): reuse internal metadata matcher"

This reverts commit c87ca0328f.

* refactor(rebalance): reuse data movement log constants

* fix(rebalance): isolate migration-owned state

* fix(rebalance): preserve pre-gate retry compatibility
2026-08-13 06:12:26 +00:00
3a0dbccc2e perf(ecstore): reduce inline PUT commit overhead (#6033)
* perf(metrics): attribute PUT stage costs

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): move PUT metadata during shuffle

Co-Authored-By: heihutu <[email protected]>

* perf(s3): reuse PUT object lock state

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): trim PUT metadata fanout clones

Build per-disk PUT metadata only for committed writer slots, move the response metadata out of the fanout vector, and preserve fresh FileInfo shuffle semantics.

Co-Authored-By: heihutu <[email protected]>

* perf(metrics): make PUT stage attribution opt-in

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): commit inline PUT shards directly

Co-Authored-By: heihutu <[email protected]>

* perf(ecstore): streamline rename staging cleanup

Use the directory-specific removal operation for rename_data staging parents. This avoids a guaranteed failed file-removal probe on Unix-like hosts and lets Windows remove the empty directory directly while preserving best-effort non-empty handling.

Co-Authored-By: heihutu <[email protected]>

* test(ecstore): cover inline PUT rename failures

Cache the detailed stage metrics gate once per PUT and exercise exact-quorum and quorum-minus-one failures after inline shard encoding.

Co-Authored-By: heihutu <[email protected]>

---------

Co-authored-by: heihutu <[email protected]>
2026-08-13 02:04:20 +00:00
59d8d93832 perf(ecstore): release PUT lock before old-data cleanup (#6023)
Co-authored-by: heihutu <[email protected]>
2026-08-12 17:40:32 +00:00
59494d5089 perf(get): reuse reader paths and lock namespaces (#6015)
Co-authored-by: heihutu <[email protected]>
2026-08-12 17:37:25 +00:00
9546baf1ab perf(get): share metadata cache hits (#6010)
Keep fresh metadata fanout results owned while sharing cache-backed metadata through Arc, avoiding deep clones on eligible local cache hits without enabling unsafe distributed caching.

Co-authored-by: heihutu <[email protected]>
2026-08-12 16:53:06 +00:00
24ca61eb6e perf(get): include small objects in codec streaming (#6004)
Co-authored-by: heihutu <[email protected]>
2026-08-12 16:04:15 +00:00
d92c563b9e perf(ecstore): keep decode scratch buffers inline (#6002)
Keep common shard-indexed decode scratch vectors inline while preserving heap fallback for larger supported erasure layouts. Consume scratch iterators directly at the stripe-state boundary to avoid reallocating.

Co-authored-by: heihutu <[email protected]>
2026-08-12 15:40:32 +00:00
Zhengchao AnandGitHub 1021d7228a fix(ecstore): scope UploadPart commit lock per part number (#5990)
put_object_part's commit phase held an exclusive write lock on the whole
upload_id_path namespace, so concurrent UploadPart commits for different
part numbers of one upload serialized behind a single lock and returned
503 once the 5s lock-acquire timeout elapsed.

Adopt MinIO's PutObjectPart lock scope: a shared read lock on the
uploadId namespace plus an exclusive write lock on
{upload_id_path}/part.{N}. Different part numbers now commit
concurrently; same-part retries still serialize (backlog#853);
complete/abort keep the uploadId write lock and still exclude every
in-flight part commit. The lock-loss fence covers both guards.

Fixes #5961
2026-08-12 14:37:22 +00:00
housemeandGitHub 3b49842df0 perf(ecstore): reduce small PUT fixed costs (#5987) 2026-08-12 20:07:40 +08:00
housemeandGitHub 848b330825 perf(ecstore): reduce inline GET fixed costs (#5985) 2026-08-12 19:29:46 +08:00
housemeandGitHub 270a003c55 fix(ecstore): attribute internal metadata GET metrics (#5983) 2026-08-12 19:29:36 +08:00
1b6ae33ce0 perf(get): trim direct-read metadata allocations (#5976)
Co-authored-by: heihutu <[email protected]>
2026-08-12 07:27:58 +00:00
924958bab5 perf(get): slim metadata fanout allocations (#1803) (#5968)
Every GET fans out a `read_version` across all disks to resolve xl.meta. Each
fanout allocated an `Arc<ReadOptions>` (3 bools) plus four `Arc<String>`
(`Arc::new(x.to_string())` = two allocations each) and cloned them into every
spawned task. This trims the per-fanout allocation footprint.

- `ReadOptions` is three bools, so it is now `Copy`. The fanout drops the
  `Arc<ReadOptions>` and hands each spawned task a copy; the two pre-existing
  `ReadOptions::clone()` sites (set_disk/read.rs, set_disk/ops/heal.rs) stop
  cloning a `Copy` type.
- The four request strings use `Arc::<str>::from(&str)` (one allocation each)
  instead of `Arc::new(..to_string())` (string buffer + Arc = two each) — four
  fewer allocations per fanout, transparent to the `read_version(&str)` call.

Behavior is unchanged: the fanout still spawns one task per disk (the spawn is
deliberate — `read_version_call_counter_observes_spawned_fanout` verifies the
process-global counter observes every per-disk increment across workers), quorum
/ early-stop / full-wait semantics are untouched, and no result ordering or
error handling changed.

Two larger items from the audit are intentionally NOT in this PR:
- `tokio::spawn` -> `FuturesUnordered`: the spawn is a tested, deliberate
  design (cross-worker counter observation for #1309/#1314), and converting
  would also change panic isolation. Left as-is.
- `vec![FileInfo::default(); N]`: `FileInfo`'s empty containers (String /
  HashMap / Vec) do not allocate, so this is one `Vec` allocation, not the
  per-element allocation the audit implied — not a real hot spot.

`cargo fmt`, `cargo clippy -p rustfs-ecstore --lib` (0 warnings),
`cargo check --lib --tests`, and the 26 fanout / call-counter unit tests pass
on macOS (the change is fully cross-platform).

Co-authored-by: heihutu <[email protected]>
Co-authored-by: zhi22915 <[email protected]>
2026-08-12 02:38:13 +00:00
968ec4a8be perf(get): enable inline direct-read by default + versioned buckets (#1802) (#5966)
A small object whose data shards are inlined in xl.meta can be reassembled
straight from the already-resolved metadata, skipping the Erasure reconstruct
pipeline. The fast path existed but was opt-in
(`RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY`, default off), so every deployment
paid the full shard-read fan-out for eligible small GETs by default.

- Flip `DEFAULT_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY` to `true`. The path is
  correctness-neutral on a miss: `try_get_object_direct_data_shards_*` returns
  None when the inline reassembly cannot satisfy the read, and the GET then
  proceeds through the normal shard-read pipeline. The env stays as a kill
  switch (`=false` restores the legacy path).
- Drop the bucket-level `versioned` / `version_suspended` exclusions. The
  decision is made on `fi` — the already-resolved target version — so
  reassembling its inlined data is correct on a versioned bucket too. An
  explicit versionId GET still falls back (`opts.version_id`), and a
  delete-marker latest is still rejected. The two now-unused fallback reasons
  and their metric labels are removed.
- Tests updated: a versioned latest-version object is now eligible / `Use`
  (covers the newly allowed path); the removed reasons' label assertions are
  dropped.

cargo check --lib --tests and the direct_memory unit tests pass on macOS
(the change is fully cross-platform).

Co-authored-by: heihutu <[email protected]>
2026-08-12 02:06:39 +00:00
cxymdsandGitHub 6cce3d60bb fix(quota): reject oversized multipart completion (#5958)
* fix(quota): reject oversized multipart completion

* fix(arch): route quota test through app facade
2026-08-11 21:30:05 +08:00
housemeandGitHub ba6a0f25d9 perf(get): tune body stream buffers (#5959) 2026-08-11 20:48:03 +08:00
3747d19ce5 perf(ecstore): hedge bounded GET metadata fanout (#5935)
Keep opt-in bounded GET data-read fanout from waiting on a single pending ReadVersion response when an unscheduled spare disk can satisfy quorum. Add a deterministic 2+2 regression that pauses the third scheduled metadata read and verifies the spare is started before returning.

Co-authored-by: heihutu <[email protected]>
Co-authored-by: zhi22915 <[email protected]>
2026-08-10 17:11:04 +00:00
1148e76279 test(ecstore): update prepared GET fanout default (#5932)
Assert the prepared GET metadata path keeps the default full data-read fanout after PR #5929 made bounded data-read fanout opt-in.

Co-authored-by: heihutu <[email protected]>
2026-08-10 15:38:37 +00:00
fe2516ee86 perf(ecstore): keep bounded GET fanout opt-in (#5929)
Keep GET data-read metadata early-stop and bounded fanout behind explicit environment switches so the default path preserves full fanout read-failure tolerance.

Retain the focused opt-in A/B coverage and the invalid parity full-fanout guard for heterogeneous set layouts.

Co-authored-by: heihutu <[email protected]>
2026-08-10 22:15:34 +08:00
276eea1fba test(heal): cover replacement target evidence failures (#5919)
Co-authored-by: heihutu <[email protected]>
2026-08-10 14:50:55 +08:00
88e285c523 perf(ecstore): gate bounded GET metadata fanout (#5917)
Co-authored-by: heihutu <[email protected]>
2026-08-10 05:20:21 +00:00
f17ea7f146 fix(heal): harden replacement rebuild tracking (#5892)
* 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]>
2026-08-10 08:32:47 +08:00
Zhengchao AnandGitHub be0cea83b7 test(ecstore): pin persisted metadata key literals and bucket config goldens (#5904) 2026-08-09 22:12:26 +00:00
唐小鸭andGitHub 6333f21a2e feat(replication): SSE-C ciphertext passthrough replication (#5898)
Complete the encrypted-object replication series (backlog#1783, PR-C of
3, after #5872 and #5885): SSE-C objects replicate as ciphertext
passthrough — the source holds no customer key, so the stored bytes and
their encryption metadata travel verbatim and the replica decrypts only
with the original customer key, single-part and multipart.

- Sender: SSE-C objects read raw (raw_data_movement_read), transfer at
  ciphertext size, and range multipart parts over stored part sizes.
- Receiver: authorized replication PUTs restore the stored SSE-C keys
  from the transport headers (exact lowercase forms - the read-path
  check is case-sensitive), set ObjectOptions.preserve_ciphertext, and
  skip compression, bucket-default SSE, and sse_encryption behind one
  restore-derived gate. Multipart uses an internal session marker to
  store parts verbatim and strips it on complete.
- Convergence: the replication HEAD sends
  x-rustfs-source-replication-check; the target authorizes it as
  ReplicateObjectAction and skips SSE-C read validation for that
  request only, so keyless convergence HEADs see etag/size/mtime
  instead of 400 and SSE-C replicas stop re-driving forever.
- e2e: SSE-C contract flips to a key-gated readable replica (no-key and
  wrong-key GETs fail - the direct silent-plaintext detector); new
  multipart passthrough contract with ETag/marker/stability assertions.
2026-08-09 23:53:04 +08:00
73e4ef4dd4 feat(replication): replicate managed-SSE objects via target re-encryption (#5885)
Open the managed-SSE replication gate (backlog#1783, PR-B of 3, after
#5872): the replication reader already decrypts through the injected
object-encryption resolver, so the source sends plaintext plus an
encryption intent header (AES256 / aws:kms, never the source key id) and
the target re-encrypts on its normal PUT path with its own KMS. No DEK
crosses sites.

- replication_put_object_options: fail closed only on Unsupported;
  insert the SSE intent after the strip loop.
- TargetClient::create_multipart_upload sends the full opts.header()
  set, fixing multipart replicas losing content-type/user metadata
  (plaintext included).
- Preserve source ETag and mtime on replicas (authorized replication
  only): receiver wires x-rustfs-source-etag into preserve_etag for PUT
  and CompleteMultipartUpload, resolve_complete_etag consumes it, and
  complete options carry source_etag/source_mtime (absent mtime
  degrades to epoch, not now_utc). Without this every replication HEAD
  comparison re-drives re-encrypted objects forever.
- e2e: managed SSE contracts flip to success on an independent-KMS
  dual-process pair (byte-identical plain GET proves target-owned
  envelopes; ETag/mtime preserved; version stable across scanner
  cycles; resync converges; multipart keeps structure and metadata);
  new target-without-KMS fail-closed contract; SSE-C stays FAILED.

Co-authored-by: houseme <[email protected]>
2026-08-09 10:21:35 +00:00
a71726ef49 perf(get): reduce response write allocations (#5890)
Avoid cloning cache-served GET bodies, preserve downstream vectored writes through the GET close-detection wrapper, and remove per-stripe EC decode sidecar allocations.

Co-authored-by: heihutu <[email protected]>
Co-authored-by: zhi22915 <[email protected]>
2026-08-09 08:58:41 +00:00
GatewayJandGitHub 70deb3284b fix(select): pin object snapshot for query lifetime (#5835) 2026-08-09 14:08:53 +08:00