[core] Build expired partitions from their values instead of a joined string - #9277
Open
PDGGK wants to merge 1 commit into
Open
[core] Build expired partitions from their values instead of a joined string#9277PDGGK wants to merge 1 commit into
PDGGK wants to merge 1 commit into
Conversation
convertToPartitionString joined each expired partition's values with a comma and split the string back apart. A partition value that itself contains a comma yields extra tokens, so every field after it shifts left and the last one is dropped -- silently, since a join of N values always splits into at least N tokens. The resulting map is what gets dropped. Under the update-time strategy a single-key partition "us,ca" expires as "us", deleting a live partition and leaving the expired one in place. Sort on the joined form as before, but build the partition from the values already in hand.
PDGGK
force-pushed
the
fix-partition-expire-comma
branch
from
August 18, 2026 15:18
40f9cdb to
03b08be
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
NormalPartitionExpiredecides which partitions to drop, then rebuilds each one by joining its values with,and splitting the string back apart:The values come from
strategy.toPartitionValue(...)on real partition data (:162) and are never escaped, so a partition value that itself contains a comma produces extra tokens.toPartitionStringreads only the firstpartitionKeys.size()of them, so every field after the comma shifts left and the trailing token is dropped. It never throws — a join of N values always splits into at least N tokens, so the index is always in range and the corruption is silent.The list this produces is not just a log line. It is handed straight to
doBatchExpire, which passes it tocommit.dropPartitions(...)(:195), or topartitionModification.dropPartitions(...)(:186) on a metastore-partitioned table.What it does
Two reproductions, both added as tests.
A live partition is deleted and the expired one is kept. Single partition key,
partition.expiration-strategy = update-time(which accepts any value, not just dates),partition.expiration-time = 1 s. Writeus,ca, wait, writeus, expire — onlyus,cais old enough:Exactly inverted: the live partition is dropped, the expired one survives.
usis gone from the table.Multi-key, default date strategy. Keys
(f0, f1),partition.timestamp-formatter = yyyyMMdd, write("20230101","us,ca")and("20230105","51"):Here the drop targets a nonexistent partition, so nothing is deleted — but expiration silently no-ops for
20230101:us,caon every subsequent run, and the partition never ages out. The first token is still the expired date, which is why this direction does not lose data; underupdate-timethere is no such coincidence.Reading further, the same corrupted map is what
toDonePartitions(:199) appends.doneto, so on a metastore-partitioned table the suffix lands on the wrong field.The round trip has already leaked once: the
split(DELIMITER, -1)and its comment were added by #7643 so that a trailing empty partition value would survive it. That fixed one consequence of the join/split; a value containing the delimiter is another.What changes
Sort on the joined form exactly as before, but build the partition from the values that were already in hand:
The sort key is the same string as today and
Stream.sortedis stable in both forms, so the ordering — and therefore which partitions survive themaxExpireNumtruncation — is unchanged for every input. Only the reconstruction goes away.toPartitionStringtakesObject[]and callstoString()on each element, so passingvalues.toArray()is the same worksplitwas feeding it.toPartitionValue(PartitionExpireStrategy:63-72) substitutespartitionDefaultNamefor nulls and always returns exactlypartitionKeys.size()elements, so there is no null or length case the old path handled and this one does not.Test evidence
PartitionExpireTest— 10 tests, 0 failures with the change.Mutation control, on a forced clean rebuild of
paimon-core(rm -rf target/classes target/test-classes) so the result is not an incremental-build artefact: with the two new tests kept andNormalPartitionExpirereverted, both fail, and on the shift itself —Wider run:
*PartitionExpire*and*Partition*Testacrosspaimon-core— 275 tests, 0 failures.The
update-timetest needs the two writes to fall on opposite sides of the cut-off, so it sleeps 2s against a 1s expiration time and pins the check time immediately after the second write — a later stall cannot then move the cut-off past the live partition.API and Format
No change to any option, on-disk format or public signature. Output is identical for every partition value that does not contain a comma.