mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
fix(osm): key cached place searches to the Overpass instance that answered
The /api/osm/features cache key was the query alone, kept for a day, so after an operator repointed OVERPASS_UPSTREAMS the old instance's answers were still served. The key now includes the configured-upstream tag, as main's other Overpass caches check it, so a new instance is always asked.
This commit is contained in:
1 parent
474cce51eb
commit
dfdbb134d5
2 files changed
+43
-4
No files matched your search
@@ -6,7 +6,10 @@ import {
|
|||||||
OSM_FEATURE_LIMITS,
|
OSM_FEATURE_LIMITS,
|
||||||
summarizeOsmFeatures,
|
summarizeOsmFeatures,
|
||||||
} from './osmFeatures.js';
|
} from './osmFeatures.js';
|
||||||
import { resolveOverpassUpstreams } from './constants.js';
|
import {
|
||||||
|
resolveOverpassUpstreams,
|
||||||
|
resolveOverpassUpstreamTag,
|
||||||
|
} from './constants.js';
|
||||||
import { fetchOverpassPayload, overpassPayloadIsData } from './transport.js';
|
import { fetchOverpassPayload, overpassPayloadIsData } from './transport.js';
|
||||||
|
|
||||||
/** Places of interest change slowly: answers are kept a day. */
|
/** Places of interest change slowly: answers are kept a day. */
|
||||||
@@ -148,13 +151,16 @@ function sender(res) {
|
|||||||
* substitute for bulk place search.
|
* substitute for bulk place search.
|
||||||
*
|
*
|
||||||
* @param {{use: Function}} middlewares
|
* @param {{use: Function}} middlewares
|
||||||
* @param {{fetchPayload?: Function, featuresCache?: object, cacheRoot?: string|null, configured?: () => boolean}} [deps]
|
* @param {{fetchPayload?: Function, featuresCache?: object, cacheRoot?: string|null, configured?: () => boolean, upstreamTag?: () => string}} [deps]
|
||||||
*/
|
*/
|
||||||
export function installOsmFeaturesRoute(
|
export function installOsmFeaturesRoute(
|
||||||
middlewares,
|
middlewares,
|
||||||
{
|
{
|
||||||
fetchPayload = fetchOverpassPayload,
|
fetchPayload = fetchOverpassPayload,
|
||||||
configured = () => resolveOverpassUpstreams().length > 0,
|
configured = () => resolveOverpassUpstreams().length > 0,
|
||||||
|
// Keys each answer to the instance that gave it, as the other Overpass
|
||||||
|
// caches do, so repointing OVERPASS_UPSTREAMS never serves the old one's.
|
||||||
|
upstreamTag = resolveOverpassUpstreamTag,
|
||||||
cacheRoot = path.join(process.cwd(), '.gev-cache'),
|
cacheRoot = path.join(process.cwd(), '.gev-cache'),
|
||||||
featuresCache = createBoundedCache({
|
featuresCache = createBoundedCache({
|
||||||
dir: cacheRoot ? path.join(cacheRoot, 'osm-features') : null,
|
dir: cacheRoot ? path.join(cacheRoot, 'osm-features') : null,
|
||||||
@@ -203,7 +209,7 @@ export function installOsmFeaturesRoute(
|
|||||||
res.once?.('close', abort);
|
res.once?.('close', abort);
|
||||||
try {
|
try {
|
||||||
const answer = await features(
|
const answer = await features(
|
||||||
`${CACHE_VERSION}|features|${compiled.ql}`,
|
`${CACHE_VERSION}|features|${upstreamTag()}|${compiled.ql}`,
|
||||||
compiled.ql,
|
compiled.ql,
|
||||||
(parsed) => summarizeOsmFeatures(parsed, compiled),
|
(parsed) => summarizeOsmFeatures(parsed, compiled),
|
||||||
controller.signal,
|
controller.signal,
|
||||||
|
|||||||
@@ -158,7 +158,10 @@ test('answers are compacted: centres, kept tags, truncation and counts', () => {
|
|||||||
assert.equal(count.count, 5);
|
assert.equal(count.count, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
function mount(fetchPayload, { featuresCache, configured = () => true } = {}) {
|
function mount(
|
||||||
|
fetchPayload,
|
||||||
|
{ featuresCache, configured = () => true, upstreamTag } = {},
|
||||||
|
) {
|
||||||
const routes = new Map();
|
const routes = new Map();
|
||||||
const writes = [];
|
const writes = [];
|
||||||
const memoryCache = () => {
|
const memoryCache = () => {
|
||||||
@@ -177,6 +180,7 @@ function mount(fetchPayload, { featuresCache, configured = () => true } = {}) {
|
|||||||
fetchPayload,
|
fetchPayload,
|
||||||
cacheRoot: null,
|
cacheRoot: null,
|
||||||
configured,
|
configured,
|
||||||
|
...(upstreamTag ? { upstreamTag } : {}),
|
||||||
featuresCache: featuresCache || memoryCache(),
|
featuresCache: featuresCache || memoryCache(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -254,6 +258,35 @@ test('the route answers, caches, and runs one upstream query at a time', async (
|
|||||||
assert.equal(refused.body.code, 'AREA_TOO_LARGE');
|
assert.equal(refused.body.code, 'AREA_TOO_LARGE');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a cached answer is served only by the Overpass instance that gave it', async () => {
|
||||||
|
let tag = 'instance-a';
|
||||||
|
let upstream = 0;
|
||||||
|
const { request } = mount(
|
||||||
|
async () => {
|
||||||
|
upstream += 1;
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: JSON.stringify({
|
||||||
|
elements: [{ type: 'node', id: 7, lat: 1, lon: 1, tags: {} }],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ upstreamTag: () => tag },
|
||||||
|
);
|
||||||
|
assert.equal((await request('preset=hospital&bbox=0,0,2,2')).status, 200);
|
||||||
|
const hit = await request('preset=hospital&bbox=0,0,2,2');
|
||||||
|
assert.equal(hit.headers['X-OSM-Cache'], 'HIT');
|
||||||
|
assert.equal(upstream, 1);
|
||||||
|
tag = 'instance-b';
|
||||||
|
const switched = await request('preset=hospital&bbox=0,0,2,2');
|
||||||
|
assert.equal(switched.headers['X-OSM-Cache'], 'MISS');
|
||||||
|
assert.equal(
|
||||||
|
upstream,
|
||||||
|
2,
|
||||||
|
'the new instance is asked, not the old answer served',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('an upstream refusal is not an empty answer', async () => {
|
test('an upstream refusal is not an empty answer', async () => {
|
||||||
const { request } = mount(async () => ({
|
const { request } = mount(async () => ({
|
||||||
status: 429,
|
status: 429,
|
||||||
|
|||||||
Reference in new issue
Block a user