Tracking (Direct/msdata): Add Range.CheckOverlapping overload accepting custom IComparer<T> — blocker for #5897
bugQUERYRoutingHierarchicalPartitioning
## Tracking issue — implementation work happens in the internal `msdata/CosmosDB` repository
This is a public tracker for changes to the **Direct package** (`Microsoft.Azure.Cosmos.Direct`) that the v3 SDK depends on. The actual code change happens in the internal `msdata/CosmosDB` repository in `Product/Microsoft.Azure.Documents/SharedFiles/Routing/Range.cs`. Filing it here so the dependency, blocker status, and rollout sequence are visible from the public v3 SDK repo.
**Type:** Direct-package bug; required dependency for #5897.
---
## Summary
`Microsoft.Azure.Documents.Routing.Range<T>.CheckOverlapping(Range<T>, Range<T>)` (Direct package, `internal sealed`) is **hardcoded** to use `Comparer<T>.Default` for all boundary comparisons. For `T = string`, that is `StringComparer.Ordinal`. There is no overload that accepts a caller-supplied `IComparer<T>`.
The v3 SDK's `CollectionRoutingMap.GetOverlappingRanges`:
1. Binary-searches a `SortedList<Range<string>, …>` with a **swappable** comparer — `LengthAwareMin/MaxComparer` (which normalize trailing `'0'` bytes via `TrimEnd('0')`) is the production default for HPK containers.
2. Verifies overlap with `Range<string>.CheckOverlapping` — which uses **ordinal** semantics.
When stored boundaries differ from input EPKs only in trailing-zero padding (the scenario the backend Elasticity team mitigated for the affected customer on 2026-05-15), the two stages disagree, producing an empty overlapping-range list. Consumer impact:
- Silent zero-result query (data loss to the application).
- `ArgumentOutOfRangeException` from `RequestInvokerHandler.SendAsync` (`overlappingRanges[0]` on empty list) — see production telemetry in #5859.
The principled fix for the v3 consumer (#5897) cannot land until the Direct package exposes an overload of `CheckOverlapping` that accepts the same `IComparer<T>` used for the surrounding binary search.
---
## Proposed change (Direct package, `Range.cs`)
```csharp
namespace Microsoft.Azure.Documents.Routing
{
internal sealed partial class Range<T>
{
// NEW: overload accepting the caller's comparer.
public static bool CheckOverlapping(Range<T> range1, Range<T> range2, IComparer<T> boundsComparer)
{
if (boundsComparer == null) throw new ArgumentNullException(nameof(boundsComparer));
if (range1 == null || range2 == null || range1.IsEmpty || range2.IsEmpty)
return false;
int cmp1 = boundsComparer.Compare(range1.Min, range2.Max);
int cmp2 = boundsComparer.Compare(range2.Min, range1.Max);
if (cmp1 <= 0 && cmp2 <= 0)
{
if ((cmp1 == 0 && !(range1.IsMinInclusive && range2.IsMaxInclusive)) ||
(cmp2 == 0 && !(range2.IsMinInclusive && range1.IsMaxInclusive)))
{
return false;
}
return true;
}
return false;
}
// PRESERVED: legacy overload, now a thin delegator. Bit-identical output to today.
public static bool CheckOverlapping(Range<T> range1, Range<T> range2)
=> CheckOverlapping(range1, range2, TComparer);
}
}
```
**Optional but recommended** companion: make `Range<T>.MinComparer` / `Range<T>.MaxComparer` constructible with a user-supplied bounds comparer (mirroring the public ctor that already exists on `LengthAwareMin/MaxComparer`). Static `Instance` fields preserved for binary compatibility. No existing call site changes.
---
## Acceptance criteria
- [ ] New public overload `Range<T>.CheckOverlapping(Range<T>, Range<T>, IComparer<T>)` exists in `Product/Microsoft.Azure.Documents/SharedFiles/Routing/Range.cs`.
- [ ] Existing parameterless-comparer overload preserved; delegates to the new overload with `TComparer`; output is bit-identical for every existing call site.
- [ ] (Recommended) `MinComparer` / `MaxComparer` expose a public ctor accepting `IComparer<T>`; `Instance` static field preserved.
- [ ] Unit tests added in the Direct package:
- `Range_CheckOverlapping_WithCustomComparer_AgreesWithLengthAware_OnZeroPaddedBoundaries` — covers the exact failure pattern (96-char EPK vs 128-char zero-padded boundary range using a `LengthAware`-style comparer).
- `Range_CheckOverlapping_WithCustomComparer_PreservesOrdinalSemantics_WhenComparerIsOrdinal` — round-trip equivalence with the legacy overload.
- `Range_CheckOverlapping_ThrowsOnNullComparer`.
- Property/table-driven test verifying commutativity: `CheckOverlapping(a, b, c) == CheckOverlapping(b, a, c)`.
- [ ] All existing tests for `Range<T>`, `CollectionRoutingMap`, and the Direct routing layer pass unchanged.
- [ ] `internal sealed` preserved — no public API surface exposure.
- [ ] Direct package release notes call out the additive overload.
- [ ] New Direct package version published.
- [ ] v3 SDK `Directory.Build.props` Direct floor bumped (driven by #5897).
---
## Rollout sequence
1. Merge the `Range.cs` change into `master` of `msdata/CosmosDB`.
2. Publish a new `Microsoft.Azure.Cosmos.Direct` package version containing the overload.
3. Bump the floor in `Azure/azure-cosmos-dotnet-v3 :: Microsoft.Azure.Cosmos/Directory.Build.props` (driven by #5897).
4. Wire the v3 SDK consumer (`CollectionRoutingMap.GetOverlappingRanges`) to pass the length-aware comparer through to the new overload (driven by #5897).
5. Verify end-to-end via the v3 unit tests added under #5897 — the test that today fails with empty results must return the correct overlapping-range list.
---
## Risk / blast radius
| Surface | Impact |
|---|---|
| Existing callers of `CheckOverlapping(Range<T>, Range<T>)` | **None.** Delegates to new overload with `TComparer` → bit-identical output. |
| Other components depending on `Range<T>` (`PartitionKeyRange`, `RoutingMapProvider`, `BackendRequestRouter`, gateway query planner) | **None.** Pure addition; no signature changes; `internal sealed` preserved. |
| Public API of Direct package | **None.** Type stays `internal`. |
| Performance | **Negligible.** One extra virtual dispatch on the new overload only; existing call sites unchanged. |
| Binary compatibility | **Preserved.** Additive method; legacy overload retained. |
---
## References
- #5859 — investigation umbrella & production telemetry (MFS `eastus2euap` canary, 4 AOOR occurrences in May 2026)
- #5897 — v3-side principled fix that depends on this Direct overload
- #5898 — v3-side defensive `Count == 0` guard in `RequestInvokerHandler.SendAsync` (lands independently)
- `msdata/CosmosDB :: Product/Microsoft.Azure.Documents/SharedFiles/Routing/Range.cs` — file to modify
- `Azure/azure-cosmos-dotnet-v3 :: Microsoft.Azure.Cosmos/src/Routing/CollectionRoutingMap.cs` — downstream v3 consumer (`GetOverlappingRanges`)
---
*This issue is a public tracker only — implementation, PR, and review happen inside `msdata/CosmosDB`. Status updates will be posted here as comments to keep the v3-SDK dependency chain visible.*
0 条评论