Repository navigation
Flink: Forward watermarks in EqualityConvertCommitter with no active cycle - #18439
developer-rpai wants to merge 6 commits into
Conversation
2784848 to
68bc0b0
Compare
| } | ||
|
|
||
| @Test | ||
| void forwardsWatermarkWithoutActiveCycle() throws Exception { |
There was a problem hiding this comment.
We are testing our internal workings, not that the change actually solves the original problem of watermarks created by the task is escaping the task scope
There was a problem hiding this comment.
Agreed. I added TestConvertEqualityDeletesSharedLock, which runs ConvertEqualityDeletes next to another task sharing the same lock and checks the lock is released after each cycle.
The planner emits phase watermarks before the plan reaches the committer. Forwarding watermarks while no plan has been received would release the maintenance lock early, reintroducing the race fixed in apache#17038. The planner now emits a cycle-start marker on the metadata stream before its first phase watermark; the committer forwards watermarks only while no cycle is active and holds them back from the marker until the commit.
Reflow Javadoc paragraphs in EqualityConvertPlan and EqualityConvertCommitter to match palantir-java-format.
|
|
||
| @Override | ||
| public void processElement2(StreamRecord<EqualityConvertPlan> record) { | ||
| if (record.getValue().cycleStart()) { |
There was a problem hiding this comment.
Hi @developer-rpai , I'm catching the new flink integration, what is the cycle meaning here ?
There was a problem hiding this comment.
A cycle is one run of the convert task for a single trigger: the planner plans the conversion, the DV writers write, and the committer commits at the plan's doneTimestamp. With the separate flag gone, the committer tracks this through planResult now.
| if (planResult == null) { | ||
| // The cycle started but the plan has not arrived yet. The planner emits phase watermarks | ||
| // before the plan; forwarding them would release the maintenance lock before the commit, | ||
| // letting the next trigger run a concurrent cycle on the same staging snapshot. | ||
| return; | ||
| } | ||
|
|
||
| if (mark.getTimestamp() < planResult.doneTimestamp()) { | ||
| // Hold back watermarks until the cycle commits so the LockRemover keeps the maintenance | ||
| // lock for the whole cycle. | ||
| return; | ||
| } |
There was a problem hiding this comment.
Those lines are the same as the left side, let's revert it back to the original version so that we won't introduce any unneeded change.
There was a problem hiding this comment.
Good catch, that block only reworded the existing comment. Restored the original in cef37c0.
| * #METADATA_STREAM} side output after the read commands. | ||
| * #METADATA_STREAM} side output after the read commands. A {@link EqualityConvertPlan#cycleStart} | ||
| * marker is emitted first, before any phase watermark, so the committer can hold back watermarks | ||
| * until the plan arrives. |
There was a problem hiding this comment.
what does you mean so the committer can hold back watermarks until the plan arrives. here ?
There was a problem hiding this comment.
Reworded it in cef37c0. It now says the planner emits phase watermarks before the plan, so the marker tells the committer a cycle has started and it holds watermarks until the commit.
mxm
left a comment
There was a problem hiding this comment.
Thanks for the PR @developer-rpai!
| private transient Table table; | ||
| private transient List<DVWriteResult> bufferedResults; | ||
| private transient EqualityConvertPlan planResult; | ||
| private transient boolean cycleInFlight; |
There was a problem hiding this comment.
I think we can remove this flag, see the change below.
| if (planResult == null || mark.getTimestamp() < planResult.doneTimestamp()) { | ||
| // Hold back watermarks until the cycle commits so the LockRemover keeps the maintenance lock | ||
| // for the whole cycle. Forwarding the planner's mid-cycle phase watermarks will release the | ||
| // lock early and could let the next trigger run a concurrent cycle on the same staging | ||
| // snapshot. | ||
| if (!cycleInFlight) { | ||
| // No conversion cycle is active: forward the watermark. It may originate from another | ||
| // task's trigger sharing the maintenance lock, and holding it back would stall lock release | ||
| // for that task. | ||
| super.processWatermark(mark); | ||
| return; | ||
| } | ||
|
|
||
| if (planResult == null) { | ||
| // The cycle started but the plan has not arrived yet. The planner emits phase watermarks | ||
| // before the plan; forwarding them would release the maintenance lock before the commit, | ||
| // letting the next trigger run a concurrent cycle on the same staging snapshot. | ||
| return; | ||
| } | ||
|
|
||
| if (mark.getTimestamp() < planResult.doneTimestamp()) { | ||
| // Hold back watermarks until the cycle commits so the LockRemover keeps the maintenance | ||
| // lock for the whole cycle. | ||
| return; | ||
| } |
There was a problem hiding this comment.
This should work without using a flag:
@Override
public void processWatermark(Watermark mark) throws Exception {
if (planResult == null) {
// No cycle in flight: the watermark comes from another task's trigger or a recovery trigger
// sharing the maintenance lock. Holding it back would keep that lock forever.
super.processWatermark(mark);
return;
}
if (mark.getTimestamp() < planResult.doneTimestamp()) {
// Hold back watermarks until the cycle commits so the LockRemover keeps the maintenance lock
// for the whole cycle. Forwarding the planner's mid-cycle phase watermarks will release the
// lock early and could let the next trigger run a concurrent cycle on the same staging
return;
}There was a problem hiding this comment.
Thanks for the review and the sketch — you're right, the cycleInFlight flag is redundant. I'll drop it and restructure processWatermark along the lines you suggested.
There was a problem hiding this comment.
Pushed in cef37c0. One wrinkle with only the null check: the planner emits its phase watermarks before the plan, so those would get forwarded and bring back the early release from #17038. So the cycle-start marker now sets planResult itself. Its doneTimestamp is Long.MAX_VALUE, so your mark < doneTimestamp check holds everything back until the real plan replaces it, and the flag is gone. If a MAX watermark arrives at end of input while the marker is still current, it's forwarded without a commit. Does that work for you?
Drop the cycleInFlight flag. The committer stores the planner's cycle-start marker as the plan result so its maximal done timestamp holds phase watermarks until the real plan arrives, and forwards watermarks when no cycle is active. An end-of-input watermark while the marker is current is forwarded without a commit. Co-authored-by: Rakesh Pai <developer-rpai@users.noreply.github.com> Generated-by: Cursor
When ConvertEqualityDeletes shares the Flink-maintained lock with another maintenance task, the lock is never released: EqualityConvertCommitter.processWatermark drops watermarks while planResult == null, stalling the unioned watermark that LockRemoverOperator needs to emit the lock release. The planner now emits a cycle-start marker on the metadata stream before its first phase watermark; the committer forwards watermarks while no cycle is active and holds them back from the marker until the commit, so the pre-plan phase watermarks cannot release the lock early (the #17038 race). Fixes #18435.
Test plan: new holdsBackPhaseWatermarkBeforePlanArrives asserts a pre-plan phase watermark is held back and the cycle still commits; forwardsWatermarkWithoutActiveCycle covers idle forwarding; existing holdsBackWatermarkUntilCommit covers mid-cycle hold-back; planner tests assert the marker precedes the plan. Note: the same pattern exists in flink/v1.20 and flink/v2.2 — left for a follow-up to keep this minimal. Could not compile locally (no JDK on this machine); relying on CI.