Skip to content

Save content with related content performance improvments#35773

Draft
freddyDOTCMS wants to merge 6 commits into
mainfrom
save-content-with-related-content-performance-improvments
Draft

Save content with related content performance improvments#35773
freddyDOTCMS wants to merge 6 commits into
mainfrom
save-content-with-related-content-performance-improvments

Conversation

@freddyDOTCMS
Copy link
Copy Markdown
Member

@freddyDOTCMS freddyDOTCMS commented May 20, 2026

Summary

Performance degradation when saving content with large many-to-many relationship graphs (reported in support ticket #36004). Saving a single Promotion or Card content type with hundreds/thousands of related items was generating tens of thousands of JDBC queries per save — up to 10 minutes in production.

Multiple root causes were identified across the content save pipeline, all related to unbounded getRelatedContent() / getRelatedContentFromIndex() calls that load full hydrated Contentlet objects (including per-language DB queries via filterRelatedContentByLiveAndLanguage()) when only identifiers or counts were needed.

Performance measured locally with 1 contentlet related to 20,000 items:

  • Before fixes: 41 seconds
  • After fixes in this PR: 9 seconds (78% reduction)

Code Fix Summary

Location Problem Fix
ESContentletAPIImpl.validateRelationships() getRelatedContent() called for every child in MANY_TO_MANY Already fixed in PR #35407 — guarded with ONE_TO_MANY check
ESMappingAPIImpl.dependenciesLeftToReindex() getRelatedContent() loaded full contentlets to get old ES state Replaced with searchIndex() returning lightweight identifiers only
ESContentletAPIImpl.relateContent() getRelatedContentFromIndex() loaded all related content just to get a count for tree ordering Replaced with a single COUNT(*) SQL query
ESContentletAPIImpl.addRestrictedContentForLimitedUser() getRelatedContentFromIndex() loaded all related content to find restricted items ⚠️ Temporarily commented out — needs proper fix before merge
ESContentletAPIImpl.deleteRelatedContent() N individual DELETE statements per related item 1 bulk DELETE WHERE parent=? AND relation_type=?
ESContentletAPIImpl.relateContent() insert loop N individual saveTree() calls (DELETE+INSERT each) Single JDBC batch insert via new TreeFactory.insertTrees()

⚠️ Known Incomplete Items

  • addRestrictedContentForLimitedUser() has been temporarily commented out for testing purposes. A proper fix (likely a short-circuit for admin/system users who can read all content) must be implemented before this PR is ready for merge.
  • Additional testing required.

Checklist

  • Tests
  • Translations
  • Security Implications Contemplated (add notes if applicable)
  • Fix addRestrictedContentForLimitedUser() before merge

Related

@github-actions
Copy link
Copy Markdown
Contributor

❌ Issue Linking Required

This PR could not be linked to an issue. All PRs must be linked to an issue for tracking purposes.

How to fix this:

Option 1: Add keyword to PR body (Recommended - auto-removes this comment)
Edit this PR description and add one of these lines:

  • This PR fixes #123 or Fixes: #123

  • This PR closes #123 or Closes: #123

  • This PR resolves #123 or Resolves: #123

  • Other supported keywords: fix, fixed, close, closed, resolve, resolved
    Option 2: Link via GitHub UI (Note: won't clear the failed check)

  1. Go to the PR → Development section (right sidebar)

  2. Click "Link issue" and select an existing issue

  3. Push a new commit or re-run the workflow to clear the failed check
    Option 3: Use branch naming
    Create a new branch with one of these patterns:

  • 123-feature-description (number at start)

  • issue-123-feature-description (issue-number at start)

  • feature-issue-123 (issue-number anywhere)

Why is this required?

Issue linking ensures proper tracking, documentation, and helps maintain project history. It connects your code changes to the problem they solve.---

This comment was automatically generated by the issue linking workflow

@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label May 20, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 20, 2026

Claude finished @freddyDOTCMS's task in 1m 52s —— View job


Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Fetch full PR diff
  • Analyze diff against each category
  • Apply appropriate label

Result: ✅ Safe to Rollback — label AI: Safe To Rollback applied.

Analysis summary

The PR contains pure Java performance refactors in three files:

  • ESContentletAPIImpl.java — replaces per-row TreeFactory.deleteTreesByParentAndChildAndRelationType calls with a single bulk delete; replaces getRelatedContentFromIndex size lookup with a SELECT COUNT(*) on tree; batches inserts via the new insertTrees. Also comments out a sameParentAndChild branch.
  • ESMappingAPIImpl.java — swaps contentletAPI.getRelatedContent(...) for contentletAPI.searchIndex(...) when collecting oldRelatedIds. This changes query strategy, not ES mapping.
  • TreeFactory.java — adds three new static helpers: deleteTreesByParentAndRelationType, deleteTreesByChildAndRelationType, insertTrees. Purely additive.

No category from docs/core/ROLLBACK_UNSAFE_CATEGORIES.md matches:

Category Match Note
C-1 Structural data model No Same tree table, same columns
C-2 ES mapping change No No edits to ESMappingConstants, getESMappingType(), or putMapping call sites
C-3 JSON model version bump No CURRENT_MODEL_VERSION untouched
C-4 DROP TABLE/COLUMN No No DDL
H-1…H-7 No No migrations, renames, PK changes, new field types, storage provider changes, dropped procs, or NOT NULL adds
M-1 Column type change No No DDL
M-2 Push publishing bundle No No bundler changes
M-3 REST/GraphQL contract No No public endpoint signatures changed
M-4 OSGi public API No TreeFactory additions are additive; existing deleteTreesByParentAndChildAndRelationType is still present
· Branch

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

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant