feat(bigquery): add ArrowQueryResult and ArrowQueryResultImpl for Arrow result streaming - #13944
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for Arrow-formatted query results in the BigQuery client, adding ArrowQueryPageFetcher to fetch Arrow pages and updating response parsing to handle Arrow schemas and record batches. The review feedback highlights critical issues that need to be addressed: a potential NotSerializableException in ArrowQueryPageFetcher due to a non-serializable schema field, memory leaks from unclosed Arrow vectors and record batches, and overly restrictive type checks (instanceof List instead of instanceof Collection) when determining page row counts.
2f4435e to
76600f0
Compare
76600f0 to
ec26676
Compare
ec26676 to
68798eb
Compare
bb63672 to
88530f7
Compare
88530f7 to
80ae0af
Compare
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Apache Arrow query results format in the BigQuery client. It refactors Arrow schema conversion and vector creation logic into a new helper class ArrowPojoUtils, adds an ArrowQueryPageFetcher to stream Arrow-formatted query results, and updates BigQueryImpl and QueryRequestInfo to handle Arrow serialization options and deserialize Arrow record batches. The review feedback highlights several improvement opportunities: preventing a potential resource leak in ArrowPojoUtils.createVectors by closing already allocated vectors if an exception is thrown, avoiding a potential NullPointerException in BigQueryImpl by using serviceOptions.getCredentialsProvider(), and extracting duplicate logic for computing firstPageRows to improve maintainability.
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Apache Arrow query results format in the BigQuery client. It extracts Arrow conversion utilities into a new ArrowPojoUtils helper class, implements ArrowQueryPageFetcher to stream results via the BigQuery Storage Read API, and updates query execution paths to handle Arrow schemas and record batches. The review feedback focuses on critical resource management and performance optimizations, specifically ensuring exception-safe LIFO cleanup of Arrow vectors, allocators, and schema roots to prevent memory leaks, reusing allocator and root instances across page fetches, enforcing the maxResults limit, and properly handling credentials and universe domains.
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for retrieving query results in Apache Arrow format using the fast query path and the BigQuery Storage API. It adds a new ArrowQueryPageFetcher to handle paginated Arrow record batches, introduces ArrowPojoUtils for Arrow schema and vector utilities, and updates BigQueryImpl to deserialize Arrow schemas and record batches. The review feedback highlights a critical resource leak in ArrowQueryPageFetcher due to its stateful nature across pages, suggesting a stateless implementation using try-with-resources to ensure gRPC streams, clients, and off-heap memory allocators are safely closed.
8f1d641 to
101d2d0
Compare
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Arrow query results format in BigQuery, extracting Arrow conversion utilities into ArrowPojoUtils and implementing ArrowQueryPageFetcher in BigQueryImpl to read Arrow rows via the Storage Read API. The reviewer identifies a critical issue where direct imports and references to Apache Arrow classes in BigQueryImpl.java will cause NoClassDefFoundError at runtime for users who do not have Arrow on their classpath. To resolve this, the reviewer recommends encapsulating all Arrow-specific logic and deserialization within ArrowDeserializer and refactoring BigQueryImpl to avoid direct bytecode dependencies on optional Arrow classes.
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Apache Arrow query results format in the BigQuery client, enabling faster query path execution. Key changes include the addition of ArrowPojoUtils for schema conversions, updates to ArrowDeserializer to handle row loading and deserialization, and the implementation of ArrowQueryPageFetcher in BigQueryImpl to fetch pages of Arrow-formatted results. The review feedback highlights three improvement opportunities: caching the BigQueryReadClient and stream iterator in transient fields within ArrowQueryPageFetcher to prevent significant gRPC overhead on pagination, removing an unused BufferAllocator in ArrowDeserializer.deserializeSchema, and restructuring vector resource management in loadArrowRows to avoid a potential double-closing issue.
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for retrieving query results in Apache Arrow format from BigQuery. It adds the ArrowDeserializer and ArrowPojoUtils helper classes to convert Arrow schemas and deserialize record batches, implements ArrowQueryPageFetcher to fetch pages of Arrow rows, and integrates these changes into BigQueryImpl and QueryRequestInfo. Feedback on these changes highlights several critical issues: potential resource leaks if the stream iterator is not fully exhausted, a risk of generating invalid stream names if the job location is null, high memory consumption due to a hardcoded page size of 100,000 rows, performance overhead from repeatedly instantiating RootAllocator, and reduced type safety from using Object instead of concrete Arrow types in method signatures.
…seable from BigQuery
10b3e9c to
1b283db
Compare
| * underlying gRPC streaming channels are deterministically released. | ||
| */ | ||
| @BetaApi | ||
| public interface ArrowQueryResult extends AutoCloseable, Iterable<VectorSchemaRoot> { |
There was a problem hiding this comment.
nit: perhaps we should add the javadoc that this represents the canonical BigQuery TableResult
There was a problem hiding this comment.
Thanks for the comment, updated.
| if (!yieldedInitialBatch | ||
| && initialRecordBatchBytes != null | ||
| && initialRecordBatchBytes.length > 0) { |
There was a problem hiding this comment.
nit: Can we move this to a helper function explain the logic in here. I also see this being used elsewhere as well
There was a problem hiding this comment.
Thanks for the comment, done.
| } catch (NoSuchElementException | BigQueryException e) { | ||
| throw e; |
There was a problem hiding this comment.
qq, can you explain why these two exceptions specifically?
There was a problem hiding this comment.
Thanks for the question. These two exceptions are rethrown directly to avoid improper wrapping:
NoSuchElementException: Required by the standard JavaIterator.next()contract when no more elements remain. Rethrowing directly prevents it from falling through tocatch (Exception e)and getting wrapped into aBigQueryException.BigQueryException: Avoids redundant double-wrapping (BigQueryExceptionwrapping anotherBigQueryException), preserving the original error code, message, and root cause.
Any other unexpected exception caught in the subsequent catch (Exception e) block is wrapped in a BigQueryException to ensure uniform error handling in the veneer.
I added a comment to clarify.
| if (isClosed()) { | ||
| throw new NoSuchElementException("Query stream was closed."); | ||
| } |
There was a problem hiding this comment.
qq, why we do need to throw an exception here?
There was a problem hiding this comment.
Thanks for the question. When close() is called, it cancels the active gRPC stream. Catching this and checking if (isClosed()) ensures we throw NoSuchElementException (fulfilling the Java Iterator contract) rather than misreporting an intentional cancellation as an unexpected network failure (BigQueryException).
| @@ -0,0 +1 @@ | |||
| mock-maker-inline | |||
There was a problem hiding this comment.
This enables Mockito's inline mock maker, which allows mocking final classes (such as the auto-generated BigQueryReadClient and GAX ServerStream) in unit tests (ArrowQueryResultTest) without having to spin up an in-memory gRPC server.
| loadBatch(new ByteArrayReadableSeekableByteChannel(bytes)); | ||
| } | ||
|
|
||
| /** | ||
| * Deserializes an Arrow record batch from a protobuf {@link ByteString} and loads it into the | ||
| * root vector. | ||
| * | ||
| * @param byteString serialized Arrow record batch bytes as a {@link ByteString} | ||
| * @throws IOException if deserialization fails | ||
| */ | ||
| private void loadBatch(ByteString byteString) throws IOException { | ||
| loadBatch(Channels.newChannel(byteString.newInput())); |
There was a problem hiding this comment.
do the channels created in here need to be closed?
There was a problem hiding this comment.
Thanks for the feedback. I've updated the code accordingly.
| static ArrowQueryResultImpl fromReadSession( | ||
| ReadSession readSession, JobId jobId, BigQueryReadClient readClient) { | ||
| Schema pojoSchema = null; | ||
| if (readSession.hasArrowSchema()) { |
There was a problem hiding this comment.
qq, what happens if there is no arrow schema?
There was a problem hiding this comment.
If readSession has no Arrow schema:
- If streams are present: The constructor throws an
IllegalArgumentException("Arrow schema cannot be null when query results or streams are present.") since records cannot be deserialized without a schema. - If no streams are present (e.g. 0 rows / empty result):
arrowSchemadefaults to an emptySchema(new Schema(Collections.emptyList())), maintaining non-null internal state and yielding an empty iterator (0 batches).
This PR introduces the
ArrowQueryResultinterface andArrowQueryResultImplclass for streaming and paginating query results backed by Apache Arrow record batches.It provides zero-copy
VectorSchemaRootstreaming and implements the standardTableResultiteration contract over Arrow batches.