Skip to content

Repeated fragment spreading causes perf issues with near-operation-files + typescript-operations #752

@vhfmag

Description

@vhfmag

Which packages are impacted by your issue?

@graphql-codegen/near-operation-file-preset

Describe the bug

While investigating an issue with graphql codegen taking too long in the pipeline of one of my company's projects, I came across a performance issue caused when spreading a deeply nested fragment multiple times in the same file. The issue happens when near-operation-file and typescript-operations are used together, and is ultimately caused due to the first supplying repeated input to the latter.

Here's how it works:

  1. near-operation-file resolves document imports here and then generates a list of externalFragments for each given file here. That list includes all nested fragments, and if a fragment is spread N times in the same file, it and of its nested fragments will be included N times. externalFragments is then passed as a config to plugins here.
  2. typescript-operations includes externalFragments into allFragments here and then passes it to TypeScriptDocumentsVisitor here, which passes it to SelectionSetToObject here, which passes it to getFieldNames here.
  3. getFieldNames iterates over its first argument (selections) and when it finds a fragment spread, it calls itself passing loadedFragments.filter(def => def.name === selection.name.value).flatMap(...) as the selections argument here. That's where the issue happens: because the same fragment is included multiple times in loadedFragments, the recursive function call will iterate multiple times over the same fragment. If that fragment has a nested one, the same will happen to it, and so on.
  4. All that means that if a fragment is spread M times and has N levels of fragment spreads (e.g. if fragment A spreads fragment B, which spreads fragment C, which doesn't spread anything, N = 3), getFieldNames will be called at least M^N times.

Your Example Website or App

https://github.com/vhfmag/codegen-perf-issue-repro

Steps to Reproduce the Bug or Issue

  1. Clone the repo
  2. Run yarn codegen
  3. Add a new fragment to frags.ts and spread it into the last one
  4. Run yarn codegen — it should take 4 times as long as it did before because we spread fragment A 4 times in the ops.ts file
  5. Steps 3 & 4 can be repeated indefinitely

Example change:

BeforeAfter
// ...
const J = gql`
  fragment J on User {
    j
    ...K
  }
`;
const K = gql`
  fragment K on User {
    k
  }
`;
// ...
const J = gql`
  fragment J on User {
    j
    ...K
  }
`;
const K = gql`
  fragment K on User {
    k
    ...L
  }
`;
const L = gql`
  fragment L on User {
    l
  }
`;

Expected behavior

Time complexity should increase linearly with M & N

Screenshots or Videos

No response

Platform

  • OS: macOS & linux
  • NodeJS: 18.18.2
  • graphql version: 16.2.0
  • @graphql-codegen/near-operation-file-preset version(s): 3.0.0

Codegen Config File

{
  schema: "schema.graphql",
  documents: "src/**/*.ts",
  generates: {
    "types.ts": {
      preset: "near-operation-file",
      presetConfig: {
        baseTypesPath: "types.ts",
        folder: "__generated__",
      },
      config: {
        avoidOptionals: {
          field: true,
          inputValue: false,
          object: false,
          defaultValue: false,
        },
        arrayInputCoercion: false,
        extractAllFieldsToTypes: true,
        printFieldsOnNewLines: true,
        omitOperationSuffix: true,
        namingConvention: "keep",
      },
      plugins: ["typescript", "typescript-operations"],
    },
  },
}

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions