Skip to content

Commit b56cb36

Browse files
committed
[FSSDK-11787] fix config parsing preformance
1 parent 017e10b commit b56cb36

File tree

5 files changed

+154
-30
lines changed

5 files changed

+154
-30
lines changed

lib/project_config/project_config.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str
180180
audience.conditions = JSON.parse(audience.conditions as string);
181181
});
182182

183-
projectConfig.audiencesById = {
184-
...keyBy(projectConfig.audiences, 'id'),
185-
...keyBy(projectConfig.typedAudiences, 'id'),
186-
}
183+
184+
projectConfig.audiencesById = {};
185+
keyBy(projectConfig.audiences, 'id', projectConfig.audiencesById);
186+
keyBy(projectConfig.typedAudiences, 'id', projectConfig.audiencesById);
187187

188188
projectConfig.attributes = projectConfig.attributes || [];
189189
projectConfig.attributeKeyMap = {};
@@ -267,11 +267,7 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str
267267
// Creates { <variationKey>: <variation> } map inside of the experiment
268268
experiment.variationKeyMap = keyBy(experiment.variations, 'key');
269269

270-
// Creates { <variationId>: { key: <variationKey>, id: <variationId> } } mapping for quick lookup
271-
projectConfig.variationIdMap = {
272-
...projectConfig.variationIdMap,
273-
...keyBy(experiment.variations, 'id')
274-
};
270+
keyBy(experiment.variations, 'id', projectConfig.variationIdMap);
275271

276272
objectValues(experiment.variationKeyMap || {}).forEach(variation => {
277273
if (variation.variables) {
@@ -373,10 +369,7 @@ const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => {
373369

374370
holdout.variationKeyMap = keyBy(holdout.variations, 'key');
375371

376-
projectConfig.variationIdMap = {
377-
...projectConfig.variationIdMap,
378-
...keyBy(holdout.variations, 'id'),
379-
};
372+
keyBy(holdout.variations, 'id', projectConfig.variationIdMap);
380373

381374
if (holdout.includedFlags.length === 0) {
382375
projectConfig.globalHoldouts.push(holdout);

lib/utils/fns/index.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from 'vitest';
22

3-
import { groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '.'
3+
import { groupBy, objectEntries, objectValues, find, sprintf, keyBy } from '.'
44

55
describe('utils', () => {
66
describe('groupBy', () => {
@@ -68,7 +68,7 @@ describe('utils', () => {
6868
{ key: 'baz', firstName: 'james', lastName: 'foxy' },
6969
]
7070

71-
expect(keyByUtil(input, item => item.key)).toEqual({
71+
expect(keyBy(input, 'key')).toEqual({
7272
foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' },
7373
bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' },
7474
baz: { key: 'baz', firstName: 'james', lastName: 'foxy' },

lib/utils/fns/index.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ export function isSafeInteger(number: unknown): boolean {
2525
return typeof number == 'number' && Math.abs(number) <= MAX_SAFE_INTEGER_LIMIT;
2626
}
2727

28-
export function keyBy<K>(arr: K[], key: string): { [key: string]: K } {
29-
if (!arr) return {};
30-
return keyByUtil(arr, function(item) {
31-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
32-
return (item as any)[key];
28+
export function keyBy<K>(arr: K[], key: string, base: Record<string, K> = {}): Record<string, K> {
29+
if (!arr) return base;
30+
31+
arr.forEach((e) => {
32+
base[(e as any)[key]] = e;
3333
});
34+
35+
return base;
3436
}
3537

3638

@@ -81,15 +83,6 @@ export function find<K>(arr: K[], cond: (arg: K) => boolean): K | undefined {
8183
return found;
8284
}
8385

84-
export function keyByUtil<K>(arr: K[], keyByFn: (item: K) => string): { [key: string]: K } {
85-
const map: { [key: string]: K } = {};
86-
arr.forEach(item => {
87-
const key = keyByFn(item);
88-
map[key] = item;
89-
});
90-
return map;
91-
}
92-
9386
// TODO[OASIS-6649]: Don't use any type
9487
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9588
export function sprintf(format: string, ...args: any[]): string {
@@ -129,6 +122,5 @@ export default {
129122
objectValues,
130123
objectEntries,
131124
find,
132-
keyByUtil,
133125
sprintf,
134126
};

memory-test.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env node
2+
3+
const v8 = require('v8');
4+
const fs = require('fs');
5+
6+
// Simulate your BatchEventProcessor pattern
7+
class MemoryTestProcessor {
8+
constructor() {
9+
this.eventCountWaitPromise = Promise.resolve();
10+
this.eventCountInStore = undefined;
11+
}
12+
13+
async readEventCountInStore(store) {
14+
if (this.eventCountInStore !== undefined) {
15+
return;
16+
}
17+
18+
// Simulate async store operation
19+
await new Promise(resolve => setTimeout(resolve, 1));
20+
this.eventCountInStore = Math.floor(Math.random() * 100);
21+
}
22+
23+
async findEventCountInStore() {
24+
if (this.eventCountInStore === undefined) {
25+
const store = { name: 'test-store' };
26+
// This is the line you're concerned about
27+
this.eventCountWaitPromise = this.eventCountWaitPromise.then(() => this.readEventCountInStore(store));
28+
return this.eventCountWaitPromise;
29+
}
30+
return Promise.resolve();
31+
}
32+
33+
// Reset for testing multiple iterations
34+
reset() {
35+
this.eventCountInStore = undefined;
36+
this.eventCountWaitPromise = Promise.resolve();
37+
}
38+
}
39+
40+
function takeHeapSnapshot(filename) {
41+
const heapSnapshot = v8.writeHeapSnapshot(filename);
42+
const stats = fs.statSync(heapSnapshot);
43+
console.log(`Heap snapshot written to ${heapSnapshot} (${Math.round(stats.size / 1024)} KB)`);
44+
return heapSnapshot;
45+
}
46+
47+
function getMemoryUsage() {
48+
const usage = process.memoryUsage();
49+
return {
50+
rss: Math.round(usage.rss / 1024 / 1024),
51+
heapUsed: Math.round(usage.heapUsed / 1024 / 1024),
52+
heapTotal: Math.round(usage.heapTotal / 1024 / 1024),
53+
external: Math.round(usage.external / 1024 / 1024)
54+
};
55+
}
56+
57+
async function runMemoryTest() {
58+
const processor = new MemoryTestProcessor();
59+
60+
console.log('Starting memory profiling test...');
61+
console.log('Initial memory:', getMemoryUsage());
62+
63+
// Take initial heap snapshot
64+
takeHeapSnapshot('./heap-before.heapsnapshot');
65+
66+
// Test 1: Rapid succession calls (your concern scenario)
67+
console.log('\n--- Test 1: Rapid Promise Chaining ---');
68+
const iterations = 50000;
69+
70+
for (let i = 0; i < iterations; i++) {
71+
await processor.findEventCountInStore();
72+
73+
// Reset every 1000 iterations to simulate eventCountInStore being set/reset
74+
if (i % 1000 === 0) {
75+
processor.reset();
76+
77+
// Force garbage collection
78+
if (global.gc) {
79+
global.gc();
80+
}
81+
82+
if (i % 10000 === 0) {
83+
console.log(`Iteration ${i}, Memory:`, getMemoryUsage());
84+
}
85+
}
86+
}
87+
88+
// Force final GC
89+
if (global.gc) {
90+
global.gc();
91+
}
92+
93+
console.log('Final memory after test:', getMemoryUsage());
94+
takeHeapSnapshot('./heap-after.heapsnapshot');
95+
96+
// Test 2: Memory leak simulation (for comparison)
97+
console.log('\n--- Test 2: Intentional Memory Leak (for comparison) ---');
98+
const leakyPromises = [];
99+
100+
for (let i = 0; i < 1000; i++) {
101+
// This WILL create a memory leak
102+
const promise = new Promise(resolve => {
103+
const largeObject = new Array(1000).fill('leak-test-data');
104+
setTimeout(() => resolve(largeObject), 1000);
105+
});
106+
leakyPromises.push(promise);
107+
}
108+
109+
console.log('Memory after creating leaky promises:', getMemoryUsage());
110+
takeHeapSnapshot('./heap-leaky.heapsnapshot');
111+
}
112+
113+
// Run the test
114+
console.log('Run this with: node --expose-gc memory-test.js');
115+
console.log('Or: node --max-old-space-size=512 --expose-gc memory-test.js\n');
116+
117+
runMemoryTest().catch(console.error);

nginx-deployment.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: nginx-deployment
5+
labels:
6+
app: nginx
7+
spec:
8+
replicas: 3
9+
selector:
10+
matchLabels:
11+
app: nginx
12+
template:
13+
metadata:
14+
labels:
15+
app: nginx
16+
spec:
17+
containers:
18+
- name: nginx
19+
image: nginx:1.14.2
20+
ports:
21+
- containerPort: 80
22+

0 commit comments

Comments
 (0)