parthchandra opened a new pull request, #7491:
URL: https://github.com/apache/datafusion-comet/pull/7491
… not grow with the table
## Which issue does this PR close?
Closes # 7490.
Rationale for this change
Each native Iceberg scan task deserialized and serialized the whole scan's
delete-file metadata, even when its own files needed a tiny fraction of it. On
large merge-on-read tables this made executor heap use grow with the size of
the table, and tasks ran out of memory where Spark does not. See the issue for
the numbers.
The goal is to match the memory Spark's own Iceberg reader uses, on both the
driver and the executors.
### What changes are included in this PR?
Per-partition pools (executors)
- The five per-file pools (partition values, delete-file sets, residuals,
delete files, delete-file paths) are no longer sent in the common block. Each
partition carries the entries its own tasks use, in a new field
IcebergScan.own_pools of a new message type IcebergFilePools.
- The common block keeps only the small pools (schemas, partition specs and
types, name mappings, projected field ids) and the scan's settings.
- IcebergScanPools builds each partition's message on the driver,
renumbering task indices into its own pools. On the executor,
IcebergPlanDataInjector.inject merges it with the common block
(IcebergScanPools.forPartition).
- Native code needs no change to read these plans. It now rejects a plan
that still carries own_pools, as a safety net.
Lazy per-partition data (driver)
- New PartitionPlanData replaces Array[Array[Byte]] as the per-partition
plan data type in CometExecRDD, NativeExecContext, CometNativeShuffleInputRDD
and PlanDataInjector.findAllPlanData.
- A partition object holds a PlanDataSlice. That builds the partition's
bytes only while Spark serializes its task, through custom
writeObject/readObject. So the driver holds each partition's tasks once, and
never every partition's message at the same time. This matches when Spark
serializes Iceberg's input partitions.
- CometScanWithPlanData.partitionPlanData defaults to the scan's existing
byte arrays, so native Parquet and contrib scans (Delta, Lance) behave as
before.
Logging
- The driver log now reports the whole scan's pool sizes, the size of the
common block, and per-partition task bytes.
Behavior compared with the latest release
- Query results, fallback decisions and configs are unchanged.
- Per-task executor memory for Iceberg scan planning data no longer grows
with the table.
- Two driver log lines changed format.
- Pool entries that no task uses are no longer sent. Before, native
validated every entry in the pool, so a malformed but unused delete file could
fail a scan.
Known limits (documented in the IcebergScanPools scaladoc and the Iceberg
user guide)
- A coalesce without a shuffle combines several input partitions into one
task. That task then carries a copy of a shared delete file for every input
partition that uses it, for example with unpartitioned equality deletes.
Spark's Iceberg reader writes a shared delete file once per task. A test pins
the current behavior.
- Each partition's message is built during task serialization, which Spark
runs while holding its scheduler lock. Spark's Iceberg reader does comparable
serialization work at the same point.
- The driver keeps the scan's pools as protobuf objects for the life of the
plan, as Spark keeps Iceberg's task objects.
### How are these changes tested?
Unit tests (no Spark session)
- IcebergScanPoolsSuite:
- Randomized tests, over 200 seeds:
- every task resolves to the same entries once shipped and injected
- each partition ships exactly the entries its tasks use, once each
- the common block holds none of them
- The common block, each partition's message, and the injected plan stay
the same size as the scan grows from 2 to 10,000 partitions. That holds both
with partition-local delete files and with delete files shared across
partitions.
- Fields outside the five pools survive.
- An out-of-range index fails with an error naming the pool.
- A guard fails if a new pool or pool index field is added without
updating IcebergScanPools.
- PlanDataInjectorSuite:
- The injected plan from the real injectPlanData and serializeOperator
path does not grow with the scan.
- A partition's plan data is built only when its partition is serialized,
and arrives intact.
- A coalesced task carries one copy per input partition. This pins the
documented limit.
- CometNativeShuffleInputRDDSuite:
- The same lazy-serialization check for shuffle map tasks.
- Building an RDD's partitions builds no plan data.
- A native planner test checks that a plan still carrying own_pools is
rejected.
Integration tests, compared with Spark
- New SQL file test sql-tests/iceberg/merge_on_read_partition_layout.sql:
- A merge-on-read table partitioned by identity on five columns: a string
with non-ASCII values, a date, a decimal, a timestamp and a timestamp without
time zone.
- The values include dates and timestamps before 1970, the epoch, a leap
day, both ends of the decimal range, daylight-saving gap and overlap times, and
a partition where every value is null.
- It runs with preserve-data-grouping both on and off, in a non-UTC
session time zone.
- New CometIcebergNativeSuite tests:
- delete files shipped per partition, with grouped and ungrouped reads
- V3 deletion vectors sharing one Puffin file across partitions
- a global equality delete read across many partitions
- Existing assertions that read pools from the common block now read them
from each partition's message.
Test runs
- Unit suites pass on Scala 2.12 and 2.13.
- CometIcebergNativeSuite and CometFuzzIcebergSuite pass on Spark 3.4, apart
from one test that also fails on the base commit with Spark 3.4. That test is
unrelated to this change and is tracked in #.
--
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]