Skip to content

Commit 7361307

Browse files
gitKrystanMehulKChaudhari
authored andcommitted
More dramatic update for update-with-same-state perf test (emberjs#9743)
* More dramatic update * Iterate parent of each child * Add big many to many scenario * Tidy up * Enbiggen
1 parent 8371871 commit 7361307

11 files changed

+224
-96
lines changed

tests/performance/app/router.js

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Router.map(function () {
2121
this.route('destroy');
2222
this.route('unused-relationships');
2323
this.route('update-with-same-state');
24+
this.route('update-with-same-state-m2m');
2425
});
2526

2627
export default Router;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Route from '@ember/routing/route';
2+
import { inject as service } from '@ember/service';
3+
4+
const REMOVAL_COUNT = 10;
5+
6+
export default Route.extend({
7+
store: service(),
8+
9+
async model() {
10+
performance.mark('start-data-generation');
11+
12+
const initialPayload = await fetch('./fixtures/big-many-to-many.json').then((r) => r.json());
13+
const initialPayload2 = structuredClone(initialPayload);
14+
const payloadWithRemoval = await fetch('./fixtures/big-many-to-many-with-removal.json').then((r) => r.json());
15+
16+
performance.mark('start-push-initial-payload');
17+
this.store.push(initialPayload);
18+
19+
performance.mark('start-peek-records');
20+
const peekedCars = this.store.peekAll('car');
21+
const peekedColors = this.store.peekAll('color');
22+
23+
performance.mark('start-record-materialization');
24+
peekedColors.slice();
25+
peekedCars.slice();
26+
27+
performance.mark('start-relationship-materialization');
28+
const seen = new Set();
29+
peekedCars.forEach((car) => iterateCar(car, seen));
30+
const removedColors = [];
31+
32+
performance.mark('start-local-removal');
33+
for (const car of peekedCars) {
34+
const colors = car.colors;
35+
removedColors.push(colors.splice(0, REMOVAL_COUNT));
36+
}
37+
38+
performance.mark('start-push-minus-one-payload');
39+
this.store.push(payloadWithRemoval);
40+
41+
performance.mark('start-local-addition');
42+
peekedCars.forEach((car, index) => {
43+
car.colors = removedColors[index].concat(car.colors);
44+
});
45+
46+
performance.mark('start-push-plus-one-payload');
47+
this.store.push(initialPayload2);
48+
49+
performance.mark('end-push-plus-one-payload');
50+
},
51+
});
52+
53+
function iterateChild(record, seen) {
54+
if (seen.has(record)) {
55+
return;
56+
}
57+
seen.add(record);
58+
59+
record.cars;
60+
}
61+
62+
function iterateCar(record, seen) {
63+
seen.add(record);
64+
record.colors.forEach((color) => iterateChild(color, seen));
65+
}

tests/performance/app/routes/update-with-same-state.js

+6-10
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ export default Route.extend({
99

1010
const initialPayload = await fetch('./fixtures/add-children-initial.json').then((r) => r.json());
1111
const initialPayload2 = structuredClone(initialPayload);
12-
13-
const minusOnePayload = structuredClone(initialPayload);
14-
minusOnePayload.data.relationships.children.data.pop();
15-
minusOnePayload.included.pop();
12+
const payloadWithRemoval = await fetch('./fixtures/add-children-with-removal.json').then((r) => r.json());
1613

1714
performance.mark('start-push-initial-payload');
1815
this.store.push(initialPayload);
@@ -32,13 +29,13 @@ export default Route.extend({
3229
const children = await parent.children;
3330

3431
performance.mark('start-local-removal');
35-
const removedChild = children.pop();
32+
const removedChildren = children.splice(0, 19000);
3633

3734
performance.mark('start-push-minus-one-payload');
38-
this.store.push(minusOnePayload);
35+
this.store.push(payloadWithRemoval);
3936

4037
performance.mark('start-local-addition');
41-
children.push(removedChild);
38+
parent.children = removedChildren.concat(children);
4239

4340
performance.mark('start-push-plus-one-payload');
4441
this.store.push(initialPayload2);
@@ -53,10 +50,9 @@ function iterateChild(record, seen) {
5350
}
5451
seen.add(record);
5552

56-
record.bestFriend.get('name');
57-
record.secondBestFriend.get('name');
58-
record.friends.forEach((child) => iterateChild(child, seen));
53+
record.parent;
5954
}
55+
6056
function iterateParent(record, seen) {
6157
seen.add(record);
6258
record.children.forEach((child) => iterateChild(child, seen));

tests/performance/app/templates/application.hbs

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
<li><LinkTo @route='add-children-to-materialized'>Add Children To Materialized</LinkTo></li>
1313
<li><LinkTo @route='add-children-then-materialize'>Add Children Then Materialize</LinkTo></li>
1414
<li><LinkTo @route='unused-relationships'>Unused Relationships</LinkTo></li>
15-
<li><LinkTo @route='update-with-same-state'>Update With Same State</LinkTo></li>
15+
<li><LinkTo @route='update-with-same-state'>Update With Same State (One to Many)</LinkTo></li>
16+
<li><LinkTo @route='update-with-same-state-m2m'>Update With Same State (Many to Many)</LinkTo></li>
1617
</ol>

tests/performance/fixtures/create-cars-payload.js

-82
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
const COLORS = ['red', 'white', 'black', 'pink', 'green', 'blue', 'yellow', 'orange', 'green', 'teal'];
2+
const SIZES = ['square', 'rectangle', 'circle', 'oval', 'cube', 'small', 'medium', 'large', 'extra large'];
3+
const MAKES = ['suv', 'sedan', 'minivan', 'electric', 'hybrid', 'truck', 'sport'];
4+
5+
let FIXTURE_ID = 0;
6+
7+
type JSONIdentifier = { id: string; type: string };
8+
9+
type JSONAPIResource = {
10+
id: string;
11+
type: string;
12+
attributes: Record<string, string>;
13+
relationships?: Record<string, { data: JSONIdentifier | JSONIdentifier[] }>;
14+
};
15+
16+
type JSONAPIPayload = {
17+
data: JSONAPIResource[];
18+
included: JSONAPIResource[];
19+
};
20+
21+
function getIndex(index: number, fixtures: unknown[]) {
22+
const count = fixtures.length;
23+
return index % count;
24+
}
25+
26+
function assignToMany(resource: JSONAPIResource, id: string) {
27+
resource.relationships = resource.relationships || {};
28+
const cars = (resource.relationships.cars = resource.relationships.cars || { data: [] });
29+
assert('Expected cars.data to be an array', Array.isArray(cars.data));
30+
cars.data.push({
31+
type: 'car',
32+
id,
33+
});
34+
}
35+
36+
function getRelatedResource(fixtures: JSONAPIResource[], index: number, id: string) {
37+
const resource = fixtures[getIndex(index, fixtures)];
38+
assignToMany(resource, id);
39+
return { id: resource.id, type: resource.type };
40+
}
41+
42+
function createCarsPayload(n: number, c = 1): JSONAPIPayload {
43+
const colors = getColorResources(c);
44+
const makes = getMakeResources();
45+
const sizes = getSizeResources();
46+
const data = new Array<JSONAPIResource>(n);
47+
for (let i = 0; i < n; i++) {
48+
const id = `urn:car:${FIXTURE_ID++}`;
49+
data[i] = {
50+
id,
51+
type: 'car',
52+
attributes: {},
53+
relationships: {
54+
make: {
55+
data: getRelatedResource(makes, i, id),
56+
},
57+
size: {
58+
data: getRelatedResource(sizes, i, id),
59+
},
60+
colors: {
61+
data:
62+
c === 1
63+
? [
64+
getRelatedResource(colors, i, id),
65+
getRelatedResource(colors, i + 1, id),
66+
getRelatedResource(colors, i + 2, id),
67+
]
68+
: new Array(colors.length).fill(null).map((_v, ii) => getRelatedResource(colors, i + ii, id)),
69+
},
70+
},
71+
};
72+
}
73+
74+
const fixture = {
75+
data,
76+
included: ([] as JSONAPIResource[]).concat(colors, makes, sizes),
77+
};
78+
79+
return fixture;
80+
}
81+
82+
function getColorResources(c: number) {
83+
return COLORS.flatMap((name) => {
84+
if (c > 1) {
85+
return new Array(c)
86+
.fill(null)
87+
.map((_v, i) => createJsonApiResource(`urn:color:${FIXTURE_ID++}`, 'color', { name: `${name}-${i}` }));
88+
} else {
89+
return [createJsonApiResource(`urn:color:${FIXTURE_ID++}`, 'color', { name })];
90+
}
91+
});
92+
}
93+
94+
function getSizeResources() {
95+
return SIZES.map((name) => createJsonApiResource(`urn:size:${FIXTURE_ID++}`, 'size', { name }));
96+
}
97+
98+
function getMakeResources() {
99+
return MAKES.map((name) => createJsonApiResource(`urn:make:${FIXTURE_ID++}`, 'make', { name }));
100+
}
101+
102+
function createJsonApiResource(id: string, type: string, attributes: Record<string, string>): JSONAPIResource {
103+
return {
104+
id,
105+
type,
106+
attributes,
107+
};
108+
}
109+
110+
function deleteHalfTheColors(payload: JSONAPIPayload) {
111+
const payloadWithRemoval = structuredClone(payload);
112+
113+
for (const carDatum of payloadWithRemoval.data) {
114+
assert('Expected carDatum to have relationships', carDatum.relationships);
115+
assert('Expected carDatum to have colors array', Array.isArray(carDatum.relationships.colors.data));
116+
const colorsLength = carDatum.relationships.colors.data.length;
117+
const removedColors = carDatum.relationships.colors.data.splice(0, colorsLength / 2);
118+
for (const removed of removedColors) {
119+
const included = payloadWithRemoval.included.find((r) => r.type === 'color' && r.id === removed.id);
120+
assert('Expected to find color in included', included);
121+
assert('Expected color to have relationships', included.relationships);
122+
assert('Expected color to have cars', Array.isArray(included.relationships.cars.data));
123+
included.relationships.cars.data = included.relationships.cars.data.filter((car) => car.id !== carDatum.id);
124+
}
125+
}
126+
127+
return payloadWithRemoval;
128+
}
129+
130+
function assert(message: string, condition: unknown): asserts condition {
131+
if (!condition) {
132+
throw new Error(`Assertion failed: ${message}`);
133+
}
134+
}
135+
136+
module.exports = { createCarsPayload, deleteHalfTheColors };
Binary file not shown.
Binary file not shown.
Binary file not shown.

tests/performance/fixtures/index.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ function write(name, json) {
2323
}
2424

2525
const createParentPayload = require('./create-parent-payload');
26-
const createCarsPayload = require('./create-cars-payload');
26+
const { createCarsPayload, deleteHalfTheColors } = require('./create-cars-payload.ts');
2727
const createParentRecords = require('./create-parent-records');
2828
const { createComplexPayload: createComplexRecordsPayload } = require('./create-complex-payload.ts');
2929

3030
async function main() {
31-
write('add-children-initial', createParentPayload(19600));
31+
const initialChildrenPayload = createParentPayload(19600);
32+
write('add-children-initial', initialChildrenPayload);
3233
write('add-children-final', createParentPayload(20000));
34+
const payloadWithRemoval = structuredClone(initialChildrenPayload);
35+
payloadWithRemoval.data.relationships.children.data.splice(0, 19000);
36+
payloadWithRemoval.included.splice(0, 19000);
37+
write('add-children-with-removal', payloadWithRemoval);
38+
3339
write('destroy', createParentPayload(500, 50));
3440
write('relationship-materialization-simple', createCarsPayload(10000));
3541
write('relationship-materialization-complex', createParentRecords(200, 10, 20));
@@ -40,5 +46,9 @@ async function main() {
4046
write('example-parent', createParentPayload(2, 2));
4147
write('basic-record-materialization', createParentRecords(10000, 2, 3));
4248
write('complex-record-materialization', await createComplexRecordsPayload(100));
49+
50+
const initialBigM2M = createCarsPayload(100, 100);
51+
write('big-many-to-many', initialBigM2M);
52+
write('big-many-to-many-with-removal', deleteHalfTheColors(initialBigM2M));
4353
}
4454
main();

tests/performance/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"scripts": {
1818
"build": "vite build",
1919
"start": "bun ./server/index.ts",
20-
"lint": "eslint . --quiet --cache --cache-strategy=content"
20+
"lint": "eslint . --quiet --cache --cache-strategy=content",
21+
"fixtures": "bun run ./fixtures/index.js"
2122
},
2223
"devDependencies": {
2324
"@babel/core": "^7.26.9",

0 commit comments

Comments
 (0)