Solution 2 - ProspectOne/flexbalancer-js-docs GitHub Wiki
Solution 2: The Performance with Penalty and Availability.
The case: we need to select the answer with the best provider performance and uptime(availability) bigger than 97 percents (both for the last hour, as it is provided by fetchCdnRumUptime
and fetchCdnRumPerformance
functions).
We also want to apply penalty for the particular provider performance, making it bigger...
Why?
Well, it might happen that one of our CDN Providers has stable better performance statistics than others and thus always will be the only one selected, so all our 'balancing' with the single provider will make no sense. So we are going to apply 'penalty' - let's call it padding
and worsen the performance results with the purpose to have our answers balanced.
If all providers have 'low' availability for the last hour - we will use the default
provider.
First - our configuration
:
const configuration = {
// The List of the CDN providers we are interested in
// The `name` must be one of the valid provider aliases from TCDNProvider type
providers: [
{
name: 'jsdelivr-cdn' as TCDNProvider,
cname: 'www.foo.com',
padding: 0,
ttl: 20, // We can provide different TTLs for different providers
},
{
name: 'akamai' as TCDNProvider,
cname: 'www.bar.com',
padding: 0,
},
{
name: 'cloudflare' as TCDNProvider,
cname: 'www.baz.com',
padding: 5,
}
],
// The Default provider which will be chosen if no suitable providers are found.
defaultProvider: 'jsdelivr-cdn',
// The Default TTL to be set when the application chooses a provider.
defaultTtl: 30,
// The Minimal threshold under which a provider will be considered unavailable
availabilityThreshold: 97,
};
Notice that we have added padding
property. It can also be negative number and in that case it works as bonus
to a provider performance.
Now, add our functions (the same we have used in the previous case):
/**
* Returns index of lowest number in array
*/
const getLowest = (array: number[]): number => array.indexOf(Math.min(...array));
/**
* Picks item with lowest value in property
*/
const getLowestByProperty = <T>(array: T[], property):T => array[getLowest(array.map(item => item[property]))];
Let's parse our configuration
and keep providers with availability more than 97 percent:
const {availabilityThreshold, defaultProvider, providers, defaultTtl} = configuration;
let decision;
// Filter by the availability threshold
const availableProviders = providers.filter(
(provider) => (req.location.country ?
fetchCdnRumUptime(provider.name, 'country', req.location.country) :
fetchCdnRumUptime(provider.name)) > availabilityThreshold);
Let's use our req
(Request) to determine the country user came from - it provides such information:
readonly location: {
...
city?: number;
country?: TCountry; // This is one we need
state?: TState | null;
continent?: TContinent;
...
};
You can get more information regarding our Request
at Custom Answers API.
In case the country is not determined we will use the global (world) CDN provider performance for the last hour.
Now, let's get all CDN performances and apply the penalties.
// Get CDN performances and apply the paddings for the providers available
const providersPerformance = availableProviders.map(
(provider) => ({
provider,
performance: req.location.country ?
// Get the performance for the country if we know it
fetchCdnRumPerformance(provider.name, 'country', req.location.country) + provider.padding :
// If we don't know the country - we get the global performance instead
fetchCdnRumPerformance(provider.name) + provider.padding
})
);
If we have non-empty list of the CDN providers with required availability - we choose the provider with the best performance:
// If we have a providers to choose from - choose the one with the best performance
if (providersPerformance.length) {
decision = getLowestByProperty(providersPerformance, 'performance').provider;
res.setCNAMERecord(decision.cname);
res.setTTL(decision.ttl ? decision.ttl : defaultTtl);
return;
}
If no providers with the desired uptime - we just use the default
one:
// No available providers - return default
decision = providers.find(provider => provider.name === defaultProvider);
// Prepare the response
res.setCNAMERecord(decision.cname);
res.setTTL(decision.ttl ? decision.ttl : defaultTtl);
return;
That's it! Take a look at the complete script:
const configuration = {
// The List of the CDN providers we are interested in
// The `name` must be one of the valid provider aliases from TCDNProvider type
providers: [
{
name: 'jsdelivr-cdn' as TCDNProvider,
cname: 'www.foo.com',
padding: 0,
ttl: 20, // We can provide different TTLs for different providers
},
{
name: 'akamai' as TCDNProvider,
cname: 'www.bar.com',
padding: 0,
},
{
name: 'cloudflare' as TCDNProvider,
cname: 'www.baz.com',
padding: 5,
}
],
// The Default provider which will be chosen if no suitable providers are found.
defaultProvider: 'jsdelivr-cdn',
// The Default TTL to be set when the application chooses a provider.
defaultTtl: 30,
// The Minimal threshold under which a provider will be considered unavailable
availabilityThreshold: 97,
};
/**
* Returns index of lowest number in array
*/
const getLowest = (array: number[]): number => array.indexOf(Math.min(...array));
/**
* Picks item with lowest value in property
*/
const getLowestByProperty = <T>(array: T[], property):T => array[getLowest(array.map(item => item[property]))];
function onRequest(req: IRequest, res: IResponse) {
const {availabilityThreshold, defaultProvider, providers, defaultTtl} = configuration;
let decision;
// Filter by the availability threshold
const availableProviders = providers.filter(
(provider) => (req.location.country ?
fetchCdnRumUptime(provider.name, 'country', req.location.country) :
fetchCdnRumUptime(provider.name)) > availabilityThreshold);
// Get CDN performances and apply the paddings for the providers available
const providersPerformance = availableProviders.map(
(provider) => ({
provider,
performance: req.location.country ?
// Get the performance for the country if we know it
fetchCdnRumPerformance(provider.name, 'country', req.location.country) + provider.padding :
// If we don't know the country - we get the global performance instead
fetchCdnRumPerformance(provider.name) + provider.padding
})
);
// If we have a providers to choose from - choose the one with the best performance for the last hour
if (providersPerformance.length) {
decision = getLowestByProperty(providersPerformance, 'performance').provider;
res.setCNAMERecord(decision.cname);
res.setTTL(decision.ttl ? decision.ttl : defaultTtl);
return;
}
// No available providers - return default
decision = providers.find(provider => provider.name === defaultProvider);
// Prepare the response
res.setCNAMERecord(decision.cname);
res.setTTL(decision.ttl ? decision.ttl : defaultTtl);
return;
}