Skip to content

feat(core): refacto graph query for perf improvement - #165

Merged
brandPittCode merged 26 commits into
mainfrom
feat/improve-graph-perf
Oct 13, 2026
Merged

brandPittCode merged 26 commits into
mainfrom
feat/improve-graph-perf

Conversation

@brandPittCode

@brandPittCode brandPittCode commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

PR Description

What this PR Provides

This PR adds several optimizations that enable a low response time for the graph and get entity endpoint

Refactored Graph UUID Discovery via Recursive CTE

  • 100% PostgreSQL CTE Compliant: Changed the query to have exactly one UNION ALL connecting the entire anchor set to the recursive set, eliminating the recursive term ordering conflict.
  • Optimal Index Utilization: CROSS JOIN LATERAL forces PostgreSQL to perform direct, index-driven lookups (er.entity_id = eg.id and rte.target_entity_uuid = eg.id) for each node at the current graph frontier.
Execution Phase Before Optimization After Optimization Gain
Recursive CTE Query 19 ms 3 ms 84% faster
Total Request Duration 42 ms 18 ms 57% faster

Drop Defensive Pre-Validation for Entity Template Existence

Skipped the initial template existence pre-check (SELECT et FROM entity_template...) to query the entity table directly. If the template or entity does not exist, the domain exception is thrown immediately without wasting an initial SQL roundtrip.


Move to EntitySummary for Graph Root Entity & Eliminate 2 Database Calls

Refactored the root entity lookup to fetch only essential entity metadata using the lightweight EntitySummary domain model. This eliminates 2 redundant SQL roundtrip calls previously used to fetch properties and relations.


Bypass JPA Entity Mapping with PostgreSQL Native JSONB

Added EntityGraphJsonProjection subqueries to aggregate nested array structures directly inside PostgreSQL memory before network transmission, preventing Cartesian explosion in exactly 1 SQL statement. Jackson parses the JSON directly into the domain model, completely skipping Hibernate entity lifecycle overhead and lazy-loading triggers.


Optimize Jackson Deserialization in PostgresEntityGraphAdapter

Replaced the instantiation of TypeReference objects inside per-entity loops with pre-compiled ObjectReader static instances created during Spring bean initialization. Reusing thread-safe ObjectReader instances across concurrent requests eliminates Jackson reflection setup overhead during array parsing.


Replace Java Streams with Direct for Loops in Hot Mapping Code

Refactored hot mapping paths in EntityDtoOutFromEntityNodeMapper (which previously spent 66 ms assembling DTO trees) from functional streams (.stream().map(...).toList()) to standard for loops with pre-sized collections. This eliminates thousands of temporary iterator, stream, and lambda allocations during 1,000+ entity graph traversals, directly protecting Cloud Run containers from garbage collection CPU pauses.

Added a new optional query parameter, includeEntityRootProperties to the GraphService

This optimizes response time by changing defaults around property inclusion: includeProperties is now false by default, while includeEntityRootProperties is true by default. This keeps the root entity informative for common API consumers, but avoids loading/mapping properties across related graph nodes unless explicitly requested. In practice, this significantly reduces query/mapping overhead and payload size for graph traversals, improving average latency and tail percentiles (p90/p95) while preserving backward-friendly behavior at the root level.

Optimization results

Baseline:

Load Test Comparison: Baseline vs. Optimized

Infrastructure: 1 vCPU Cloud 2GB RAM
PostgreSQL instance in Aiven: 1 vCPU 4GB RAM

URL: api/v1/entities/supportgroup/CE-IDP?relations_depth=1

Metric Before Optimization (baseline-depth1-c1) After Optimization (opt--depth1-c1) Gain / Reduction
Request Volume 5,000 5,000 —
Success Rate (200 OK) 100% (5,000/5,000) 100% (5,000/5,000) Identical
Average (Mean) 223.21 ms 66.06 ms -70.4%
Median (p50) 197.62 ms 62.35 ms -68.5%
75th Percentile (p75) 233.87 ms 67.67 ms -71.1%
90th Percentile (p90) 285.88 ms 77.24 ms -73.0%
95th Percentile (p95) 400.23 ms 87.28 ms -78.2%
99th Percentile (p99) 509.61 ms 119.52 ms -76.5%
Minimum 163.18 ms 51.71 ms -68.3%
Maximum 788.71 ms 449.96 ms -42.9%
Standard Deviation (Std Dev) 70.77 ms 14.70 ms -79.2%

Load Test Comparison (Concurrency = 50, 10 calls)

Metric Non-Optimized (baseline-product-depth4-c50) Optimized (opt-product-depth4-c50) Gain / Reduction
Request Volume 10 10 —
Success Rate 100% (10/10) 100% (10/10) Identical
Average (Mean) 3,860.21 ms 1,380.58 ms -64.2%
Median (p50) 4,494.69 ms 1,574.24 ms -65.0%
75th Percentile (p75) 4,842.10 ms 1,711.51 ms -64.7%
90th Percentile (p90) 4,962.78 ms 1,774.91 ms -64.2%
95th Percentile (p95) 4,965.55 ms 1,780.28 ms -64.2%
99th Percentile (p99) 4,967.76 ms 1,784.59 ms -64.1%
Minimum 515.77 ms 178.46 ms -65.4%
Maximum 4,968.31 ms 1,785.66 ms -64.1%
Standard Deviation (Std Dev) 1,564.85 ms 560.03 ms -64.2%

Large graph results

On a single 1 vCPU Cloud Run container, processing, parsing, and mapping 1,589 full entities in memory dropped from ~300–600 ms down to just 35 ms total.

Step-by-Step Breakdown of the 993 ms Server Request

Step Component Duration What Is Happening
Auth Check JitProvisioningFilter 15 ms Pre-controller service account validation
Root Lookup PostgresEntityAdapter 5 ms Index seek for root entity UUID
CTE Discovery PostgresEntityGraphAdapter 374 ms PostgreSQL 4-level deep recursive CTE graph traversal
JSONB Fetch PostgresEntityGraphAdapter 145 ms PostgreSQL native jsonb_agg query for 1,589 entities
Projection Mapping PostgresEntityGraphAdapter 33 ms Jackson ObjectReader parses 1,589 entity payloads in JVM RAM
Template Lookup EntityDtoOutFromEntityNodeMapper 46 ms DB Query 4: Uncached schema lookup for entity_template
DTO Mapping EntityDtoOutFromEntityNodeMapper 2 ms Pre-sized HashMap & imperative for loop graph assembly
HTTP Serialization EntityController 373 ms Spring MVC encoding & writing the 140.48 KiB JSON string to the socket
TOTAL SERVER EntityController 993 ms Complete Spring Boot Server Time (< 1.0s)

Fixes

Review

The reviewer must double-check these points:

  • The reviewer has tested the feature
  • The reviewer has reviewed the implementation of the feature
  • The documentation has been updated
  • The feature implementation respects the Technical Doc / ADR previously produced
  • The Pull Request title has a ! after the type/scope to identify the breaking
    change in the release note and ensure we will release a major version.

How to test

Please refer (copy/paste) the test section from the User Story. This should include

  • Run unit tests

Breaking changes (if any)

  • N/A

Context of the Breaking Change

For example: we redefined the component types list in the DPAC referential

Result of the Breaking Change

For example: your component of type xxx will migrate to the type yyy

@github-code-quality

github-code-quality Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Code Coverage Overview

Languages: Java

Java / code-coverage/jacoco

The overall line coverage in commit 3693dd9 in the feat/improve-graph-p... branch remains at 91%, unchanged from commit fcfb416 in the main branch.

Show a line coverage summary of the most impacted files.
File main fcfb416 feat/improve-graph-p... 3693dd9 +/-
com/decathlon/i...oOutMapper.java 97% 95% -2%
com/decathlon/i...raphHelper.java 95% 95% 0%
com/decathlon/i...gProcessor.java 81% 81% 0%
com/decathlon/i...NodeMapper.java 92% 92% 0%
com/decathlon/i...oOutMapper.java 87% 87% 0%
com/decathlon/i...figuration.java 100% 100% 0%
com/decathlon/i...yValidator.java 100% 100% 0%
com/decathlon/i...aphService.java 88% 90% +2%
com/decathlon/i...aphAdapter.java 73% 89% +16%
com/decathlon/i...eryOptions.java 0% 100% +100%

Updated October 13, 2026 12:36 UTC

@brandPittCode
brandPittCode requested a balanced review from Copilot October 7, 2026 15:50

Copilot AI 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.

🟡 Changes recommended

The implementation ignores property-loading controls, permits recursive path multiplication, and changes established validation and ordering behavior.

6 open findings
What changed in this PR

Optimizes entity graph retrieval through UUID summaries, recursive SQL traversal, and native JSONB projection mapping.

Changes:

  • Reworks graph traversal and entity loading for fewer database round trips.
  • Adds UUIDs to entity summaries and uses summaries for root lookup.
  • Reduces mapping allocations and removes unused logging.
File Description
RelationValidationServiceTest.java Updates summary fixtures with UUIDs.
EntityServiceTest.java Updates summary construction.
EntityGraphServiceTest.java Adapts graph tests to summary-based root lookup.
application.yml Adds whitespace.
application-local.yml Enables local SQL logging.
JpaEntityRepository.java Adds UUID projections, optimized CTE, and JSONB query.
PostgresEntityGraphAdapter.java Maps native JSON projections into domain entities.
EntityGraphJsonProjection.java Defines the native graph projection.
HmacSha256SecurityValidator.java Removes unused logging support.
DecodingProcessor.java Removes unused logging support.
EntityDtoOutMapper.java Removes unused logging support.
EntityDtoOutFromEntityNodeMapper.java Removes unused logging support.
EntityGraphFlatDtoOutMapper.java Uses imperative property mapping.
EntityGraphController.java Removes redundant whitespace.
SecurityConfiguration.java Removes unused logging support.
EntityGraphService.java Uses lightweight summaries for graph roots.
EntitySummary.java Adds the entity UUID.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI 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.

@brandPittCode brandPittCode changed the title feat(): refacto graph query for per improv feat(graph): refacto graph query for perf improvement Oct 13, 2026
@brandPittCode brandPittCode changed the title feat(graph): refacto graph query for perf improvement feat(core): refacto graph query for perf improvement Oct 13, 2026
evebrnd
evebrnd previously approved these changes Oct 13, 2026
@sonarqubecloud

Copy link
Copy Markdown

@brandPittCode
brandPittCode requested a review from evebrnd October 13, 2026 13:08
@brandPittCode
brandPittCode merged commit 75392db into main Oct 13, 2026
26 of 27 checks passed
@brandPittCode
brandPittCode deleted the feat/improve-graph-perf branch October 13, 2026 13:19
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