mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: prevent double daily metrics insert (#5906)
This commit is contained in:
parent
967ee13e62
commit
f6c0624869
@ -216,7 +216,8 @@ test('count previous day metrics', async () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await clientMetricsStore.countPreviousDayMetricsBuckets();
|
||||
const result =
|
||||
await clientMetricsStore.countPreviousDayHourlyMetricsBuckets();
|
||||
|
||||
expect(result).toMatchObject({ enabledCount: 2, variantCount: 4 });
|
||||
});
|
||||
|
@ -339,7 +339,7 @@ export class ClientMetricsStoreV2 implements IClientMetricsStoreV2 {
|
||||
.del();
|
||||
}
|
||||
|
||||
async countPreviousDayMetricsBuckets(): Promise<{
|
||||
async countPreviousDayHourlyMetricsBuckets(): Promise<{
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
}> {
|
||||
@ -363,6 +363,30 @@ export class ClientMetricsStoreV2 implements IClientMetricsStoreV2 {
|
||||
};
|
||||
}
|
||||
|
||||
async countPreviousDayMetricsBuckets(): Promise<{
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
}> {
|
||||
const enabledCountQuery = this.db(DAILY_TABLE)
|
||||
.whereRaw("date >= CURRENT_DATE - INTERVAL '1 day'")
|
||||
.andWhereRaw('date < CURRENT_DATE')
|
||||
.count()
|
||||
.first();
|
||||
const variantCountQuery = this.db(DAILY_TABLE_VARIANTS)
|
||||
.whereRaw("date >= CURRENT_DATE - INTERVAL '1 day'")
|
||||
.andWhereRaw('date < CURRENT_DATE')
|
||||
.count()
|
||||
.first();
|
||||
const [enabledCount, variantCount] = await Promise.all([
|
||||
enabledCountQuery,
|
||||
variantCountQuery,
|
||||
]);
|
||||
return {
|
||||
enabledCount: Number(enabledCount?.count || 0),
|
||||
variantCount: Number(variantCount?.count || 0),
|
||||
};
|
||||
}
|
||||
|
||||
// aggregates all hourly metrics from a previous day into daily metrics
|
||||
async aggregateDailyMetrics(): Promise<void> {
|
||||
const rawQuery: string = `
|
||||
|
@ -255,7 +255,7 @@ export class InstanceStatsService {
|
||||
this.eventStore.filteredCount({ type: FEATURES_EXPORTED }),
|
||||
this.eventStore.filteredCount({ type: FEATURES_IMPORTED }),
|
||||
this.getProductionChanges(),
|
||||
this.clientMetricsStore.countPreviousDayMetricsBuckets(),
|
||||
this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets(),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -235,20 +235,37 @@ test('get hourly client metrics for a toggle', async () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('aggregate previous day metrics when metrics count is below limit', async () => {
|
||||
const enabledCount = 2;
|
||||
const variantCount = 4;
|
||||
let limit = 5;
|
||||
type MetricSetup = {
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
enabledDailyCount: number;
|
||||
variantDailyCount: number;
|
||||
limit: number;
|
||||
};
|
||||
const setupMetricsService = ({
|
||||
enabledCount,
|
||||
variantCount,
|
||||
enabledDailyCount,
|
||||
variantDailyCount,
|
||||
limit,
|
||||
}: MetricSetup) => {
|
||||
let aggregationCalled = false;
|
||||
let recordedWarning = '';
|
||||
|
||||
const clientMetricsStoreV2 = {
|
||||
aggregateDailyMetrics() {
|
||||
aggregationCalled = true;
|
||||
return Promise.resolve();
|
||||
},
|
||||
countPreviousDayMetricsBuckets() {
|
||||
countPreviousDayHourlyMetricsBuckets() {
|
||||
return { enabledCount, variantCount };
|
||||
},
|
||||
countPreviousDayMetricsBuckets() {
|
||||
return {
|
||||
enabledCount: enabledDailyCount,
|
||||
variantCount: variantDailyCount,
|
||||
};
|
||||
},
|
||||
} as unknown as IClientMetricsStoreV2;
|
||||
const config = {
|
||||
flagResolver: {
|
||||
@ -273,18 +290,62 @@ test('aggregate previous day metrics when metrics count is below limit', async (
|
||||
config,
|
||||
lastSeenService,
|
||||
);
|
||||
return {
|
||||
service,
|
||||
aggregationCalled: () => aggregationCalled,
|
||||
recordedWarning: () => recordedWarning,
|
||||
};
|
||||
};
|
||||
|
||||
test('do not aggregate previous day metrics when metrics already calculated', async () => {
|
||||
const { service, recordedWarning, aggregationCalled } = setupMetricsService(
|
||||
{
|
||||
enabledCount: 2,
|
||||
variantCount: 4,
|
||||
enabledDailyCount: 2,
|
||||
variantDailyCount: 4,
|
||||
limit: 6,
|
||||
},
|
||||
);
|
||||
|
||||
await service.aggregateDailyMetrics();
|
||||
|
||||
expect(recordedWarning).toBe(
|
||||
expect(recordedWarning()).toBe('');
|
||||
expect(aggregationCalled()).toBe(false);
|
||||
});
|
||||
|
||||
test('do not aggregate previous day metrics when metrics count is below limit', async () => {
|
||||
const { service, recordedWarning, aggregationCalled } = setupMetricsService(
|
||||
{
|
||||
enabledCount: 2,
|
||||
variantCount: 4,
|
||||
enabledDailyCount: 0,
|
||||
variantDailyCount: 0,
|
||||
limit: 5,
|
||||
},
|
||||
);
|
||||
|
||||
await service.aggregateDailyMetrics();
|
||||
|
||||
expect(recordedWarning()).toBe(
|
||||
'Skipping previous day metrics aggregation. Too many results. Expected max value: 5, Actual value: 6',
|
||||
);
|
||||
expect(aggregationCalled).toBe(false);
|
||||
expect(aggregationCalled()).toBe(false);
|
||||
});
|
||||
|
||||
test('aggregate previous day metrics', async () => {
|
||||
const { service, recordedWarning, aggregationCalled } = setupMetricsService(
|
||||
{
|
||||
enabledCount: 2,
|
||||
variantCount: 4,
|
||||
enabledDailyCount: 0,
|
||||
variantDailyCount: 0,
|
||||
limit: 6,
|
||||
},
|
||||
);
|
||||
|
||||
recordedWarning = '';
|
||||
limit = 6;
|
||||
await service.aggregateDailyMetrics();
|
||||
|
||||
expect(recordedWarning).toBe('');
|
||||
expect(aggregationCalled).toBe(true);
|
||||
expect(recordedWarning()).toBe('');
|
||||
expect(aggregationCalled()).toBe(true);
|
||||
});
|
||||
|
@ -61,7 +61,15 @@ export default class ClientMetricsServiceV2 {
|
||||
|
||||
async aggregateDailyMetrics() {
|
||||
if (this.flagResolver.isEnabled('extendedUsageMetrics')) {
|
||||
const { enabledCount, variantCount } =
|
||||
const {
|
||||
enabledCount: hourlyEnabledCount,
|
||||
variantCount: hourlyVariantCount,
|
||||
} =
|
||||
await this.clientMetricsStoreV2.countPreviousDayHourlyMetricsBuckets();
|
||||
const {
|
||||
enabledCount: dailyEnabledCount,
|
||||
variantCount: dailyVariantCount,
|
||||
} =
|
||||
await this.clientMetricsStoreV2.countPreviousDayMetricsBuckets();
|
||||
const { payload } = this.flagResolver.getVariant(
|
||||
'extendedUsageMetrics',
|
||||
@ -72,15 +80,21 @@ export default class ClientMetricsServiceV2 {
|
||||
? parseInt(payload?.value)
|
||||
: 3600000;
|
||||
|
||||
const totalCount = enabledCount + variantCount;
|
||||
const totalHourlyCount = hourlyEnabledCount + hourlyVariantCount;
|
||||
const totalDailyCount = dailyEnabledCount + dailyVariantCount;
|
||||
const previousDayDailyCountCalculated =
|
||||
totalDailyCount > totalHourlyCount / 24; // heuristic
|
||||
|
||||
if (totalCount <= limit) {
|
||||
await this.clientMetricsStoreV2.aggregateDailyMetrics();
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`Skipping previous day metrics aggregation. Too many results. Expected max value: ${limit}, Actual value: ${totalCount}`,
|
||||
);
|
||||
if (previousDayDailyCountCalculated) {
|
||||
return;
|
||||
}
|
||||
if (totalHourlyCount > limit) {
|
||||
this.logger.warn(
|
||||
`Skipping previous day metrics aggregation. Too many results. Expected max value: ${limit}, Actual value: ${totalHourlyCount}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await this.clientMetricsStoreV2.aggregateDailyMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,10 @@ export interface IClientMetricsStoreV2
|
||||
): Promise<string[]>;
|
||||
clearMetrics(hoursAgo: number): Promise<void>;
|
||||
clearDailyMetrics(daysAgo: number): Promise<void>;
|
||||
countPreviousDayHourlyMetricsBuckets(): Promise<{
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
}>;
|
||||
countPreviousDayMetricsBuckets(): Promise<{
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
|
@ -29,6 +29,12 @@ export default class FakeClientMetricsStoreV2
|
||||
clearDailyMetrics(daysBack: number): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
countPreviousDayHourlyMetricsBuckets(): Promise<{
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
}> {
|
||||
return Promise.resolve({ enabledCount: 0, variantCount: 0 });
|
||||
}
|
||||
countPreviousDayMetricsBuckets(): Promise<{
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
|
Loading…
Reference in New Issue
Block a user