Skip to content

[EPIC] In-memory cache audit: correctness and performance follow-ups for InMemoryTableScanExec support #6834

Description

@andygrove

What is the problem the feature request solves?

Comet's support for InMemoryTableScanExec was audited on main (bfacc43, the day #5634 made spark.comet.exec.inMemoryCache.enabled default to true on Spark 3.5+). This epic collects the correctness and performance findings so they can be worked and tracked in one place. Items are listed by priority.

Correctness

Verified sound during the audit, with no action needed: canonicalization and exchange reuse across scans; statistics bounds (all-null batches, NaN, collated strings, the StartsWith exclusion); the write-side layout check and read-side node/buffer validation; dictionary decoding; the zero-column count(*) path; Kryo registrations and the plugin's install gate; delegation of unsupported types; observed metrics; leak handling on the compress and decompress failure paths. Predicates that contain subqueries sit inside the kept Spark scan, out of reach of subquery planning and reuse (#6577 covers the subqueries exposure), but the stats filter only extracts literals, so they are never evaluated there.

Performance

  • Spark 3.4 and 3.5 build the cache through rows whenever AQE is on. Spark marks a cached AdaptiveSparkPlanExec columnar only from 4.0 (CachedBatchSerializer.convertToColumnarPlanIfPossible), so on 3.x InMemoryRelation takes the row path: native plan, columnar-to-row, UnsafeRow, then back to Arrow before compression. The materialization table in docs/source/user-guide/latest/in-memory-cache.md (944 ms against Spark's 3285 ms) was measured on 4.1 and does not apply to 3.x. Comet cannot change this without a Spark hook; document it under Limitations.

  • Statistics gathering is a per-cell JVM loop. ArrowCachedBatchSerializer.gatherColumnStats calls isNullAt and a typed getter per row per column through the ColumnVector interface, including for nested columns that only record a null count, where an Arrow-backed vector answers from its validity bitmap. The benchmark has a case for the collector but the docs record no number for it. Take null counts from getNullCount when Arrow-backed as a first step.

  • Decode and encode are JVM-only. A read copies the body from heap to off-heap, inflates zstd via zstd-jni, runs VectorLoader, wraps CometVectors and only then exports over FFI. Handing the IPC bytes to native and decoding with arrow-rs would remove that JVM work and allocation; the same holds for statistics and compression on the write side. Fits alongside the prefetch item in Adopt techniques from Spark's ArrowCachedBatchSerializer (SPARK-57268) in Comet's cache format #5487.

  • Dictionary-encoded columns are stored plain, so low-cardinality strings pay full inflate and decode cost on every read. Storing dictionaries needs a per-batch schema or dictionary message.

  • The serializer returns no vectorTypes, so Spark's generated columnar-to-row code reads through the ColumnVector interface rather than a concrete class.

  • Cached reads that feed Spark operators are slower than Spark's own cache format #5485: Spark consumers of Comet's format are slower than Spark's own format, up to 1.6x and 2.5x on wide relations. Open and accurately documented.

Related

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions