Decathlon / Decathlon/internal-developer-platform

[Perf][P0] Make relation inclusion opt-in on entity list endpoint (reverse #90 regression)

Open
#130 0 comments 0 reactions 0 assignees View on GitHub
performance
Dominant language
Java
Stars
9
Forks
0
Avg merge
3d 1h
Merged PRs (30d)
5

Description

## Context

Follow-up from `idp-v2-vs-app-referential-analysis.md` (performance benchmark of IDP v2 vs the app-referential POC, re-run 2026-08-17 after 15 commits landed on `main`).

**Correction (per PR review):** the list endpoint already returned first-level relations before #90, split into `relations` (outbound) / `relations_as_target` (inbound), populated via a dedicated paginated `getRelationsAsTarget` lookup. `feat(api)!: update entity output for having relations grouped (#90)` didn't newly introduce relations to `GET /api/v1/entities/{templateIdentifier}` responses — it replaced that mechanism with the shared `entityGraphService.getEntityGraphPageByTemplate(depth=1)` graph-traversal service (the same one used by the dedicated graph endpoint), unifying the output into a single `relations` map. Functionally equivalent at depth 1, but with materially worse performance characteristics for the list use case — that's the actual regression measured below.

## Evidence (measured against a live seeded dataset — 3,972 entities / 6,501 relationships, mirroring app-referential's real data)

| Query | Before #90 | After #90 | Delta |
|---|---|---|---|
| `subdomain=ORDER` (18 of 953 products) | ~28–30 ms | ~38–52 ms | **~30–70% slower** |
| `type=DECATHLON_API` (1,042 of 3,972 components) | ~77–90 ms | ~120–207 ms | **~1.5–2.3x slower** |
| Graph traversal depth 1→3 | flat ~27–37 ms regardless of depth | now scales with depth: ~29→45→48-61 ms | new scaling problem, previously absent |

Payload size for a single entity + 145 relations: **29,555 B → 39,323 B (+33%)**, worsening the token-cost gap vs app-referential (14.7 KB for the same data).

Root cause: list/search/graph responses always attach nested `hasPart`/`partOf` relation arrays per item via the shared graph-traversal fetch path, so cost scales with `result-set size × relations per item`, not just page size.

## Fix

Make relation inclusion **opt-in** on the list endpoint via an `include_relations` query parameter (default `false`). Default path skips relation-graph resolution entirely and returns properties only; callers that need relations per item opt in explicitly. Get-by-id and other single-entity paths are unaffected.

## Status

Implemented and validated against the live seeded dataset — see the PR fixing this issue for full before/after numbers (latency and payload size, per-item relation-overhead breakdown).

## Priority

P0 — this is a **regression**, not a pre-existing gap, and is the highest-leverage/lowest-risk fix (highest ROI, smallest surface area).

## Related

Part of a wider prioritized performance plan derived from the analysis (see companion issues for P1–P4: ORM/REST overhead, payload/token footprint, EAV I/O tax, minor local-DX/CSRF fixes).

Contributor guide

Open the contributing guide

Research direction

Start at the entity list endpoint and trace its use of entityGraphService.getEntityGraphPageByTemplate(depth=1), comparing that path with the documented pre-#90 behavior. Verify that include_relations defaults to false, relation lookup is skipped by default, opt-in responses still include relations, and get-by-id paths remain unaffected; use the seeded-dataset latency and payload measurements as validation.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend, performance
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.