Skip to content

dl/coordinator: bound pending-file memory under commit backlogs - #30958

Merged
andrwng merged 6 commits into
redpanda-data:devfrom
andrwng:iceberg-commit-chunk
Jul 7, 2026
Merged

dl/coordinator: bound pending-file memory under commit backlogs#30958
andrwng merged 6 commits into
redpanda-data:devfrom
andrwng:iceberg-commit-chunk

Conversation

@andrwng

@andrwng andrwng commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

When an Iceberg topic accumulates a large backlog of uncommitted data files, e.g. the catalog has been rejecting the coordinator's commits for a while, the coordinator can build up an unbounded set of pending files in its replicated state. Reconciling that backlog forces it to materialize the whole set at once (the per-commit state copy plus the accumulated Iceberg file list), which can OOM the broker.

This series bounds that memory along two independent axes:

  1. Per-commit: commit the backlog in fixed-size chunks instead of all at once.
  2. Aggregate: cap how large the pending set is allowed to grow in the first place.

High level changes:

  • add topic_state::copy_bounded(): a copy() variant that returns at most N pending files, keeping a batch's files together so a chunk is committed atomically.
  • commit pending files in bounded chunks: drain the backlog over multiple passes via copy_bounded().
  • back off translation under coordinator backpressure: translators treat a new too_many_pending_files error as a deschedule-and-back-off signal rather than a retryable error.
  • shed load when a coordinator has too many pending files: reject add-files/fetch-offset once the aggregate pending count crosses the threshold, until the backlog drains.

The approach taken here is relatively simple, opting to go with tunable count-based backpressure, rather than more rigorous memory-based backpressure. This is mostly because the implementation was easier, though the mechanisms for backpressure should be reusable if we decide to change this in the future.

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v26.1.x
  • v25.3.x
  • v25.2.x

Release Notes

Improvements

  • The Iceberg Topics Coordinator will now commit to the Iceberg catalog in chunks, avoiding an OOM when the catalog has rejected Redpanda commits for prolonged periods of time.
  • Iceberg Topics will stop creating new parquet files if there is a large backlog of files not yet committed to the Iceberg catalog. This helps avoid an OOM when the catalog has rejected Redpanda commits for prolonged periods of time.

Copilot AI review requested due to automatic review settings June 30, 2026 01:00
@andrwng
andrwng requested a review from a team as a code owner June 30, 2026 01:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the Iceberg topics coordinator against OOM scenarios caused by large backlogs of uncommitted pending files, by (1) committing in bounded chunks and (2) shedding load when the aggregate pending-file set grows too large. It also updates translators to treat coordinator backpressure as a deschedule/backoff signal and adds tests for the new bounded-copy/chunked-commit behavior.

Changes:

  • Add topic_state::copy_bounded() to bound the in-memory pending set per commit while preserving coordinator-offset (watermark) ordering semantics.
  • Update iceberg_file_committer/coordinator loop to commit pending files in bounded chunks across multiple passes (no sleep between passes when more work remains).
  • Add coordinator backpressure (too_many_pending_files) and translator behavior/tests to back off instead of retrying under load shedding; add new tunables for per-commit and aggregate pending-file limits.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/v/datalake/translation/tests/partition_translator_tests.cc Adds a translator test ensuring backpressure causes polling+backoff and resumes once load shedding stops.
src/v/datalake/translation/partition_translator.cc Treats too_many_pending_files as a backoff/deschedule signal with rate-limited logging.
src/v/datalake/coordinator/types.h Introduces errc::too_many_pending_files and formatting support.
src/v/datalake/coordinator/tests/state_update_test.cc Adds unit tests validating copy_bounded() ordering/batch semantics.
src/v/datalake/coordinator/tests/state_test_utils.h Updates test committer to new commit_result return type.
src/v/datalake/coordinator/tests/iceberg_file_committer_test.cc Updates tests for chunked commits and commit_result; adds a cross-partition chunking test.
src/v/datalake/coordinator/tests/coordinator_test.cc Adds backpressure and “drain without sleeping” tests; adds a chunked committer test double.
src/v/datalake/coordinator/state.h Declares topic_state::copy_bounded().
src/v/datalake/coordinator/state.cc Implements copy_bounded() using a merge by added_pending_at to preserve watermark correctness.
src/v/datalake/coordinator/iceberg_file_committer.h Adds a binding for max_files_per_commit and updates interface return type.
src/v/datalake/coordinator/iceberg_file_committer.cc Uses copy_bounded() and returns commit_result{updates, has_more} to drive multi-pass draining.
src/v/datalake/coordinator/frontend.cc Maps coordinator backpressure error to RPC error code.
src/v/datalake/coordinator/file_committer.h Introduces commit_result (updates + has_more) to support chunked draining.
src/v/datalake/coordinator/coordinator.h Wires in max_pending_files config and tracks leader-local backpressure cache state.
src/v/datalake/coordinator/coordinator.cc Implements aggregate backpressure checking + immediate multi-pass commit draining when has_more is set.
src/v/datalake/coordinator/coordinator_manager.cc Wires new config bindings into coordinator + committer construction.
src/v/datalake/coordinator/BUILD Adds chunked_vector dependency needed by the new bounded-copy implementation.
src/v/config/configuration.h Adds two new tunables for per-commit chunk size and coordinator pending-file cap.
src/v/config/configuration.cc Defines defaults/docs/bounds for the new Iceberg tunables.

Comment on lines +578 to +582
if (has_too_many_pending_files()) {
vlog(
datalake_log.debug,
"Rejecting request to add files for {}: too many pending files",
tp);
Comment on lines +542 to +546
if (
backpressured_as_of_.has_value()
&& now - *backpressured_as_of_ < commit_interval_()) {
return true;
}
Comment on lines +191 to +193
// Threshold of total pending files across this coordinator's topics above
// which it sheds load. See should_reject_for_backpressure().
config::binding<size_t> max_pending_files_;
Comment on lines +206 to +210
// Backpressure state (leader-local): when set, we are shedding load and
// this is when we last confirmed it; we keep rejecting without recomputing
// until a recheck interval past it. Recomputed by a new leader. See
// should_reject_for_backpressure().
std::optional<ss::lowres_clock::time_point> backpressured_as_of_;
Comment thread src/v/datalake/coordinator/types.h Outdated
Comment on lines 72 to 75
case errc::too_many_pending_files:
return fmt::format_to(out, "errc::too_many_pending_files");
}
}
@andrwng
andrwng force-pushed the iceberg-commit-chunk branch 3 times, most recently from 89d851b to 1dbd886 Compare June 30, 2026 02:22
@vbotbuildovich

vbotbuildovich commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

CI test results

test results on build#86484
test_status test_class test_method test_arguments test_kind job_url passed reason test_history
FLAKY(PASS) EndToEndCloudTopicsStorageModeToggleTest test_toggle_storage_mode null integration https://buildkite.com/redpanda/redpanda/builds/86484#019f166b-b014-4898-a4e7-3dc7d4a5b88d 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0031, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=EndToEndCloudTopicsStorageModeToggleTest&test_method=test_toggle_storage_mode
test results on build#86631
test_status test_class test_method test_arguments test_kind job_url passed reason test_history
FLAKY(PASS) ShadowLinkingRandomOpsTest test_node_operations {"failures": true, "workload_set": "cloud_combos"} integration https://buildkite.com/redpanda/redpanda/builds/86631#019f205f-8c01-4953-82dd-c4ac1b4372b9 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0176, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=ShadowLinkingRandomOpsTest&test_method=test_node_operations
test results on build#86768
test_status test_class test_method test_arguments test_kind job_url passed reason test_history
FLAKY(PASS) ListOffsetsLeaderEpochRedpandaTest test_list_offsets_epoch {"correct_epoch": false} integration https://buildkite.com/redpanda/redpanda/builds/86768#019f38cd-57ea-4757-82a4-4f6f014b0955 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0014, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=ListOffsetsLeaderEpochRedpandaTest&test_method=test_list_offsets_epoch
FAIL src/v/security/tests/acl_store_fuzz src/v/security/tests/acl_store_fuzz unit https://buildkite.com/redpanda/redpanda/builds/86768#019f38b0-5491-49a2-bd73-467d5aa8516e 0/1
test results on build#86776
test_status test_class test_method test_arguments test_kind job_url passed reason test_history
FLAKY(PASS) ConsumerOffsetsConsistencyTest test_flipping_leadership null integration https://buildkite.com/redpanda/redpanda/builds/86776#019f3944-44c1-4264-bddb-1b45b1a748fd 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=ConsumerOffsetsConsistencyTest&test_method=test_flipping_leadership
FLAKY(PASS) ShadowLinkingRandomOpsTest test_node_operations {"failures": true, "workload_set": "cloud_combos"} integration https://buildkite.com/redpanda/redpanda/builds/86776#019f3944-44be-4dc1-802d-c64b7cdfe253 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0202, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=ShadowLinkingRandomOpsTest&test_method=test_node_operations

@nvartolomei nvartolomei left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall lgtm;

  • please add a ducktape test where max is set to 2 and add a topic with something like 10 partitions and let's make sure we don't drop files and we make progress in reasonable time too

  • add a metric for coordinator rejections; would be useful to monitor it across fleet i reckon

Comment thread src/v/datalake/coordinator/state.cc Outdated
Comment thread src/v/datalake/coordinator/state.cc
Comment thread src/v/config/configuration.cc Outdated
Comment thread src/v/datalake/coordinator/coordinator.h Outdated
Comment thread src/v/datalake/translation/partition_translator.cc
@nvartolomei

nvartolomei commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

On the ducktape test:

Previous suggestion is flawed so ignore it. I want a backlog of multiple files spread across pending entries which we then commit slowly to the catalog.

andrwng added 2 commits June 30, 2026 23:01
Introduce a variant of topic_state::copy() that bounds the copy to a
given number of pending files, so a large backlog can later be committed
in chunks rather than materialized all at once.

The chunk includes all files at or below any accepted offset: if an
entry at offset O is included, every entry (in any partition) at offset
<= O is too. This ensure that any updates made by a given coordinator
batch is committed atomically.

No caller yet; wired into the committer next.
When a topic accumulates a large backlog of pending files (e.g. if the
catalog has been rejecting our commit requests), committing them all at
once forces the coordinator to materialize the whole set at once (the
per-commit state copy and the accumulated Iceberg file list), which can
OOM the broker.

This commit bounds each commit via copy_bounded(), draining the backlog
in chunks over multiple passes.

To ensure the backlog drains promptly rather than one chunk per commit
interval, the committer reports via commit_result::has_more whether the
bounded copy left files behind, and the coordinator loop immediately
retries a commit (skipping its inter-pass sleep) while a topic still has
a chunked backlog.
@andrwng
andrwng force-pushed the iceberg-commit-chunk branch 3 times, most recently from d8ed497 to fa058a8 Compare July 2, 2026 18:37
@andrwng
andrwng requested a review from nvartolomei July 2, 2026 22:24
nvartolomei
nvartolomei previously approved these changes Jul 3, 2026
err_msg="pending file backlog never reached the backpressure threshold",
)

# Relax the limits so the coordinator drains the backlog promptly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we relax limits the test doesn't prove that forward progress is being made.

One other thing test is missing is asserting that translators do respect backpressure signal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, made some more tweaks to the test to assert that we still make progress while backpressured and that we don't translate files during backpressure

andrwng added 4 commits July 6, 2026 01:12
Adds a new field to the coordinator fetch offsets response, allowing it
to signal to translators that the coordinator is under load. The offsets
are still returned and used to report lag.
Caps the total pending file count across all topics owned by a
coordinator, configurable via iceberg_coordinator_max_pending_files.
Once it reaches the threshold the coordinator rejects add-files and
fetch-offset requests with too_many_pending_files, which the translator
treats as a backoff signal until the committer drains the backlog below
the threshold.

Counting the pending files walks the whole pending set, so once we trip
the threshold we hold the rejecting state for a recheck interval rather
than recomputing on every request.

The test injects a small threshold via the property binding and asserts
add-files and fetch-offset are rejected once the backlog crosses it.
Add a coordinator_probe exposing per-operation counters for requests
shed due to too many pending files, split into add-files and
fetch-offset rejections. This is the coordinator's first metric and
makes load shedding observable to operators.
Spreads a topic across multiple partitions and commits slowly (one file
per commit) so pending files pile up faster than they drain, tripping
the coordinator's pending-file threshold. Asserts the backpressure
metric fires, then relaxes the limits and verifies every record still
lands in the table exactly once.
return before == after

wait_until(
quiesced,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this assertion. Why do we expect pending_file_count and number of files created to be exactly the same in a ~5 second window? I'd expect the metrics to vary slightly down and high as both components make slow progress.

hold steady over a window shorter than the commit interval

We don't respect (sleep) the commit interval if there is more work to do though, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right, I didn't take into account that we aren't waiting the full commit message. I think what this test was actually showing was that we finish translating and committing still even with the backpressure..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm going to merge this and tweak the test in a followup, given it's been sitting without pro code changes for a couple days

@nvartolomei nvartolomei left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally lgtm but still confused about the test.

Good to merge if I understand incorrectly the test https://github.com/redpanda-data/redpanda/pull/30958/changes#r3536399532

@andrwng
andrwng merged commit 7040803 into redpanda-data:dev Jul 7, 2026
19 checks passed
@vbotbuildovich

Copy link
Copy Markdown
Collaborator

/backport v26.1.x

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

/backport v25.3.x

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

/backport v25.2.x

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

Failed to create a backport PR to v25.2.x branch. I tried:

git remote add upstream https://github.com/redpanda-data/redpanda.git
git fetch --all
git checkout -b backport-pr-30958-v25.2.x-583 remotes/upstream/v25.2.x
git cherry-pick -x f4ff8dbabe da1e9d53be 36ba2a2cf4 829ebce66e 80c26d8541 08821cca17

Workflow run logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants