fix(content): replace unbounded query loop with LIMIT-1 DB call in validateRelationships() (#35489)#35491
fix(content): replace unbounded query loop with LIMIT-1 DB call in validateRelationships() (#35489)#35491fabrizzio-dotCMS wants to merge 5 commits intomainfrom
Conversation
…uery in validateRelationships() (#35489) Two hotspots in ESContentletAPIImpl.validateRelationships() caused N×M query explosion (500 related identifiers × 40 languages = 20 000+ SQL queries per save): 1. The required-relationship existence check called getRelatedContent(), which routes through RelationshipCache and fires one query per identifier per language. Replaced with FactoryLocator.getRelationshipFactory().dbRelatedContent(…, 1, 0) — a single LIMIT-1 query on tree + contentlet_version_info that stops at first hit. 2. The ONE_TO_MANY cardinality check inside the per-child loop called getRelatedContent() unconditionally for every cardinality (including MANY_TO_MANY), triggering the same cache-miss explosion. Guarded the DB call behind an if (ONE_TO_MANY) check and replaced it with the same dbRelatedContent(…, 1, 0) call. Refs: #35489 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @fabrizzio-dotCMS's task in 2m 23s —— View job PR Review
Overall: The performance fix is sound and the logic is preserved correctly. A few things worth a second look: 1.
|
…tent migration - Remove DotSecurityException from catch — dbRelatedContent() uses systemUser internally and never throws it - Replace size() > 0 with isEmpty() for idiomatic Java Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem
Re-saving a contentlet with required many-to-many relationships took up to 10 minutes on environments with ~500 related records and ~40 configured languages. Profiling showed 99% of CPU time in
ESContentletAPIImpl.validateRelationships().Two hotspots caused an N×M SQL query explosion (500 identifiers × 40 languages = 20 000+ queries per save):
Required-relationship existence check (the
!foundInRelationshipspath) calledgetRelatedContent(), which routes throughRelationshipCacheand fires onecontentlet_version_infoSELECT per identifier per language — just to answer a boolean "does anything exist?".ONE_TO_MANY cardinality check inside the per-child loop also called
getRelatedContent()unconditionally for every cardinality, including MANY_TO_MANY, triggering the same cache-miss explosion even when the result was never needed.Fix
Both calls are replaced with
FactoryLocator.getRelationshipFactory().dbRelatedContent(relationship, contentlet, hasParent, false, null, 1, 0), which issues a singleLIMIT 1query directly againsttree + contentlet_version_infoand stops at the first matching row. The cardinality check is also guarded behindif (ONE_TO_MANY)so it is skipped entirely for other cardinalities.This mirrors the approach from PR #35407 (which fixed a similar bottleneck in
RelationshipFactoryImplfor an older branch) and adapts it to the refactoredvalidateRelationships()inmain.Changes
dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.javagetRelatedContent()in required-relationship existence check withdbRelatedContent(…, 1, 0)if (ONE_TO_MANY)guard and replacedgetRelatedContent()withdbRelatedContent(…, 1, 0)Acceptance Criteria
Test
References
🤖 Generated with Claude Code