Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: made type-metadata.storage.ts much faster #2212

Merged

Conversation

roypeled
Copy link
Contributor

@roypeled roypeled commented Jun 1, 2022

Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

This change mainly modified type-metadata.storage.ts file. This file stored all the objects' metadata in arrays, and during an application bootstrap, it would iterate all the data multiple times to filter the required metadata (mostly filter by class type, but also property field names and more).

What I did was to modify how the data is stored with multiple new classes which store the metadata within Maps, thus retrieving the correct data is a simple get instead of a filter.

From my tests on my (pretty large) GQL project, the bootstrap time went from 90s to 8s.

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #2206

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
@roypeled
Copy link
Contributor Author

roypeled commented Jun 1, 2022

OK, I see failing e2e tests here, but the ones I ran locally with npm run test:e2e passed (and there weren't a lot).
So I guess I'm missing a step here.
Is there another test suite somewhere?

@kamilmysliwiec
Copy link
Member

You might need to clear your local jest cache (with --clearCache) - perhaps you're not actually executing those tests

@roypeled
Copy link
Contributor Author

roypeled commented Jun 1, 2022

Yep, I didn't notice that I was running only the graphql package tests. Working on solving the breaking tests now.

Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
@roypeled
Copy link
Contributor Author

roypeled commented Jun 1, 2022

All test pass!

} from '../metadata';
import { ObjectTypeMetadata } from '../metadata/object-type.metadata';

export class MapByName<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a dedicated folder, let's say, "collections" and then have a file per class there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's a good idea. I didn't know where to place this file but your suggestion is appropriate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
@@ -0,0 +1,15 @@
export class ArrayCollection<T> extends Array<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending built-in collections is typically considered a bad practice. Any reason we cant use Array directly?

Copy link
Contributor Author

@roypeled roypeled Jun 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for every collection that we are saving per metadata class, we also need to save a global collection of all the classes collections in a flat array.
I thought the easiest way to do that is to hook on arrays push and I shift methods, but it maybe bette to have this class create an internal array and expose the required methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed another fix, now I'm wrapping the array instead of extending it. Good comment!

Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
@rezoled
Copy link

rezoled commented Jun 12, 2022

any update on this?

@@ -0,0 +1,35 @@
export class ArrayCollection<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still doesn't seem right. ArrayCollection class basically wraps another array inside and hooks into some of the methods (push and unshit) to simply insert these elements into the global array . We should - at the very least - rename this class so it properly describes what's doing under the hood.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Do you think that ArrayWithUpdateHooks is appropriate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about ArrayWithGlobalCacheCollection? Not sure which one is better tbh

export class ArrayCollection<T> {
private array: T[] = [];

constructor(private globalArray: Array<T>) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
constructor(private globalArray: Array<T>) {}
constructor(private readonly globalArray: Array<T>) {}

@@ -0,0 +1,35 @@
export class ArrayCollection<T> {
private array: T[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private array: T[] = [];
private readonly array: T[] = [];

import { MetadataListByNameCollection } from './metadata.list.by.name.collection';
import { PropertyDirectiveMetadata } from '../metadata';

export class FieldDirectiveCollection extends MetadataListByNameCollection<PropertyDirectiveMetadata> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename file to field-directive.collection.ts

Comment on lines 9 to 10
if (this.sdls.has(value.sdl) && this.fieldNames.has(value.fieldName))
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (this.sdls.has(value.sdl) && this.fieldNames.has(value.fieldName))
return;
if (this.sdls.has(value.sdl) && this.fieldNames.has(value.fieldName)) {
return;
}

style: for the sake of consistency


this.sdls.add(value.sdl);
this.fieldNames.add(value.fieldName);
this.globalArray && this.globalArray.push(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.globalArray && this.globalArray.push(value);
this.globalArray?.push(value);

@@ -0,0 +1,37 @@
import { MetadataByNameCollection } from './metadata.by.name.collection';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename file to metadata-list-by-name.collection.ts

@@ -0,0 +1,26 @@
export class MetadataByNameCollection<T> {
protected map = new Map<string, T>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this._objectType = val;
this.all.objectType.push(val);
}
get objectType() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing blank lines between getters and setters

@@ -0,0 +1,150 @@
import {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have a dedicated test file per collection (as we now have them - collection classes - in dedicated files as well)

Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
Changed the data modeling of type-metadata storage to store the data in maps instead of arrays.
This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
@rezoled
Copy link

rezoled commented Jun 21, 2022

Fixed all comments, thanks!

@rezoled
Copy link

rezoled commented Jun 26, 2022

Hey @kamilmysliwiec, can we merge this?

@rezoled
Copy link

rezoled commented Jul 6, 2022

@kamilmysliwiec we are waiting on an update for this, if you can help us this would be great

@kamilmysliwiec kamilmysliwiec merged commit 6cb8391 into nestjs:master Jul 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants