Skip to content

Commit

Permalink
feat(arns): add a promise cache for arns middleware
Browse files Browse the repository at this point in the history
This protects against multiple calls to `nameResolver.resolver(name)` while resolving a single name. The cache is given a short TTL and requests are removed after they are resolved. The best way to verify this behavior is trigger concurrent requests to resolve an arns name (e.g. using `hey`) and observing the logs to resolve the name only appear once for all the requests. Once the original promise is resolved, every outstanding request is resolved with the result and the name & promise are removed from the request cache.
  • Loading branch information
dtfiedler committed Sep 10, 2024
1 parent f1bd0cf commit d114821
Showing 1 changed file with 27 additions and 2 deletions.
29 changes: 27 additions & 2 deletions src/middleware/arns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@ import * as config from '../config.js';
import { headerNames } from '../constants.js';
import { sendNotFound } from '../routes/data/handlers.js';
import { DATA_PATH_REGEX } from '../constants.js';
import { NameResolver } from '../types.js';
import { NameResolution, NameResolver } from '../types.js';
import * as metrics from '../metrics.js';
import NodeCache from 'node-cache';

const EXCLUDED_SUBDOMAINS = new Set('www');

// simple cache that stores the arns resolution promises to avoid duplicate requests to the name resolver
const arnsRequestCache = new NodeCache({
stdTTL: 60, // short cache in case we forget to delete
checkperiod: 60,
useClones: false, // cloning promises is unsafe
});

export const createArnsMiddleware = ({
dataHandler,
nameResolver,
Expand Down Expand Up @@ -67,9 +76,25 @@ export const createArnsMiddleware = ({
return;
}

const getArnsResolutionPromise = async (): Promise<NameResolution> => {
if (arnsRequestCache.has(arnsSubdomain)) {
const arnsResolutionPromise =
arnsRequestCache.get<Promise<NameResolution>>(arnsSubdomain);
if (arnsResolutionPromise) {
return arnsResolutionPromise;
}
}
const arnsResolutionPromise = nameResolver.resolve(arnsSubdomain);
arnsRequestCache.set(arnsSubdomain, arnsResolutionPromise);
return arnsResolutionPromise;
};

const start = Date.now();
const { resolvedId, ttl, processId } =
await nameResolver.resolve(arnsSubdomain);
await getArnsResolutionPromise().finally(() => {
// remove from cache after resolution
arnsRequestCache.del(arnsSubdomain);
});
metrics.arnsResolutionTime.observe(Date.now() - start);
if (resolvedId === undefined) {
sendNotFound(res);
Expand Down

0 comments on commit d114821

Please sign in to comment.