GraphQL N+1 Problem

GraphQL N+1 문제

Resolving a related field separately for every row multiplies database calls.

···
html
<div class="viz"><h3>RELATED FIELD LOOKUP</h3><div class="row"><div class="box">row 1<br>row 2<br>row 3</div><span class="arrow">→</span><div id="queries" class="box code">Q1 Q2 Q3</div></div><div class="muted" id="count">1 list + 3 related = 4 DB calls</div></div>
css
.viz{width:94%;height:88%;max-width:960px;padding:clamp(10px,2vmin,18px);border:1px solid var(--line);border-radius:14px;background:var(--surface);font:600 clamp(12px,1.35vw,16px)/1.35 var(--font-sans,system-ui,sans-serif);display:flex;flex-direction:column;gap:clamp(7px,1.8vmin,13px);overflow:hidden}.viz h3{margin:0;color:var(--accent);font:700 clamp(12px,1.35vw,16px)/1.2 var(--font-sans,system-ui,sans-serif);letter-spacing:.04em}.viz .row{display:flex;align-items:center;justify-content:center;gap:clamp(5px,1.4vmin,12px);flex:1;min-height:0}.viz .box{padding:clamp(5px,1.5vmin,12px);border:1px solid var(--line);border-radius:9px;background:var(--bg);text-align:center}.viz .muted{color:var(--muted)}.viz .accent{color:var(--accent)}.viz .code{font-family:ui-monospace,monospace}.viz .pill{padding:3px 7px;border:1px solid var(--line);border-radius:99px;white-space:nowrap}.viz .active{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,var(--surface))}.viz .arrow{color:var(--accent);font:700 18px ui-monospace,monospace}#queries{min-width:42%;color:var(--accent-3)}#queries.batched{color:var(--accent);border-color:var(--accent)}
js
let batch=false;function draw(){batch=!batch;const q=document.getElementById('queries');q.textContent=batch?'batch([1,2,3])':'Q1 · Q2 · Q3';q.classList.toggle('batched',batch);document.getElementById('count').textContent=batch?'1 list + 1 batch = 2 DB calls':'1 list + 3 related = 4 DB calls'}draw();setInterval(draw,1600)

A GraphQL list returns many objects, and a resolver for a related field may query once for each object. One list query plus N related queries gives N+1 calls. The screen may look correct while latency grows with list size.

The demo compares separate related lookups for three rows with a request-scoped batch. A batch loader collects keys, queries together, and maps results back to each key.

When to use

Check for it when database calls grow with the number of rows in a nested GraphQL list.

Open as page ↗