[Feature] Hedging Detection API — public accessors on CosmosDiagnostics
feature-requestneeds-investigation
## Summary
Add a public **Hedging Detection API** to `Microsoft.Azure.Cosmos.CosmosDiagnostics` so customers can post-hoc determine whether a successful or failed point/feed operation went through cross-region hedging, which regions were dispatched against, and which regions responded. The new surface is strictly additive, non-breaking, and is reachable from both `ItemResponse<T>.Diagnostics` (success path) and `CosmosException.Diagnostics` (error path).
This is part of a cross-SDK feature being implemented in parallel across .NET, Java, and Python (with a spec-only deliverable for Rust). See cross-SDK design doc *"Hedging Detection API - Technical Design - Final"* (2026-05) for the contract.
## Public API additions
```csharp
namespace Microsoft.Azure.Cosmos
{
public readonly struct RequestedRegion : IEquatable<RequestedRegion>
{
public string RegionName { get; }
public RequestedRegionReason Reason { get; }
public RequestedRegion(string regionName, RequestedRegionReason reason);
public override bool Equals(object obj);
public bool Equals(RequestedRegion other);
public override int GetHashCode();
public override string ToString(); // "{regionName}:{reason}"
}
public enum RequestedRegionReason : byte
{
Initial,
OperationRetry,
TransportRetry, // reserved — not populated today (SE-001: signal lives in closed-source Direct package)
Hedging,
RegionFailover,
CircuitBreakerProbe,
}
public abstract class CosmosDiagnostics
{
// Existing members unchanged.
public virtual bool HedgingStarted() => false;
public virtual IReadOnlyList<RequestedRegion> GetRequestedRegions() => Array.Empty<RequestedRegion>();
public virtual IReadOnlyList<string> GetRespondedRegions() => Array.Empty<string>();
}
}
```
`CosmosTraceDiagnostics` (internal sealed) overrides all three to return live state. New state is populated at orchestrator dispatch sites (hedging strategy, retry policy, region failover, PPCB probe) and response-handling sites; appends are thread-safe via a private `object regionLock`. JSON shape of `Diagnostics.ToString()` is **unchanged** — new state is NOT serialized into the trace tree.
## Acceptance criteria (testable)
- [ ] **AC1** Single-region client, ReadItem success, no retries → `HedgingStarted() == false`; `GetRequestedRegions().Count == 1` with reason `Initial`; `GetRespondedRegions().Count == 1`.
- [ ] **AC2** Multi-region client, hedging enabled, primary responds under threshold → `HedgingStarted() == false`; `GetRequestedRegions().Count == 1` (**no phantom `Hedging` entry**); `GetRespondedRegions().Count == 1`.
- [ ] **AC3** Multi-region client, hedging enabled, primary slow, hedge arm wins → `HedgingStarted() == true`; `GetRequestedRegions()` has ≥2 entries: `(primary, Initial)` and `(hedgeRegion, Hedging)`; `GetRespondedRegions()` has ≥1 entry.
- [ ] **AC4** 410 Gone retry on same region → `GetRequestedRegions()` includes consecutive entries `(region, Initial)` then `(region, OperationRetry)`.
- [ ] **AC5** Region failover after 503 → `GetRequestedRegions()` includes `(originalRegion, Initial)` then `(secondaryRegion, RegionFailover)`.
- [ ] **AC6** PPCB probe after a region was previously circuit-broken → `GetRequestedRegions()` includes an entry with reason `CircuitBreakerProbe`. (Requires new `RequestContext` PPCB flag.)
- [ ] **AC7** All-regions-down error → `CosmosException.Diagnostics.GetRequestedRegions()` non-empty; `GetRespondedRegions()` may be empty.
- [ ] **AC8** `CosmosException.Diagnostics` exposes same data as the would-be-successful-response `Diagnostics`.
- [ ] **AC9** Backward compat — existing customer subclass of `CosmosDiagnostics` that does NOT override the new methods returns the safe defaults and does not throw.
- [ ] **AC10** Contract baselines (`DotNetSDKAPI.net6.json`, `DotNetSDKAPI.net8.json`) regenerated via `Microsoft.Azure.Cosmos\tools\UpdateContracts.ps1`; diffs committed; only the new public members appear.
- [ ] **AC11** `CosmosDiagnostics.ToString()` JSON for a known fixed diagnostic input matches the baseline byte-for-byte (golden-file test).
- [ ] **AC12** `RegionContactedInDiagnosticsBenchmark` mean latency does not regress by >5% vs baseline.
- [ ] **AC13** Hedge arm registered then cancelled **before threshold delay elapses** → `GetRequestedRegions()` does NOT contain a `Hedging` entry for the cancelled region (uses fault injection; primary completes in <50% of threshold so `Task.Delay(threshold, ct)` throws `OperationCanceledException`).
- [ ] **AC14** `GetRespondedRegions()` allows duplicates (same region producing two responses); XML doc warns that `Count > 1` does NOT imply multi-region success.
- [ ] **AC15** Reason coverage — `TransportRetry` value never appears in v1 production paths (covered by a fault-injection AC test that enumerates the union of reasons across the full matrix; documents SE-001).
- [ ] **AC16** Live multi-region smoke test (≥1) — runs against the team's existing multi-region test account with hedging enabled, injects primary-slow latency, and asserts `HedgingStarted() == true`, `GetRequestedRegions()` includes both regions, `GetRespondedRegions()` includes the secondary region.
## Files in scope
- New: `Microsoft.Azure.Cosmos\src\Diagnostics\RequestedRegion.cs`, `RequestedRegionReason.cs`
- Modify: `Microsoft.Azure.Cosmos\src\Diagnostics\CosmosDiagnostics.cs`, `CosmosTraceDiagnostics.cs`, `AvailabilityStrategy.cs` / `ParallelHedgingAvailabilityStrategy.cs`, `ClientRetryPolicy.cs`, `GlobalEndpointManager.cs`, `PartitionKeyRangeCache` response pipeline, `RequestContext` (PPCB flag), `changelog.md`
- Tests: new `RequestedRegionTests.cs`, new `HedgingDetectionEmulatorTests.cs` (under `Microsoft.Azure.Cosmos.EmulatorTests\FaultInjection\`), live-account multi-region test
- Contracts: `DotNetSDKAPI.net6.json`, `DotNetSDKAPI.net8.json` (regenerated)
- OpenSpec: refresh `openspec/changes/hedging-detection-api/` (predecessor PR #5741 left artifacts on closed branch `feature/hedging-detection-api`)
## Out of scope
- Wiring into OpenTelemetry / OTLP exporter — separate work item.
- Changing the existing JSON `ToString()` shape — explicitly preserved.
- Live wire-format changes — none; this is local diagnostics only.
- Cross-SDK parity automation — handled at the cross-SDK design layer.
## Cross-SDK companion issues
- **Java**: forthcoming companion issue in `Azure/azure-sdk-for-java` (`sdk/cosmos`).
- **Python**: forthcoming companion issue in `Azure/azure-sdk-for-python` (`sdk/cosmos`).
- **Rust**: forthcoming spec-only issue + draft PR in `Azure/azure-sdk-for-rust` (sequenced after PR #4330).
## Notes for the implementer
- Full internal spec, landscape research, plan, risk register (`side-effects.json`), questions+answers, and an `.docx` review bundle are available from the workflow author (`@NaluTripician`) on request — they are team-only and not linked here.
- Phase 1 review gate completed on 2026-05-14 with three substantive decisions: (Q3=B) Python collapsed to single Option B path; (Q5=B) Rust accepts `Retry → OperationRetry` JSON wire-format break with `#[serde(alias = "retry")]`; (Q10) at least one live multi-region smoke test required per implementing SDK.
- This issue is being dispatched to the **Coding Agent Harness** for end-to-end implementation; reviewers may receive a draft PR shortly.
0 条评论