1- from typing import Any , Tuple , Type , Union
1+ from http import HTTPStatus
2+ from typing import Any , Optional , Tuple , Type , Union
23
34import requests
45import tenacity
78
89from .error import ClickhouseError
910
11+ RETRYABLE_CLICKHOUSE_ERROR_CODES = {
12+ 999 , # KEEPER_EXCEPTION
13+ }
14+
15+ RETRYABLE_HTTP_STATUS_CODES = {
16+ HTTPStatus .TOO_MANY_REQUESTS ,
17+ HTTPStatus .BAD_GATEWAY ,
18+ HTTPStatus .SERVICE_UNAVAILABLE ,
19+ HTTPStatus .GATEWAY_TIMEOUT ,
20+ }
21+
22+
23+ def _get_clickhouse_error_code (exc : ClickhouseError ) -> Optional [int ]:
24+ """
25+ Extract ClickHouse exception code from the response.
26+
27+ ClickHouse sets the X-ClickHouse-Exception-Code header in HTTP 500
28+ responses when the error occurs before streaming starts.
29+ """
30+ if exc .response is None :
31+ return None
32+ header_value = exc .response .headers .get ("X-ClickHouse-Exception-Code" , "" )
33+ try :
34+ return int (header_value )
35+ except (TypeError , ValueError ):
36+ return None
37+
1038
1139def is_transient_error (exc : BaseException ) -> bool :
1240 """
1341 Determine if an error is transient and can be retried.
14-
15- Retryable errors:
16- - requests.exceptions.ConnectionError (network issues, DNS, connection reset)
17- - requests.exceptions.Timeout, ReadTimeout (transient network issues)
18- - requests.exceptions.ChunkedEncodingError (transient network)
19- - ClickhouseError with HTTP status codes from proxy/load balancer:
20- - 429: Too Many Requests
21- - 502: Bad Gateway
22- - 503: Service Unavailable
23- - 504: Gateway Timeout
24-
25- Non-retryable errors:
26- - HTTP 500: real ClickHouse DB errors (not idempotent to retry)
27- - HTTP 4xx: client errors (syntax, permissions, unknown tables)
28- - All other exceptions
2942 """
3043 # Network-related errors are retryable
3144 if isinstance (
@@ -42,9 +55,11 @@ def is_transient_error(exc: BaseException) -> bool:
4255 # ClickHouse HTTP errors - check status code. Do not rely on
4356 # requests.Response truthiness: 4xx/5xx responses are falsy.
4457 if isinstance (exc , ClickhouseError ):
45- retryable_status_codes = {429 , 502 , 503 , 504 }
46- status_code = exc .response .status_code if exc .response is not None else None
47- return status_code in retryable_status_codes
58+ if exc .response is None :
59+ return False
60+ if exc .response .status_code in RETRYABLE_HTTP_STATUS_CODES :
61+ return True
62+ return _get_clickhouse_error_code (exc ) in RETRYABLE_CLICKHOUSE_ERROR_CODES
4863
4964 return False
5065
0 commit comments