refactor(bigquery-jdbc): optimize and unify concurrent metadata methods - #13811
refactor(bigquery-jdbc): optimize and unify concurrent metadata methods#13811keshavdandeva wants to merge 26 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors BigQueryDatabaseMetaData.java to streamline metadata retrieval methods (such as getProcedures, getTables, and getColumns) by introducing concurrent helper methods like processTargetRoutinesConcurrently and processTargetTablesConcurrently. The review feedback highlights several critical issues with this refactoring: an architectural regression where heavy metadata retrieval now blocks synchronously on the caller thread; a performance bottleneck due to sequential routine listing for wildcard patterns; a potential NullPointerException when handling execution exceptions; silent failures on thread interruption in waitForTasksCompletion; and a missing empty schema check that leads to redundant API calls.
f39053c to
78bdd90
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors BigQueryDatabaseMetaData.java to streamline metadata fetching by introducing unified concurrent processing helpers and an asynchronous queue population mechanism, significantly reducing boilerplate code across various metadata methods. The review feedback highlights several critical issues where valid null parameters (such as columnNamePattern, schemaPattern, routineNamePattern, and tableNamePattern) are passed directly to compileSqlLikePattern, which would trigger NullPointerExceptions. Additionally, a bug was identified in getColumns where literal column name filtering is ignored due to conditional regex compilation. Lastly, an improvement was suggested to prevent redundant nesting of SQLExceptions during asynchronous error handling.
| Pattern tableRegex = compileSqlLikePattern(tableNamePattern); | ||
| boolean isLiteralName = tableNamePattern != null && !hasWildcards; | ||
|
|
||
| if (targetDatasets.size() == 1 && isLiteralName) { |
There was a problem hiding this comment.
This is for both methods processTargetTablesConcurrently and processTargetRoutinesConcurrently.
- You can simplify the code, there is no need in this micro-optimization of special-casing single table. Calling it in executor thread has nearly 0 overhead compared to the cost of network call
- Similar, no need to handle Literal/non-Literal in different ways. It is less error-prone to:
- If not literal: build list of tables first
- Process list of tables regardless of where it came from, from Literal list or was just resolved.
Also for (2), it might not be a good idea to schedule a task from within a task. E.g. what if there are MAX_THREADS top-level tasks? They'll deadlock since their child tasks can't be scheduled.
There was a problem hiding this comment.
Okay so, regarding removing micro-optimization: I guess you are right, makes sense to remove it and make code uniform and easier to read and flow through one pipeline.
regarding deadlock risk: I want to clarify that there is no nested task scheduling (no deadlock risk). The listing tasks submitted in Stage 1 do not schedule child tasks onto the executor. They simply return an in-memory List<Callable<Void>> At L4788, the calling thread calls future.get() and waits for 100% of the Stage 1 listing tasks to finish completely. Only after all worker threads from Stage 1 have returned to the pool, we submit the Stage 2 detail tasks (processSingleTable) to metadataExecutor. I added comments so it is easier to read/understand
b/535543457
This PR significantly refactors and optimizes the concurrent execution model for JDBC metadata operations (e.g.,
getTables,getColumns,getProcedures,getExportedKeys) inBigQueryDatabaseMetaData.java.Previously, these methods relied on bespoke, scattered threading logic that was susceptible to thread starvation deadlocks (where bounded pools wait on tasks submitted to the same pool) and suffered from sequential dataset listing bottlenecks.
This PR introduces a unified, robust Two-Tier Scatter-Gather Architecture, eliminating deadlocks, restoring parallel execution for broad dataset scans, and standardizing error handling across all metadata fetchers.
Key Changes
Unified Scatter-Gather Concurrency Framework:
Runnableimplementations in each metadata method with shared helper functions (fetchAndPopulateQueueAsync,processTargetTablesConcurrently,processTargetRoutinesConcurrently).fetchAndPopulateQueueAsynctask now runs on the unboundedqueryExecutor(viaconnection.getExecutorService()). This thread safely blocks while coordinating API calls.metadataExecutoris now exclusively used for concurrent BigQuery HTTP API calls (listTables,getTable, etc.). Workers never wait on other workers, completely removing the deadlock risk.Parallelized Dataset Discovery (Phase 1):
listTables/listRoutines) to the API pool.Robust Resource Management & Cancellation:
activeFuturestracking inside the new concurrent helpers.InterruptedExceptionorSQLExceptionoccurs, thefinallyblocks aggressively cancel all active futures, preventing runaway background API calls and memory leaks.Specific Bug Fixes:
getExportedKeysCatalog Fallback: Fixed an issue where an exact schema match string without wildcards was incorrectly evaluated as a regex pattern during the fallback flow, by explicitly passingisPattern=falsetogetTargetDatasets().Performance Benchmarks:
getColumns(null, null, null, null)) are up to 2.6x faster (reducing latency from 23.5s to 8.8s on a 12,000-column project) because the API worker pool is no longer bottlenecked by blocked orchestrator threads.