fix(street-level): run at most six Mapillary tile fetches at once

Tiles a camera move leaves behind keep downloading after the client drops
them; the cap bounds their memory and quota, as main's proxy did.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-08 16:20:50 +04:00
1 parent 35bfe203a6
commit c18af6ad1c
3 files changed
+46 -3

No files matched your search

+2 -2
View File
@@ -8,8 +8,8 @@ import {
} from './mapillary/tiles.js';
/**
* Tile requests per client IP per minute, cache hits included: 24 new 25-tile
* views a minute, more than a person flying the camera reaches.
* Tile requests per client IP per minute, cache hits included: over 60 new
* 9-tile views a minute, more than a person flying the camera reaches.
*/
export const TILE_ROUTE_MAX_PER_MIN = 600;
/** The longest upstream Retry-After passed on, in seconds. */
+23 -1
View File
@@ -17,6 +17,11 @@ const DROP_LAYERS = ['image'];
/** A z14 tile with its image layer is ~11 MB; anything past this is wrong. */
export const TILE_MAX_BYTES = 48 * 1024 * 1024;
const FETCH_TIMEOUT_MS = 60_000;
/**
* Upstream fetches at once; more wait their turn. A client drops the tiles a
* camera move leaves behind, but their fetches run on, so this bounds them.
*/
export const TILE_UPSTREAM_CONCURRENCY = 6;
/** Tiles change only when new imagery is processed. */
export const TILE_TTL_MS = 24 * 60 * 60 * 1000;
export const TILE_MEMORY_BUDGET_BYTES = 96 * 1024 * 1024;
@@ -91,6 +96,23 @@ async function fetchUpstream({ z, x, y }) {
}
}
let upstreamActive = 0;
/** @type {Array<() => void>} fetches waiting for a slot, oldest first */
const upstreamWaiting = [];
/** Run `task` once an upstream slot is free. */
async function withUpstreamSlot(task) {
while (upstreamActive >= TILE_UPSTREAM_CONCURRENCY)
await new Promise((resolve) => upstreamWaiting.push(resolve));
upstreamActive++;
try {
return await task();
} finally {
upstreamActive--;
upstreamWaiting.shift()?.();
}
}
/** @type {Map<string, {bytes: Buffer, at: number}>} insertion-ordered LRU */
const memory = new Map();
let memoryBytes = 0;
@@ -137,7 +159,7 @@ export async function fetchTile(address) {
inFlight,
address.key,
async () => {
const bytes = await fetchUpstream(address);
const bytes = await withUpstreamSlot(() => fetchUpstream(address));
remember(address.key, bytes);
return bytes;
},