Operation Signatures

Understand how GraphOS identifies equivalent operations


When you're viewing your graph's operation metrics, GraphOS Studio groups operations based on the set of fields they include, not based on operation names. That means that operations with the same name but different sets of fields are displayed separately:

Separate operations with same name in Studio

Though each operation in the above example is named MyQuery, GraphOS treats them as different operations because they request different fields.

To help GraphOS identify functionally identical operations, your router generates an operation signature for each operation it reports to Studio. This signature is a normalized representation for an operation with deterministic field order, whitespace, and values for in-line argument values.

Why do we need an operation signature?

Consider the following operations:

GraphQL
1query GetPostDetails($postId: String!) {
2  post(id: $postId) {
3    author
4    content
5  }
6}
7
8query GetPostDetails($postId: String!) {
9  post(id: $postId) {
10    content # Different field order
11    author
12  }
13}
14
15query GetPostDetails($postId: String!) {
16  post(id: $postId) {
17    writer: author # Field alias
18    content
19  }
20}

Despite some cosmetic differences (including comments), all these operations execute identically on a particular GraphQL server. Therefore, Studio should group them when displaying performance metrics.

Apollo libraries use a signature algorithm to generate the exact same operation signature for all these operations, allowing Studio to group them.

Signature algorithm

note
This signature algorithm is subject to change. It is provided here for informational purposes, not as a canonical specification.

Feel free to jump to an example or view the source.

The signature algorithm performs the following modifications on an operation to generate its signature:

  1. Transform in-line argument values.

  2. Remove extraneous characters.

  3. Reorder definitions.

1. Transform in-line argument values

If an operation includes any in-line argument values, the algorithm transforms those values according to their type:

  • Boolean and enum values are preserved.

  • Int and Float values are replaced with 0.

  • String, list, and object values are replaced with their "empty" counterpart ("", [], or {}).

Argument values provided as GraphQL variable names are preserved.

note
Organizations using the GraphOS Router can enable enhanced operation signature generation. Enhanced operation signatures include the input object shapes, while still redacting actual values. See the router configuration page to learn more.

2. Remove extraneous characters

The algorithm removes most unnecessary characters in the operation definition, including comments and redundant whitespace.

If the operation document includes multiple operations, then operations besides the executed operation are removed.

If the operation document includes fragments that aren't used by the executed operation, then those fragments are removed (other fragments are preserved).

3. Reorder definitions

Any preserved fragment definitions appear first, sorted alphanumerically by fragment name. The definition of the executed operation appears after all fragment definitions.

note
Whenever the names of two sorted items are identical, the algorithm preserves the original order of those items relative to each other.

Fields

For a given object or fragment's fields, field selections are sorted in the following order:

  1. Individually listed fields

  2. Named fragment spreads

  3. In-line fragments

Within each of these, sorting is alphanumeric by field name or fragment name.

Field aliases

All field aliases are removed. If an operation includes three instances of the same field with different aliases, then that field is listed in the signature three times with its non-aliased name.

note
Organizations using the GraphOS Router can enable enhanced operation signature generation that does not remove field aliases. See the router configuration page to learn more.

Directives and arguments

If multiple directives are applied to the same location in the operation document, those directives are sorted alphanumerically.

If a single field accepts multiple arguments, those arguments are sorted alphanumerically by name.

Example signature

Consider this operation:

GraphQL
1# Operation definition needs to appear after all fragment definitions
2query GetUser {
3  user(id: "hello") {
4    # Replace string argument value with empty string
5    ...NameParts # Spread fragment needs to appear after individual fields
6    timezone # Needs to appear alphanumerically after `name`
7    aliased: name # Need to remove alias
8  }
9}
10
11# Excessive characters (including this comment!) need to be removed
12
13fragment NameParts on User {
14  firstname
15  lastname
16}

The signature algorithm generates the following signature for this operation:

GraphQL
1fragment NameParts on User {
2  firstname
3  lastname
4}
5query GetUser {
6  user(id: "") {
7    name
8    timezone
9    ...NameParts
10  }
11}

Signatures and sensitive data

The signature algorithm's primary purpose is to group operations that differ only cosmetically in terms of whitespace, field order, aliases, etc. As an additional effect, the algorithm does remove most in-line argument values, which, in theory, helps maintain data privacy.

However, you should not rely on this. Ideally, your sensitive data should never reach GraphOS Studio in the first place. Whenever possible, use GraphQL variables instead of in-line values for arguments. This helps you control exactly which values are reported to Apollo. For details, see GraphOS Studio data privacy and compliance.

Logging signatures at runtime

Apollo Router

Since Apollo Router v1.43.1, the router adds operation signatures to request contexts at the apollo_operation_signature key. You can also use the router's native logging features to log context values as trace attributes or you can read the context via Rhai scripts or coprocessors.

With router v1.49.0 and later, you can enable enhanced operation signature generation that includes include input object types and field aliases. See the router configuration page to learn more.

Apollo Server

To view operation signature hashes in your own logging tools, you can create an Apollo Server plugin that accesses hash values from the shared context. The relevant values are available in the queryHash and operationName properties:

TypeScript
index.ts
1const logOperationSignaturePlugin = {
2  async requestDidStart() {
3    return {
4      async didResolveOperation(ctx) {
5        console.log({
6          queryHash: ctx.queryHash,
7          operationName: ctx.operationName
8        });
9      }
10    };
11  }
12};
13
14const server = new ApolloServer({
15  schema,
16  plugins: [logOperationSignaturePlugin]
17});
Feedback

Forums