Repository navigation
Conversation
|
A second, independent measurement on today's code, for both engines. It's the same CDC
The two runs of each combination were interleaved.
|
|
Hi @Neuw84, thanks for the PR! I've run into a similar problem: building the equality delete set dominates the task. But in my case (CDC with https://github.com/memiiso/debezium-server-iceberg), there are many more data and delete files than the 64 and 616 in your benchmark, and the data files reference overlapping sets of delete files. As far as I understand, with this PR applied, a delete file that participates in several sets is loaded and cached once per set. For example, here are the ID ranges of the files from three commits:
So caching per location list works well when data files reference the same set of delete files, but it duplicates reads and memory for the shared files when two sets differ even by one file. Another concern is the key length. Even in your case, with 616 delete files, the key for the full set is probably larger than 64 KB, and keys are built and hashed for every split. This cost is negligible compared to rebuilding the delete set, but it can become significant when a set contains more delete files. I've been working on a related approach in #16956. I'd be happy if you could take a look and test it in your environment. It introduces an For now, the PR uses it only to share equality deletes within a Spark task, but it might be a good idea to make it thread-safe and cache it per executor under a If it's easier to test with prebuilt artifacts, I publish iceberg-spark-runtime jars and container images built from Iceberg releases with #16956 and a few of my other fixes applied. The links below show exactly what's included on top of each release: |
|
Hi @kinolaev, This makes #9563 to also cache equality delete files per executor not per task. Note that the test I was testing it is heavy ( millions of position and equality deletes) but the performance gain is more than 2x and I think is a worthy option to have under executor cache locality umbrella. |
|
I agree that caching equality deletes on the executor for the entire duration of a job (or longer) is the right long-term goal. However, with the current implementation, performance improves when delete file sets are identical, but degrades when they are only partially overlapping. Could you consider making this behavior opt-in? Similar but non-identical delete file sets are quite common in CDC scenarios, so having a configuration flag would help prevent potential regressions. |
…bound the key Addresses review on apache#18258: - Opt-in: BaseDeleteLoader.cacheEqualityDeleteSets() defaults to false, so loaders keep caching one entry per delete file unless they enable it. Spark 4.2 enables it with spark.sql.iceberg.executor-cache.equality-delete-sets.enabled (default false). Merged sets help when tasks share the same delete files and duplicate shared files when the groups only partially overlap. - Key length: the merged-set key is a SHA-256 digest of the projection and the sorted file locations instead of their concatenation, so it no longer grows with the number of delete files.
|
Thanks @kinolaev, both points are fair. Pushed 30d281b:
|
Reverts the SHA-256 key from the previous commit. Building the key is negligible next to building the delete set, and with merged-set caching now opt-in the plain key keeps the change smaller.
|
Small follow-up to my previous comment: I've reverted the hashed key in c78e1d3 and kept the plain one. As you said, building the key is negligible next to building the delete set, and with merged-set caching now opt-in your main concern, partially overlapping delete file sets, is covered without it. That keeps the PR smaller. |
Closes #18257.
What
BaseDeleteLoader.loadEqualityDeletescaches each equality delete file's rows, but it builds the mergedStructLikeSetagain in every scan task. On a table with many equality deletes, those inserts dominate the task, and every task on an executor repeats them for the same delete files.This change can cache the merged set through the loader's existing
canCache/getOrLoadhooks. It is opt-in:BaseDeleteLoader.cacheEqualityDeleteSets(),falseby default, so every loader keeps today's one-entry-per-delete-file behavior unless it enables it. Spark 4.2 enables it withspark.sql.iceberg.executor-cache.equality-delete-sets.enabled(defaultfalse), read when the executor cache is created, like the other executor-cache settings. Merged sets pay off when many tasks reference the same delete files. They duplicate reads and memory when groups only partially overlap: a delete file in several different groups is held once per group (see the review discussion).estimateEqDeletesSizealready uses), somax-entry-size/max-total-sizedecide as they do today.StructLikeSet.containsis safe for concurrent readers.Only Spark 4.2 is changed here; the older Spark versions can follow as backports.
Results
This is a Spark 4.1.3 cold
MERGE INTO(CDC: 42.0 M updates, 11.0 M deletes, 4.6 M inserts) into a v2 table with 432 M rows, on 8 × m5.4xlarge, with merged-set caching enabled:These runs predate the opt-in switch; with the switch on, the code path is the one measured.
Related
On the same workload, the next-largest cost in the scan was building position-delete indexes:
Deletes.toPositionIndexeshashed and looked up the data-file path for every row. That was 20.5 % of the target-scan stage in JFR. #17864 has since fixed it onmain(not in a release yet). We backported it to 1.11.0 for our measurements, and the two changes are independent: this one is equality deletes only, #17864 is v2 position delete files only.#16956 proposes a snapshot-wide
EqualityDeletesstructure, which also covers data files whose delete file groups only partially overlap.Tests
TestBaseDeleteLoaderEqualityDeleteCache:main).TestSparkExecutorCache(Spark 4.2): the new property defaults tofalseand is read from the session.TestGenericReaderDeletespasses unchanged.AI Disclosure
Model: Claude Opus 5.5
Platform/Tool: Claude Code
Human Oversight: reviewed