From f64ba6b60c10788fb0e33695df56814c5be2b263 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 25 Sep 2023 19:48:56 +0300 Subject: [PATCH] change update to upsert Signed-off-by: andreas-unleash --- src/lib/db/feature-toggle-store.ts | 43 ++++++++++++------- .../client-metrics/last-seen-service.ts | 2 +- .../services/last-seen-service.e2e.test.ts | 43 +++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/lib/db/feature-toggle-store.ts b/src/lib/db/feature-toggle-store.ts index 35b36f4e15..219ec7ec29 100644 --- a/src/lib/db/feature-toggle-store.ts +++ b/src/lib/db/feature-toggle-store.ts @@ -171,24 +171,37 @@ export default class FeatureToggleStore implements IFeatureToggleStore { return present; } + private upsert = (params: any) => { + const { table, object, constraint } = params; + const insert = this.db(table).insert(object); + const update = this.db.queryBuilder().update(object); + return this.db + .raw(`? ON CONFLICT ${constraint} DO ? returning *`, [ + insert, + update, + ]) + .get('rows') + .get(0); + }; + async setLastSeen(data: LastSeenInput[]): Promise { const now = new Date(); - const environmentArrays = this.mapMetricDataToEnvBuckets(data); - try { - for (const env of Object.keys(environmentArrays)) { - const toggleNames = environmentArrays[env].sort(); - await this.db(FEATURE_ENVIRONMENTS_METRICS_TABLE) - .upsert({ last_seen_at: now }) - .where('environment', env) - .whereIn( - 'feature_name', - this.db(FEATURE_ENVIRONMENTS_METRICS_TABLE) - .select('feature_name') - .whereIn('feature_name', toggleNames) - .forUpdate() - .skipLocked(), - ); + const enhancedData = data.map((value) => ({ + feature_name: value.featureName, + last_seen_at: now, + environment: value.environment, + })); + const dataToPersist: typeof enhancedData = []; + for (const input of enhancedData) { + if (await this.exists(input.feature_name)) { + dataToPersist.push(input); } + } + try { + await this.db(FEATURE_ENVIRONMENTS_METRICS_TABLE) + .insert(dataToPersist) + .onConflict(['feature_name', 'environment']) + .merge(['last_seen_at']); } catch (err) { this.logger.error('Could not update lastSeen, error: ', err); } diff --git a/src/lib/services/client-metrics/last-seen-service.ts b/src/lib/services/client-metrics/last-seen-service.ts index c4d9564f49..77226784b6 100644 --- a/src/lib/services/client-metrics/last-seen-service.ts +++ b/src/lib/services/client-metrics/last-seen-service.ts @@ -58,7 +58,7 @@ export class LastSeenService { .filter( (clientMetric) => clientMetric.yes > 0 || clientMetric.no > 0, ) - .forEach((clientMetric) => { + .forEach(async (clientMetric) => { const key = `${clientMetric.featureName}:${clientMetric.environment}`; this.lastSeenToggles.set(key, { featureName: clientMetric.featureName, diff --git a/src/test/e2e/services/last-seen-service.e2e.test.ts b/src/test/e2e/services/last-seen-service.e2e.test.ts index c00f05d2a9..75b441adad 100644 --- a/src/test/e2e/services/last-seen-service.e2e.test.ts +++ b/src/test/e2e/services/last-seen-service.e2e.test.ts @@ -129,3 +129,46 @@ test('Should not update anything for 0 toggles', async () => { service.destroy(); }); + +test('Should handle 1000 toggle updates', async () => { + // jest.useFakeTimers(); + const service = new LastSeenService(stores, config, 30); + const time = Date.now(); + for (let i = 0; i <= 1000; i++) { + await stores.featureToggleStore.create('default', { name: `tb${i}` }); + } + const metrics: IClientMetricsEnv[] = []; + + for (let i = 0; i < 999; i++) { + metrics.push({ + featureName: `tb${i}`, + appName: 'some-App', + environment: 'default', + timestamp: new Date(time), + yes: 1, + no: 0, + }); + } + + metrics.push({ + featureName: 'tb1000', + appName: 'some-App', + environment: 'default', + timestamp: new Date(time), + yes: 0, + no: 0, + }); + + service.updateLastSeen(metrics); + + // bypass interval waiting + await service.store(); + + const t1 = await stores.featureToggleStore.get('tb1'); + const t2 = await stores.featureToggleStore.get('tb1000'); + + expect(t2.lastSeenAt).toBeNull(); + expect(t1.lastSeenAt.getTime()).toBeGreaterThanOrEqual(time); + + service.destroy(); +});