Skip to content

feat(bigquery): share one BigQuery Storage client per Client - #18652

Open
ohmayr wants to merge 1 commit into
fix/bigquery-arrow-max-resultsfrom
feat/bigquery-arrow-shared-bqstorage-client
Open

ohmayr wants to merge 1 commit into
fix/bigquery-arrow-max-resultsfrom
feat/bigquery-arrow-shared-bqstorage-client

Conversation

@ohmayr

@ohmayr ohmayr commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Shares one BigQueryReadClient per Client for Arrow results, instead of creating a new one for every read and never closing it.

  • Creates it on first use, with the client's credentials and universe domain, and closes it in Client.close().
  • Uses a bqstorage_client passed by the caller instead, without closing it.
  • Sets gRPC keepalive on its channel (grpc.keepalive_time_ms: 60000, grpc.keepalive_timeout_ms: 60000).
  • Adds unit tests and system tests for sharing, closing, and cancelling one read early.

@ohmayr
ohmayr requested review from a team as code owners October 13, 2026 08:34
@ohmayr
ohmayr requested review from sycai and removed request for a team October 13, 2026 08:35
@ohmayr
ohmayr added this pull request to stack #18648 October 13, 2026 08:37

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a shared, thread-safe BigQuery Storage client for ARROW query results, allowing multiple queries to reuse a single client instance. It also configures the shared client's gRPC transport with keepalive pings to prevent idle connections from being dropped, and ensures the shared client is closed when the main BigQuery client is closed. The feedback suggests defensively handling the options parameter in create_channel to prevent a potential TypeError if None is passed.

Comment on lines +187 to +191
def create_channel(*args, options=(), **channel_kwargs):
options = list(options) + [
("grpc.keepalive_time_ms", _BQSTORAGE_KEEPALIVE_TIME_MS)
]
return transport_class.create_channel(*args, options=options, **channel_kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If options is explicitly passed as None to create_channel, calling list(options) will raise a TypeError: 'NoneType' object is not iterable. Using options or [] ensures defensive handling and prevents potential runtime crashes.

Suggested change
def create_channel(*args, options=(), **channel_kwargs):
options = list(options) + [
("grpc.keepalive_time_ms", _BQSTORAGE_KEEPALIVE_TIME_MS)
]
return transport_class.create_channel(*args, options=options, **channel_kwargs)
def create_channel(*args, options=None, **channel_kwargs):
options = list(options or []) + [
("grpc.keepalive_time_ms", _BQSTORAGE_KEEPALIVE_TIME_MS)
]
return transport_class.create_channel(*args, options=options, **channel_kwargs)
References
  1. When processing optional parameters with a helper function, prefer placing the null or None check within the function body rather than at the call site to simplify the calling code and improve encapsulation.

@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-shared-bqstorage-client branch from 47c18a9 to 18c7c06 Compare October 13, 2026 10:02
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-shared-bqstorage-client branch from 18c7c06 to de75b51 Compare October 15, 2026 02:29
ARROW query results created a new BigQueryReadClient for every read,
even ones that never opened a stream, and to_arrow_iterable() never
closed it, so each read left a gRPC channel open until garbage
collection. The new client also ignored the BigQuery client's
universe domain.

Client now creates one BigQueryReadClient under a lock the first time
an ARROW result needs a stream, with its credentials and universe
domain, and reuses it. close() closes it, and a later query creates a
new one. Results that fit in the first page never create it. A
bqstorage_client passed to to_arrow_iterable() is used instead and
never closed. Other BigQuery Storage reads keep creating their own
clients.

The shared client's gRPC channel sets keepalive (60s ping time, 60s
ping timeout) through the generated transport's channel callable.
gRPC stops pinging after two pings while ReadRows sends nothing, so
longer pauses rely on ReadRowsStream resuming at the next unread row.
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-shared-bqstorage-client branch from de75b51 to 3cba84a Compare October 15, 2026 03:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant