DEV Community

life
life

Posted on

A backlog policy is a comparator with a version number

A queue longer than the shift still gets worked in some order. Whatever is nearest, whatever the wave printed first, whatever a supervisor's phone decided, all three are comparators. The difference between a comparator and a policy is that a policy has a name, a version, and a record of the times it was stepped around.

That distinction costs almost nothing to build in a quiet month and cannot be bought in a busy one, which is why it belongs in the schema rather than in a runbook.

Three stages, and the third is the one people skip

A hard gate first. Blocked lots, quarantined units, missing export data. These are not low priority, they are ineligible, and mixing them into a sort key produces a queue where a held order keeps floating back up.

Then the priority key, as a tuple rather than a score. A score invites someone to tune it during an incident, and a tuned score is an unlogged policy change.

type Order = {
  id: string;
  placedAt: Date;
  promiseWithin: number | null;   // hours from placedAt, null if none published
  cutOffAt: Date | null;          // carrier handover this unit can still make
  expiresAt: Date | null;         // lot or channel acceptance window
  channel: string;
  blocked: boolean;
};

const key = (o: Order) => [
  o.cutOffAt ? 0 : 1,                       // can still make today's dispatch
  o.expiresAt ? 0 : 1,                      // short-dated before long-dated
  o.promiseWithin === null ? 999 : o.promiseWithin,
  o.placedAt.getTime(),                     // oldest first inside each band
] as const;

export function compareOrders(a: Order, b: Order): number {
  if (a.blocked !== b.blocked) return a.blocked ? 1 : -1;
  const ka = key(a), kb = key(b);
  for (let i = 0; i < ka.length; i += 1) {
    if (ka[i] !== kb[i]) return ka[i] < kb[i] ? -1 : 1;
  }
  return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;   // total order, no ties
}
Enter fullscreen mode Exit fullscreen mode

The last line is the whole article. Without an explicit final tie-break, two orders sharing every key are separated by whatever order the database returned them in, and that sequence changes after a restart, a reindex or a shard rebalance. A backlog whose order changes when nothing about the orders changed is not late, it is unpredictable, and unpredictability is what makes the same customer wait twice.

A version number makes a decision reviewable

create table queue_policy (
  id          bigint primary key,
  version     int   not null,
  effective_at timestamptz not null,
  key_spec     jsonb  not null,   -- the tuple above, stored as data
  published_by text  not null
);

create table wave_run (
  id          bigint primary key,
  ran_at      timestamptz not null,
  site        text   not null,
  policy_id   bigint not null references queue_policy(id),
  order_ids   bigint[] not null   -- the sequence actually released
);
Enter fullscreen mode Exit fullscreen mode

Stamping every released wave with the policy row it used converts "why did this one go first" into a query instead of an interview. Changing the rule mid-peak is legitimate, and the version column is where it becomes visible as a change rather than disappearing as a disagreement about what the rule was.

Overrides are the part that needs a name

A queue with no human exit is a machine that will not help you during an outage. A queue with an unlogged exit is a machine that helps whoever asks loudest. The second table costs one line per event and it is the one that survives an audit.

create table wave_override (
  run_id     bigint not null,
  order_id   bigint not null,
  moved_from int    not null,
  moved_to   int    not null,
  reason     text   not null,     -- carrier_exception, customer_safety, channel_penalty, pricing_error
  actor      text   not null,
  noted_at   timestamptz not null default now()
);
Enter fullscreen mode Exit fullscreen mode

Reason as an enumerated string rather than free text, because the useful report is a count by reason. Peaks that quietly route around their own rule for one reason are a capacity problem. Peaks that route around it for four reasons are a policy nobody wrote.

Replay is the test worth writing

Take a day's snapshot, run the comparator against the policy version stamped on each wave, and diff the two sequences. Every position that differs should have an override row explaining it, and every override row should explain a difference. Anything else is a bug in one of the two, and it is far cheaper to find during a quiet week than during the week everyone is watching your delivery dates.

Property test the comparator too: antisymmetry and transitivity over a few thousand generated orders. Both fail in real implementations as soon as someone adds a fourth key that reads a mutable field, and both fail in ways that show up only when the queue is long.

Three Chinese buildings hold seller stock under the name FulfillNexa by SBT (fulfillnexa.com), 8,000 square meters in Dongguan, 13,000 in Suzhou and 3,000 in Shenzhen. Nothing above is a service claim, and no rate card or transit promise comes with the code either.

Top comments (0)