Type-graphql: FieldResolver is too slow

Created on 6 Dec 2019  路  9Comments  路  Source: MichalLytek/type-graphql

Is your feature request related to a problem? Please describe.
This is an advanced topic of #254

If you use FieldResolver, execution needs more time.

import 'reflect-metadata';
import { buildSchemaSync, Field, FieldResolver, ObjectType, Query, Resolver, Root } from 'type-graphql';
import { graphql } from 'graphql';

@ObjectType({ simpleResolvers: true })
class Item {
  @Field()
  a: number;
  @Field()
  b: number;
  @Field()
  c: number;
  @Field()
  d: number;
  @Field()
  e: number;
}

@Resolver(Item)
class ItemResolver {
  @Query(() => [Item])
  items() {
    return new Array(5000).fill(0).map((v, i) => ({ a: i, b: i, c: i, d: i, e: i }))
  }

  @FieldResolver()
  a(@Root() source) {
    return source.a;
  }

  @FieldResolver()
  b(@Root() source) {
    return source.b;
  }

  @FieldResolver()
  c(@Root() source) {
    return source.c;
  }

  @FieldResolver()
  d(@Root() source) {
    return source.d;
  }

  @FieldResolver()
  e(@Root() source) {
    return source.e;
  }
}

const schema = buildSchemaSync({
  resolvers: [ItemResolver]
});

const start = Date.now();
graphql(schema, '{ items { a b c d e } }')
  .then((r) => {
    console.log(r.data!.items.length);
    console.log(r.data!.items[30].c);
    console.log('time =', Date.now() - start);
  });

Describe the solution you'd like
I didn't think whether some option likes simple is a solution or not.

But in the end, some optimization technique applied in graphql-js may be needed, I think.

For example,

    if (isPromise(result)) {
      return result.then(undefined, error => {
        exeContext.errors.push(error);
        return Promise.resolve(null);
      });
    }
    return result;

(from https://github.com/graphql/graphql-js/blob/master/src/execution/execute.js)

In graphql-js, those two declarations become difference result.

const Item = new GraphQLObjectType({
  name: 'Item',
  fields: {
    a: { type: GraphQLString, resolve: (s) => s.a },
  }
});

const Item = new GraphQLObjectType({
  name: 'Item',
  fields: {
    a: { type: GraphQLString, resolve: async (s) => s.a },
  }
});
Community Enhancement Solved

Most helpful comment

Are you open to { simpleResolvers: true } at a global level?

With #488 if you don't use middlewares nor auth checker, there's no performance penalty - see the PR and the benchmarks results.

All 9 comments

The problem here is not about promises - it's a really small overhead that's hard to measure.

The real cause is that field resolvers are a normal resolvers - they have injected transformed args and other params, you can have custom decorators, etc. So they are not generated as root[field] but a normal class method call, so we also need resolve the dependencies, get an instance from container, etc.

@FieldResolver are designed to work with "rich resolvers" that e.g. resolve relation between tables in database. If you need something like this:

  @FieldResolver()
  a(@Root() source) {
    return source.a;
  }

It is better to use a getter that is called just like the simple resolver:

  @Field({ name: "a" })
  get getA() {
    return this.a;
  }

It is a good idea to use getter instead of FieldResolver.
But I reminded the original issue of mine, and it was an async resolver. So it it not helpful.

Anyway, getParams for '@Root' may be the cause of the performance issue.
So, I think it is still useful to optimize Promises.

I tried to optimize by myself, and make a PR #488. Please check it.
In my test, the performance is improved dramatically. (maybe no need for simple option?)

From code readability and performance perspective, I don't like to mix the sync and async stuff just for some percent of use cases that use field resolver for simple sync access.

I think that all we can think of now is to add a { raw: true } support for full resolvers like @Query or @FieldResolver that will skip all the TypeGraphQL things and executes using a standard (root, args, ctx, info) signature.
This will allow to "fallback" into almost the plain graphql-js execution when someone needs that.

I would love to do { raw: true } at a global level

{ raw: true } at a global level

@benawad
So what do you need TypeGraphQL for? Maybe #339 (https://github.com/BramKaashoek/typescript-typedefs) fits you better?

@MichalLytek I still like writing the resolvers in the TypeGraphQL style, I'd just like to have a raw option if I don't need to use middleware/validation/etc

For now we will just merge #488 - further optimization will be applied on vNext that will aim to have as minimal overhead as possible:
https://github.com/MichalLytek/type-graphql/tree/vNext
https://github.com/MichalLytek/type-graphql/projects/2

@MichalLytek Awesome, I'm looking forward to that!

Are you open to { simpleResolvers: true } at a global level?

Are you open to { simpleResolvers: true } at a global level?

With #488 if you don't use middlewares nor auth checker, there's no performance penalty - see the PR and the benchmarks results.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

robertchung97 picture robertchung97  路  3Comments

reilem picture reilem  路  3Comments

itsgracian picture itsgracian  路  3Comments

maplesteve picture maplesteve  路  3Comments

MichalLytek picture MichalLytek  路  3Comments