Skip to content

feat(compression-coordinator): Introduce max_concurrent_tasks config to limit job-handler concurrency. - #2435

Merged
Bill-hbrhbr merged 36 commits into
y-scope:mainfrom
Bill-hbrhbr:coordinator/limit-job-submission-concurrency
Aug 12, 2026
Merged

feat(compression-coordinator): Introduce max_concurrent_tasks config to limit job-handler concurrency.#2435
Bill-hbrhbr merged 36 commits into
y-scope:mainfrom
Bill-hbrhbr:coordinator/limit-job-submission-concurrency

Conversation

@Bill-hbrhbr

@Bill-hbrhbr Bill-hbrhbr commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

This PR limits the number of compression jobs processed concurrently to prevent the coordinator from creating an unbounded number of job-handler tasks and consuming excessive memory when a large backlog accumulates.

The coordinator now only fetches as many new jobs as it has capacity to process. Once the configured concurrency limit is reached, no additional jobs are fetched until existing jobs finish and capacity becomes available.

On startup, the coordinator recovers work from the previous instance before scheduling new jobs. This includes both jobs that were already submitted to Spider and jobs that were dispatched but did not progress far enough to be marked as running. The former resume tracking their existing Spider jobs, while the latter are re-dispatched.


NOTE

The is_first_fetch flag and its two-query fetch logic have been removed because they do not work well with bounded concurrency. Previously, the first fetch after startup returned all pending jobs, including those with an existing dispatch_time, to recover jobs that may not have completed dispatch before the previous coordinator exited.

With bounded concurrency, recovery can no longer rely on the first fetch returning everything, since the coordinator should only fetch as many new jobs as it has capacity to process. Recovery is therefore handled explicitly by recover_previous_jobs, which identifies all pending jobs with an existing dispatch_time and re-dispatches them before normal scheduling begins.

This leaves the steady-state dispatcher with a single, clear eligibility rule: only pending jobs without a dispatch_time are considered new work.


The concurrency limit is configured through a new max_concurrent_tasks field in ClpConfig, with a default of 1000.


This PR opens #2472 to be resolved in a future PR.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

Summary by CodeRabbit

New Features

  • Added a configurable limit for concurrent compression tasks, defaulting to 1,000.
  • Scheduling now adapts to available capacity and avoids dispatching work when capacity is unavailable.
  • Pending and interrupted compression jobs are recovered and resumed more reliably.

Bug Fixes

  • Invalid concurrency settings, including zero or unsupported values, now produce clear configuration errors.
  • Improved startup handling for previously dispatched compression jobs.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The change adds max_concurrent_jobs to Python and Rust configuration. The coordinator validates this limit, controls handlers with a semaphore, and bounds pending-job scheduling while preserving startup recovery behaviour.

Changes

Compression coordinator concurrency

Layer / File(s) Summary
Concurrency configuration
components/clp-py-utils/clp_py_utils/clp_config.py, components/clp-rust-utils/src/clp_config/package/config.rs
CompressionCoordinator exposes a non-zero max_concurrent_jobs setting with a default of 1000.
Coordinator validation and recovery
components/compression-coordinator/src/error.rs, components/compression-coordinator/src/coordination.rs
The coordinator validates the limit, initializes shared semaphore permits, and defines errors for invalid configuration and semaphore failures.
Bounded pending-job scheduling
components/compression-coordinator/src/coordination.rs
Scheduling checks available permits, limits subsequent pending-job queries, and retains permits for spawned handlers until completion.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CompressionCoordinator
  participant JobDatabase
  participant JobHandler
  CompressionCoordinator->>CompressionCoordinator: Check semaphore capacity
  CompressionCoordinator->>JobDatabase: Fetch pending jobs within capacity
  CompressionCoordinator->>JobHandler: Spawn handlers with retained permits
  JobHandler->>CompressionCoordinator: Release permit on completion
Loading

Possibly related issues

Possibly related PRs

  • y-scope/clp#2417: Introduces coordinator scheduling and recovery logic extended by this change.
  • y-scope/clp#2421: Introduces the CompressionCoordinator configuration extended with max_concurrent_jobs.
  • y-scope/clp#2402: Modifies the compression-coordinator Error enum that this change also extends.

Suggested reviewers: jackluo923, 20001020ycx

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes the concurrency limit, but it names max_concurrent_tasks while the changes introduce max_concurrent_jobs. Update the title to use max_concurrent_jobs and match the final configuration name.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Bill-hbrhbr Bill-hbrhbr changed the title feat(compression-coordinator): Limit concurrent job-handler tasks. feat(compression-coordinator): Introduce max_concurrent_tasks config to limit job-handler task concurrency. Aug 1, 2026
@Bill-hbrhbr Bill-hbrhbr changed the title feat(compression-coordinator): Introduce max_concurrent_tasks config to limit job-handler task concurrency. feat(compression-coordinator): Introduce max_concurrent_tasks config to limit job-handler concurrency. Aug 1, 2026
@Bill-hbrhbr
Bill-hbrhbr marked this pull request as ready for review August 1, 2026 06:51
@Bill-hbrhbr
Bill-hbrhbr requested a review from a team as a code owner August 1, 2026 06:51

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/clp-py-utils/clp_py_utils/clp_config.py`:
- Line 798: Add a shared Tokio semaphore upper-bound validation for
max_concurrent_tasks in components/clp-py-utils/clp_py_utils/clp_config.py:798
and components/clp-rust-utils/src/clp_config/package/config.rs:488, preserving
the existing positive-value validation. In
components/compression-coordinator/src/coordination.rs:137-138, validate the
configured value before constructing job_handler_sem and return the established
configuration error when it exceeds the supported limit.

In `@components/compression-coordinator/src/coordination.rs`:
- Around line 49-52: Update the recovery admission logic around the
coordinator’s recovered-handler tracking so every recovered handler contributes
to the active count, including handlers that do not hold a permit. Prevent new
jobs from being admitted until the total active recovered and newly started
handlers is below max_concurrent_tasks, and revise the recovery documentation
near the constructor to describe this limit-enforced behavior.
- Around line 270-275: Update the pending-job refill flow around
fetch_new_job_rows so it retrieves eligible jobs in bounded pages rather than
loading and retaining the entire backlog in pending_job_queue. Add durable
paging state, such as a cursor that advances only after successful processing or
equivalent recovery-safe state, and preserve first-fetch recovery semantics so
retries resume without skipping jobs or causing unbounded memory growth.

In `@components/package-template/src/etc/clp-config.template.json.yaml`:
- Line 72: Update the commented max_concurrent_tasks configuration entry in the
template to explicitly state that its value must be greater than zero,
distinguishing it from compression_scheduler.max_concurrent_tasks_per_job where
zero disables the limit.
- Line 67: Update the compression coordinator configuration note near the
“Compression coordinator config” comment to document every runtime prerequisite:
`spider`, `logs_input.type: s3`, and `archive_output.storage.type: s3`; clarify
that the related template examples must use these required S3 settings rather
than `fs` when enabling the configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8fd3f43d-d433-437d-ab55-235ab39cfb5f

📥 Commits

Reviewing files that changed from the base of the PR and between 155fbda and e083517.

📒 Files selected for processing (4)
  • components/clp-py-utils/clp_py_utils/clp_config.py
  • components/clp-rust-utils/src/clp_config/package/config.rs
  • components/compression-coordinator/src/coordination.rs
  • components/package-template/src/etc/clp-config.template.json.yaml

Comment thread components/clp-py-utils/clp_py_utils/clp_config.py Outdated
Comment thread components/compression-coordinator/src/coordination.rs Outdated
Comment thread components/compression-coordinator/src/coordination.rs Outdated
Comment thread components/package-template/src/etc/clp-config.template.json.yaml Outdated
Comment thread components/package-template/src/etc/clp-config.template.json.yaml Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/compression-coordinator/src/coordination.rs (1)

294-300: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Record a job as dispatched only after handler creation succeeds.

dispatched_job_ids.push(job_id) runs before configuration deserialisation and create_job_handle. If either operation fails, run still passes the ID to mark_jobs_dispatched. An Error::UnsupportedInputConfig job therefore receives dispatch_time without a coordinator handler, which removes it from the subsequent pending-job query and can interfere with the documented legacy-scheduler hand-off.

Move the push after create_job_handle returns Ok:

Proposed fix
 let job_id = job_row.id;
- dispatched_job_ids.push(job_id);

 let clp_io_config: ClpIoConfig =
     match BrotliMsgpack::deserialize(&job_row.serialized_clp_io_config) {
         Ok(clp_io_config) => clp_io_config,
         Err(e) => {
             ...
         }
     };

 let Ok(job_handle) = self.create_job_handle(job_id, clp_io_config).await else {
     continue;
 };
+dispatched_job_ids.push(job_id);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/compression-coordinator/src/coordination.rs` around lines 294 -
300, Move dispatched_job_ids.push(job_id) in the run flow to after configuration
deserialization and successful create_job_handle completion, so only jobs with a
coordinator handler are marked dispatched; preserve failure handling without
passing failed job IDs to mark_jobs_dispatched.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/package-template/src/etc/clp-config.template.json.yaml`:
- Line 67: Update the compression coordinator configuration note near the
existing logs_input.type and spider requirements to also state that
archive_output.storage.type must be "s3". Keep the documentation aligned with
the coordinator prerequisites and the archive output setting shown in the
template.

---

Outside diff comments:
In `@components/compression-coordinator/src/coordination.rs`:
- Around line 294-300: Move dispatched_job_ids.push(job_id) in the run flow to
after configuration deserialization and successful create_job_handle completion,
so only jobs with a coordinator handler are marked dispatched; preserve failure
handling without passing failed job IDs to mark_jobs_dispatched.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b74354c7-b2d8-4659-a5ac-82902ed1fabb

📥 Commits

Reviewing files that changed from the base of the PR and between e083517 and 46e219c.

📒 Files selected for processing (3)
  • components/compression-coordinator/src/coordination.rs
  • components/compression-coordinator/src/error.rs
  • components/package-template/src/etc/clp-config.template.json.yaml

Comment thread components/package-template/src/etc/clp-config.template.json.yaml Outdated
Comment thread components/package-template/src/etc/clp-config.template.json.yaml Outdated
Comment thread components/compression-coordinator/src/coordination.rs Outdated
Comment thread components/package-template/src/etc/clp-config.template.json.yaml
job_polling_interval: Duration,
cancellation_token: CancellationToken,
job_handler_sem: Arc<Semaphore>,
pending_job_queue: VecDeque<PendingJobRowProjection>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure if we need this. Can you explain why we can't do the following instead:

  • On the main loop, fetch compression jobs with a limit and ID ordering (sth like SELECT * FROM t ORDER BY id ASC LIMIT 100;)
  • Get a permit before spawning the coroutine. The permit automatially drops itself if the coroutine exits, aborts, or crashed.
  • Let the semaphore to block coroutine creation implicitly. The main loop may be blocked, which is fine because it shouldn't push more jobs into Spider.

This should lead to the behavior we expect iiuc.

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's a risk that we won't be able to merge this PR before the release since it's not in an expected shape yet. Let's fix the recovery path bug first.

Comment on lines +528 to +549
async fn try_deserialize_clp_io_config(
&self,
job_id: CompressionJobId,
serialized_config: &[u8],
) -> Option<ClpIoConfig> {
match BrotliMsgpack::deserialize(serialized_config) {
Ok(clp_io_config) => Some(clp_io_config),
Err(e) => {
tracing::error!(
error = % e,
job_id = % job_id,
"Failed to deserialize CLP I/O config. Skipping."
);
self.mark_job_failed(
job_id,
&format!("Failed to deserialize CLP I/O config: {e}"),
)
.await;
None
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a really bad idea to return an option instead of a result, and log inside the helper. The best practice for designing this type of helper is:

  • Return a result. Let the caller decide whether it wants to inspect the error or just propagate.
  • Log on the caller side.
    • You may see create_job_handle logs inside the helper. That is because it needs to print different logs based on the type of the error and execute different reaction calls accordingly. This makes it an exception since logging it inside would actually make things cleaner. In this helper, errors are printed unconditionally so it should be logged outside.

Comment on lines +275 to +333
let Ok(job_handle) = self.create_job_handle(job_id, clp_io_config).await else {
let Some(job_handle) = self.create_job_handle(job_id, clp_io_config).await else {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similar to https://github.com/y-scope/clp/pull/2435/changes#r3736535562: you didn't change the implementation of create_job_handle, then why would you need to update the return type? This is polluting the diff in some sense and making the PR harder to review.

Comment thread components/compression-coordinator/src/coordination.rs Outdated
/// A projection of the columns read from a compression job row.
#[derive(Debug, sqlx::FromRow)]
struct RunningJobRowProjection {
struct JobRowProjection {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bad idea to merge PendingJobRowProjection and RunningJobRowProjection into one type: the user needs to check whether spider_job_id is NULL now. Since we will have https://github.com/y-scope/clp/pull/2435/changes#r3736798212, let roll back to two separate types instead of one.
In Rust, if you really want a type to cover both, a better idea is probably to make an enum containing both types.

Comment thread components/compression-coordinator/src/coordination.rs Outdated
continue;
};

let permit = self.job_handler_sem.clone().try_acquire_owned().ok();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure if we really need to enforce the permit here: the recovery may not necessarily happen from a failure recovery, but a restart with clp config changed. If the new max_concurrent_jobs is changed to a smaller number, this recovery path will be blocked for an improper reason: eventually, a number of compression jobs are already submitted to Spider that exceeds the configured concurrency.
My proposal:

  • For now, it's probably easier if we don't do anything to the permit: let the already-submitted jobs run in background. As we would assume the recovery is a rare case, the number of jobs should be roughly bounded by old_max_concurrent_jobs + new_max_concurrent_jobs. There are extreme cases where this bound can be broken by a series of frequent restarts, but it should be safe to ignore this case for now.
  • To improve the naive implementation, we could compare the number of jobs to recover vs. the currently configured max_concurrent_jobs:
    • If num_jobs_to_recover > max_concurrent_jobs: print a warning, make a join of all the recovered job handlers, so the main loop only starts when all previously submitted jobs are finished.
    • Otherwise, do the current implementation to recover jobs while each job holds an acquired permit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@coderabbitai we decide to stick to the first option for this PR. Can you create an issue to keep track of it, documenting that on restart, recovered jobs may disobey the configured rate limit? You should also document my section option as the proper way to implement it.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="y-scope/clp"
body=$(cat <<'EOF'
## Summary

The compression coordinator does not apply `max_concurrent_tasks` to jobs recovered during startup.

After a restart, the coordinator resumes already submitted Spider jobs without acquiring `job_handler_sem` permits. If the configuration changed to a smaller `max_concurrent_tasks` value, the recovered jobs can exceed the configured concurrency limit.

This behaviour is intentional for now. It avoids blocking recovery when Spider already has more submitted compression jobs than the new limit permits.

## Required follow-up

Implement bounded startup recovery in `components/compression-coordinator/src/coordination.rs`.

1. Count the jobs that require recovery before spawning recovery handlers.
2. Compare that count with the configured `max_concurrent_tasks`.
3. If the recovery count is less than or equal to the configured limit, recover each job while it holds a semaphore permit.
4. If the recovery count exceeds the configured limit:
   - Emit a warning that recovered jobs exceed the configured concurrency limit.
   - Start all recovery handlers.
   - Join all recovered job handlers before the normal coordinator poll loop starts.
5. Ensure normal scheduling starts only after the required recovery behaviour completes.

## Rationale

The current behaviour is acceptable because coordinator restart recovery should be rare. It can temporarily run approximately the prior limit plus the new limit. Repeated rapid restarts can exceed that approximate bound. The follow-up implementation must make restart behaviour explicit and bounded.

## Affected area

- `components/compression-coordinator/src/coordination.rs`
- Startup recovery and `job_handler_sem` permit management.

## Acceptance criteria

- Recovery behaviour is selected by comparing the number of recovery jobs with `max_concurrent_tasks`.
- Recovery jobs hold permits when the count does not exceed the limit.
- When the count exceeds the limit, the coordinator logs a warning and waits for all recovered handlers before normal scheduling.
- Tests cover both recovery-count cases.

## Context

Requested by `@LinZhihao-723`.

- Pull request: https://github.com/y-scope/clp/pull/2435
- Review discussion: https://github.com/y-scope/clp/pull/2435#discussion_r3736993293
EOF
)

gh issue create \
  --repo "$repo" \
  --title "Bound compression coordinator recovery after configuration changes" \
  --body "$body" \
  --assignee "LinZhihao-723"

Length of output: 4193


@LinZhihao-723, I created and assigned #2472: Bound compression coordinator recovery after configuration changes.

The issue documents the current restart behaviour and the proposed recovery-count design.


🧬 Code Graph Analysis Results

components/compression-coordinator/src/job_handle.rs

  • Lines 30–35: SpiderOption defines compression retry settings, commit execution policy, and result polling backoff parameters passed to compression job handles.

  • Lines 42–61: S3CompressionJobHandle stores the database, job ID, Spider submitter/resource group, compression configuration, and shared SpiderOption.

  • Lines 72–119: S3CompressionJobHandle::new validates and transforms the CLP I/O configuration, returning Error::UnsupportedInputConfig or Error::InvalidDataset for unsupported or invalid inputs; otherwise constructs a handle with the shared Spider options.

  • Lines 133–142: S3CompressionJobHandle::run submits the job and waits for completion; failures are reported through report_failure.

components/clp-rust-utils/src/clp_config/package/config.rs

  • Lines 483–494: CompressionCoordinator configuration defines the job polling interval, maximum concurrent jobs, retry settings, polling backoff, and commit-task timeouts.

components/compression-coordinator/src/coordination.rs

  • Lines 72–147: Coordinator::new retrieves running jobs from the database and spawns detached recovery handles for each previously submitted Spider job.

  • Lines 326–335: fetch_new_job_rows returns all pending jobs on the first fetch, including jobs previously dispatched but not started; subsequent fetches return only pending jobs with no dispatch time.

You are interacting with an AI system.

Comment thread components/clp-py-utils/clp_py_utils/clp_config.py Outdated

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall it's in a good shape now. Some more comments.

Comment thread components/compression-coordinator/src/coordination.rs Outdated
Comment on lines +278 to +285
let permit = self
.job_handler_sem
.clone()
.acquire_owned()
.await
.map_err(|e| {
Error::Semaphore(format!("Failed to acquire a job handler permit: {e}"))
})?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this is mainly for spider job submission rate control, we could do it after job IO dispatching. Let's move this after job handle creation.

Comment on lines +428 to +431
async fn fetch_new_job_rows(
&mut self,
limit: usize,
) -> Result<Vec<PendingJobRowProjection>, Error> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This API design is a bit error-prone: limit is actually not used in the first fetch, which is not documented in the docstring.
Took a closer look, it makes more sense to move self.job_handler_sem.available_permits inside this method and drop limit parameter. In this way, this method is self-contained where it's behavior is easier to document (we might need to rename it to fetch_new_job_rows_with_concurrency_limit).

Comment on lines +406 to +458
let query = if self.is_first_fetch {
self.is_first_fetch = false;
let is_first_fetch = self.is_first_fetch;
self.is_first_fetch = false;

let query = if is_first_fetch {
FIRST_FETCH_QUERY
} else {
SUBSEQUENT_FETCH_QUERY
};
let rows = sqlx::query_as::<_, PendingJobRowProjection>(query)
.bind(CompressionJobStatus::Pending)
.fetch_all(&self.db_pool)
.await?;

let mut query_builder =
sqlx::query_as::<_, PendingJobRowProjection>(query).bind(CompressionJobStatus::Pending);
if !is_first_fetch {
query_builder = query_builder.bind(
i64::try_from(limit)
.expect("limit is bounded by Semaphore::MAX_PERMITS, which fits in i64"),
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

        let query = if self.is_first_fetch {
            self.is_first_fetch = false;
            sqlx::query_as::<_, PendingJobRowProjection>(FIRST_FETCH_QUERY)
                .bind(CompressionJobStatus::Pending)
        } else {
            sqlx::query_as::<_, PendingJobRowProjection>(SUBSEQUENT_FETCH_QUERY)
                .bind(CompressionJobStatus::Pending)
                .bind(
                    i64::try_from(limit)
                        .expect("limit is bounded by Semaphore::MAX_PERMITS, which fits in i64"),
                )
        };

Rewrite in a cleaner way.

  • Avoid making a variable mutable in Rust whenever possible.

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a comment in the recover to mention that we don't bound recovered jobs, referring to this issue: #2472

Bill-hbrhbr and others added 2 commits August 10, 2026 22:28
Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>
@Bill-hbrhbr

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/compression-coordinator/src/coordination.rs`:
- Around line 440-453: Remove the is_first_fetch branch in the scheduling flow
and use the dispatch_time IS NULL query with an available-permits limit on every
iteration, preventing fetch_all from loading the entire pending backlog. Run
stale-dispatch recovery before normal scheduling, and preserve the existing
permit conversion and query execution through the scheduling method around
FIRST_FETCH_QUERY and SUBSEQUENT_FETCH_QUERY.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e7f9a89f-68e9-40f1-a34f-883e786d3b04

📥 Commits

Reviewing files that changed from the base of the PR and between e044371 and 765be0d.

📒 Files selected for processing (4)
  • components/clp-py-utils/clp_py_utils/clp_config.py
  • components/clp-rust-utils/src/clp_config/package/config.rs
  • components/compression-coordinator/src/coordination.rs
  • components/compression-coordinator/src/error.rs

Comment thread components/compression-coordinator/src/coordination.rs

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved with some final updates.
For the PR title, how about:

feat(compression-coordinator): Add configurable limit for concurrent compression jobs handled by the coordinator.

Comment on lines +160 to +161
// NOTE: The current implementation does not enforce concurrency limits for recovered jobs
// since they were already submitted to Spider. See #2472.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Downgrade this to a code-level comment: this should be more like a TODO instead of a formal behavior to documenet in the method-level docstring.

Comment on lines +430 to +432
/// * This query runs only once, so limiting it could leave previously dispatched jobs
/// unfetched.
/// * The recovery set is bounded by the previous coordinator's concurrency limit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should use bullet point since they are unordered.

Comment on lines +3 to +17
//!
//! The coordinator is responsible for the compression jobs in the `compression_jobs` table that
//! are in one of the following states:
//!
//! | `status` | `spider_id` | `dispatch_time` | Description |
//! |----------|-------------|-----------------|--------------------------------------------------|
//! | PENDING | NULL | NULL | New jobs awaiting dispatch. |
//! | PENDING | NULL | NOT NULL | Jobs dispatched but not yet submitted to Spider. |
//! | RUNNING | NOT NULL | NOT NULL | Jobs submitted to Spider. |
//!
//! NOTE:
//!
//! * These are the only legal states for a job that hasn't terminated.
//! * A non-NULL `dispatch_time` indicates that the coordinator has picked up the job and granted it
//! permission to run under the concurrency limit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Adding a section to show all legal states.

@Bill-hbrhbr
Bill-hbrhbr merged commit 5c51991 into y-scope:main Aug 12, 2026
28 checks passed
@Bill-hbrhbr
Bill-hbrhbr deleted the coordinator/limit-job-submission-concurrency branch August 12, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants