Repository navigation
[fix](fe) Hold a new routine load task that has nothing to consume for max_batch_interval - #70573
Open
raghav-reglobe wants to merge 3 commits into
Open
raghav-reglobe wants to merge 3 commits into
raghav-reglobe wants to merge 3 commits into
Conversation
…ne that ended at EOF A routine load task that finds nothing to consume goes back to the scheduler's queue. The scheduler holds a task back for max_batch_interval only when the task is at EOF or marked for delay, and both flags are computed when a task commits. A task that has not committed yet (the job was just created or resumed, or a new master rebuilt the tasks) carries neither, so it came back on the next pass, 1 ms later, and asked the data source for its offsets again: several hundred lookups a second through a BE to the broker, for as long as nothing arrived. The branch now marks the task for delay before it re-queues it. The task then looks again once per max_batch_interval, like a task that ended at EOF, and the flag is computed again at its next commit. Test: a task with nothing to consume is asked once in five scheduler passes, and once more after max_batch_interval. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… made, with a state of its own The first version marked every task that found nothing to consume with delaySchedule. That also held a task renewed after a commit that was not at EOF, which is expected to be scheduled again as soon as data arrives (regression suite test_routine_load_eof), and the mark stayed until the next commit, so a task with data that could not get a BE slot waited again. Now the wait has its own state on the task (waitingForData). It is set only for a task that has never been scheduled, it comes off as soon as a check finds data, and a task that renews another starts without it. delaySchedule and isEof are left to the commit that decides them. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… s max_batch_interval The suite creates its job on an empty topic and sends afterwards. A new task that finds nothing now waits max_batch_interval before it looks again, so at the default 60 s the suite's 62 s wait could run out when the job's first look fell before the send. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
3 tasks done
Contributor
Author
Local pipeline review — ✅ PASSschema: doris-repo-review/v1
status: PASS
pr: apache/doris#70573
commit: 8a371547fd64b49067d61f916ee5d1f1d7aa03b2
base: 89d4f2826e2c9edbba01f5f70fc1d243e89f17b5
reviewed_at: 2026-11-14T09:21+05:30
reviewer: raghav-reglobe
model: claude-opus-5-5
effort: xhigh
findings: {blocker: 0, major: 0, minor: 4, nit: 4}
rounds: 1
converged: trueNotes for maintainers
Reviewed locally with the |
raghav-reglobe
marked this pull request as ready for review
November 14, 2026 03:51
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.
Background
A routine load job reads its data source (a Kafka topic or a Kinesis stream) through tasks. The FE's
RoutineLoadTaskSchedulertakes a task from its queue, asks whether the source has anything new for it (for Kafka, the latest offsets, fetched through a BE), and either submits the task to a BE or puts it back in the queue. To avoid many small transactions it holds a task back formax_batch_intervalwhen the task's last run ended at EOF.What problem does this PR solve?
Issue Number: close #70568
Related PR: None
PR Type: Bugfix
Problem Summary:
A Kafka routine load task that has not run since it was made and finds nothing to consume asks the broker for its offsets on every scheduler pass, 1 ms apart: several hundred lookups a second through a BE, for as long as nothing arrives. It happens for a new job on an empty topic and again after every resume with nothing new. The scheduler now marks such a task as waiting for data and looks at it again once per
max_batch_interval, as it already does for a task that ended at EOF. The mark comes off as soon as a look finds data.Details
divideRoutineLoadJobmade it and that finds nothing to consume: a new job with nothing after its start offsets (a new job starts at the END of its topic by default, so that is any topic where nothing arrives right away), and every job after aRESUMEwhen nothing new has arrived. By reading, it is the same whenever a job's tasks are made again: an auto resume, a change of the topic's partitions on a running job, a change of the master FE. I have not run those three.RoutineLoadTaskScheduler.process()holds a task back only ifneedDedalySchedule()is true, that isdelaySchedule || isEof. Both flags are set when a task's transaction ends: at a commit (RoutineLoadTaskInfo.handleTaskByTxnCommitAttachment), anddelaySchedulealso on the task that renews an aborted one. A task made bydivideRoutineLoadJobstarts with both false. WhenhasMoreDataToConsume()is false it goes back to the queue with neither flag set, so the next pass, one millisecond later, asks again. A task with nothing to consume never commits, so nothing ever sets a flag.RoutineLoadTaskSchedulerTestnever ran the scheduler over a task with nothing to consume, and the defect changes no result: it shows only in the counterroutine_load_get_meta_countand in load on the BE and the broker.RoutineLoadTaskInfogets a state of its own,waitingForData.delayScheduleandisEofare not touched: the end of a task's transaction decides them, and Kafka usesdelaySchedulefor the read-committed case.scheduleOneTasksets it when a task that has never been scheduled (lastScheduledTime == -1, asdivideRoutineLoadJobmakes it) finds nothing to consume, and takes it off as soon as a look finds data.process()holds a task that is at EOF, delayed or waiting for data untilmax_batch_intervalhas passed since it was last scheduled.test_routine_load_eofexpects such a task to be scheduled again as soon as data arrives (at least 20 commits in 30 s at one message a second), which leaves no room for a wait ofmax_batch_intervalthere. The first version of this PR (head 92dc170) held that task as well and failed that suite's scenario when I ran it (5 commits; table below). The lookups in that case are measured below. If you want them bounded too, a short fixed wait on that path would do it; I left it out because it changes how fast a busy job picks up its next batch, which is yours to decide. This PR closes the issue although that case remains; say if you want the issue kept open for it.KinesisRoutineLoadJob.hasMoreDataToConsumealways returns true.RUNNINGorNEED_SCHEDULEand one lag lookup for every job that is not stopped or cancelled, aPAUSEDone included. That is why an idle job still makes 12 lookups a minute plus one permax_batch_interval.process()now passes a held task whose job has been removed on toscheduleOneTask, which abandons it. Before, the missing job failed the wait check, and the task was dropped with an "interrupted" log and a stack trace.max_batch_intervalbefore it looks again. So a message that arrives at a new or resumed job after the job's first empty look is loaded up tomax_batch_intervallater than before (measured below: 0.6 to 0.7 s before, 9.0 s and 8.8 s after, withmax_batch_interval= 10). A job whose last commit was at EOF already waited that long before this change. The same wait applies to a task that is made while a job runs (after a change of the topic's partitions, an auto resume or a change of the master FE) when its first look finds nothing. While a task is held, its own checks run at each look, once permax_batch_interval, and no longer on every pass: the check of the job's compute group, and the data check that also notices an unreachable broker. So an idle job is paused for such a fault up to one interval later than before, and a fault that clears within the hold does not pause it. For a job that names no partitions, the job's own check every 10 s still logs the fault and raisesroutine_load_get_meta_fail_count.test_routine_load_task_exception_recoverycreates its job on an empty topic at the defaultmax_batch_intervalof 60 s and sends afterwards. When the job's first look falls before the send, the task now waits 60 s, and the suite's wait of about 62 s can run out. It gets"max_batch_interval" = "5". The local review read every suite that creates a routine load job (73 files) for the same pattern and found no other one that can fail from it.Testing
Unit tests, in
RoutineLoadTaskSchedulerTest(none sleeps, none can block on an empty queue):testNewTaskWithNothingToConsumeWaitsForMaxBatchInterval: a task as a new job makes it, with nothing to consume, is asked once in five passes and then held; a held pass does not move its time; aftermax_batch_intervalit is asked once more;delayScheduleandisEofstay false; no BE is asked for a slot.testWaitingTaskGoesOnWhenDataArrives: data that arrives while the task waits is seen at its next look; the mark comes off; with no BE free the task is then tried on every pass.testNewTaskWithDataIsNotMarked.testTaskRenewedAfterCommitNotAtEofIsNotHeld: a task that carries an earlier task's time and has neither flag is asked on every pass.testTaskRenewingWaitingTaskStartsWithoutMark.testWaitingTaskOfRemovedJobIsAbandoned.On this branch, on a build machine of ours: the FE builds,
mvn checkstyle:checkon fe-core reports 0 violations, and the eleven routine load test classes pass: 126 tests, 0 failures, 0 errors. The six new tests use the new field, so they do not compile on master or on the first version of this PR; I did not run them there in any other form.Manual tests on single-node clusters with a Kafka of their own (FE, BE and a one-broker Kafka 3.9 in one pod), built from a master snapshot of early November (1503e39) with unrelated patches: without the change, with the first version of this PR, and with this version.
Lookups, counted with the FE metric
routine_load_get_meta_countover 40 s windows (20 s forPAUSED), one job,max_batch_interval= 10:RUNNINGat the end of the topic, after a commitPAUSEDThe
PAUSEDrow is the job's own lag check and is not changed by this PR.The scenario of
test_routine_load_eof(one message a second,max_batch_interval= 5; the debug pointRoutineLoadTaskInfo.judgeEofmakes every commit count as not at EOF), run by hand with the suite's three windows:The last row is the case this PR leaves as it is. The fourth row shows that the suite's bound is close to what this scenario gives on my setup even without the change.
I have not run the regression suites themselves; I have no setup for the regression framework. A suite for the lookup rate is possible, since
test_routine_load_metricsalready reads this metric from the FE. Tell me if you want one in this PR.Release note
Fix: a Kafka routine load job whose task has not run yet and has nothing to consume (a new job with nothing after its start offsets, or a resumed job with nothing new) no longer asks the broker for its offsets hundreds of times a second. Such a task now looks again once per
max_batch_interval, so data that arrives after its first empty look is loaded up tomax_batch_intervallater (60 s by default).Check List (For Author)
AI Disclosure
🤖 Generated with Claude Code