Repository navigation
Conversation
|
Assigning reviewers: R: @chamikaramj for label java. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
|
Reminder, please take a look at this pr: @chamikaramj |
|
Assigning new set of reviewers because Pr has gone too long without review. If you would like to opt out of this review, comment R: @kennknowles for label java. Available commands:
|
|
I honestly don't understand the PR description. Can you try to revise it to make it simpler and explain what is done and why? |
I updated the PR description. Basically the PR improves throughput by short-circuiting collection of unknown fields by interacting with the underlying hashmap directly, instead of using reflection in a per-row hot path. |
Problem
A Dataflow streaming pipeline writing deeply nested rows to BigQuery through the Storage Write API with
withAutoSchemaUpdate(true)could not keep up with its input. ~20% of conversion CPU went toTableRowmap operations on the unknown-fields collector, not to converting values.It happens when rows are wide and nested (~50 STRUCT fields each), the schema is current, and auto schema update is on. The converter then creates a nested unknown-fields
TableRowfor every struct, checks whether it stayed empty, and removes it: ~150TableRowcalls per row that usually find nothing. Each call is expensive becauseTableRowextendsGenericData:get/set/remove/computeIfAbsentintern the key and do a reflective declared-field lookup before falling through to the backingHashMap, andisEmpty()allocates an entry-set view just to test for zero.Fix
Use the backing map directly via
GenericData.getUnknownKeys(). For any key that isn't a declared field,getUnknownKeys().put(k, v)stores exactly whatset(k, v)would. Three helpers (unknownFieldMap,setUnknownField,isUnknownMapEmpty) make that substitution for ordinary keys and keep the originalTableRowpath for declared fields. Same collectors, same allocation, same output, same public API.Safety
GenericData.set()ends inputon the same mapgetUnknownKeys()returns; only the preceding lookup is skipped.TableRowisfinaland doesn't enableignoreCase, so there's no key normalization to preserve.f, the one declared field, keeps its original path: a column namedfstill goes throughsetF, and a non-nullf(even an empty list) still counts as an entry in the emptiness check, asAbstractMap.isEmpty()did.TableRow.getClassInfo()at class load. If a future client version adds fields, they take the original path and the emptiness shortcut disables itself.Other paths
No path gets slower: the helpers add one
containson a one-element list in place of an intern and a reflective lookup. Rows that do carry unknown columns save the same cost. Users without auto schema update or unknown columns never ran this code and see no change. Legacy cell-format rows still convert throughgetF().Testing
New
TableRowToStorageApiProtoUnknownFieldsTest(17 tests): unknown columns as bytes and JSON, nested and repeated collectors, empty-struct pruning, a column namedf,fas an empty list, legacy cell format. Existing tests pass unchanged. A backport in the pipeline above raised committed throughput ~27% and cut CPU per record ~20% in a matched staging comparison.Closes #40492. Independent of #40203 and #40204.