Slow performance for queries with many fragments
### Issue Description

# General Info

- Using Relay 14.1.0 GraphQL client
- Using Apollo Server 4.4.0

# Overview

## Perf with no fragments

When I do not include fragments in my query, it is extremely fast.

```graphql
metadataAccountForMint(input: $input) {
  __typename
}
```

Note that these traces, as well as the ones below, do not include any network overhead—they are just tracing the time it takes for the GraphQL server to execute the request.

<img width="1943" alt="image" src="https://user-images.githubusercontent.com/4356178/220188932-7c72e8c1-73e3-412d-83e4-9f0b81864a4c.png">
<img width="1949" alt="image" src="https://user-images.githubusercontent.com/4356178/220189818-ddcac7b4-4b42-422e-8847-160fff22291e.png">


## Perf with many fragments

However, when I include a lot of fragments in my query, it becomes at least 20x slower. In the second example, you can see that the **time increases by over 30ms** 😱.

```graphql
metadataAccountForMint(input: $input) {
  ...Foo_MetadataAccount
}
```

*Note: `Foo_MetadataAccount` includes a bunch of other fragments, which include more fragments, and so on.*

Even though this request includes a lot of fragments, it is still reasonably sized (32 KB).

<img width="1942" alt="image" src="https://user-images.githubusercontent.com/4356178/220189105-dbc463c8-d3dc-4fca-94d0-e3b400dc2869.png">

<img width="1947" alt="image" src="https://user-images.githubusercontent.com/4356178/220189765-4ba53186-bad1-4393-9469-0b4d2eb4c966.png">


The **only** difference between these traces and the previous one is that the query for these traces includes a bunch of fragments.

Note that the resolver just returns `null`, to eliminate variability.

The weird thing is that it takes a long time for the resolver to even start resolving. This is seemingly out of my control—I can optimize the performance of my resolvers, but cannot figure out a way to optimize the time it takes to start running those resolvers.

## Perf when manually including every field

Just to double check that my use of fragments is actually contributing to the slow perf, I also tried manually including every field in the query. This definitely made the query slower than when just including `__typename`, but still much faster than including the fragments.

<img width="1945" alt="image" src="https://user-images.githubusercontent.com/4356178/220194987-73c93c00-a32f-42dc-8eff-7769da8a20a2.png">
<img width="1942" alt="image" src="https://user-images.githubusercontent.com/4356178/220195073-bb78a181-1a5c-411e-a6be-09347cf90eaf.png">

Query is [here](https://pastebin.com/jL8DYVhT), linking separately because it is quite large.

# Tracing Implementation

I am using Sentry + an Apollo Server plugin to implement tracing. Here's my plugin code:

```typescript
import { Span } from "@sentry/tracing";
import MyContext from "src/types/MyContext";
import { ApolloServerPlugin } from "@apollo/server";

const apolloSentryPlugin: ApolloServerPlugin<MyContext> = {
  async requestDidStart({ contextValue: contextValueRequestDidStart }) {
    let resolveOperationSpan: Span;
    const resolveSourceSpan =
      contextValueRequestDidStart.transaction.startChild({
        description: "resolveSource",
        op: "resolveSource",
      });

    return {
      async didResolveOperation() {
        if (resolveOperationSpan != null) {
          resolveOperationSpan.finish();
        }
      },
      async didResolveSource() {
        resolveOperationSpan =
          contextValueRequestDidStart.transaction.startChild({
            description: "resolveOperation",
            op: "resolveOperation",
          });
        resolveSourceSpan.finish();
      },
      async executionDidStart() {
        const executionSpan =
          contextValueRequestDidStart.transaction.startChild({
            description: "execution",
            op: "execution",
          });

        return {
          async executionDidEnd() {
            executionSpan.finish();
          },
          willResolveField({ contextValue, info }) {
            // hook for each new resolver
            const span = contextValue.transaction.startChild({
              description: `${info.parentType.name}.${info.fieldName}`,
              op: "resolver",
            });
            return () => {
              // this will execute once the resolver is finished
              span.finish();
            };
          },
        };
      },
      async parsingDidStart({ contextValue }) {
        const span = contextValue.transaction.startChild({
          description: "parsing",
          op: "parsing",
        });

        return async () => {
          span.finish();
        };
      },
      async validationDidStart({ contextValue }) {
        const span = contextValue.transaction.startChild({
          description: "validation",
          op: "validation",
        });

        return async () => {
          span.finish();
        };
      },
      async willSendResponse({ contextValue }) {
        // hook for transaction finished
        contextValue.transaction.finish();
      },
    };
  },
};

export default apolloSentryPlugin;
```

As you can see from the trace above, I've eliminated parsing, validation, and resolving as the performance bottlenecks, and have narrowed it down to the time it takes to run `willResolveField` after `executionDidStart` is called.

### Link to Reproduction

A bit tricky to reproduce this independently of our production website

### Reproduction Steps

_No response_