Skip to content

[manager] Set the memory limits from the local OS/container when they are not specified#6603

Open
mihaibudiu wants to merge 3 commits into
feldera:mainfrom
mihaibudiu:issue5921
Open

[manager] Set the memory limits from the local OS/container when they are not specified#6603
mihaibudiu wants to merge 3 commits into
feldera:mainfrom
mihaibudiu:issue5921

Conversation

@mihaibudiu

Copy link
Copy Markdown
Contributor

Fixes #5921

If users forget to set up the max memory in the pipeline config derive some sensible defaults by querying the local OS/container.

Checklist

  • Unit tests added/updated
  • Documentation updated

@mihaibudiu
mihaibudiu requested a review from snkas July 8, 2026 17:49

@mythical-fred mythical-fred left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sensible default and the wiring looks right — I like that the per-host case in local_runner.rs splits the machine share by n_hosts and that pipeline-process fallback in parse_config runs before the logger, using eprintln!.

A few things worth a look before you merge:

1. available_memory_mb() semantics don't match the docstring / user doc.
The docstring says "min of the host's available memory and the cgroup budget", and docs/operations/memory.md says "min(host memory size, container memory limit)". The implementation, however, uses system.available_memory() (currently-free host memory) and limits.free_memory (currently-free within the cgroup) — those are point-in-time free amounts, not the host size or the cgroup limit. In a container that started with the pipeline as the first process the two are close, but in a co-tenant pod or a busy host they can diverge quite a bit and produce non-deterministic startup limits across restarts.

If you want a stable ceiling that survives across restarts, prefer total_memory for both:

let mut available = system.total_memory();
if let Some(limits) = system.cgroup_limits() {
    available = available.min(limits.total_memory);
}

Otherwise, please update the docstring and memory.md to say "currently available" rather than "size / limit", so operators know the default can drift restart-to-restart.

2. sysinfo MB vs MiB. sysinfo returns bytes; you divide by 1_000_000 (decimal MB). The max_rss_mb / memory_mb_max fields have historically been treated as MB throughout the config, so this is internally consistent — just flagging in case anyone thought it was MiB.

3. Cosmetic: System::new_with_specifics(RefreshKind::new().with_memory()) avoids allocating the full snapshot, but the cost here is negligible.

4. running_in_kubernetes() doc. The comment references pipeline-manager rejecting user-supplied KUBERNETES_* variables — worth linking to that validation code (or referencing the type/function) so future readers can confirm the env-var isn't user-forgeable.

5. Test coverage. reports_nonzero_memory is Linux-only and doesn't exercise the cgroup path or the mb == 0 fallback. A unit test that injects a mock available/limit pair through a thin seam would guard the min-selection logic. Not blocking.

Not a blocker overall; the heuristic is fine for "no config supplied" mode. Approving on the assumption that (1) is either a docstring fix or a total_memory swap.

@mythical-fred mythical-fred left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for addressing (1) — swapping to total_memory() + limits.total_memory gives the stable ceiling I was after, and the parse_config fallback in server.rs now runs before the logger via eprintln!. The controller warning split (running_in_kubernetes() → pod-limit wording vs local info-level) reads well too.

One small naming/docstring inconsistency to clean up before/after merge:

  • The function is called available_memory_mb and its docstring says "min of the host's available memory and the cgroup budget", but the implementation uses system.total_memory() and limits.total_memory — i.e. installed RAM and the cgroup limit, not currently-free memory. Consider renaming to total_memory_mb (matches sysinfo's terminology) and updating the docstring plus the call sites in local_runner.rs / server.rs. As written, a future reader will assume this reports free memory and reach for it in places where it does not do what they want.

  • docs/operations/memory.md: "min(host memory size, container memory limit)" is now accurate — good.

Non-blocking; APPROVE stands.

@snkas snkas 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.

I think it is a good idea to always have (1) a memory limit on the pipeline, and (2) the pipeline knowing this limit and doing all in its ability to stay under it to not cause an OOM. Whether the memory limit (1) is enforced depends on the form factor.

In the act of keeping under this limit, ideally performance degrades and a warning bubbles up saying "limited memory is causing large amount of spillover to disk, negatively impacting performance" or even quantifying the negative impact. It might even suggest a new memory limit.

I'm not sure if using the host or container memory size as the default is the way to go, or instead having just a default memory size limit (e.g., 2 GiB). The latter would be more predictable, especially if the underlying cluster on which is scheduled is heterogeneous / has other pipelines running in it. When you create a new pipeline, the amount of resources you set for it could be a mandatory thing to think about (e.g., the same as for AWS Lambda when you spin up workers).

I think @ryzhyk / @blp would be best to chime in on this.

///
/// This is the min of the host's available memory and the cgroup budget
/// (what a container or Kubernetes pod memory limit sets).
pub fn total_memory_mb() -> Option<u64> {

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.

In these variable names, I suggest usage in the form of available_byte and then available_megabyte, writing it all out. In my experience, "mb" being lowercase is confusing as it reads as megabit.

std::env::var_os("KUBERNETES_SERVICE_HOST").is_some()
}

/// Returns the memory, in MB, of the current system, or `None`

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.

in megabyte (MB)

base_config.outputs = program_info.outputs;
base_config.program_ir = program_info.program_ir;

// The memory budget is per host, and all host processes share this

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.

How do you know the hosts are colocated?

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.

Because we are in the local_runner.rs file

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.

True, overlooked that. There should be a counterpart downstream PR as well for this to find out if it works as expected in non-local form factor.

@mythical-fred mythical-fred left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Follow-up on tip b48c34e5: rebase onto main plus my non-blocking rename nit addressed — available_memory_mb is now total_memory_megabyte, with the docstring clarified ("in megabytes (MB)"). Everything else unchanged from the reviewed tip. APPROVE stands.

setting at least one of these parameters to prevent out-of-memory failures.

On single-host deployments (for example, in Docker), if neither parameter
is set, `resources.memory_mb_max` is set to min(host memory size, container memory 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.

I don't think that this is a good place for a math formula.

Suggested change
is set, `resources.memory_mb_max` is set to min(host memory size, container memory limit).
is set, `resources.memory_mb_max` is set to the host memory size or the container memory limit, whichever is smaller.

@mihaibudiu
mihaibudiu force-pushed the issue5921 branch 2 times, most recently from 11dd031 to ce1e33d Compare July 15, 2026 21:23
@mihaibudiu
mihaibudiu enabled auto-merge July 15, 2026 21:23
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@snkas

snkas commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

CI failed with:

FAILED tests/workloads/test_now.py::TestNow::test_now - AssertionError: Validation failed for view v
[2026-07-16 02:47:14] {"error": "Resources exhausted: Failed to allocate additional 39.0 MB for HashJoinInput[6] with 1014.0 MB already allocated for this reservation - 10.6 MB remain available for the total pool"}

@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@mihaibudiu

Copy link
Copy Markdown
Contributor Author

This has failed twice with the same error: looks like datafusion is running our of memory.

@mihaibudiu
mihaibudiu enabled auto-merge July 16, 2026 17:31
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@ryzhyk
ryzhyk force-pushed the issue5921 branch 2 times, most recently from cae8371 to 4d62f1c Compare July 18, 2026 16:36
@ryzhyk
ryzhyk enabled auto-merge July 18, 2026 16:43
@ryzhyk
ryzhyk added this pull request to the merge queue Jul 18, 2026
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 20, 2026
@mihaibudiu
mihaibudiu enabled auto-merge July 20, 2026 16:31
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 20, 2026
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 21, 2026
@mihaibudiu
mihaibudiu enabled auto-merge July 21, 2026 00:59
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 21, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 21, 2026

@mythical-fred mythical-fred left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sensible default and the wiring looks right — I like that the per-host case in local_runner.rs splits the machine share by n_hosts and that pipeline-process fallback in parse_config runs before the logger, using eprintln!.

A few things worth a look before you merge:

1. available_memory_mb() semantics don't match the docstring / user doc.
The docstring says "min of the host's available memory and the cgroup budget", and docs/operations/memory.md says "min(host memory size, container memory limit)". The implementation, however, uses system.available_memory() (currently-free host memory) and limits.free_memory (currently-free within the cgroup) — those are point-in-time free amounts, not the host size or the cgroup limit. In a container that started with the pipeline as the first process the two are close, but in a co-tenant pod or a busy host they can diverge quite a bit and produce non-deterministic startup limits across restarts.

If you want a stable ceiling that survives across restarts, prefer total_memory for both:

let mut available = system.total_memory();
if let Some(limits) = system.cgroup_limits() {
    available = available.min(limits.total_memory);
}

Otherwise, please update the docstring and memory.md to say "currently available" rather than "size / limit", so operators know the default can drift restart-to-restart.

2. sysinfo MB vs MiB. sysinfo returns bytes; you divide by 1_000_000 (decimal MB). The max_rss_mb / memory_mb_max fields have historically been treated as MB throughout the config, so this is internally consistent — just flagging in case anyone thought it was MiB.

3. Cosmetic: System::new_with_specifics(RefreshKind::new().with_memory()) avoids allocating the full snapshot, but the cost here is negligible.

4. running_in_kubernetes() doc. The comment references pipeline-manager rejecting user-supplied KUBERNETES_* variables — worth linking to that validation code (or referencing the type/function) so future readers can confirm the env-var isn't user-forgeable.

5. Test coverage. reports_nonzero_memory is Linux-only and doesn't exercise the cgroup path or the mb == 0 fallback. A unit test that injects a mock available/limit pair through a thin seam would guard the min-selection logic. Not blocking.

Not a blocker overall; the heuristic is fine for "no config supplied" mode. Approving on the assumption that (1) is either a docstring fix or a total_memory swap.

@mythical-fred

Copy link
Copy Markdown

Apologies — the review I just submitted (#pullrequestreview-4741092000) is a stale payload from my July 8 pass. Please ignore it; those points were already addressed and re-approved on 2026-07-09.

The actual finding on the newest commit 94352734:

python/tests/workloads/test_now.py line ~106 uses memory_memory_max=1024, which is not a valid Resources kwarg. It should be memory_mb_max=1024. As written, the test will raise TypeError: __init__() got an unexpected keyword argument 'memory_memory_max' before it even starts the pipeline. The stray extra _memory was almost certainly a slip while typing.

Otherwise still LGTM.

mihaibudiu and others added 3 commits July 21, 2026 09:52
… are not specified

Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
`Balancer::set_hint` unwrapped the `stream_to_cluster` lookup, so a hint
targeting a stream that is not an input to an adaptively balanced join
panicked and killed the worker thread, crashing the pipeline.

This could happen when adaptive joins were disabled altogether or the hint
applied to a stream that wasn't part of an adaptive join (not sure if the
compiler can ever generate such a hint).

We now cleanly return an error instead of panicking.

Signed-off-by: Leonid Ryzhyk <ryzhyk@gmail.com>
The compiler unconditionally generated balancing hints whether adaptive joins
were enabled or not, which could cause a pipeline running without adaptive
joins to fail.

This commit modifies the generated code to ignore hints with a warning when
adaptive joins are disabled in dev tweaks.

Signed-off-by: Leonid Ryzhyk <ryzhyk@gmail.com>
@mihaibudiu
mihaibudiu enabled auto-merge July 21, 2026 16:53
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 21, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 21, 2026
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 21, 2026

@mythical-fred mythical-fred left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-review at 8c8e8d3a — APPROVE.

Two things landed since my last comment:

  1. The memory_memory_max=1024 typo in python/tests/workloads/test_now.py is fixed to memory_mb_max=1024. That was the real issue on the last commit.
  2. Two small stacked commits: 5d17958e (Balancer::set_hint returns NotRegisteredWithBalancer instead of panicking on unregistered streams) and 8c8e8d3a (compiler wraps the balancer-hint block in if circuit.adaptive_joins_enabled() { ... }, with DBSPHandle::adaptive_joins_enabled() / Runtime::adaptive_joins() exposing the dev-tweaks flag and a warning when hints are skipped). Both belt-and-suspenders against the panic path — I already looked at the same pattern in my 4d62f1c1 review and it's still fine.

Apologies again for the stale-payload mishap on review 4741092000; the actual finding was the typo which is now addressed.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 22, 2026
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 22, 2026
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 22, 2026
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.

[manager] Query o/s for memory resources

5 participants