AnishMahto commented on code in PR #58209:
URL: https://github.com/apache/spark/pull/58209#discussion_r3832053217


##########
sql/pipelines/src/main/scala/org/apache/spark/sql/pipelines/autocdc/Scd2BatchProcessor.scala:
##########
@@ -94,17 +94,22 @@ case class Scd2BatchProcessor(
         // tail can detect its own redundancy via LEAD(1): if the next row is 
a non-tail at
         // the same instant, the synthetic close the tail encodes is already 
represented by
         // that event and the tail is dropped downstream.
-        //
-        // Any tiebreaking beyond this rule only meaningfully fires when the 
user's source
-        // has emitted two or more events at the same sequence, violating the 
uniqueness
-        // contract above. Behavior in that case is publicly undefined and the 
remaining
-        // tiebreaker clauses exist as a best-effort to keep retries and 
replays deterministic.
         orderDecompositionTailsFirst,
         // Upsert-representing rows sort before tombstones because rows detect 
if they are being
         // bisected by LEAD(1). This allows upserts to match against 
same-sequence deletes, an
         // arbitrary but deterministic convention. When this happens, the 
delete event will survive
         // and persist as a tombstone in the auxiliary table.
-        orderUpsertRepresentingRowsFirst
+        orderUpsertRepresentingRowsFirst,
+        // Amongst upsert-representing rows, there's one valid case where rows 
are still tied, even
+        // if the user's change feed source did not emit duplicate sequences: 
the auxiliary merge
+        // commits before the target merge, which re-reads that table, so a 
row this batch wrote to
+        // the auxiliary table re-enters the window beside the copy the 
microbatch or the target
+        // table still holds. The copies differ only in the boundaries each 
one recorded, so
+        // descending order keeps the earliest of them - 
dropRedundantRowsPostDecomposition drops a
+        // tie's leading row - and nulls sort first so a copy that recorded no 
boundary never
+        // displaces one that did.
+        startAtCol.desc_nulls_first,
+        endAtCol.desc_nulls_first

Review Comment:
   In a nutshell; in order to construct a unioned timeline for determining 
affected rows, both aux and target tables need to read each other.
   
   But merging into each table is sequential, not atomic, and the aux table is 
merged into first. That means its new row-set will reflect when the target 
table determines its affected rows, which could (correctly) already include 
rows from the incoming microbatch, that were merged into the aux.
   
   This is a scenario where rows with duplicate keys, sequence, and data 
columns can arise, which is valid. In such a scenario the calculated intervals 
for the duplicate rows will be different, and by ordering deterministically we 
can consistently drop one the correct redundant row on the second merge.



-- 
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]

Reply via email to