Skip to content

fix(lcm): bound error collections embedded in exception messages - #2089

Merged
milandufek merged 2 commits into
masterfrom
md-lcm-heap-space-fix
Jul 30, 2026
Merged

fix(lcm): bound error collections embedded in exception messages#2089
milandufek merged 2 commits into
masterfrom
md-lcm-heap-space-fix

Conversation

@milandufek

@milandufek milandufek commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Why

LcmJavaHeapSpaceError fires ~50×/month on NA3 (GRIF-951): java.lang.OutOfMemoryError: Java heap space in the user_filters brick during GoodData::LCM2::SynchronizeUserFilters, with RubyHash.inspect/RubyArray.inspect frames in the stack trace.

Root cause: UserFilterBuilder.execute_mufs (and the variables path in execute) raise exceptions with the raw error Array/Hash embedded as the message:

fail GoodData::FilterMaqlizationError, errors
fail "Creating MUFs resulted in errors: #{create_errors}"

Stringifying that collection runs inspect over up to one entry per user/value across an entire domain sync — and Ruby re-derives the string on every downstream .message / "#{e}" / JSON.pretty_generate call (4 such call sites via lcm2.rb, run_brick.rb, execution_result_middleware.rb). Measured: 200k error entries → 53.9 MB message per derivation. Container memory bumps can't fix this (NA3 already went 4Gi→5Gi→6Gi with no effect).

What

  • Cap the exception message (and the logged error dump) to the first MAX_ERRORS_IN_MESSAGE = 10 entries plus a total count, at all four raise sites — same pattern already used by synchronize_users.rb. Bounded message: ~3 KB.
  • Test-asserted message prefixes (Creating/Deleting MUFs resulted in errors) are preserved.
  • New regression test: 500 synthetic failed users → message stays bounded and reports count: 500, first 10.

Notes for reviewers

  • FilterMaqlizationError is a bare RuntimeError; the only consumers read .message (both in spec/integration/user_filters_spec.rb), and for ≤10 errors the message still contains the full detail, so those tests are unaffected.
  • Pre-existing, out of scope: the delete-errors fail is currently dead code (since b7adb56 delete_results is nil inside its own defining block). The bounding here is defense-in-depth for when that gets fixed separately.

JIRA: GRIF-951

🤖 Generated with Claude Code

https://claude.ai/code/session_015xTbPyZxxECyx93VgEfW4p

Summary by CodeRabbit

  • Bug Fixes
    • Prevented large error payloads from overwhelming memory when user filter operations fail.
    • Updated failure error messages to include the total failure count plus a limited, concise sampled subset.
    • Applied bounded, sampled error reporting consistently across filter maqlification, creation, deletion, and variable processing failures.
  • Tests
    • Added coverage ensuring large batches of failures produce bounded error messages (including sampling behavior).

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7dbf4ac-aaa5-418b-b847-6ff3b5040653

📥 Commits

Reviewing files that changed from the base of the PR and between fe82ba6 and 68a1723.

📒 Files selected for processing (2)
  • lib/gooddata/models/user_filters/user_filter_builder.rb
  • spec/unit/models/user_filters/user_filter_builder_spec.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • spec/unit/models/user_filters/user_filter_builder_spec.rb
  • lib/gooddata/models/user_filters/user_filter_builder.rb

📝 Walkthrough

Walkthrough

User filter and variable execution errors now cap sampled error details in logs and exceptions while retaining total failure counts. Tests cover MUF creation failures containing 500 errors.

Changes

Bounded error reporting

Layer / File(s) Summary
Error sampling contract
lib/gooddata/models/user_filters/user_filter_builder.rb
Adds limits for error count and entry length, plus a helper that produces bounded samples.
Execution error paths
lib/gooddata/models/user_filters/user_filter_builder.rb, spec/unit/models/user_filters/user_filter_builder_spec.rb
Updates MUF maqlization, creation, deletion, and variable maqlization failures to log and raise count-based messages with bounded samples; adds coverage for 500 creation errors.

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

Poem

I’m a rabbit with errors tucked neat,
Ten little samples, trimmed and petite.
Five hundred thumps no longer overflow,
Counts tell the tale while the details stay low.
Heap-safe hops through the filter night—
Bounded messages, crisp and light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: bounding error collections in exception messages.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

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

Actionable comments posted: 3

🤖 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 `@lib/gooddata/models/user_filters/user_filter_builder.rb`:
- Around line 589-592: Move the delete_errors failure check out of the
each_slice(...).flat_map block in the user-filter deletion flow, so it runs only
after delete_results has been assigned. Then inspect the accumulated
delete_results (or each batch’s results) and preserve the existing logging and
failure message when errors are present.
- Around line 496-498: Bound serialized error samples by adding one shared
formatter that truncates rendered entries or enforces a total byte budget, then
reuse its output for both logs and exception messages at
lib/gooddata/models/user_filters/user_filter_builder.rb#L496-L498 (MUF
maqlization), `#L553-L555` (MUF creation), `#L590-L592` (MUF deletion), and
`#L662-L664` (variable maqlization). Preserve the existing error counts while
ensuring each reported sample remains within the configured bound.

In `@spec/unit/models/user_filters/user_filter_builder_spec.rb`:
- Around line 213-216: Strengthen the error-message assertions in the `raises an
error with a message bounded to the first few errors` example by verifying it
includes `user0@example.com` and `user9@example.com`, while excluding
`user10@example.com`; retain the existing count, “first 10”, and size
assertions.
🪄 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: CHILL

Plan: Pro

Run ID: cf657ebd-afc8-4c7d-b2de-e4c7d5a8296b

📥 Commits

Reviewing files that changed from the base of the PR and between 97a77ca and c9c33f0.

📒 Files selected for processing (2)
  • lib/gooddata/models/user_filters/user_filter_builder.rb
  • spec/unit/models/user_filters/user_filter_builder_spec.rb

Comment thread lib/gooddata/models/user_filters/user_filter_builder.rb Outdated
Comment thread lib/gooddata/models/user_filters/user_filter_builder.rb
Comment thread spec/unit/models/user_filters/user_filter_builder_spec.rb
milandufek added a commit that referenced this pull request Jul 29, 2026
Capping only the entry count leaves the message unbounded when a single
entry embeds a large value list (CI measured 21KB for 10 entries carrying
1KB details duplicated by the user-hash merge). Render samples through a
shared bounded_error_sample helper that truncates each entry to 1000 chars,
and assert sampled entries explicitly in the regression test.

Addresses CodeRabbit review findings 1 and 3 on PR #2089.

JIRA: GRIF-951
Claude-Session: https://claude.ai/code/session_015xTbPyZxxECyx93VgEfW4p

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

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 `@lib/gooddata/models/user_filters/user_filter_builder.rb`:
- Around line 34-35: Update bounded_error_sample to avoid calling e.inspect
before truncation; replace it with a formatter that enforces a hard recursive
and total serialization budget while producing each error entry. Preserve
MAX_ERRORS_IN_MESSAGE and MAX_ERROR_ENTRY_CHARS as the limits for the number of
entries and final per-entry message size.
🪄 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: CHILL

Plan: Pro

Run ID: 20ef3dcd-8fff-47c4-b43f-796cd1ffa99d

📥 Commits

Reviewing files that changed from the base of the PR and between c9c33f0 and fe82ba6.

📒 Files selected for processing (2)
  • lib/gooddata/models/user_filters/user_filter_builder.rb
  • spec/unit/models/user_filters/user_filter_builder_spec.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • spec/unit/models/user_filters/user_filter_builder_spec.rb

Comment thread lib/gooddata/models/user_filters/user_filter_builder.rb
SynchronizeUserFilters (and the variables path) raised exceptions with the
raw error Array/Hash as the message. Stringifying it runs Array#inspect /
Hash#inspect over up to one entry per user/value of a whole domain sync,
and every downstream #message / "#{e}" / JSON.pretty_generate call re-derives
the same giant string (measured 53.9 MB for 200k entries, 4 call sites) --
exhausting the JRuby heap and firing LcmJavaHeapSpaceError (~50x/month on NA3).

Cap the message (and the error log dump) to the first 10 entries plus a total
count, following the existing synchronize_users.rb pattern. Full collections
are no longer embedded in exceptions; bounded message is ~3 KB.

JIRA: GRIF-951
Claude-Session: https://claude.ai/code/session_015xTbPyZxxECyx93VgEfW4p
Capping only the entry count leaves the message unbounded when a single
entry embeds a large value list (CI measured 21KB for 10 entries carrying
1KB details duplicated by the user-hash merge). Render samples through a
shared bounded_error_sample helper that truncates each entry to 1000 chars,
and assert sampled entries explicitly in the regression test.

Addresses CodeRabbit review findings 1 and 3 on PR #2089.

JIRA: GRIF-951
Claude-Session: https://claude.ai/code/session_015xTbPyZxxECyx93VgEfW4p
@milandufek
milandufek force-pushed the md-lcm-heap-space-fix branch from fe82ba6 to 68a1723 Compare July 29, 2026 13:02
@milandufek
milandufek added this pull request to the merge queue Jul 30, 2026
Merged via the queue into master with commit 9171f43 Jul 30, 2026
27 of 28 checks passed
@milandufek
milandufek deleted the md-lcm-heap-space-fix branch July 30, 2026 07:45
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.

2 participants