andygrove commented on PR #5051:
URL: 
https://github.com/apache/datafusion-comet/pull/5051#issuecomment-5443337945

   @viirya thanks — you were right on both counts, and the Kryo one turned out 
to be wider than either of us had it. Fixed in 7e19dff20.
   
   **On the blast radius.** Agreed, and your framing is what made me go 
looking. #5487 framed it as a DISK_ONLY concern and that was under-scoped: the 
`_SER` levels, replication, cross-executor fetches and the disk half of the 
default `MEMORY_AND_DISK` all serialize the `CachedBatch`, so a plain 
`.cache()` that spills is enough. Took your option 1.
   
   **But it is not only the cache.** While writing the test I checked whether 
the registrator needed anything beyond the cached batch, and 
`CometBroadcastExchangeExec` broadcasts an `Array[ChunkedByteBuffer]` from 
`Utils.serializeBatches`. Spark registers `ChunkedByteBuffer` in 
`KryoSerializer.toRegister` but not an array of them, so a native broadcast 
join fails under `registrationRequired=true` on `main` today, with no cache 
involved:
   
   ```
   java.lang.IllegalArgumentException: Class is not registered: 
org.apache.spark.util.io.ChunkedByteBuffer[]
     at 
org.apache.spark.sql.comet.CometBroadcastExchangeExec.doExecuteBroadcast(CometBroadcastExchangeExec.scala:232)
   ```
   
   Same registration fixes it, since the cache write path hands back the same 
type, so I fixed it here rather than filing it — but it is a pre-existing bug 
rather than one this PR introduces, so say the word if you would rather it were 
split out.
   
   **One thing I could not do, and it changes the shape of the fix.** Comet 
cannot install this for the user. `KryoSerializer` reads 
`spark.kryo.registrator` into a `val` in its constructor, and `SparkEnv.create` 
builds it at `SparkContext.scala:478`, whereas `PluginContainer` is constructed 
at line 574. So unlike `spark.sql.cache.serializer` — a `StaticSQLConf` read 
lazily from the session, which is exactly why `maybeSetCacheSerializer` can 
inject it — this one is already captured by the time any plugin runs. Setting 
it from the driver plugin would reach executors but not the driver's own 
`SparkEnv`, so it would work in cluster mode and fail in local mode, which is 
worse than a documented requirement.
   
   So the fix is three parts rather than one:
   
   * `org.apache.comet.CometKryoRegistrator`, with the class list split between 
`Utils.arrowBytesKryoClasses` (the Arrow-bytes carriers, shared by broadcast 
and cache) and `ArrowCachedBatchSerializer.kryoClasses` (the cached batch and 
its statistics row).
   * Documented on `spark.comet.exec.inMemoryCache.enabled`, as your option 2, 
since option 1 does not remove the need to tell people about it.
   * `CometDriverPlugin` warns at startup when `spark.serializer` is Kryo, 
`registrationRequired` is true, and the registrator is absent, naming the conf 
to add. That is the part that addresses "hard to attribute" — the warning fires 
before anything is cached or broadcast.
   
   **Two findings worth recording from building the list.**
   
   `DefaultCachedBatch` had to be registered too. A schema Comet cannot store 
is delegated to Spark's serializer, and Spark registers that class itself only 
from 4.1 — 3.4, 3.5 and 4.0 do not, along with `GenericInternalRow`. So the 
delegated path fails on those versions without Comet registering it, and some 
entries that look redundant on 4.1 are load-bearing on 3.5.
   
   `java.math.MathContext` cannot be registered at all on JDK 17 — 
`kryo.register` builds a `FieldSerializer` eagerly and gets 
`InaccessibleObjectException: module java.base does not "opens java.math"`. 
Decimal bounds above `Long` precision survive anyway because Chill's 
`scala.math.BigDecimal` serializer writes the `java.math.BigDecimal` inside as 
a class-and-object rather than reflecting over the `MathContext`, which is why 
the list registers the `java.math` types and not the Scala one. Worth knowing 
before anyone tries to extend this list by intuition.
   
   **Testing.** `CometInMemoryCacheKryoSuite`, four tests, all 
reverted-and-rechecked rather than assumed: cache round trip at `DISK_ONLY` and 
`MEMORY_AND_DISK_SER` over a relation carrying every type whose bounds the 
statistics row records, read back through a predicate so the bounds go through 
Kryo too; the native broadcast; and the delegated `DefaultCachedBatch` path. 
Without the registrator they fail with exactly the `Class is not registered` 
you predicted. Green on the 3.4, 3.5, 4.0 and 4.1 profiles, along with 
`CometInMemoryCacheSuite`, `UtilsSuite`, `CometArrowStreamSuite`, 
`NativeUtilSuite` and `CometPluginsSuite`.
   
   I did not test the `MEMORY_AND_DISK` spill case itself. Both levels in the 
suite serialize deterministically on put; forcing a spill is not cheap to 
arrange, so the case that motivated your comment is covered by argument rather 
than by a test.
   
   **On the nit** — confirmed, that is intended and not a bug. Because 
`spark.sql.cache.serializer` is static, the format is an application-lifetime 
decision made at startup; flipping `spark.comet.exec.inMemoryCache.enabled` off 
later only routes scans back to Spark's execution path and the footprint stays 
Arrow. The config doc says that, and I would rather keep the honest explanation 
than split the flag, since a relation whose cached format could change 
mid-session could not be read back reliably.
   
   Agreed on AQE (#5245) being the follow-up to weight most, and SPARK-37742 
within it for the reason you and @peterxcli both landed on.
   


-- 
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