mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-28 10:51:15 +08:00
* perf(ecstore): avoid head-of-line blocking in delete recovery Recover independent keys through a bounded rolling window while preserving per-key hint generations, admission budgets, cancellation retry, and the existing transaction decision engine. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * perf(ecstore): bound durable delete preparation and bootstrap recovery Prepare independent Linux delete transactions with bounded concurrency while retaining per-key result order and serial fallback for overlapping, rollback, and legacy requests. Preserve startup discovery until bucket metadata is initialized, and add release performance and process-crash acceptance runners. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * test(ecstore): verify clean exits in recovery acceptance Reject forced or OOM exits when measuring clean startup and verify original ETags across mixed-layout crash recovery and a second restart. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * test(ecstore): distinguish prepared and decided batch crashes Observe real preparation and receipt states separately, retain non-vacuous acknowledged-delete sentinels, and require read-only physical convergence before S3 reads can trigger cleanup. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * test(ecstore): move final delete recovery options Move the options value into the batch-ordering regression's final case instead of cloning it. Keep the earlier clone needed for reuse. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * fix(ecstore): replay delete receipts after interrupted proof GC Retire only the exact empty transaction leaf before journal activation. Preserve occupied directories and prefix children, and cover partial cross-disk proof GC without a fresh delete. Tighten crash evidence to distinguish directory-only remnants from retained content. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com> Co-authored-by: zhi22915 <qiuzgang@gmail.com>
94 lines
4.5 KiB
Python
94 lines
4.5 KiB
Python
"""Regression checks for benchmark evidence and drift verdicts."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
from types import SimpleNamespace
|
|
|
|
import delete_recovery_perf as perf
|
|
|
|
|
|
class EvidenceTests(unittest.TestCase):
|
|
def test_only_empty_directory_trees_are_not_payload_residue(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory) / "key"
|
|
(root / "transaction").mkdir(parents=True)
|
|
self.assertFalse(perf.harness.has_retained_content(root))
|
|
proof = root / "transaction" / "xl.meta.bkp"
|
|
proof.write_bytes(b"backup")
|
|
self.assertTrue(perf.harness.has_retained_content(root))
|
|
proof.unlink()
|
|
payload = root / "unknown-entry"
|
|
payload.write_bytes(b"payload")
|
|
self.assertTrue(perf.harness.has_retained_content(root))
|
|
|
|
def test_symlinks_cannot_be_mistaken_for_empty_directories(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory) / "key"
|
|
root.mkdir()
|
|
(root / "link").symlink_to(Path(directory) / "missing-target")
|
|
self.assertTrue(perf.harness.has_retained_content(root))
|
|
|
|
def test_clean_restart_rejects_forced_exit_or_oom(self):
|
|
for state in ({"ExitCode": 137, "OOMKilled": False}, {"ExitCode": 0, "OOMKilled": True}):
|
|
cluster = perf.harness.Cluster.__new__(perf.harness.Cluster)
|
|
cluster.names = ["owned-test-server"]
|
|
with patch.object(perf.harness, "docker", side_effect=[SimpleNamespace(), SimpleNamespace(stdout=json.dumps(state).encode())]):
|
|
with self.assertRaises(AssertionError):
|
|
cluster.stop_clean()
|
|
|
|
def test_expired_operation_window_cannot_be_attributed_to_the_candidate(self):
|
|
with self.assertRaisesRegex(ValueError, "expired"):
|
|
perf.stage_delta({"prepare": {"count": 4, "ns": 100}}, {"prepare": {"count": 3, "ns": 90}})
|
|
|
|
def test_operation_service_times_keep_their_units(self):
|
|
self.assertEqual(perf.stage_delta({}, {"prepare": {"count": 4, "ns": 2_000_000}}),
|
|
{"prepare": {"calls": 4, "service_ms": 2.0}})
|
|
|
|
def test_binary_identity_and_release_configuration_are_required(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
binary, receipt = Path(directory) / "binary", Path(directory) / "receipt.json"
|
|
binary.write_bytes(b"candidate")
|
|
value = {"binary_sha256": perf.binary_digest(binary), "source_head": "a" * 40,
|
|
"build": {"profile": "debug", "opt_level": 0}}
|
|
receipt.write_text(json.dumps(value))
|
|
with self.assertRaisesRegex(ValueError, "release"):
|
|
perf.binary_receipt(binary, receipt)
|
|
binary.write_bytes(b"different executable")
|
|
with self.assertRaisesRegex(ValueError, "does not match"):
|
|
perf.binary_receipt(binary, receipt)
|
|
|
|
def test_control_or_candidate_drift_withholds_every_improvement_claim(self):
|
|
for unstable in ("A2", "B2"):
|
|
samples = self.samples()
|
|
for sample in samples[unstable]:
|
|
sample["wall_seconds"] *= 2
|
|
result = perf.comparison_report(samples, 10)
|
|
self.assertEqual(result["verdict"], "inconclusive")
|
|
self.assertNotIn("wall_time_reduction_percent", result)
|
|
|
|
def test_candidate_startup_timeout_withholds_improvement_claims(self):
|
|
samples = self.samples()
|
|
samples["B1"][0]["clean_restart"]["discovery_within_30_seconds"] = False
|
|
result = perf.comparison_report(samples, 10)
|
|
self.assertFalse(result["candidate_discovery_within_30_seconds"])
|
|
self.assertEqual(result["verdict"], "inconclusive")
|
|
self.assertNotIn("wall_time_reduction_percent", result)
|
|
|
|
def test_stable_controls_allow_a_quantified_result(self):
|
|
result = perf.comparison_report(self.samples(), 10)
|
|
self.assertEqual(result["verdict"], "comparable")
|
|
self.assertAlmostEqual(result["wall_time_reduction_percent"]["inline-batch"], 30)
|
|
|
|
@staticmethod
|
|
def samples():
|
|
return {cohort: [{"case": case, "wall_seconds": value, "clean_restart": {"discovery_within_30_seconds": True}} for case in
|
|
("inline-batch", "inline-single", "external-batch") for _ in range(3)]
|
|
for cohort, value in (("A1", 10), ("B1", 7), ("B2", 7), ("A2", 10))}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|