Open dasimandl opened 3 years ago
@dasimandl this is interesting I wonder if this is nests-query or @nestjs/graphql and the schema generation. If you could provide a sample repo I can look into this some more to see where the bottle neck is. If its in nestjs/graphql we may want to open the issue there.
@doug-martin Hello sorry for the long delay on this. I finally got around to creating a demo application and hosted on a public github repo. https://github.com/dasimandl/nestjs-query-scaling-bug-demo
Performance results: (see README for steps to reproduce)
NOTE: This was conducted by incrementally uncommenting the resolvers and imports from NestjsQueryGraphQLModule in the demo.module.ts file. The data below was gathered by saving to trigger a reload three times and doing a mental average/rounding.
Count (Resolvers) | Load Time (ms) | change (ms) |
---|---|---|
0 | 150 | - |
1 | 200 | 50 |
5 | 400 | 200 |
10 | 750 | 350 |
15 | 1000 | 250 |
20 | 1200 | 200 |
25 | 1450 | 250 |
30 | 1650 | 200 |
35 | 1850 | 200 |
40 | 2100 | 250 |
45 | 2350 | 250 |
50 | 2600 | 250 |
55 | 2750 | 150 |
60 | 3000 | 250 |
65 | 3300 | 300 |
70 | 3650 | 350 |
83 | 4500 | 850 |
@doug-martin I am following up to see if you had a chance to review this bug yet?
@doug-martin I am following to check the status on this open issue?
@dasimandl maybe i have same problem https://github.com/doug-martin/nestjs-query/issues/1452
i profiler and find this function need many time
@doug-martin
in crud.resolver.ts file
import { PagingStrategies } from '../types';
import { Aggregateable, AggregateResolverOpts, AggregateResolver } from './aggregate.resolver';
import { Relatable } from './relations';
import { Readable, ReadResolverFromOpts, ReadResolverOpts } from './read.resolver';
import { Creatable, CreateResolver, CreateResolverOpts } from './create.resolver';
import { Referenceable, ReferenceResolverOpts } from './reference.resolver';
import { MergePagingStrategyOpts, ResolverClass } from './resolver.interface';
import { Updateable, UpdateResolver, UpdateResolverOpts } from './update.resolver';
import { DeleteResolver, DeleteResolverOpts } from './delete.resolver';
import { BaseResolverOptions } from '../decorators/resolver-method.decorator';
import { mergeBaseResolverOpts } from '../common';
import { RelatableOpts } from './relations/relations.resolver';
import { CursorConnectionOptions } from '../types/connection/cursor';
import { performance } from 'perf_hooks';
export interface CRUDResolverOpts<
DTO,
C = DeepPartial<DTO>,
U = DeepPartial<DTO>,
R extends ReadResolverOpts<DTO> = ReadResolverOpts<DTO>,
PS extends PagingStrategies = PagingStrategies.CURSOR
> extends BaseResolverOptions,
Pick<CursorConnectionOptions, 'enableTotalCount'> {
/**
* The DTO that should be used as input for create endpoints.
*/
CreateDTOClass?: Class<C>;
/**
* The DTO that should be used as input for update endpoints.
*/
UpdateDTOClass?: Class<U>;
enableSubscriptions?: boolean;
pagingStrategy?: PS;
enableAggregate?: boolean;
create?: CreateResolverOpts<DTO, C>;
read?: R;
update?: UpdateResolverOpts<DTO, U>;
delete?: DeleteResolverOpts<DTO>;
referenceBy?: ReferenceResolverOpts;
aggregate?: AggregateResolverOpts;
}
export interface CRUDResolver<
DTO,
C,
U,
R extends ReadResolverOpts<DTO>,
QS extends QueryService<DTO, C, U> = QueryService<DTO, C, U>
> extends CreateResolver<DTO, C, QS>,
ReadResolverFromOpts<DTO, R, QS>,
UpdateResolver<DTO, U, QS>,
DeleteResolver<DTO, QS>,
AggregateResolver<DTO, QS> {}
/**
* Factory to create a resolver that includes all CRUD methods from [[CreateResolver]], [[ReadResolver]],
* [[UpdateResolver]], and [[DeleteResolver]].
*
* ```ts
* import { CRUDResolver } from '@nestjs-query/query-graphql';
* import { Resolver } from '@nestjs/graphql';
* import { TodoItemDTO } from './dto/todo-item.dto';
* import { TodoItemService } from './todo-item.service';
*
* @Resolver()
* export class TodoItemResolver extends CRUDResolver(TodoItemDTO) {
* constructor(readonly service: TodoItemService) {
* super(service);
* }
* }
* ```
* @param DTOClass - The DTO Class that the resolver is for. All methods will use types derived from this class.
* @param opts - Options to customize the resolver.
*/
// eslint-disable-next-line @typescript-eslint/no-redeclare -- intentional
export const CRUDResolver = <
DTO,
C = DeepPartial<DTO>,
U = DeepPartial<DTO>,
R extends ReadResolverOpts<DTO> = ReadResolverOpts<DTO>,
PS extends PagingStrategies = PagingStrategies.CURSOR
>(
DTOClass: Class<DTO>,
opts: CRUDResolverOpts<DTO, C, U, R, PS> = {},
): ResolverClass<DTO, QueryService<DTO, C, U>, CRUDResolver<DTO, C, U, MergePagingStrategyOpts<DTO, R, PS>>> => {
const {
CreateDTOClass,
UpdateDTOClass,
enableSubscriptions,
pagingStrategy,
enableTotalCount,
enableAggregate,
create = {},
read = {},
update = {},
delete: deleteArgs = {},
referenceBy = {},
aggregate,
} = opts;
const referencableTime = performance.now()
const referencable = Referenceable(DTOClass, referenceBy);
const startTimeRelatable = performance.now()
const relatable = Relatable(
DTOClass,
mergeBaseResolverOpts({ enableTotalCount, enableAggregate } as RelatableOpts, opts),
);
const endTimeRelatable = performance.now()
console.log(`Readable............................: ${endTimeRelatable - startTimeRelatable}ms`);
const startTimeAggregateable = performance.now()
const aggregateable = Aggregateable(DTOClass, {
enabled: enableAggregate,
...mergeBaseResolverOpts(aggregate ?? {}, opts),
});
const endTimeAggregateable = performance.now()
console.log(`Aggregateable............................: ${endTimeAggregateable - startTimeAggregateable}ms`);
const startTimeCreatable = performance.now()
const creatable = Creatable(DTOClass, {
CreateDTOClass,
enableSubscriptions,
...mergeBaseResolverOpts(create ?? {}, opts),
});
const endTimeCreatable = performance.now()
console.log(`Creatable............................: ${endTimeCreatable - startTimeCreatable}ms`);
const startTimeReadable = performance.now()
const readable = Readable(DTOClass, {
enableTotalCount,
pagingStrategy,
...mergeBaseResolverOpts(read, opts),
} as MergePagingStrategyOpts<DTO, R, PS>);
const endTimeReadable = performance.now()
console.log(`Readable............................: ${endTimeReadable - startTimeReadable}ms`);
const startTimeUpdateable = performance.now()
const updateable = Updateable(DTOClass, {
UpdateDTOClass,
enableSubscriptions,
...mergeBaseResolverOpts(update, opts),
});
const endTimeUpdateable = performance.now()
console.log(`Updateable............................: ${endTimeUpdateable - startTimeUpdateable}ms`);
const startTimeDelete = performance.now()
const deleteResolver = DeleteResolver(DTOClass, { enableSubscriptions, ...mergeBaseResolverOpts(deleteArgs, opts) });
const endTimeDelete = performance.now()
console.log(`Delete............................: ${endTimeDelete - startTimeDelete}ms`);
// return referencable(relatable(aggregateable(creatable(readable(updateable(deleteResolver))))));
return referencable(relatable(aggregateable(creatable(readable(updateable(deleteResolver))))));
};
resum
i don't know why deleteResolver
need many time to init
@doug-martin in query-graphql/common/external.utils.ts i put some console log
export function getGraphqlEnumMetadata(objType: object): EnumMetadata | undefined {
// hack to get enums loaded it may break in the future :(
const generateStartTime = performance.now()
console.log(chalk.cyan('TypeMetadataStorage.getEnumsMetadata()....................', TypeMetadataStorage.getEnumsMetadata()))
LazyMetadataStorage.load();
const result = TypeMetadataStorage.getEnumsMetadata().find((o) => o.ref === objType);
const generateEndTime = performance.now()
console.log(chalk.cyan(`getGraphqlEnumMetadata...........................`, result));
console.log(chalk.cyan(`getGraphqlEnumMetadata........................... ${generateEndTime - generateStartTime}ms`));
return result
}
And have need many time to execute function TypeMetadataStorage.getEnumsMetadata().find((o) => o.ref === objType);
Because need to find in big array bellow return from TypeMetadataStorage.getEnumsMetadata()
I hope this gets resolved in upcoming version(s), I really want to use this powerful solution <3
Hey @dasimandl @meodemsao ; do you recommend any workaround for this problem?
@pratiksyngenta i hot fixed this problem by add more enumName properties in decorate and not use function
// hack to get enums loaded it may break in the future :(
const generateStartTime = performance.now()
console.log(chalk.cyan('TypeMetadataStorage.getEnumsMetadata()....................', TypeMetadataStorage.getEnumsMetadata()))
LazyMetadataStorage.load();
const result = TypeMetadataStorage.getEnumsMetadata().find((o) => o.ref === objType);
const generateEndTime = performance.now()
console.log(chalk.cyan(`getGraphqlEnumMetadata...........................`, result));
console.log(chalk.cyan(`getGraphqlEnumMetadata........................... ${generateEndTime - generateStartTime}ms`));
return result
}
Hello,
First off I would like to say that this library is exceptionally powerful, well thought out and well documented. I was having a lot of success during my prototyping phase, but I'm now facing some scaling issues as I implement in a production environment.
Have you read the Contributing Guidelines? YES
To Reproduce I do not have an easy way of reproducing this issue as I am working on private repositories. Before I create a mocked scaled out application example for this, I wanted to see if anyone else has been faced with a similar scaling issues.
Expected behavior This is more of a question around scaling the number of auto-generated resolvers. Below is an explanation of the issue I was faced with.
Explanation of test: I setup perf_hooks to calculate the time it takes for the AppModule to be imported (this is where GraphQL is initiated) within the main.ts file. I compared the cost of additional entities incrementing 5 at a time. Currently scale of the application is 74 entities setup with GraphQL decorators, but will continue to grow overtime.
NOTE: These times are not exact, however I tried to run the test at lease two times and take an average.
Desktop:
Additional context N/A