byronwang123 opened a new pull request, #7636:
URL: https://github.com/apache/kyuubi/pull/7636
### Why are the changes needed?
Kyuubi's Arrow result format speeds up query result delivery through
efficient columnar encode/decode, but it does not reduce the number of bytes
transferred over the wire. Benchmarks on a real cluster (TPC-DS SF10,
34-column `catalog_sales`) show the Server -> Client fetch is the dominant
cost: on a cross-subnet link the throughput is only ~19-25 MB/s and the fetch
phase
accounts for 60-97% of the total query time. Compressing the Arrow IPC body
directly attacks that bottleneck.
This carries on #3877 (relevant issue #3865), which was deferred in 2022 to
wait for an upstream Arrow compression approach. Spark 4.1.0 has since
introduced `spark.sql.execution.arrow.compression.codec` /
`spark.sql.execution.arrow.compression.zstd.level` (SQLConf,
`.version("4.1.0")`), so the upstream-aligned way is now available.
Design goals that keep this small and upstream-consistent:
- Reuse the Spark upstream configuration keys verbatim. On Spark < 4.1 (where
the keys are not yet registered as typed SQLConf entries) they are read as
raw
strings via `getConfString`, so no Kyuubi-private config key is introduced.
- No Kyuubi-specific wire protocol: the batch payload stays standard Arrow
IPC.
The codec is carried by the Arrow `ArrowRecordBatch` body compression
metadata, and the JDBC client decompresses via a
`CompressionCodec.Factory` driven by that metadata -- no server-side hint on
the wire.
- The zstd codec is constructed directly with the configured level
(`new ZstdCompressionCodec(level)`) rather than through the type-enum
factory overload, which would silently drop the level. This mirrors the latest
Spark `ArrowCompressionUtils` behavior (SPARK-57383).
- First version exposes `none` (default, zero behavior change) and `zstd`
only.
LZ4 is intentionally not included: Arrow 16's built-in
`Lz4CompressionCodec`
(commons-compress pure-Java streaming) took 162 s to compress a 670 KB
batch vs 19 ms for zstd in our benchmark, so it is unusable on this runtime.
Incremental result parallel production is a separate, known bottleneck
(`RDD.toLocalIterator` is partition-serial) and is intentionally out of
scope of
this PR. Compression also significantly reduces the memory footprint of
materialized Arrow results (e.g. 4.67 GB -> 850 MB), which may enable
follow-up improvements for incremental result production in a later PR.
### How was this patch tested?
Unit / integration tests (added in this PR):
- `KyuubiArrowConvertersSuite`: the configured zstd level is honored (level
1 vs
19 produce different bytes) and compressed batches round-trip through a
compression-aware `VectorLoader`; slice() of a compressed head batch when
a LIMIT cuts inside it.
- `SparkDatasetHelperSuite`: the session conf flows into the produced IPC
batches and the emitted batch actually carries `CompressionType.ZSTD` metadata.
- `SparkArrowbasedOperationSuite`: end-to-end over JDBC -- a zstd result set
round-trips, and a `LIMIT 150` with `maxRecordsPerBatch=100` (so the tail
batch is sliced from 100 down to 50 rows) round-trips correctly, exercising the
compression-aware loader/unloader pairing on a compressed, sliced batch.
Commands:
```
build/mvn test -pl externals/kyuubi-spark-sql-engine -am -Dtest=none
-DwildcardSuites=org.apache.spark.sql.execution.arrow.KyuubiArrowConvertersSuite,org.apache.spark.sql.kyuubi.SparkDatasetHelperSuite
\
build/mvn test -pl externals/kyuubi-spark-sql-engine -am -Dtest=none
-DwildcardSuites=org.apache.kyuubi.engine.spark.operation.SparkArrowbasedOperationSuite
\
```
Performance was measured with this PR's build on a real cluster (nntest:
Kyuubi
1.13.0-SNAPSHOT + Spark 3.5.9 + 4-executor YARN, TPC-DS SF10), fetching over
a cross-subnet Thrift connection with a thin client that counts the exact Arrow
IPC bytes returned per batch. Comparing `codec=none` vs `codec=zstd` on the
same query (so the byte delta is the server-side compression, measured end to
end):
- cs_numeric (8 numeric columns, ~1.17 GB): 1,167,279,640 -> 247,522,728
bytes (-78.8%); cross-subnet fetch 19.3 s -> 7.5 s (-61%). Normal collect path.
- cs_full (34-column full table, incremental, ~4.67 GB): 4,674,336,136 ->
849,920,016 bytes (-81.8%); fetch 205.8 s -> 94.1 s (-54%). This path runs
the
codec on the executors (`mapPartitionsInternal`), so it also confirms the
compression dependency reaches the executor classpath.
- `none` codec reproduces the pre-change byte count exactly (cs_numeric none
= 1,167,279,640 bytes, matching the baseline), i.e. zero regression on the
default path. (The Arrow 16 `VectorUnloader(root)` constructor itself
delegates to `VectorUnloader(root, true, NoCompressionCodec.INSTANCE, true)`,
so the `none` path in this PR emits identical bytes.)
Wall-clock depends on the network; the byte reductions do not.
Correctness of this build was verified by the unit and embedded-engine JDBC
end-to-end tests above (42 tests passed in total: KyuubiArrowConvertersSuite,
SparkDatasetHelperSuite, and SparkArrowbasedOperationSuite), and
additionally on
the same YARN cluster: over a Kerberos JDBC/beeline connection with
`spark.sql.execution.arrow.compression.codec=zstd` and
`spark.sql.execution.arrow.maxRecordsPerBatch=100`, a `SELECT ... LIMIT 150`
(tail batch sliced from 100 down to 50 rows) returned exactly 150 rows. The
engine JVM was confirmed (via `jcmd VM.class_hierarchy`) to have loaded
`org.apache.arrow.compression.ZstdCompressionCodec`, i.e. the server actually
compressed rather than silently falling back to `none`. This exercised both
compression code paths -- the driver-side slice in `doCollectLimit` (the
LIMIT
path) and the upstream config plumbing via `getConfString` -- with no
Kyuubi-specific wire hint.
Native library shading: a standalone shaded JDBC fat jar (`java -jar`, no
external classpath) was verified to load zstd-jni native, run the Arrow zstd
IPC
compression path, and pass the allocator-leak check. This directly addresses
the
native-shading concern raised on the original #3877.
Dependency / license:
- `dev/dependencyList` regenerated via `build/dependency.sh --replace`
(adds arrow-compression, zstd-jni, commons-compress, commons-io).
- `LICENSE-binary`: zstd-jni added under the BSD section. Note zstd-jni is
dual-licensed (BSD-2-Clause OR GPL-2-with-classpath-exception); it is
redistributed here under the BSD-2-Clause terms. commons-compress /
commons-io / arrow-compression are Apache-2.0 and covered by the main
license.
--
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]