bhollis-dbx opened a new pull request, #57321:
URL: https://github.com/apache/spark/pull/57321

   ### What changes were proposed in this pull request?
   
   `from_protobuf` / `to_protobuf` used with a binary `FileDescriptorSet` hold 
theirdescriptor behind a `@transient lazy val`, and building it parses the 
bytes into a large `FileDescriptor` object graph -- the tree that holds the 
(potentially millions of) `FieldDescriptor` instances. There is no caching, so 
each task instance in an executor JVM parses the set independently. Under high 
fan-out (many partitions -> many concurrent tasks in one JVM) with large 
descriptor sets (hundreds of KB, thousands of transitive fields), that produces 
many redundant, concurrently-retained copies of the same tree, driving 
sustained executor GC pressure.
   
   This PR adds a process-wide bounded cache at the `parseFileDescriptorSet` 
layer -- the layer that materializes the `FieldDescriptor` graph -- so 
concurrent task instances share one parsed graph per `FileDescriptorSet` 
instead of each rebuilding it. Because both `buildDescriptorFromFDS` and the 
`convertAnyFieldsToJson` `buildTypeRegistry(bytes)` path
     go through this layer, a given set is parsed at most once per JVM 
regardless of entry point. Building a `DescriptorWithExtensions` on top of the 
cached graph is cheap and happens once per task-instance init, so it is not 
itself cached; the extension-support flag is read per call, so a shared parse 
still yields the flag-appropriate result.
   
   The cache is keyed on a content hash of the descriptor bytes (which keeps 
the key small and avoids pinning the byte array) and does not cache failed 
parses. It uses `NonFateSharingCache` (SPARK-43300) so that a task cancelled 
while populating an entry does not cause spurious failures in other tasks 
blocked on the same key. Its size is bounded by a new internal config, 
`spark.sql.protobuf.descriptorCacheSize` (default 8; setting it to 0 disables 
the cache as a kill-switch).
   
   ### Why are the changes needed?
   
   To eliminate redundant, concurrently-retained copies of large parsed 
descriptor graphs and the resulting executor GC pressure for descriptor-heavy 
`from_protobuf` / `to_protobuf` workloads.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. A new internal config is added, but behavior is unchanged.
   
   ### How was this patch tested?
   
   New unit tests in `ProtobufFunctionsSuite` covering: repeated builds on the 
same bytes sharing one cached parse, distinct bytes cached separately, the 
`buildTypeRegistry(bytes)` path sharing the parse, the extension-support flag 
being honored per call over a shared parse, the disabled (size 0) kill-switch, 
an unknown message name surfacing the domain exception, and a failed parse not 
being cached (and surfacing the domain exception rather than a Guava wrapper). 
Existing `from_protobuf` / `to_protobuf` round-trip coverage exercises 
correctness with the cache enabled.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Yes, drafted with assistance from generative AI tooling.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to