Skip to content

[Bug] Bounded workflows silently drop records in batch mode with multiple keys #939

Closed

Description

@Sxnan

Search before asking

  • I searched in the issues and found nothing similar.

Description

When a Flink Agents workflow runs with RuntimeExecutionMode.BATCH, records may be silently dropped if one operator subtask processes multiple keys. The job finishes successfully without errors, but usually only the last key processed by each subtask produces output.

ActionExecutionOperator stores pending action tasks in keyed state while scheduling workflow continuations through the mailbox. Flink's batch keyed-state backend assumes that a key is fully processed before moving to the next key and clears keyed state whenever the current key changes. Therefore, when the operator processes key B before the mailbox continuation for key A runs, A's pending tasks are cleared. Switching back to A can also clear B's tasks.

Expected: every input record completes its workflow and produces output.

Actual: pending workflows are silently discarded while the Flink job reports FINISHED.

Setting execution.batch-state-backend.enabled=false avoids the issue.

Possible fix

In batch mode, the operator must finish the complete action chain for the current key before allowing input processing to move to another key. Alternatively, Flink Agents jobs should disable the batch keyed-state backend. A regression test should use a real BATCH execution environment with at least two keys assigned to the same subtask.

How to reproduce

  1. Create a Python workflow with multiple synchronous stages:

    InputEvent -> Stage1Event -> Stage2Event -> Stage3Event -> OutputEvent

  2. Run it with:

    • RuntimeExecutionMode.BATCH
    • parallelism 1
    • five input records
    • a different key for every record
  3. Observe that only one record reaches the output:

    5 inputs -> 1 output

Control results:

  • Using one fixed key: 5 inputs -> 5 outputs
  • Setting execution.batch-state-backend.enabled=false: 5 inputs -> 5 outputs
  • The issue remains reproducible when all action delays are removed.

Version and environment

  • Flink Agents: 0.2.1
  • Apache Flink: 1.20.3
  • Python: 3.11
  • Java: OpenJDK 11
  • Runtime mode: BATCH
  • OS: macOS ARM64

The issue was reproduced locally with the official flink-agents==0.2.1 package and does not require a vendor-specific Flink distribution.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Activity

  1. added
    bug[Issue Type] Something isn't working as expected.
    priority/majorDefault priority of the PR or issue.
    on Jul 30, 2026
  2. weiqingy commented on Aug 1, 2026

    @weiqingy
    Collaborator

    Thanks for the detailed repro. Confirmed the mechanism on current main, not just 0.2.1 / Flink 1.20.3:

    • actionTasksKState and pendingInputEventsKState are keyed state, while currentProcessingKeysOpState is operator state. That is why the job still reports FINISHED: endInput waits on the operator state that survived, while the per-key task lists are already gone.
    • At Flink 2.3.0, which main builds against, BatchExecutionKeyedStateBackend.setCurrentKey still clears every registered state on any key change, so this is not specific to 1.20.x.
    • The operator crosses that boundary more than once per record: processEvent submits a mail, processActionTaskForKey starts with setCurrentKey(key), and an unfinished task submits another mail.

    Agreed that enabled=false is an escape hatch, not a fix. The option defaults to true (ExecutionOptions.USE_BATCH_STATE_BACKEND), so a plain BATCH job hits this with no configuration at all, and that default path is the one that has to work.

    Two ways I can see to get there:

    1. Finish the current key's whole action chain before returning from processElement, which is what the batch backend assumes callers do anyway. waitInFlightEventsFinished already does this at endInput by spinning on mailboxExecutor.yield().
    2. Keep the in-flight structures off keyed state in batch, since BATCH does not checkpoint and a plain per-key map would survive the key changes.

    I lean toward the first. The second does not stop at those two lists: sequenceNumberKState, sensoryMemState and shortTermMemState are keyed too, so preserving cross-key overlap means moving all per-key runtime state off the batch backend, which is close to reimplementing enabled=false inside the operator. And the overlap that draining gives up is not something enabled=true can support anyway.

    One open question either way: the behavior has to be conditional on the batch backend, and getKeyedStateBackend() instanceof BatchExecutionKeyedStateBackend is the signal I found. Is there a cleaner one?

    Batch also has no coverage today, every set_runtime_mode in the e2e suites passes STREAMING, so the regression test you described would be the first of its kind. Glad to take this on.

    WDYT?

  3. wellkilo commented on Sep 28, 2026

    @wellkilo
    Contributor

    I'd like to take this on and implement the underlying runtime fix, if nobody is actively working on it.

    I checked current main and the merged #1104 safeguard. The intended scope is:

    1. Detect the actual batch keyed-state backend inside ActionExecutionOperator at runtime, so explicit BATCH and AUTOMATIC modes are handled according to the backend Flink actually created.
    2. When that backend is active, finish the current input's action chain before processElement returns. This keeps all keyed state for one key alive until the batch backend's key-complete contract is satisfied.
    3. Preserve the existing cross-key mailbox concurrency for streaming/general-purpose keyed backends.
    4. Replace the graph-construction rejection from [runtime] Reject RuntimeExecutionMode.BATCH with the default batch state backend #1104 with a real multi-key BATCH regression test using the default batch state backend, plus a control that pins streaming concurrency.

    The public CompileUtils.connectToAgent signatures and event contracts would remain unchanged. The externally visible change is that the currently rejected configuration completes normally without dropping records.

    @weiqingy, I noticed your earlier "glad to take this on" comment. Please let me know if you already have an active implementation; otherwise I will proceed with the above focused scope.

  4. weiqingy commented on Sep 29, 2026

    @weiqingy
    Collaborator

    @wellkilo I haven’t received any reply from @Sxnan yet, so I haven’t started. Please feel free to go ahead and take it. @Sxnan can chime in with more context.

  5. added 3 commits that reference this issue on Nov 9, 2026
    8ba0573
    47dcdbd
    f5f94d8
  6. added
    fixVersion/1.0.0The feature or bug should be implemented/fixed in the 1.0.0 version.
    on Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug[Issue Type] Something isn't working as expected.fixVersion/1.0.0The feature or bug should be implemented/fixed in the 1.0.0 version.priority/majorDefault priority of the PR or issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions