Skip to content

perf(import): remove redundant work in addDefinition's dependency traversal#8259

Open
lemonmade wants to merge 1 commit into
ardatan:masterfrom
lemonmade:perf/import-faster-addDefinition
Open

perf(import): remove redundant work in addDefinition's dependency traversal#8259
lemonmade wants to merge 1 commit into
ardatan:masterfrom
lemonmade:perf/import-faster-addDefinition

Conversation

@lemonmade

Copy link
Copy Markdown

Problem

When processImport assembles a definition's imported dependencies, the inner addDefinition closure recurses across the entire dependency graph. Two things were recomputed on every recursive call:

  1. const fileDefinitionMap = visitedFiles.get(filePath)filePath is invariant for the lifetime of the visitFile call, so this Map.get (over a long absolute-path key) was repeated across the whole — potentially very large — recursion.
  2. The per-field dependency-name derivation — for every field of every definition added to a set, a fresh Map was built and the field re-visited (visitFieldDefinitionNode / visitInputValueDefinitionNode) to collect dependency names. That result is a pure function of the field node and the file's static dependenciesByDefinitionName, but it was recomputed every time the owning definition was added to any set.

On a large, densely-connected schema these two lines dominate what remains of import time after the print memoization in #8258 (CPU profiling attributed ~85% of the post-#8258 time to addDefinition and its field loop).

Fix

  • Hoist the invariant visitedFiles.get(filePath) lookup out of addDefinition.
  • Memoize the per-field dependency-name list by field-node identity.

Both are pure performance changes — no algorithmic/output change.

Validation

Not addressed here

The deeper cost is structural: each definition's transitive closure is still rebuilt independently (an O(n²) traversal). Reducing that needs a shared closure cache and is more invasive, so I've left it out of this PR to keep it small and obviously-safe. Happy to follow up if there's interest.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5a7277c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@graphql-tools/import Patch
@graphql-tools/graphql-file-loader Patch
@graphql-tools/node-require Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6ab2074-22e4-4796-ae88-026b79b2c12e

📥 Commits

Reviewing files that changed from the base of the PR and between 21382c9 and 5a7277c.

📒 Files selected for processing (2)
  • .changeset/import-closure-traversal.md
  • packages/import/src/index.ts

📝 Walkthrough

Summary by CodeRabbit

  • Performance Improvements
    • Improved import processing speed, especially for large schemas and deeply nested dependencies.
    • Reduced repeated traversal and dependency derivation work, resulting in faster execution with no change to generated output.
    • Import tests still pass, and real-world schema results remain byte-identical. Reported runtime improved from ~17s to ~13s.

Walkthrough

@graphql-tools/import now reuses cached definition lookups and memoized field dependency derivation during addDefinition traversal. A Changeset entry documents the performance-only change.

Changes

addDefinition traversal optimization

Layer / File(s) Summary
Hoist and memoize in addDefinition
packages/import/src/index.ts
Updates visitFile documentation, hoists the per-file definition map lookup into a reusable local, and introduces a cache for field and input-value dependency maps keyed by AST node identity.
Reuse cached dependency maps
packages/import/src/index.ts
Checks the field dependency cache before recomputing dependency names, then stores computed results for later reuse during traversal.
Document the optimization
.changeset/import-closure-traversal.md
Adds a Changeset note describing the traversal memoization change and stating that output is unchanged.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

🐇 I hopped through the graph, then cached the trail,
Reused the names so the loops could fail.
No extra churn, just a faster race—
A tidy little memoized space.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: a performance optimization in addDefinition's dependency traversal for import.
Description check ✅ Passed The description accurately summarizes the performance-only import changes and validation in the diff.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lemonmade lemonmade force-pushed the perf/import-faster-addDefinition branch from 9d95a37 to c4414d1 Compare July 2, 2026 19:56
@lemonmade

Copy link
Copy Markdown
Author

Added JSDoc for visitFile and the addDefinition helper to satisfy the docstring-coverage check (the previous revision touched these functions, which were undocumented). No logic change; the 80 import tests still pass.

@lemonmade

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…inition

addDefinition recurses across the whole dependency graph. Two things were
recomputed on every call: the invariant visitedFiles.get(filePath), and each
field's dependency-name set (a pure function of the field node). Hoist the
former and memoize the latter by field identity.

Pure performance change — output is byte-identical (80 import tests pass; a
real ~12k-line schema produces identical results). On top of the print()
memoization this took a real codegen pass from ~17s to ~13s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lemonmade lemonmade force-pushed the perf/import-faster-addDefinition branch from c4414d1 to 5a7277c Compare July 2, 2026 20:14
@lemonmade

Copy link
Copy Markdown
Author

@coderabbitai The Docstring Coverage check reports 0.00% even though the two functions this PR modifies — visitFile and its inner addDefinition helper — now both have JSDoc, and the latest revision adds no new functions. Which specific functions is the check counting as missing docstrings? Want to make sure I'm addressing the right ones.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 8259 --repo ardatan/graphql-tools --json statusCheckRollup --jq '.statusCheckRollup[] | {name: .name, workflowName: .workflowName, conclusion: .conclusion, detailsUrl: .detailsUrl}'

Length of output: 7225


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "docstring" -i . --hidden -g '!node_modules' -g '!.git' | head -100

Length of output: 567


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 8259 --repo ardatan/graphql-tools --json comments --jq '.comments[] | select(.body | test("(?i)docstring")) | {author: .author.login, body: .body}' | head -c 4000
echo "---REVIEWS---"
gh api repos/ardatan/graphql-tools/pulls/8259/reviews --jq '.[] | select(.body | test("(?i)docstring")) | {user: .user.login, body: .body}' | head -c 4000

Length of output: 4657


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 8259 --repo ardatan/graphql-tools --json comments --jq '.comments[] | select(.body | test("(?i)docstring")) | .body' > /tmp/cr_comment.txt
wc -l /tmp/cr_comment.txt
sed -n '1,400p' /tmp/cr_comment.txt

Length of output: 7854


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr diff 8259 --repo ardatan/graphql-tools > /tmp/pr.diff
wc -l /tmp/pr.diff
grep -n "^+function\|^+export function\|^+const .*= (\|^+  function\|=>" /tmp/pr.diff | head -80

Length of output: 457


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n /tmp/pr.diff

Length of output: 6336


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/ardatan/graphql-tools/pulls/8259/commits --jq '.[] | {sha: .sha[0:12], date: .commit.author.date, message: .commit.message}'
echo "---"
gh pr view 8259 --repo ardatan/graphql-tools --json comments --jq '.comments[] | {author: .author.login, createdAt: .createdAt, snippet: (.body[0:80])}'

Length of output: 2072


@lemonmade Good news: your fix is correct — it looks like the 0.00% you're seeing is a stale result, not a check that's still finding undocumented functions.

Here's what happened, based on the PR history:

  • The pre-merge checks table (and its "Docstring Coverage is 0.00%" line) was generated right after the commit was first pushed (5a7277cc266f...), before the JSDoc was added to visitFile and addDefinition.
  • When you amended that commit to add the docstrings and force-pushed, the commit SHA stayed associated with the same review range, and packages/import/src/index.ts was flagged internally as "similar to previous changes" — so the incremental @coderabbitai review you ran afterward re-checked for new review comments but didn't recompute the pre-merge checks table (including Docstring Coverage).
  • Looking at the current diff on the PR, both visitFile and the addDefinition closure do have JSDoc now, so there's nothing else in this PR that should be counted as missing a docstring.

To get an up-to-date Docstring Coverage number, please ask for a full review rather than the incremental one — that forces recomputation of the pre-merge checks table. I've triggered that now.

✅ Action performed

Full review finished.

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.

1 participant