Skip to content

Commit

Permalink
perf(core): Use memoization when caching zone members
Browse files Browse the repository at this point in the history
Relates to #988. Zone members (countries) were being newly translated for _every_ call to
ZoneService.findAll(). In practice this was resulting in thousands of calls to `translateDeep`.
This optimization cuts the calls down by a factor of 5-10x in various scenarios.
  • Loading branch information
michaelbromley committed Sep 28, 2021
1 parent d428ffc commit 54dfbf4
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
42 changes: 35 additions & 7 deletions packages/core/src/common/self-refreshing-cache.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Json } from '@vendure/common/lib/shared-types';

import { Logger } from '../config/logger/vendure-logger';

/**
Expand All @@ -12,6 +14,15 @@ export interface SelfRefreshingCache<V> {
*/
value(): Promise<V>;

/**
* @description
* Allows a memoized function to be defined. For the given arguments, the `fn` function will
* be invoked only once and its output cached and returned.
* The results cache is cleared along with the rest of the cache according to the configured
* `ttl` value.
*/
memoize<Args extends any[], R>(args: Args, fn: (value: V, ...args: Args) => R): Promise<R>;

/**
* @description
* Force a refresh of the value, e.g. when it is known that the value has changed such as after
Expand Down Expand Up @@ -43,12 +54,15 @@ export async function createSelfRefreshingCache<V>(
const initialValue = await refreshFn();
let value = initialValue;
let expires = new Date().getTime() + ttl;
const memoCache = new Map<string, any>();
const hashArgs = (...args: any[]) => JSON.stringify([args, expires]);
const refreshValue = (): Promise<V> => {
Logger.debug(`Refreshing the SelfRefreshingCache "${name}"`);
return refreshFn()
.then(newValue => {
value = newValue;
expires = new Date().getTime() + ttl;
memoCache.clear();
return value;
})
.catch(err => {
Expand All @@ -60,14 +74,28 @@ export async function createSelfRefreshingCache<V>(
return value;
});
};
const getValue = async (): Promise<V> => {
const now = new Date().getTime();
if (expires < now) {
return refreshValue();
}
return value;
};
const memoize = async <Args extends any[], R>(
args: Args,
fn: (value: V, ...args: Args) => R,
): Promise<R> => {
const cached = memoCache.get(hashArgs(args));
if (cached) {
return cached;
}
let result: Promise<R>;
memoCache.set(hashArgs(args), (result = getValue().then(val => fn(val, ...args))));
return result;
};
return {
async value() {
const now = new Date().getTime();
if (expires < now) {
return refreshValue();
}
return value;
},
value: getValue,
refresh: refreshValue,
memoize,
};
}
9 changes: 5 additions & 4 deletions packages/core/src/service/services/zone.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ export class ZoneService {
}

async findAll(ctx: RequestContext): Promise<Zone[]> {
const zones = await this.zones.value();
return zones.map(zone => {
zone.members = zone.members.map(country => translateDeep(country, ctx.languageCode));
return zone;
return this.zones.memoize([ctx.languageCode], (zones, languageCode) => {
return zones.map(zone => {
zone.members = zone.members.map(country => translateDeep(country, ctx.languageCode));
return zone;
});
});
}

Expand Down

0 comments on commit 54dfbf4

Please sign in to comment.