Skip to content

Commit c879eed

Browse files
simorenohCopilot
andauthored
[Cosmos] reformat availability_strategy use (#45141)
* address missing comments * Update CHANGELOG.md * Update sdk/cosmos/azure-cosmos/azure/cosmos/container.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Delete fix_docstrings.py * Apply suggestion from @simorenoh * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fixing copilot miss * update usage to bool/dict * Update CHANGELOG.md * Update test_availability_strategy.py * fixing weird additional logic * Update README.md * mypy pylint * Update _request_object.py * Update _request_object.py * more changes * add client verification method, mypy * update docs * Update CHANGELOG.md * Update README.md * Update README.md * typings * Update cosmos_client.py * test fixes * Update CHANGELOG.md * update behavior for True on request level * logic updates --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 6f44f87 commit c879eed

13 files changed

Lines changed: 438 additions & 264 deletions

sdk/cosmos/azure-cosmos/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Release History
22

3-
### 4.15.0 (2026-02-11)
3+
### 4.15.0 (2026-02-19)
44

55
#### Features Added
66
* GA support of Per Partition Automatic Failover and AvailabilityStrategy features.
@@ -13,6 +13,7 @@
1313
#### Other Changes
1414
* Added tests for multi-language support for full text search. See [PR 44254](https://github.com/Azure/azure-sdk-for-python/pull/44254)
1515
* 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).
16+
* 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).
1617

1718
### 4.14.6 (2026-02-02)
1819

sdk/cosmos/azure-cosmos/README.md

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -952,13 +952,28 @@ Cross region hedging availability strategy improves availability and reduces lat
952952

953953
#### Enabling Cross Region Hedging
954954

955-
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).
955+
You can enable cross region hedging by passing the `availability_strategy` parameter to the `CosmosClient` or per-request. This parameter accepts:
956+
957+
- **`True`**: Enable hedging with default values (`threshold_ms=500`, `threshold_steps_ms=100`) or override client settings to this default at the request level.
958+
- **`False`**: Explicitly disable hedging at the client or request level (overrides client-level settings)
959+
- **`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.
960+
- **`None`**: Default value only usable at the request level. Use the client-level configurations.
961+
962+
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`.
956963

957964
#### Client-level configuration
958965

959966
```python
960967
from azure.cosmos import CosmosClient
961968

969+
# Enable with default values
970+
client = CosmosClient(
971+
"<account-uri>",
972+
"<account-key>",
973+
availability_strategy=True
974+
)
975+
976+
# Enable with custom values
962977
client = CosmosClient(
963978
"<account-uri>",
964979
"<account-key>",
@@ -969,12 +984,19 @@ client = CosmosClient(
969984
#### Request-level configuration
970985

971986
```python
972-
# Override or provide the strategy per request
987+
# Override or provide the strategy per request with custom values
973988
container.read_item(
974989
item="item_id",
975990
partition_key="pk_value",
976991
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50}
977992
)
993+
994+
# Enable with default values for a specific request
995+
container.read_item(
996+
item="item_id",
997+
partition_key="pk_value",
998+
availability_strategy=True
999+
)
9781000
```
9791001

9801002
#### Disable availability strategy on request level
@@ -984,7 +1006,7 @@ container.read_item(
9841006
container.read_item(
9851007
item="item_id",
9861008
partition_key="pk_value",
987-
availability_strategy=None
1009+
availability_strategy=False
9881010
)
9891011
```
9901012

@@ -999,25 +1021,41 @@ executor = ThreadPoolExecutor(max_workers=2)
9991021
client = CosmosClient(
10001022
"<account-uri>",
10011023
"<account-key>",
1002-
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50},
1024+
availability_strategy=True, # or use a dict for custom values
10031025
availability_strategy_executor=executor
10041026
)
10051027
```
10061028

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

10091031
```python
1010-
# Customize the max concurrency on the default ThreadPoolExecutor for the sync client
1011-
from azure.cosmos import CosmosClient
1032+
# Customize the max concurrency for the async client
1033+
from azure.cosmos.aio import CosmosClient
10121034

10131035
client = CosmosClient(
10141036
"<account-uri>",
10151037
"<account-key>",
1016-
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50},
1038+
availability_strategy=True, # or use a dict for custom values
10171039
availability_strategy_max_concurrency=2
10181040
)
10191041
```
10201042

1043+
### Cross Region Hedging - Best Practices
1044+
1045+
1. Configure appropriate thresholds based on your application's needs:
1046+
- Lower thresholds: More aggressive hedging, potentially higher costs
1047+
- Higher thresholds: More conservative, may impact latency
1048+
1049+
2. Use request-level overrides judiciously:
1050+
- Disable hedging for non-critical operations
1051+
- Use custom thresholds for latency-sensitive operations
1052+
1053+
3. Monitor usage:
1054+
- Track hedging patterns
1055+
- Adjust thresholds based on observed performance
1056+
1057+
4. Enable multiple write regions when write availability is critical
1058+
10211059
## Troubleshooting
10221060

10231061
### General

sdk/cosmos/azure-cosmos/azure/cosmos/_availability_strategy_config.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121

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

24-
from typing import Optional, Any
24+
from typing import Optional, Any, Union
25+
26+
# Default values for cross-region hedging strategy
27+
DEFAULT_THRESHOLD_MS = 500
28+
DEFAULT_THRESHOLD_STEPS_MS = 100
2529

2630

2731
class CrossRegionHedgingStrategy:
@@ -37,26 +41,59 @@ class CrossRegionHedgingStrategy:
3741
"""
3842
def __init__(self, config: Optional[dict[str, Any]] = None) -> None:
3943
if config is None:
40-
self.threshold_ms = 500
41-
self.threshold_steps_ms = 100
44+
self.threshold_ms = DEFAULT_THRESHOLD_MS
45+
self.threshold_steps_ms = DEFAULT_THRESHOLD_STEPS_MS
4246
else:
43-
self.threshold_ms = config.get("threshold_ms", 500)
44-
self.threshold_steps_ms = config.get("threshold_steps_ms", 100)
47+
self.threshold_ms = config.get("threshold_ms", DEFAULT_THRESHOLD_MS)
48+
self.threshold_steps_ms = config.get("threshold_steps_ms", DEFAULT_THRESHOLD_STEPS_MS)
4549

4650
if self.threshold_ms <= 0:
4751
raise ValueError("threshold_ms must be positive")
4852
if self.threshold_steps_ms <= 0:
4953
raise ValueError("threshold_steps_ms must be positive")
5054

51-
def _validate_hedging_strategy(config: Optional[dict[str, Any]]) -> Optional[CrossRegionHedgingStrategy]:
52-
"""Validate and create a CrossRegionHedgingStrategy.
55+
56+
def _validate_request_hedging_strategy(
57+
config: Optional[Union[bool, dict[str, Any]]]
58+
) -> Union[CrossRegionHedgingStrategy, bool, None]:
59+
"""Validate and create a CrossRegionHedgingStrategy for a request.
5360
54-
:param config: Dictionary containing configuration values
55-
:type config: Optional[Dict[str, Any]]
56-
:returns: Validated configuration object
57-
:rtype: Optional[CrossRegionHedgingStrategy]
61+
:param config: Configuration for availability strategy. Can be:
62+
- None: Returns None (no strategy, uses client default if available)
63+
- True: Returns strategy with default values (threshold_ms=500, threshold_steps_ms=100)
64+
- False: Returns False (explicitly disabled, overrides client configs)
65+
- dict: Returns strategy with values from dict, using defaults for missing keys
66+
:type config: Optional[Union[bool, Dict[str, Any]]]
67+
:returns: Validated configuration object, False if explicitly disabled, or None
68+
:rtype: Union[CrossRegionHedgingStrategy, bool, None]
69+
"""
70+
if isinstance(config, dict):
71+
# Validate dict values by attempting to create a strategy object
72+
return CrossRegionHedgingStrategy(config)
73+
# For bool and None, no validation needed as they are handled in the request object's `set_availability_strategy`
74+
return config
75+
76+
77+
def validate_client_hedging_strategy(
78+
config: Union[bool, dict[str, Any]]
79+
) -> Union[CrossRegionHedgingStrategy, None]:
80+
"""Validate and create a CrossRegionHedgingStrategy for the client.
81+
82+
:param config: Configuration for availability strategy. Can be:
83+
- True: Returns strategy with default values (threshold_ms=500, threshold_steps_ms=100)
84+
- False: Returns False (default, explicitly disabled)
85+
- dict: Returns strategy with values from dict, using defaults for missing keys
86+
:type config: Union[bool, Dict[str, Any]]
87+
:returns: Validated configuration object, False if explicitly disabled, or None
88+
:rtype: Union[CrossRegionHedgingStrategy, None]
5889
"""
59-
if config is None:
90+
91+
if isinstance(config, bool):
92+
if config:
93+
# True -> use default values
94+
return CrossRegionHedgingStrategy()
95+
# False -> nothing set by client, return None to allow request level override or default to no strategy
6096
return None
6197

98+
# dict -> use values from dict
6299
return CrossRegionHedgingStrategy(config)

sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from azure.core.utils import CaseInsensitiveDict
4949

5050
from . import _base as base
51-
from .user_agent_policy import CosmosUserAgentPolicy
51+
from ._user_agent_policy import CosmosUserAgentPolicy
5252
from ._global_partition_endpoint_manager_per_partition_automatic_failover import _GlobalPartitionEndpointManagerForPerPartitionAutomaticFailover # pylint: disable=line-too-long
5353
from . import _query_iterable as query_iterable
5454
from . import _runtime_constants as runtime_constants
@@ -58,7 +58,7 @@
5858
from . import documents
5959
from . import http_constants, exceptions
6060
from ._auth_policy import CosmosBearerTokenCredentialPolicy
61-
from ._availability_strategy_config import _validate_hedging_strategy, CrossRegionHedgingStrategy
61+
from ._availability_strategy_config import validate_client_hedging_strategy, CrossRegionHedgingStrategy
6262
from ._base import _build_properties_cache
6363
from ._change_feed.change_feed_iterable import ChangeFeedIterable
6464
from ._change_feed.change_feed_state import ChangeFeedState
@@ -119,7 +119,7 @@ def __init__( # pylint: disable=too-many-statements
119119
auth: CredentialDict,
120120
connection_policy: Optional[ConnectionPolicy] = None,
121121
consistency_level: Optional[str] = None,
122-
availability_strategy: Optional[dict[str, Any]] = None,
122+
availability_strategy: Union[bool, dict[str, Any]] = False,
123123
availability_strategy_executor: Optional[ThreadPoolExecutor] = None,
124124
**kwargs: Any
125125
) -> None:
@@ -145,8 +145,8 @@ def __init__( # pylint: disable=too-many-statements
145145
"""
146146
self.client_id = str(uuid.uuid4())
147147
self.url_connection = url_connection
148-
self.availability_strategy: Optional[CrossRegionHedgingStrategy] =\
149-
_validate_hedging_strategy(availability_strategy)
148+
self.availability_strategy: Union[CrossRegionHedgingStrategy, None] =\
149+
validate_client_hedging_strategy(availability_strategy)
150150
self.availability_strategy_executor: Optional[ThreadPoolExecutor] = availability_strategy_executor
151151
self.master_key: Optional[str] = None
152152
self.resource_tokens: Optional[Mapping[str, Any]] = None

sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,36 @@ def set_excluded_locations_from_circuit_breaker(self, excluded_locations: list[s
114114
def set_availability_strategy(
115115
self,
116116
options: Mapping[str, Any],
117-
client_strategy_config: Optional[CrossRegionHedgingStrategy] = None) -> None:
117+
client_strategy_config: Union[CrossRegionHedgingStrategy, None] = None) -> None:
118118
"""Sets the availability strategy config for this request from options.
119119
If not in options, uses the client's default strategy.
120+
If False is in options, client defaults are NOT used (explicitly disabled).
120121
121122
:param options: The request options that may contain availabilityStrategy
122123
:type options: Mapping[str, Any]
123124
:param client_strategy_config: The client's default availability strategy config
124-
:type client_strategy_config: ~azure.cosmos.CrossRegionHedgingStrategyConfig
125+
:type client_strategy_config: Union[CrossRegionHedgingStrategy, None]
125126
:return: None
126127
"""
127128
# setup availabilityStrategy
128-
# First try to get from options
129-
if Constants.Kwargs.AVAILABILITY_STRATEGY in options:
130-
self.availability_strategy = options[Constants.Kwargs.AVAILABILITY_STRATEGY]
131-
# If not in options, use client default
129+
# First try to get from options (method-level takes precedence)
130+
if (Constants.Kwargs.AVAILABILITY_STRATEGY in options and
131+
options[Constants.Kwargs.AVAILABILITY_STRATEGY] is not None):
132+
strategy = options[Constants.Kwargs.AVAILABILITY_STRATEGY]
133+
if isinstance(strategy, bool):
134+
if strategy:
135+
# If True, use client config if available, otherwise default values
136+
if client_strategy_config is not None:
137+
self.availability_strategy = client_strategy_config
138+
else:
139+
self.availability_strategy = CrossRegionHedgingStrategy()
140+
# If False, user explicitly disabled - don't use any strategy
141+
else:
142+
self.availability_strategy = None
143+
else:
144+
# CrossRegionHedgingStrategy object from request validation
145+
self.availability_strategy = strategy
146+
# If not in options or None within options, use client default
132147
elif client_strategy_config is not None:
133148
self.availability_strategy = client_strategy_config
134149

sdk/cosmos/azure-cosmos/azure/cosmos/user_agent_policy.py renamed to sdk/cosmos/azure-cosmos/azure/cosmos/_user_agent_policy.py

File renamed without changes.

0 commit comments

Comments
 (0)