mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
fix: add backoff to the client features response
This commit is contained in:
parent
aa3e49f8c5
commit
7f83d3e224
@ -14,6 +14,9 @@ export const clientFeaturesSchema = {
|
||||
type: 'object',
|
||||
required: ['version', 'features'],
|
||||
properties: {
|
||||
backOff: {
|
||||
type: 'number',
|
||||
},
|
||||
version: {
|
||||
type: 'number',
|
||||
},
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
clientFeaturesSchema,
|
||||
ClientFeaturesSchema,
|
||||
} from '../../openapi/spec/client-features-schema';
|
||||
import RequestCounter from './request-counter';
|
||||
|
||||
const version = 2;
|
||||
|
||||
@ -46,6 +47,8 @@ export default class FeatureController extends Controller {
|
||||
|
||||
private readonly cache: boolean;
|
||||
|
||||
private requestCounter: RequestCounter;
|
||||
|
||||
private cachedFeatures: any;
|
||||
|
||||
constructor(
|
||||
@ -70,6 +73,7 @@ export default class FeatureController extends Controller {
|
||||
this.clientSpecService = clientSpecService;
|
||||
this.openApiService = openApiService;
|
||||
this.logger = config.getLogger('client-api/feature.js');
|
||||
this.requestCounter = new RequestCounter();
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
@ -201,23 +205,35 @@ export default class FeatureController extends Controller {
|
||||
): Promise<void> {
|
||||
const query = await this.resolveQuery(req);
|
||||
|
||||
const { appName } = req.body;
|
||||
|
||||
if (appName) {
|
||||
this.requestCounter.recordRequest(appName);
|
||||
}
|
||||
|
||||
const [features, segments] = this.cache
|
||||
? await this.cachedFeatures(query)
|
||||
: await this.resolveFeaturesAndSegments(query);
|
||||
|
||||
let commonFields = { features, version };
|
||||
|
||||
if (this.requestCounter.isRPSOverTresholdForApp(appName)) {
|
||||
commonFields.backOff = 10;
|
||||
}
|
||||
|
||||
if (this.clientSpecService.requestSupportsSpec(req, 'segments')) {
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
clientFeaturesSchema.$id,
|
||||
{ version, features, query: { ...query }, segments },
|
||||
{ ...commonFields, query: { ...query }, segments },
|
||||
);
|
||||
} else {
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
clientFeaturesSchema.$id,
|
||||
{ version, features, query },
|
||||
{ ...commonFields, query },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
22
src/lib/routes/client-api/request-counter.test.ts
Normal file
22
src/lib/routes/client-api/request-counter.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import RequestCounter from './request-counter';
|
||||
|
||||
test('Request counter can keep track of seen app names', async () => {
|
||||
jest.useFakeTimers();
|
||||
const counter = new RequestCounter();
|
||||
counter.recordRequest('app1');
|
||||
counter.recordRequest('app1');
|
||||
counter.recordRequest('app1');
|
||||
counter.recordRequest('app1');
|
||||
counter.recordRequest('app1');
|
||||
counter.recordRequest('app1');
|
||||
counter.recordRequest('app2');
|
||||
counter.recordRequest('app2');
|
||||
jest.advanceTimersByTime(400000);
|
||||
|
||||
const buckets = counter.getBuckets();
|
||||
|
||||
expect(buckets).toHaveLength(1);
|
||||
|
||||
expect(buckets[0].apps['app1'].count).toBe(6);
|
||||
expect(buckets[0].apps['app2'].count).toBe(2);
|
||||
});
|
@ -1,21 +1,16 @@
|
||||
datefns;
|
||||
|
||||
export default class RequestCounter {
|
||||
private requestCache: Object;
|
||||
private longTermCache: Object;
|
||||
private interval: number;
|
||||
|
||||
constructor() {
|
||||
this.requestCache = {};
|
||||
this.requestCache = this.createBucket();
|
||||
this.longTermCache = [];
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
this.requestCache.endTime = new Date();
|
||||
|
||||
const longTermCacheObject = {
|
||||
...this.requestCache,
|
||||
rps: this.calculateRPS(),
|
||||
};
|
||||
const longTermCacheObject = this.calculateRPS(this.requestCache);
|
||||
|
||||
this.longTermCache.push(longTermCacheObject);
|
||||
|
||||
@ -25,23 +20,36 @@ export default class RequestCounter {
|
||||
|
||||
createBucket = () => {
|
||||
const bucket = {
|
||||
count: 0,
|
||||
apps: {},
|
||||
startTime: new Date(),
|
||||
endTime: null,
|
||||
};
|
||||
return bucket;
|
||||
};
|
||||
|
||||
recordRequest = (appName: string): Promise<void> => {
|
||||
if (this.requestCache[appName]) {
|
||||
this.requestCache[count] += 1;
|
||||
recordRequest = (appName: string): void => {
|
||||
if (this.requestCache['apps'][appName]) {
|
||||
this.requestCache['apps'][appName].count += 1;
|
||||
} else {
|
||||
this.requestCache[appName] = { count: 1 };
|
||||
this.requestCache['apps'][appName] = { count: 1 };
|
||||
}
|
||||
};
|
||||
|
||||
calculateRPS = () => {
|
||||
const { count } = this.requestCache;
|
||||
return count / 300;
|
||||
getBuckets = (): Object => {
|
||||
return this.longTermCache;
|
||||
};
|
||||
|
||||
calculateRPS = (requestCache: Object) => {
|
||||
Object.keys(requestCache.apps).forEach((appName) => {
|
||||
const app = requestCache.apps[appName];
|
||||
const rps = app.count / 300;
|
||||
app.rps = rps;
|
||||
});
|
||||
|
||||
return requestCache;
|
||||
};
|
||||
|
||||
isRPSOverTresholdForApp = (appName: string) => {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user