Skip to content

Commit

Permalink
chore: improve the performance of our instance stats (#8766)
Browse files Browse the repository at this point in the history
## About the changes
Our stats are used for many places and many times to publish prometheus
metrics and some other things.

Some of these queries are heavy, traversing all tables to calculate
aggregates.

This adds a feature flag to be able to memoize 1 minute (by default) how
long to keep the calculated values in memory.

We can use the key of the function to individually control which ones
are memoized or not and for how long using a numeric variant.

Initially, this will be disabled and we'll test in our instances first
  • Loading branch information
gastonfournier authored Nov 18, 2024
1 parent 0ce976a commit 39d227c
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 74 deletions.
105 changes: 103 additions & 2 deletions src/lib/features/instance-stats/instance-stats-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ import { createFakeGetActiveUsers } from './getActiveUsers';
import { createFakeGetProductionChanges } from './getProductionChanges';
import { registerPrometheusMetrics } from '../../metrics';
import { register } from 'prom-client';
import type { IClientInstanceStore } from '../../types';
import type {
IClientInstanceStore,
IFlagResolver,
IUnleashStores,
} from '../../types';
import { createFakeGetLicensedUsers } from './getLicensedUsers';
let instanceStatsService: InstanceStatsService;
let versionService: VersionService;
let clientInstanceStore: IClientInstanceStore;
let stores: IUnleashStores;
let flagResolver: IFlagResolver;

let updateMetrics: () => Promise<void>;
beforeEach(() => {
jest.clearAllMocks();

register.clear();

const config = createTestConfig();
const stores = createStores();
flagResolver = config.flagResolver;
stores = createStores();
versionService = new VersionService(
stores,
config,
Expand Down Expand Up @@ -74,3 +82,96 @@ test('get snapshot should not call getStats', async () => {
test('before the snapshot is refreshed we can still get the appCount', async () => {
expect(instanceStatsService.getAppCountSnapshot('7d')).toBeUndefined();
});

describe.each([true, false])(
'When feature enabled is %s',
(featureEnabled: boolean) => {
beforeEach(() => {
jest.spyOn(flagResolver, 'getVariant').mockReturnValue({
name: 'memorizeStats',
enabled: featureEnabled,
feature_enabled: featureEnabled,
});
});

test(`should${featureEnabled ? ' ' : ' not '}memoize query results`, async () => {
const segmentStore = stores.segmentStore;
jest.spyOn(segmentStore, 'count').mockReturnValue(
Promise.resolve(5),
);
expect(segmentStore.count).toHaveBeenCalledTimes(0);
expect(await instanceStatsService.segmentCount()).toBe(5);
expect(segmentStore.count).toHaveBeenCalledTimes(1);
expect(await instanceStatsService.segmentCount()).toBe(5);
expect(segmentStore.count).toHaveBeenCalledTimes(
featureEnabled ? 1 : 2,
);
});

test(`should${featureEnabled ? ' ' : ' not '}memoize async query results`, async () => {
const trafficDataUsageStore = stores.trafficDataUsageStore;
jest.spyOn(
trafficDataUsageStore,
'getTrafficDataUsageForPeriod',
).mockReturnValue(
Promise.resolve([
{
day: new Date(),
trafficGroup: 'default',
statusCodeSeries: 200,
count: 5,
},
{
day: new Date(),
trafficGroup: 'default',
statusCodeSeries: 400,
count: 2,
},
]),
);
expect(
trafficDataUsageStore.getTrafficDataUsageForPeriod,
).toHaveBeenCalledTimes(0);
expect(await instanceStatsService.getCurrentTrafficData()).toBe(7);
expect(
trafficDataUsageStore.getTrafficDataUsageForPeriod,
).toHaveBeenCalledTimes(1);
expect(await instanceStatsService.getCurrentTrafficData()).toBe(7);
expect(
trafficDataUsageStore.getTrafficDataUsageForPeriod,
).toHaveBeenCalledTimes(featureEnabled ? 1 : 2);
});

test(`getStats should${featureEnabled ? ' ' : ' not '}be memorized`, async () => {
const featureStrategiesReadModel =
stores.featureStrategiesReadModel;
jest.spyOn(
featureStrategiesReadModel,
'getMaxFeatureEnvironmentStrategies',
).mockReturnValue(
Promise.resolve({
feature: 'x',
environment: 'default',
count: 3,
}),
);
expect(
featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies,
).toHaveBeenCalledTimes(0);
expect(
(await instanceStatsService.getStats())
.maxEnvironmentStrategies,
).toBe(3);
expect(
featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies,
).toHaveBeenCalledTimes(1);
expect(
(await instanceStatsService.getStats())
.maxEnvironmentStrategies,
).toBe(3);
expect(
featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies,
).toHaveBeenCalledTimes(featureEnabled ? 1 : 2);
});
},
);
Loading

0 comments on commit 39d227c

Please sign in to comment.