Skip to content

Commit 39f33b2

Browse files
kiraksitswast
andauthored
fix: retry 'job exceeded rate limits' for DDL queries (#1794)
* fix: retry 'job exceeded rate limits' for DDL queries * Fixed retry test logic to better align to library standards * added docstring for test * deleted extra coverage file * Update tests/unit/test_job_retry.py Co-authored-by: Tim Swast <[email protected]> * requested changes to retry jobs test * slight change to assert statemet * added TODO statements and fixed default job retry * modify sleep time and path names --------- Co-authored-by: Tim Swast <[email protected]>
1 parent b402a6d commit 39f33b2

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

google/cloud/bigquery/retry.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _should_retry(exc):
7373
deadline on the retry object.
7474
"""
7575

76-
job_retry_reasons = "rateLimitExceeded", "backendError"
76+
job_retry_reasons = "rateLimitExceeded", "backendError", "jobRateLimitExceeded"
7777

7878

7979
def _job_should_retry(exc):

tests/unit/test_job_retry.py

+80
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
import google.api_core.retry
2323
import freezegun
2424

25+
from google.cloud.bigquery.client import Client
26+
from google.cloud.bigquery import _job_helpers
27+
from google.cloud.bigquery.retry import DEFAULT_JOB_RETRY
28+
2529
from .helpers import make_connection
2630

2731

@@ -240,3 +244,79 @@ def test_raises_on_job_retry_on_result_with_non_retryable_jobs(client):
240244
),
241245
):
242246
job.result(job_retry=google.api_core.retry.Retry())
247+
248+
249+
def test_query_and_wait_retries_job_for_DDL_queries():
250+
"""
251+
Specific test for retrying DDL queries with "jobRateLimitExceeded" error:
252+
https://github.com/googleapis/python-bigquery/issues/1790
253+
"""
254+
freezegun.freeze_time(auto_tick_seconds=1)
255+
client = mock.create_autospec(Client)
256+
client._call_api.__name__ = "_call_api"
257+
client._call_api.__qualname__ = "Client._call_api"
258+
client._call_api.__annotations__ = {}
259+
client._call_api.__type_params__ = ()
260+
client._call_api.side_effect = (
261+
{
262+
"jobReference": {
263+
"projectId": "response-project",
264+
"jobId": "abc",
265+
"location": "response-location",
266+
},
267+
"jobComplete": False,
268+
},
269+
google.api_core.exceptions.InternalServerError(
270+
"job_retry me", errors=[{"reason": "jobRateLimitExceeded"}]
271+
),
272+
google.api_core.exceptions.BadRequest(
273+
"retry me", errors=[{"reason": "jobRateLimitExceeded"}]
274+
),
275+
{
276+
"jobReference": {
277+
"projectId": "response-project",
278+
"jobId": "abc",
279+
"location": "response-location",
280+
},
281+
"jobComplete": True,
282+
"schema": {
283+
"fields": [
284+
{"name": "full_name", "type": "STRING", "mode": "REQUIRED"},
285+
{"name": "age", "type": "INT64", "mode": "NULLABLE"},
286+
],
287+
},
288+
"rows": [
289+
{"f": [{"v": "Whillma Phlyntstone"}, {"v": "27"}]},
290+
{"f": [{"v": "Bhetty Rhubble"}, {"v": "28"}]},
291+
{"f": [{"v": "Phred Phlyntstone"}, {"v": "32"}]},
292+
{"f": [{"v": "Bharney Rhubble"}, {"v": "33"}]},
293+
],
294+
},
295+
)
296+
rows = _job_helpers.query_and_wait(
297+
client,
298+
query="SELECT 1",
299+
location="request-location",
300+
project="request-project",
301+
job_config=None,
302+
page_size=None,
303+
max_results=None,
304+
retry=DEFAULT_JOB_RETRY,
305+
job_retry=DEFAULT_JOB_RETRY,
306+
)
307+
assert len(list(rows)) == 4
308+
309+
# Relevant docs for the REST API path: https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query
310+
# and https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/getQueryResults
311+
query_request_path = "/projects/request-project/queries"
312+
313+
calls = client._call_api.call_args_list
314+
_, kwargs = calls[0]
315+
assert kwargs["method"] == "POST"
316+
assert kwargs["path"] == query_request_path
317+
318+
# TODO: Add assertion statements for response paths after PR#1797 is fixed
319+
320+
_, kwargs = calls[3]
321+
assert kwargs["method"] == "POST"
322+
assert kwargs["path"] == query_request_path

tests/unit/test_retry.py

+27
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,30 @@ def test_DEFAULT_JOB_RETRY_deadline():
129129

130130
# Make sure we can retry the job at least once.
131131
assert DEFAULT_JOB_RETRY._deadline > DEFAULT_RETRY._deadline
132+
133+
134+
def test_DEFAULT_JOB_RETRY_job_rate_limit_exceeded_retry_predicate():
135+
"""Tests the retry predicate specifically for jobRateLimitExceeded."""
136+
from google.cloud.bigquery.retry import DEFAULT_JOB_RETRY
137+
from google.api_core.exceptions import ClientError
138+
139+
# Non-ClientError exceptions should never trigger a retry
140+
assert not DEFAULT_JOB_RETRY._predicate(TypeError())
141+
142+
# ClientError without specific reason shouldn't trigger a retry
143+
assert not DEFAULT_JOB_RETRY._predicate(ClientError("fail"))
144+
145+
# ClientError with generic reason "idk" shouldn't trigger a retry
146+
assert not DEFAULT_JOB_RETRY._predicate(
147+
ClientError("fail", errors=[dict(reason="idk")])
148+
)
149+
150+
# ClientError with reason "jobRateLimitExceeded" should trigger a retry
151+
assert DEFAULT_JOB_RETRY._predicate(
152+
ClientError("fail", errors=[dict(reason="jobRateLimitExceeded")])
153+
)
154+
155+
# Other retryable reasons should still work as expected
156+
assert DEFAULT_JOB_RETRY._predicate(
157+
ClientError("fail", errors=[dict(reason="backendError")])
158+
)

0 commit comments

Comments
 (0)