Skip to content
Lakefront
Esc
↑↓navigate↵open⌘Jpreview
On this page

Router

route and the Router/Membership/hashing internals behind cache-affinity routing, hedging, and failover.

import { route } from "lakefront/router";

The cluster front door: deterministic cache-affinity routing, hedging, and failover across a fixed set of server nodes.

Functions

route

function route(options: RouteOptions): Promise<RouterServer>;
Parameter Type Default Description
options RouteOptions (required) See RouteOptions.

Returns the running RouterServer. Probes membership once, starts the health-poll loop, and opens the HTTP front door.

const router = await route({
  nodes: [
    { id: "node-a", url: "http://127.0.0.1:58001" },
    { id: "node-b", url: "http://127.0.0.1:58002" },
    { id: "node-c", url: "http://127.0.0.1:58003" },
  ],
  port: 8080,
});

quantiseWeight

function quantiseWeight(raw: number): number;

Snaps a raw capacity number to the nearest WEIGHT_LEVELS band (<= 0 maps to 0). Keeping weight banded stops routing from oscillating on every small capacity change.

rank

function rank(nodes: readonly NodeInfo[], boardId: string): NodeInfo[];

All eligible (weight > 0) nodes for boardId, best first; ties broken by id lexical order. The head is the board’s native node, the rest are failover order.

score

function score(node: NodeInfo, boardId: string): number;

-weight / ln(u), where u = hash(node, board) is normalised to (0,1) via 64-bit wyhash: gives each node a selection probability exactly proportional to its weight. weight <= 0 scores -Infinity.

isIdempotentPath

function isIdempotentPath(path: string): boolean;

Whether path names a procedure in IDEMPOTENT_PROCEDURES. Re-exported from lakefront/contract, where it is defined, for one import path.

isRetryable

function isRetryable(path: string, headers?: Headers): boolean;

isIdempotentPath(path), or headers carrying IDEMPOTENT_HEADER set to "1". Re-exported from lakefront/contract.

Classes

Router

Router is the class route wires up; construct it directly to embed routing in your own HTTP layer.

const router = new Router(options: RouterOptions);

See RouterOptions.

route

route(boardId: string): NodeInfo[]

Deterministic node preference for a board, best first: every router instance computes the same ranking from the same membership, with no coordination.

forward

forward(
  boardId: string,
  path: string,
  body: string,
  retryable?: boolean,
  authorization?: string,
): Promise<ForwardResult>
Parameter Type Default Description
boardId string (required) Board to route.
path string (required) Request path, e.g. /rpc/query.execute.
body string (required) Raw request body to forward.
retryable boolean isIdempotentPath(path) Whether a failure may hedge and fail over to other nodes.
authorization string undefined Forwarded verbatim, never parsed.

Sends to the board’s native node, hedging to the next-ranked node if the first has not answered by its own p95 (or hedgeDelayMs, before it has 8 samples). A non-retryable call (a bare mutation, no idempotencyKey) gets exactly one attempt on exactly one node: no hedge, no failover. Hedging or retrying a write that may have already landed risks a second copy that nothing downstream could undo, since the lake is append-only; a keyed mutation deduplicates at the WAL and can be hedged and retried on another node. Design and rationale are covered under routing.

counters

readonly counters: { requests: number; hedges: number; hedgeWins: number; failovers: number };

Running totals, mutated as forward runs.

Membership

const membership = new Membership(options: MembershipOptions);

See MembershipOptions.

nodes

nodes(): NodeInfo[]

Every configured member with its current weight: unhealthy members carry weight 0, which removes them from ranking without renumbering anything else.

all

all(): readonly MemberConfig[]

Every configured member, healthy or not.

healthyIds

healthyIds(): string[]

Ids currently marked healthy.

probeOnce

probeOnce(): Promise<void>

Hits GET /health on every member once, in parallel, with timeoutMs.

start

start(): void

Starts the intervalMs poll loop.

stop

stop(): void

Stops the poll loop.

setCapacity

setCapacity(id: string, capacity: number): void

Feeds a capacity signal in, quantised at nodes() time. Kept separate from health so a slow node is de-preferred, not removed.

markUnhealthy

markUnhealthy(id: string): void

Marks a member unhealthy immediately, outside the poll cadence.

LatencyWindow

Per-node latency tracking used to set the hedge delay: hedging at a node’s own recent p95 keeps roughly 5% of requests hedged in steady state rather than over- or under-hedging on a fixed timeout.

const window = new LatencyWindow();

record

record(nodeId: string, ms: number): void

Appends a sample to a 128-entry sliding window per node.

percentile

percentile(nodeId: string, p: number): number | undefined

undefined until the node has 8 or more samples: callers fall back to a configured default rather than hedging on noise.

count

count(nodeId: string): number

Samples currently held for a node.

Types

NodeInfo

interface NodeInfo {
  readonly id: string;
  readonly url: string;
  readonly weight: number; // 0 excludes the node entirely
}

RouteOptions

Field Type Default Notes
nodes readonly MemberConfig[] (required) { id, url } per server node.
port number 0 0 asks the OS to pick a free port.
hedgeDelayMs number 250 Fallback hedge delay before a node has 8+ latency samples of its own.
healthIntervalMs number 1000 Health-poll interval: forwarded to Membership.

RouterServer

Field Type
port number
url string
router Router
membership Membership
stop (): Promise<void>: stops the health-poll loop, then the HTTP server.

RouterOptions

interface RouterOptions {
  readonly membership: Membership;
  readonly hedgeDelayMs?: number; // default 250
  readonly hedgeAfterPercentile?: number; // default 95
  readonly requestTimeoutMs?: number; // default 10_000
  readonly fetch?: typeof globalThis.fetch;
}

ForwardResult

interface ForwardResult {
  readonly status: number;
  readonly body: string;
  readonly servedBy: string;
  readonly hedged: boolean;
  readonly attempted: readonly string[];
  readonly latencyMs: number;
}

MemberConfig

interface MemberConfig {
  readonly id: string;
  readonly url: string;
}

MembershipOptions

interface MembershipOptions {
  readonly members: readonly MemberConfig[];
  readonly intervalMs?: number; // default 1000
  readonly timeoutMs?: number; // default 500
  readonly fetch?: typeof globalThis.fetch;
}

Constants

WEIGHT_LEVELS

[0.25, 0.5, 1, 1.5, 2]. Quantisation bands quantiseWeight snaps to.

IDEMPOTENT_HEADER

"x-lakefront-idempotent". Re-exported from lakefront/contract, where it is defined.

IDEMPOTENT_PROCEDURES

ReadonlySet<string> of {"query.execute", "node.stats", "changes.read"}. Re-exported from lakefront/contract, where it is defined.

HTTP surface

Route Method Response
/health GET { ok: true, healthy: string[] }: currently healthy member ids.
/route?board=<id> GET { board, ranking: { id, weight }[], healthy: string[] }: introspection of the rendezvous ranking for a board, without forwarding anything.
/stats GET { requests, hedges, hedgeWins, failovers, healthy }: the router’s counters plus currently healthy member ids.
/rpc/* POST Proxied to the board’s ranked node(s). Requires header x-lakefront-board, 401 without it. Responds with x-lakefront-served-by and x-lakefront-hedged; see the wire protocol reference.
anything else any 404, { "error": "not found" }.

Standalone entrypoint

The container entrypoint (router/main.ts) reads its configuration from the environment: LAKEFRONT_NODES, LAKEFRONT_HEDGE_MS, PORT. See the environment variable reference. route itself takes options directly and reads no env.

Last updated on September 10, 2026

Was this page helpful?