Skip to content

Commit 384ea00

Browse files
committed
docs: describe how Spark operators read Comet's cache format
The Limitations section still said Spark-operator reads were slower for reasons not yet established, with a table measured through the row path this PR replaced. Explain the three ways a Spark operator reads a relation cached in Comet's format (under native execution, through the fused ColumnarToRowExec and when it applies, and through the row reader otherwise) and replace the table with one that measures both readers.
1 parent f6f245f commit 384ea00

1 file changed

Lines changed: 41 additions & 14 deletions

File tree

‎docs/source/user-guide/latest/in-memory-cache.md‎

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ relation whose format could change mid-session could not be read back reliably.
5353
codec is a runtime config, but each batch records the codec it was written with, so data cached
5454
under one setting stays readable after the setting changes. Turning
5555
`spark.comet.exec.inMemoryCache.enabled` off at runtime only sends cached scans back to Spark's
56-
execution path; the cached data stays readable either way.
56+
execution path, where Spark operators read them through the row reader described under
57+
[Limitations](#limitations); the cached data stays readable either way.
5758

5859
## Storage format
5960

@@ -175,19 +176,45 @@ registrator.
175176

176177
## Limitations
177178

178-
Reads that feed **Spark** operators rather than Comet ones are slower than Spark's own cache
179-
format, and the narrower the read, the wider the gap. Measured by the same benchmark over the same
180-
5M-row relation, with Comet off so that Spark operators consume the cached data:
181-
182-
| Read shape | Spark's cache format | Comet's cache format | Slowdown |
183-
| ----------------------- | -------------------: | -------------------: | -------: |
184-
| Row count only (0 of 6) | 35 ms | 183 ms | 5.2x |
185-
| 1 of 6 columns | 54 ms | 257 ms | 4.8x |
186-
| 3 of 6 columns | 98 ms | 331 ms | 3.4x |
187-
| 6 of 6 columns | 410 ms | 623 ms | 1.5x |
188-
189-
This is why the feature is off by default. The cause is not yet established;
190-
[#5485](https://github.com/apache/datafusion-comet/issues/5485) tracks it.
179+
Reads that feed **Spark** operators rather than Comet ones can be slower than Spark's own cache
180+
format, because every cached batch is decoded from Arrow before Spark reads it. How a Spark
181+
operator reads a relation cached in Comet's format depends on the scan below it:
182+
183+
- With native execution enabled (`spark.comet.exec.enabled=true`), the cache is scanned by
184+
`CometInMemoryTableScan`, and a Spark operator above it reads the scan's batches through
185+
`CometColumnarToRow`, as it would above any other Comet operator.
186+
- With Comet enabled but native execution disabled, the cache is scanned by Spark's
187+
`InMemoryTableScanExec`. When the operator directly above the scan takes part in whole-stage code
188+
generation, as filters, projections and aggregates do, Comet puts Spark's `ColumnarToRowExec`
189+
between the two, and the generated code reads the cached Arrow vectors directly, with no
190+
intermediate row. The plan shows this as a `ColumnarToRow` above the `InMemoryTableScan`. It
191+
needs `spark.sql.inMemoryColumnarStorage.enableVectorizedReader` (on by default) and whole-stage
192+
code generation, and applies to relations of at most `spark.sql.codegen.maxFields` fields (100 by
193+
default, counting nested fields), beyond which Spark reads a cached relation only as rows. It is
194+
not applied in plan-only mode (`spark.comet.explain.planOnly.enabled`), where Spark executes its
195+
own plan unchanged.
196+
- Otherwise the scan's row reader decodes each batch and writes its rows into one reused
197+
`UnsafeRow`. That covers Comet or `spark.comet.exec.inMemoryCache.enabled` turned off at runtime,
198+
and operators that do not take part in code generation, such as exchanges and limits, or a query
199+
that returns the cached rows as they are.
200+
201+
Measured by the same benchmark over the same 5M-row relation, with native execution off so that
202+
Spark operators consume the cached data, Comet disabled for the row reader and enabled for the fused
203+
reader (Apple M4, JDK 17, Spark 4.1; the average of two runs):
204+
205+
| Read shape | Spark's cache format | Comet's format, row reader | Comet's format, fused reader |
206+
| ----------------------- | -------------------: | -------------------------: | ---------------------------: |
207+
| Row count only (0 of 6) | 63 ms | 57 ms | 35 ms |
208+
| 1 of 6 columns | 63 ms | 77 ms | 54 ms |
209+
| 3 of 6 columns | 113 ms | 176 ms | 133 ms |
210+
| 6 of 6 columns | 306 ms | 500 ms | 334 ms |
211+
212+
The fused reader is faster than Spark's own format for the narrowest reads and within 20% of it for
213+
the others. The row reader takes up to 1.6 times as long, and it is the only reader for relations
214+
wider than `spark.sql.codegen.maxFields`: reading every column of relations of 100, 200 and 1500
215+
nullable `bigint` columns took 2.2 to 2.5 times as long as from Spark's format. These gaps are why
216+
the feature is still off by default;
217+
[#5485](https://github.com/apache/datafusion-comet/issues/5485) tracks them.
191218

192219
Comet's serializer exists because Spark's own Arrow cache format
193220
([SPARK-57268](https://issues.apache.org/jira/browse/SPARK-57268)) is only available from Spark

0 commit comments

Comments
 (0)