DEV Community

Vin Lookup
Vin Lookup

Posted on

Singleflight VIN Decode Lookups So Concurrent Identical VINs Share One In-Flight Promise

A free VIN decode edge or API route often fans out to NHTSA DecodeVinValues under concurrency. Two serverless isolates, two queue workers, or two browser tabs can hit the same normalized VIN in the same millisecond. Without a server-side singleflight gate, each request opens its own upstream call even when the work is identical and already in flight on that process.

This post is about server/edge singleflight: a process-local map from normalized VIN to one in-flight promise so concurrent identical lookups share one DecodeVinValues. Client-side request coalesce (duplicate pastes in one browser session) is a related idea with a different boundary -- see that pattern separately. Debounce decides when typing may start a call. ETag caches skip repeats after success. Here the focus is sharing one in-flight promise across concurrent server callers for the same VIN.

The failure mode

Typical sequence without singleflight on the edge:

  1. Request A arrives for VIN X; the handler starts DecodeVinValues.
  2. Request B arrives for the same normalized X before A settles (second tab, prefetch, or parallel SSR).
  3. The handler starts a second identical upstream fetch.
  4. NHTSA sees two calls; your edge latency and quota budget take a hit for no new information.
  5. Both responses return; you still paid twice for one pattern.

Client coalesce does not fix this: the duplicates already crossed the network as separate HTTP requests. A response cache helps after success, not while the first edge fetch is still pending. A global mutex that serializes all VINs is worse -- unrelated lookups wait on each other.

One promise per normalized VIN on the process

Own a module-scoped map from normalized VIN to the in-flight Promise. On decode:

  1. Normalize and validate the VIN.
  2. If the map already has an entry for that key, return it.
  3. Otherwise create the upstream promise, store it, and finally delete the key when settled.

Concurrent waiters on the same isolate share the result. Distinct VINs still run in parallel -- singleflight is keyed by identity, not a process-wide lock. Multi-isolate cold starts still duplicate until you add a shared cache; singleflight only collapses concurrency inside one runtime.

const VIN_RE = /^[A-HJ-NPR-Z0-9]{17}$/;

export function normalizeVin(raw: string): string {
  return raw.trim().toUpperCase().replace(/[\s\-._]/g, "");
}

export type DecodeRow = Record<string, string>;

export type SingleflightMap = Map<string, Promise<DecodeRow>>;

export function singleflightDecode(
  inflight: SingleflightMap,
  rawVin: string,
  fetchDecode: (vin: string) => Promise<DecodeRow>,
): Promise<DecodeRow> {
  const vin = normalizeVin(rawVin);
  if (!VIN_RE.test(vin)) {
    return Promise.reject(new Error("invalid_vin"));
  }

  const existing = inflight.get(vin);
  if (existing) return existing;

  const pending = fetchDecode(vin).finally(() => {
    if (inflight.get(vin) === pending) inflight.delete(vin);
  });
  inflight.set(vin, pending);
  return pending;
}
Enter fullscreen mode Exit fullscreen mode

The finally cleanup matters. Leaving settled promises in the map would pin memory and block honest retries after a failed attempt. Delete only when the map still points at this promise so a concurrent re-entry after failure can start fresh work.

Forbidden shortcuts

Product pressure often asks for:

  1. A process-wide lock so every decode waits on whichever VIN started first
  2. Treating singleflight as a substitute for a shared Redis/ETag cache across isolates
  3. Claiming "instant decode -- no NHTSA" when you only shared one in-flight edge call
  4. Keeping failed 5xx promises in the map so retries never leave
  5. Keying by raw query string without normalize (case or hyphens create duplicate flights)

Refuse those. Singleflight identical valid in-flight work on one process. Keep client coalesce for same-session duplicate pastes, debounce for typing, and durable caches for post-success repeats across instances. Do not invent offline or "skipped upstream" marketing from a shared promise.

export function assertNoSingleflightAbuse(moduleSource: string): void {
  const banned = [
    /global.?lock.?all.?vins/i,
    /skip.?nhtsa.?via.?singleflight/i,
    /instant.?decode.?no.?upstream/i,
    /singleflight.?replaces.?cache/i,
  ];
  for (const re of banned) {
    if (re.test(moduleSource)) {
      throw new Error(`Singleflight module misuse: ${re}`);
    }
  }
}

export async function decodeWithSingleflight(
  inflight: SingleflightMap,
  rawVin: string,
  fetchDecode: (vin: string) => Promise<DecodeRow>,
  onShared: () => void,
): Promise<DecodeRow> {
  const vin = normalizeVin(rawVin);
  const wasShared = inflight.has(vin);
  const row = await singleflightDecode(inflight, rawVin, fetchDecode);
  if (wasShared) onShared();
  return row;
}
Enter fullscreen mode Exit fullscreen mode

Instrument onShared (or a metric) so you can see how often concurrent edge callers joined. That is observability, not a claim that decode is free or that NHTSA was skipped.

API copy that stays honest

Prefer:

  • Silent share: concurrent waiters see the same loading outcome without a second upstream span
  • Optional quiet log or metric: "joined in-flight singleflight for this VIN"
  • Clear error if the shared promise rejects -- every waiter sees the same failure

Avoid:

  • "Cached instantly -- no NHTSA call" when you only singleflighted an open edge request
  • Hiding a shared failure from secondary waiters
  • Showing success for VIN B because A's in-flight promise was incorrectly keyed

Quick checks

import assert from "node:assert/strict";

const inflight: SingleflightMap = new Map();
let calls = 0;
const fetchDecode = async (vin: string): Promise<DecodeRow> => {
  calls += 1;
  await new Promise((r) => setTimeout(r, 20));
  return { VIN: vin, Make: "TEST" };
};

const p1 = singleflightDecode(inflight, "1HGCM82633A004352", fetchDecode);
const p2 = singleflightDecode(inflight, "1hgcm82633a004352", fetchDecode);
assert.equal(p1, p2);
const [a, b] = await Promise.all([p1, p2]);
assert.equal(a.Make, "TEST");
assert.equal(b.Make, "TEST");
assert.equal(calls, 1);
assert.equal(inflight.size, 0);

const p3 = singleflightDecode(inflight, "1HGCM82633A004352", fetchDecode);
await p3;
assert.equal(calls, 2);
Enter fullscreen mode Exit fullscreen mode

Review rule: singleflight modules must share by normalized VIN only while pending on this process, clear on settle, and never claim to replace client coalesce, debounce, or durable response caches.

Takeaway

Server/edge singleflight collapses concurrent identical VIN lookups into one upstream DecodeVinValues call per process. Pair it with client coalesce (same-session duplicates), debounce (start timing), and ETag caches (post-success repeats) -- do not substitute one for another. Your free VIN API stays kind to NHTSA when mid-flight work on one runtime shares a promise, then clears so the next lookup can run honestly.

I maintain VIN Lookup, a free VIN decode based on NHTSA data.

Top comments (0)