Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b9f4f82
address missing comments
simorenoh Feb 11, 2026
da7772d
Update CHANGELOG.md
simorenoh Feb 11, 2026
05069ac
Update sdk/cosmos/azure-cosmos/azure/cosmos/container.py
simorenoh Feb 12, 2026
52cc5c1
Delete fix_docstrings.py
simorenoh Feb 12, 2026
aea5c9c
Merge branch 'additional-cosmos-review' of https://github.com/Azure/a…
simorenoh Feb 12, 2026
73f9303
Apply suggestion from @simorenoh
simorenoh Feb 12, 2026
db9874a
Apply suggestion from @Copilot
simorenoh Feb 12, 2026
a72a90d
Apply suggestion from @Copilot
simorenoh Feb 12, 2026
3e93a49
fixing copilot miss
simorenoh Feb 12, 2026
5a24a99
update usage to bool/dict
simorenoh Feb 17, 2026
849dbbd
Merge branch 'main' into additional-cosmos-review
simorenoh Feb 17, 2026
1fa532d
Update CHANGELOG.md
simorenoh Feb 17, 2026
709702c
Merge branch 'additional-cosmos-review' of https://github.com/Azure/a…
simorenoh Feb 17, 2026
9b581a6
Update test_availability_strategy.py
simorenoh Feb 17, 2026
0d1cfb2
fixing weird additional logic
simorenoh Feb 18, 2026
1afdb7d
Update README.md
simorenoh Feb 18, 2026
ffc4d6c
mypy pylint
simorenoh Feb 18, 2026
a862bc8
Update _request_object.py
simorenoh Feb 18, 2026
9a0b8cb
Update _request_object.py
simorenoh Feb 18, 2026
8bac1fa
more changes
simorenoh Feb 18, 2026
a333134
add client verification method, mypy
simorenoh Feb 18, 2026
127c695
update docs
simorenoh Feb 18, 2026
147a1da
Update CHANGELOG.md
simorenoh Feb 18, 2026
16b606c
Update README.md
simorenoh Feb 18, 2026
5025ccd
Update README.md
simorenoh Feb 18, 2026
fa4f15c
typings
simorenoh Feb 18, 2026
4adb517
Update cosmos_client.py
simorenoh Feb 19, 2026
25e831b
test fixes
simorenoh Feb 19, 2026
0f6a562
Update CHANGELOG.md
simorenoh Feb 19, 2026
e0bab6d
update behavior for True on request level
simorenoh Feb 19, 2026
60659b7
logic updates
simorenoh Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Release History

### 4.15.0 (2026-02-11)
### 4.15.0 (2026-02-19)

#### Features Added
* GA support of Per Partition Automatic Failover and AvailabilityStrategy features.
Expand All @@ -13,6 +13,7 @@
#### Other Changes
* Added tests for multi-language support for full text search. See [PR 44254](https://github.com/Azure/azure-sdk-for-python/pull/44254)
* Renamed `availability_strategy_config` introduced in 4.15.0b1 to `availability_strategy` for both sync and async clients. See [PR 45086](https://github.com/Azure/azure-sdk-for-python/pull/45086).
* Request-level `availability_strategy` needs to be set to `False` in order to disable availability strategy for that request, as opposed to setting it to `None`. See [PR 45141](https://github.com/Azure/azure-sdk-for-python/pull/45141).

### 4.14.6 (2026-02-02)

Expand Down
52 changes: 45 additions & 7 deletions sdk/cosmos/azure-cosmos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -952,13 +952,28 @@ Cross region hedging availability strategy improves availability and reduces lat

#### Enabling Cross Region Hedging

You can enable cross region hedging by passing the `availability_strategy` parameter as a dictionary to the `CosmosClient` or per-request. The most common configuration keys are `threshold_ms` (delay before sending a hedged request) and `threshold_steps_ms` (step interval for additional hedged requests).
You can enable cross region hedging by passing the `availability_strategy` parameter to the `CosmosClient` or per-request. This parameter accepts:

- **`True`**: Enable hedging with default values (`threshold_ms=500`, `threshold_steps_ms=100`) or override client settings to this default at the request level.
- **`False`**: Explicitly disable hedging at the client or request level (overrides client-level settings)
- **`dict`**: Enable hedging with custom values. The keys are `threshold_ms` (delay before sending a hedged request) and `threshold_steps_ms` (step interval for additional hedged requests). Missing keys will use default values.
- **`None`**: Default value only usable at the request level. Use the client-level configurations.

Hedging will also be implicitly enabled when per-partition automatic failover is enabled, in which case the `CrossRegionHedgingStrategy` applies the default values outlined above of 500 ms for `threshold_ms` and 100 ms for `threshold_steps_ms` unless you override them via `availability_strategy`.

#### Client-level configuration

```python
from azure.cosmos import CosmosClient

# Enable with default values
client = CosmosClient(
"<account-uri>",
"<account-key>",
availability_strategy=True
)

# Enable with custom values
client = CosmosClient(
"<account-uri>",
"<account-key>",
Expand All @@ -969,12 +984,19 @@ client = CosmosClient(
#### Request-level configuration

```python
# Override or provide the strategy per request
# Override or provide the strategy per request with custom values
container.read_item(
item="item_id",
partition_key="pk_value",
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50}
)

# Enable with default values for a specific request
container.read_item(
item="item_id",
partition_key="pk_value",
availability_strategy=True
)
```

#### Disable availability strategy on request level
Expand All @@ -984,7 +1006,7 @@ container.read_item(
container.read_item(
item="item_id",
partition_key="pk_value",
availability_strategy=None
availability_strategy=False
)
```

Expand All @@ -999,25 +1021,41 @@ executor = ThreadPoolExecutor(max_workers=2)
client = CosmosClient(
"<account-uri>",
"<account-key>",
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50},
availability_strategy=True, # or use a dict for custom values
availability_strategy_executor=executor
)
```

#### Customized max concurrency for hedging for async client

```python
# Customize the max concurrency on the default ThreadPoolExecutor for the sync client
from azure.cosmos import CosmosClient
# Customize the max concurrency for the async client
from azure.cosmos.aio import CosmosClient

client = CosmosClient(
"<account-uri>",
"<account-key>",
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50},
availability_strategy=True, # or use a dict for custom values
availability_strategy_max_concurrency=2
)
```

### Cross Region Hedging - Best Practices
Comment thread
simorenoh marked this conversation as resolved.

1. Configure appropriate thresholds based on your application's needs:
- Lower thresholds: More aggressive hedging, potentially higher costs
- Higher thresholds: More conservative, may impact latency

2. Use request-level overrides judiciously:
- Disable hedging for non-critical operations
- Use custom thresholds for latency-sensitive operations

3. Monitor usage:
- Track hedging patterns
- Adjust thresholds based on observed performance

4. Enable multiple write regions when write availability is critical

## Troubleshooting

### General
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@

"""Configuration types for Azure Cosmos DB availability strategies."""

from typing import Optional, Any
from typing import Optional, Any, Union

# Default values for cross-region hedging strategy
DEFAULT_THRESHOLD_MS = 500
DEFAULT_THRESHOLD_STEPS_MS = 100
Comment thread
annatisch marked this conversation as resolved.


class CrossRegionHedgingStrategy:
Expand All @@ -37,26 +41,59 @@ class CrossRegionHedgingStrategy:
"""
def __init__(self, config: Optional[dict[str, Any]] = None) -> None:
if config is None:
self.threshold_ms = 500
self.threshold_steps_ms = 100
self.threshold_ms = DEFAULT_THRESHOLD_MS
self.threshold_steps_ms = DEFAULT_THRESHOLD_STEPS_MS
else:
self.threshold_ms = config.get("threshold_ms", 500)
self.threshold_steps_ms = config.get("threshold_steps_ms", 100)
self.threshold_ms = config.get("threshold_ms", DEFAULT_THRESHOLD_MS)
self.threshold_steps_ms = config.get("threshold_steps_ms", DEFAULT_THRESHOLD_STEPS_MS)

if self.threshold_ms <= 0:
raise ValueError("threshold_ms must be positive")
if self.threshold_steps_ms <= 0:
raise ValueError("threshold_steps_ms must be positive")

def _validate_hedging_strategy(config: Optional[dict[str, Any]]) -> Optional[CrossRegionHedgingStrategy]:
"""Validate and create a CrossRegionHedgingStrategy.

def _validate_request_hedging_strategy(
config: Optional[Union[bool, dict[str, Any]]]
Comment thread
annatisch marked this conversation as resolved.
) -> Union[CrossRegionHedgingStrategy, bool, None]:
"""Validate and create a CrossRegionHedgingStrategy for a request.

:param config: Dictionary containing configuration values
:type config: Optional[Dict[str, Any]]
:returns: Validated configuration object
:rtype: Optional[CrossRegionHedgingStrategy]
:param config: Configuration for availability strategy. Can be:
- None: Returns None (no strategy, uses client default if available)
- True: Returns strategy with default values (threshold_ms=500, threshold_steps_ms=100)
- False: Returns False (explicitly disabled, overrides client configs)
- dict: Returns strategy with values from dict, using defaults for missing keys
:type config: Optional[Union[bool, Dict[str, Any]]]
:returns: Validated configuration object, False if explicitly disabled, or None
:rtype: Union[CrossRegionHedgingStrategy, bool, None]
"""
if isinstance(config, dict):
# Validate dict values by attempting to create a strategy object
return CrossRegionHedgingStrategy(config)
# For bool and None, no validation needed as they are handled in the request object's `set_availability_strategy`
return config


def validate_client_hedging_strategy(
config: Union[bool, dict[str, Any]]
) -> Union[CrossRegionHedgingStrategy, None]:
"""Validate and create a CrossRegionHedgingStrategy for the client.

:param config: Configuration for availability strategy. Can be:
- True: Returns strategy with default values (threshold_ms=500, threshold_steps_ms=100)
- False: Returns False (default, explicitly disabled)
- dict: Returns strategy with values from dict, using defaults for missing keys
:type config: Union[bool, Dict[str, Any]]
:returns: Validated configuration object, False if explicitly disabled, or None
:rtype: Union[CrossRegionHedgingStrategy, None]
"""
if config is None:

if isinstance(config, bool):
if config:
# True -> use default values
return CrossRegionHedgingStrategy()
# False -> nothing set by client, return None to allow request level override or default to no strategy
return None

# dict -> use values from dict
return CrossRegionHedgingStrategy(config)
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from azure.core.utils import CaseInsensitiveDict

from . import _base as base
from .user_agent_policy import CosmosUserAgentPolicy
from ._user_agent_policy import CosmosUserAgentPolicy
from ._global_partition_endpoint_manager_per_partition_automatic_failover import _GlobalPartitionEndpointManagerForPerPartitionAutomaticFailover # pylint: disable=line-too-long
from . import _query_iterable as query_iterable
from . import _runtime_constants as runtime_constants
Expand All @@ -58,7 +58,7 @@
from . import documents
from . import http_constants, exceptions
from ._auth_policy import CosmosBearerTokenCredentialPolicy
from ._availability_strategy_config import _validate_hedging_strategy, CrossRegionHedgingStrategy
from ._availability_strategy_config import validate_client_hedging_strategy, CrossRegionHedgingStrategy
from ._base import _build_properties_cache
from ._change_feed.change_feed_iterable import ChangeFeedIterable
from ._change_feed.change_feed_state import ChangeFeedState
Expand Down Expand Up @@ -119,7 +119,7 @@ def __init__( # pylint: disable=too-many-statements
auth: CredentialDict,
connection_policy: Optional[ConnectionPolicy] = None,
consistency_level: Optional[str] = None,
availability_strategy: Optional[dict[str, Any]] = None,
availability_strategy: Union[bool, dict[str, Any]] = False,
availability_strategy_executor: Optional[ThreadPoolExecutor] = None,
**kwargs: Any
) -> None:
Expand All @@ -145,8 +145,8 @@ def __init__( # pylint: disable=too-many-statements
"""
self.client_id = str(uuid.uuid4())
self.url_connection = url_connection
self.availability_strategy: Optional[CrossRegionHedgingStrategy] =\
_validate_hedging_strategy(availability_strategy)
self.availability_strategy: Union[CrossRegionHedgingStrategy, None] =\
validate_client_hedging_strategy(availability_strategy)
self.availability_strategy_executor: Optional[ThreadPoolExecutor] = availability_strategy_executor
self.master_key: Optional[str] = None
self.resource_tokens: Optional[Mapping[str, Any]] = None
Expand Down
27 changes: 21 additions & 6 deletions sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,36 @@ def set_excluded_locations_from_circuit_breaker(self, excluded_locations: list[s
def set_availability_strategy(
self,
options: Mapping[str, Any],
client_strategy_config: Optional[CrossRegionHedgingStrategy] = None) -> None:
client_strategy_config: Union[CrossRegionHedgingStrategy, None] = None) -> None:
"""Sets the availability strategy config for this request from options.
If not in options, uses the client's default strategy.
If False is in options, client defaults are NOT used (explicitly disabled).

:param options: The request options that may contain availabilityStrategy
:type options: Mapping[str, Any]
:param client_strategy_config: The client's default availability strategy config
:type client_strategy_config: ~azure.cosmos.CrossRegionHedgingStrategyConfig
:type client_strategy_config: Union[CrossRegionHedgingStrategy, None]
:return: None
"""
# setup availabilityStrategy
# First try to get from options
if Constants.Kwargs.AVAILABILITY_STRATEGY in options:
self.availability_strategy = options[Constants.Kwargs.AVAILABILITY_STRATEGY]
# If not in options, use client default
# First try to get from options (method-level takes precedence)
if (Constants.Kwargs.AVAILABILITY_STRATEGY in options and
options[Constants.Kwargs.AVAILABILITY_STRATEGY] is not None):
strategy = options[Constants.Kwargs.AVAILABILITY_STRATEGY]
if isinstance(strategy, bool):
if strategy:
# If True, use client config if available, otherwise default values
if client_strategy_config is not None:
self.availability_strategy = client_strategy_config
else:
self.availability_strategy = CrossRegionHedgingStrategy()
# If False, user explicitly disabled - don't use any strategy
else:
self.availability_strategy = None
else:
# CrossRegionHedgingStrategy object from request validation
self.availability_strategy = strategy
# If not in options or None within options, use client default
elif client_strategy_config is not None:
self.availability_strategy = client_strategy_config

Expand Down
Loading
Loading