Skip to content

bug: client doesn't retry "Job exceeded rate limits" for DDL query jobs that exceed quota for table update operations  #1790

Closed
@tswast

Description

@tswast

In googleapis/python-bigquery-sqlalchemy#1009 (comment) it seems that the query in https://btx-internal.corp.google.com/invocations/ffafb866-6bc0-423f-a86b-df69fb270d57/targets/cloud-devrel%2Fclient-libraries%2Fpython%2Fgoogleapis%2Fpython-bigquery-sqlalchemy%2Fpresubmit%2Fprerelease-deps;config=default/log with rate limits exceeded errors are not retried.

Environment details

  • OS type and version:
  • Python version: python --version
  • pip version: pip --version
  • google-cloud-bigquery version: pip show google-cloud-bigquery

Steps to reproduce

Run a DDL query more than 5 times in 10 seconds, violating the five table metadata update operations per 10 seconds per table limit (https://cloud.google.com/bigquery/quotas#standard_tables).

Code example

import google.cloud.bigquery
bqclient = google.cloud.bigquery.Client()
sql = "ALTER TABLE `swast-scratch.my_dataset.my_table` ADD COLUMN IF NOT EXISTS my_string_col STRING"
for _ in range(100):
    bqclient.query_and_wait(sql)

Stack trace

BadRequest                                Traceback (most recent call last)
Input In [4], in <cell line: 1>()
      1 for _ in range(100):
----> 2     bqclient.query_and_wait(sql)

File ~/src/github.com/googleapis/python-bigquery/google/cloud/bigquery/client.py:3503, in Client.query_and_wait(self, query, job_config, location, project, api_timeout, wait_timeout, retry, job_retry, page_size, max_results)
   3497     _verify_job_config_type(job_config, QueryJobConfig)
   3499 job_config = _job_helpers.job_config_with_defaults(
   3500     job_config, self._default_query_job_config
   3501 )
-> 3503 return _job_helpers.query_and_wait(
   3504     self,
   3505     query,
   3506     job_config=job_config,
   3507     location=location,
   3508     project=project,
   3509     api_timeout=api_timeout,
   3510     wait_timeout=wait_timeout,
   3511     retry=retry,
   3512     job_retry=job_retry,
   3513     page_size=page_size,
   3514     max_results=max_results,
   3515 )

File ~/src/github.com/googleapis/python-bigquery/google/cloud/bigquery/_job_helpers.py:498, in query_and_wait(client, query, job_config, location, project, api_timeout, wait_timeout, retry, job_retry, page_size, max_results)
    481     return table.RowIterator(
    482         client=client,
    483         api_request=functools.partial(client._call_api, retry, timeout=api_timeout),
   (...)
    494         num_dml_affected_rows=query_results.num_dml_affected_rows,
    495     )
    497 if job_retry is not None:
--> 498     return job_retry(do_query)()
    499 else:
    500     return do_query()

File /opt/miniconda3/envs/dev-3.10/lib/python3.10/site-packages/google/api_core/retry.py:349, in Retry.__call__.<locals>.retry_wrapped_func(*args, **kwargs)
    345 target = functools.partial(func, *args, **kwargs)
    346 sleep_generator = exponential_sleep_generator(
    347     self._initial, self._maximum, multiplier=self._multiplier
    348 )
--> 349 return retry_target(
    350     target,
    351     self._predicate,
    352     sleep_generator,
    353     self._timeout,
    354     on_error=on_error,
    355 )

File /opt/miniconda3/envs/dev-3.10/lib/python3.10/site-packages/google/api_core/retry.py:191, in retry_target(target, predicate, sleep_generator, timeout, on_error, **kwargs)
    189 for sleep in sleep_generator:
    190     try:
--> 191         return target()
    193     # pylint: disable=broad-except
    194     # This function explicitly must deal with broad exceptions.
    195     except Exception as exc:

File ~/src/github.com/googleapis/python-bigquery/google/cloud/bigquery/_job_helpers.py:439, in query_and_wait.<locals>.do_query()
    437 # For easier testing, handle the retries ourselves.
    438 if retry is not None:
--> 439     response = retry(client._call_api)(
    440         retry=None,  # We're calling the retry decorator ourselves.
    441         span_name="BigQuery.query",
    442         span_attributes=span_attributes,
    443         method="POST",
    444         path=path,
    445         data=request_body,
    446         timeout=api_timeout,
    447     )
    448 else:
    449     response = client._call_api(
    450         retry=None,
    451         span_name="BigQuery.query",
   (...)
    456         timeout=api_timeout,
    457     )

File /opt/miniconda3/envs/dev-3.10/lib/python3.10/site-packages/google/api_core/retry.py:349, in Retry.__call__.<locals>.retry_wrapped_func(*args, **kwargs)
    345 target = functools.partial(func, *args, **kwargs)
    346 sleep_generator = exponential_sleep_generator(
    347     self._initial, self._maximum, multiplier=self._multiplier
    348 )
--> 349 return retry_target(
    350     target,
    351     self._predicate,
    352     sleep_generator,
    353     self._timeout,
    354     on_error=on_error,
    355 )

File /opt/miniconda3/envs/dev-3.10/lib/python3.10/site-packages/google/api_core/retry.py:191, in retry_target(target, predicate, sleep_generator, timeout, on_error, **kwargs)
    189 for sleep in sleep_generator:
    190     try:
--> 191         return target()
    193     # pylint: disable=broad-except
    194     # This function explicitly must deal with broad exceptions.
    195     except Exception as exc:

File ~/src/github.com/googleapis/python-bigquery/google/cloud/bigquery/client.py:827, in Client._call_api(self, retry, span_name, span_attributes, job_ref, headers, **kwargs)
    823 if span_name is not None:
    824     with create_span(
    825         name=span_name, attributes=span_attributes, client=self, job_ref=job_ref
    826     ):
--> 827         return call()
    829 return call()

File /opt/miniconda3/envs/dev-3.10/lib/python3.10/site-packages/google/cloud/_http/__init__.py:494, in JSONConnection.api_request(self, method, path, query_params, data, content_type, headers, api_base_url, api_version, expect_json, _target_object, timeout, extra_api_info)
    482 response = self._make_request(
    483     method=method,
    484     url=url,
   (...)
    490     extra_api_info=extra_api_info,
    491 )
    493 if not 200 <= response.status_code < 300:
--> 494     raise exceptions.from_http_response(response)
    496 if expect_json and response.content:
    497     return response.json()

BadRequest: 400 POST https://bigquery.googleapis.com/bigquery/v2/projects/swast-scratch/queries?prettyPrint=false: Job exceeded rate limits: Your table exceeded quota for table update operations. For more information, see https://cloud.google.com/bigquery/docs/troubleshoot-quotas

In [5]: import sys

In [6]: exc = sys.last_value

In [7]: exc
Out[7]: google.api_core.exceptions.BadRequest('POST https://bigquery.googleapis.com/bigquery/v2/projects/swast-scratch/queries?prettyPrint=false: Job exceeded rate limits: Your table exceeded quota for table update operations. For more information, see https://cloud.google.com/bigquery/docs/troubleshoot-quotas')

In [8]: exc.reason

In [9]: exc.errors
Out[9]: 
[{'message': 'Job exceeded rate limits: Your table exceeded quota for table update operations. For more information, see https://cloud.google.com/bigquery/docs/troubleshoot-quotas',
  'domain': 'global',
  'reason': 'jobRateLimitExceeded'}]

In [10]: exc.errors[0]["reason"]
Out[10]: 'jobRateLimitExceeded'

Metadata

Metadata

Assignees

Labels

api: bigqueryIssues related to the googleapis/python-bigquery API.priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions