|
| 1 | +# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 |
| 2 | + |
| 3 | +from typing import Optional |
| 4 | + |
| 5 | +from glide.constants import TSingleNodeRoute |
| 6 | + |
| 7 | + |
| 8 | +class BatchRetryStrategy: |
| 9 | + """ |
| 10 | + Defines a retry strategy for cluster batch requests, allowing control over retries in case of |
| 11 | + server or connection errors. |
| 12 | +
|
| 13 | + This strategy determines whether failed commands should be retried, impacting execution order |
| 14 | + and potential side effects. |
| 15 | +
|
| 16 | + Behavior: |
| 17 | + - If `retry_server_error` is `True`, failed commands with a retriable error (e.g., |
| 18 | + `TRYAGAIN`) will be retried. |
| 19 | + - If `retry_connection_error` is `True`, batch requests will be retried on |
| 20 | + connection failures. |
| 21 | +
|
| 22 | + Cautions: |
| 23 | + - **Server Errors:** Retrying may cause commands targeting the same slot to be executed |
| 24 | + out of order. |
| 25 | + - **Connection Errors:** Retrying may lead to duplicate executions, since the server might |
| 26 | + have already received and processed the request before the error occurred. |
| 27 | +
|
| 28 | + Example Scenario: |
| 29 | + ``` |
| 30 | + MGET key {key}:1 |
| 31 | + SET key "value" |
| 32 | + ``` |
| 33 | +
|
| 34 | + Expected response when keys are empty: |
| 35 | + ``` |
| 36 | + [None, None] |
| 37 | + "OK" |
| 38 | + ``` |
| 39 | +
|
| 40 | + However, if the slot is migrating, both commands may return an `ASK` error and be |
| 41 | + redirected. Upon `ASK` redirection, a multi-key command may return a `TRYAGAIN` |
| 42 | + error (triggering a retry), while the `SET` command succeeds immediately. This |
| 43 | + can result in an unintended reordering of commands if the first command is retried |
| 44 | + after the slot stabilizes: |
| 45 | + ``` |
| 46 | + ["value", None] |
| 47 | + "OK" |
| 48 | + ``` |
| 49 | +
|
| 50 | + Note: |
| 51 | + Currently, retry strategies are supported only for non-atomic batches. |
| 52 | +
|
| 53 | + Default: |
| 54 | + Both `retry_server_error` and `retry_connection_error` are set to `False`. |
| 55 | +
|
| 56 | + Args: |
| 57 | + retry_server_error (bool): If `True`, failed commands with a retriable error (e.g., `TRYAGAIN`) |
| 58 | + will be automatically retried. |
| 59 | +
|
| 60 | + ⚠️ **Warning:** Enabling this flag may cause commands targeting the same slot to execute |
| 61 | + out of order. |
| 62 | +
|
| 63 | + By default, this is set to `False`. |
| 64 | +
|
| 65 | + retry_connection_error (bool): If `True`, batch requests will be retried in case of connection errors. |
| 66 | +
|
| 67 | + ⚠️ **Warning:** Retrying after a connection error may lead to duplicate executions, since |
| 68 | + the server might have already received and processed the request before the error occurred. |
| 69 | +
|
| 70 | + By default, this is set to `False`. |
| 71 | +
|
| 72 | + """ |
| 73 | + |
| 74 | + def __init__( |
| 75 | + self, |
| 76 | + retry_server_error: bool = False, |
| 77 | + retry_connection_error: bool = False, |
| 78 | + ): |
| 79 | + """ |
| 80 | + Initialize a BatchRetryStrategy. |
| 81 | +
|
| 82 | + Args: |
| 83 | + retry_server_error (bool): If `True`, failed commands with a retriable error (e.g., `TRYAGAIN`) |
| 84 | + will be automatically retried. |
| 85 | +
|
| 86 | + ⚠️ **Warning:** Enabling this flag may cause commands targeting the same slot to execute |
| 87 | + out of order. |
| 88 | +
|
| 89 | + By default, this is set to `False`. |
| 90 | +
|
| 91 | + retry_connection_error (bool): If `True`, batch requests will be retried in case of connection errors. |
| 92 | +
|
| 93 | + ⚠️ **Warning:** Retrying after a connection error may lead to duplicate executions, since |
| 94 | + the server might have already received and processed the request before the error occurred. |
| 95 | +
|
| 96 | + By default, this is set to `False`. |
| 97 | +
|
| 98 | + """ |
| 99 | + self.retry_server_error = retry_server_error |
| 100 | + self.retry_connection_error = retry_connection_error |
| 101 | + |
| 102 | + |
| 103 | +class BaseBatchOptions: |
| 104 | + """ |
| 105 | + Base options settings class for sending a batch request. Shared settings for standalone and |
| 106 | + cluster batch requests. |
| 107 | +
|
| 108 | + Args: |
| 109 | + timeout (Optional[int]): The duration in milliseconds that the client should wait for the batch request |
| 110 | + to complete. This duration encompasses sending the request, awaiting a response from the server, |
| 111 | + and any required reconnections or retries. If the specified timeout is exceeded for a pending request, |
| 112 | + it will result in a timeout error. If not explicitly set, the client's default request timeout will be used. |
| 113 | + """ |
| 114 | + |
| 115 | + def __init__( |
| 116 | + self, |
| 117 | + timeout: Optional[int] = None, |
| 118 | + ): |
| 119 | + """ |
| 120 | + Initialize BaseBatchOptions. |
| 121 | +
|
| 122 | + Args: |
| 123 | + timeout (Optional[int]): The duration in milliseconds that the client should wait for the batch request |
| 124 | + to complete. This duration encompasses sending the request, awaiting a response from the server, |
| 125 | + and any required reconnections or retries. If the specified timeout is exceeded for a pending request, |
| 126 | + it will result in a timeout error. If not explicitly set, the client's default request timeout will be used. |
| 127 | + """ |
| 128 | + self.timeout = timeout |
| 129 | + |
| 130 | + |
| 131 | +class BatchOptions(BaseBatchOptions): |
| 132 | + """ |
| 133 | + Options for a batch request for a standalone client. |
| 134 | +
|
| 135 | + Args: |
| 136 | + timeout (Optional[int]): The duration in milliseconds that the client should wait for the batch request |
| 137 | + to complete. This duration encompasses sending the request, awaiting a response from the server, |
| 138 | + and any required reconnections or retries. If the specified timeout is exceeded for a pending request, |
| 139 | + it will result in a timeout error. If not explicitly set, the client's default request timeout will be used. |
| 140 | + """ |
| 141 | + |
| 142 | + def __init__( |
| 143 | + self, |
| 144 | + timeout: Optional[int] = None, |
| 145 | + ): |
| 146 | + """ |
| 147 | + Options for a batch request for a standalone client |
| 148 | +
|
| 149 | + Args: |
| 150 | + timeout (Optional[int]): The duration in milliseconds that the client should wait for the batch request |
| 151 | + to complete. This duration encompasses sending the request, awaiting a response from the server, |
| 152 | + and any required reconnections or retries. If the specified timeout is exceeded for a pending request, |
| 153 | + it will result in a timeout error. If not explicitly set, the client's default request timeout will be used. |
| 154 | + """ |
| 155 | + super().__init__(timeout) |
| 156 | + |
| 157 | + |
| 158 | +class ClusterBatchOptions(BaseBatchOptions): |
| 159 | + """ |
| 160 | + Options for cluster batch operations. |
| 161 | +
|
| 162 | + Args: |
| 163 | + timeout (Optional[int]): The duration in milliseconds that the client should wait for the batch request |
| 164 | + to complete. This duration encompasses sending the request, awaiting a response from the server, |
| 165 | + and any required reconnections or retries. If the specified timeout is exceeded for a pending request, |
| 166 | + it will result in a timeout error. If not explicitly set, the client's default request timeout will be used. |
| 167 | +
|
| 168 | + route (Optional[TSingleNodeRoute]): Configures single-node routing for the batch request. The client |
| 169 | + will send the batch to the specified node defined by `route`. |
| 170 | +
|
| 171 | + If a redirection error occurs: |
| 172 | +
|
| 173 | + - For Atomic Batches (Transactions), the entire transaction will be redirected. |
| 174 | + - For Non-Atomic Batches (Pipelines), only the commands that encountered redirection errors |
| 175 | + will be redirected. |
| 176 | +
|
| 177 | + retry_strategy (Optional[BatchRetryStrategy]): ⚠️ **Please see `BatchRetryStrategy` and read carefully before enabling these |
| 178 | + options.** |
| 179 | +
|
| 180 | + Defines the retry strategy for handling cluster batch request failures. |
| 181 | +
|
| 182 | + This strategy determines whether failed commands should be retried, potentially impacting |
| 183 | + execution order. |
| 184 | +
|
| 185 | + - If `retry_server_error` is `True`, retriable errors (e.g., TRYAGAIN) will |
| 186 | + trigger a retry. |
| 187 | + - If `retry_connection_error` is `True`, connection failures will trigger a |
| 188 | + retry. |
| 189 | +
|
| 190 | + **Warnings:** |
| 191 | +
|
| 192 | + - Retrying server errors may cause commands targeting the same slot to execute out of |
| 193 | + order. |
| 194 | + - Retrying connection errors may lead to duplicate executions, as it is unclear which |
| 195 | + commands have already been processed. |
| 196 | +
|
| 197 | + **Note:** Currently, retry strategies are supported only for non-atomic batches. |
| 198 | +
|
| 199 | + **Recommendation:** It is recommended to increase the timeout in `timeout` |
| 200 | + when enabling these strategies. |
| 201 | +
|
| 202 | + **Default:** Both `retry_server_error` and `retry_connection_error` are set to |
| 203 | + `False`. |
| 204 | +
|
| 205 | + """ |
| 206 | + |
| 207 | + def __init__( |
| 208 | + self, |
| 209 | + timeout: Optional[int] = None, |
| 210 | + route: Optional[TSingleNodeRoute] = None, |
| 211 | + retry_strategy: Optional[BatchRetryStrategy] = None, |
| 212 | + ): |
| 213 | + """ |
| 214 | + Initialize ClusterBatchOptions. |
| 215 | +
|
| 216 | + Args: |
| 217 | + timeout (Optional[int]): The duration in milliseconds that the client should wait for the batch request |
| 218 | + to complete. This duration encompasses sending the request, awaiting a response from the server, |
| 219 | + and any required reconnections or retries. If the specified timeout is exceeded for a pending request, |
| 220 | + it will result in a timeout error. If not explicitly set, the client's default request timeout will be used. |
| 221 | +
|
| 222 | + route (Optional[TSingleNodeRoute]): Configures single-node routing for the batch request. The client |
| 223 | + will send the batch to the specified node defined by `route`. |
| 224 | +
|
| 225 | + If a redirection error occurs: |
| 226 | +
|
| 227 | + - For Atomic Batches (Transactions), the entire transaction will be redirected. |
| 228 | + - For Non-Atomic Batches (Pipelines), only the commands that encountered redirection errors |
| 229 | + will be redirected. |
| 230 | +
|
| 231 | + retry_strategy (Optional[BatchRetryStrategy]): ⚠️ **Please see `BatchRetryStrategy` and read carefully before enabling these |
| 232 | + options.** |
| 233 | +
|
| 234 | + Defines the retry strategy for handling cluster batch request failures. |
| 235 | +
|
| 236 | + This strategy determines whether failed commands should be retried, potentially impacting |
| 237 | + execution order. |
| 238 | +
|
| 239 | + - If `retry_server_error` is `True`, retriable errors (e.g., TRYAGAIN) will |
| 240 | + trigger a retry. |
| 241 | + - If `retry_connection_error` is `True`, connection failures will trigger a |
| 242 | + retry. |
| 243 | +
|
| 244 | + **Warnings:** |
| 245 | +
|
| 246 | + - Retrying server errors may cause commands targeting the same slot to execute out of |
| 247 | + order. |
| 248 | + - Retrying connection errors may lead to duplicate executions, as it is unclear which |
| 249 | + commands have already been processed. |
| 250 | +
|
| 251 | + **Note:** Currently, retry strategies are supported only for non-atomic batches. |
| 252 | +
|
| 253 | + **Recommendation:** It is recommended to increase the timeout in `timeout` |
| 254 | + when enabling these strategies. |
| 255 | +
|
| 256 | + **Default:** Both `retry_server_error` and `retry_connection_error` are set to |
| 257 | + `False`. |
| 258 | + """ |
| 259 | + super().__init__(timeout) |
| 260 | + self.retry_strategy = retry_strategy |
| 261 | + self.route = route |
0 commit comments