DEV Community

WadeSterling3125
WadeSterling3125

Posted on

Batch Abort Over Video Cancellation in 2026 (When Moderation Sets the Boundary)

Pick batch cancellation for an e-commerce background-removal pipeline; reserve video cancellation for product clips, and keep their identifier boundaries separate. The deciding constraint is moderation coverage: stopping image work must contain the whole affected catalog group without accidentally reaching a video job that happens to carry a similar ID.

That is the short answer. It also points to the real design problem. Cancellation isn't a generic red stop button. It is a command against one job type, with a defined blast radius, while moderation determines which derived assets may become visible after that command.

For a one-person SaaS, this distinction protects revenue-per-hour. Time spent untangling ambiguous aborts is time not spent shipping the next weekly release.

How should batch cancellation and video cancellation boundaries differ?

A batch boundary groups related image operations. In this example, a merchant uploads a set of product photos, the system removes each background, and moderation decides whether each result may move toward publication. The batch ID belongs to that image workload. Cancelling it should target the image-batch operation.

A video boundary is narrower in kind, even when the video contains many frames. A product-demo clip is still a video job, so its ID belongs to the video namespace and its cancellation command goes to the video operation. The number of frames doesn't turn it into an image batch. Container and codec choices add another axis for video inputs; MDN's media format guide is useful for that compatibility decision, but file format should not decide the cancellation namespace.

The trap is a shared jobId: string passed through every layer. Two IDs can look alike while naming different kinds of work. If an admin screen sends only the bare string, the API layer has to guess which cancellation boundary the operator intended. Don't make it guess.

Use the job kind as part of the identity instead:

Work item Identifier scope Cancellation boundary Publication concern
Product photo set Image batch All unfinished work in that batch No unapproved derivative becomes visible
Product demo clip Video job That video job No output from the cancelled job advances

This is also why a browser AbortController isn't a substitute for job cancellation. It can stop the client from waiting on a request; it doesn't express the server-side intent to stop a media job. The durable command needs the typed job identifier, and the application needs to record that the command was requested.

The smallest implementation that keeps the boundary visible

The useful abstraction is tiny. Give the compiler a discriminated union, keep the two real routes next to their job kinds, and refuse to accept an untyped string at the cancellation entry point.

type CancellableJob =
  | { kind: "image-batch"; id: string }
  | { kind: "video"; id: string };

type CancellationResult = {
  requested: true;
  job: CancellableJob;
};

async function cancelMediaJob(
  apiOrigin: string,
  apiKey: string,
  job: CancellableJob,
): Promise<CancellationResult> {
  const encodedId = encodeURIComponent(job.id);
  const path =
    job.kind === "image-batch"
      ? `/v1/image/batch/cancel/${encodedId}`
      : `/v1/video/cancel/${encodedId}`;

  const response = await fetch(new URL(path, apiOrigin), {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (!response.ok) {
    throw new Error(`Cancellation request was not accepted: ${response.status}`);
  }

  return { requested: true, job };
}
Enter fullscreen mode Exit fullscreen mode

The return value deliberately says requested, not cancelled. A successful cancellation request and a fully settled job are separate moments. The surrounding worker can be finishing an item at the same time the operator presses cancel, so the application still needs to reconcile job state before it changes what shoppers can see.

For background removal, keep the original upload. That gives the workflow a stable source if moderation changes the decision or the merchant starts a replacement batch. It also avoids coupling an operator's abort decision to an unnecessary re-upload. The derived cutout, its moderation state, and its source reference should remain distinct records.

Here is the state transition rule in plain TypeScript. It is intentionally stricter than a UI spinner:

type AssetState =
  | "processing"
  | "cancel-requested"
  | "cancelled"
  | "awaiting-moderation"
  | "approved"
  | "rejected";

type ProductAsset = {
  sourceKey: string;
  derivativeKey?: string;
  state: AssetState;
};

function canPublish(asset: ProductAsset): boolean {
  return asset.state === "approved" && asset.derivativeKey !== undefined;
}

function requestCancellation(asset: ProductAsset): ProductAsset {
  if (asset.state !== "processing") return asset;
  return { ...asset, state: "cancel-requested" };
}
Enter fullscreen mode Exit fullscreen mode

The key invariant is boring and valuable: only approved derivatives publish. A cancellation request doesn't approve anything, and a late-arriving derivative doesn't bypass moderation. This is where the application boundary does more work than the cancel endpoint itself.

Ship that first.

What changes when the catalog grows?

At larger scale, add an append-only operation record around the same typed command. Record the job kind, scoped ID, request time, actor, and the last observed lifecycle state. Then make reconciliation idempotent: reading the same terminal state twice must not publish twice, restore cancelled work, or enqueue duplicate follow-up work.

Moderation coverage deserves its own metric. A useful numerator is the count of derivatives that reached an approval decision; the denominator is the count of derivatives eligible to be reviewed for that batch. This isn't a universal industry formula — the exact denominator depends on when a system considers an image derivative valid — but writing the definition down exposes gaps hidden by a single “batch done” percentage. I'm not sure a cross-product metric can be more precise without a shared lifecycle definition.

Observe latency in separate pieces as well: time from cancel request to a terminal job state, time from derivative creation to moderation decision, and time from approval to publication. One blended duration hides the stage that needs attention. For the same reason, output quality, lifecycle complexity, operator control, and latency should be evaluated separately on representative catalog inputs rather than collapsed into one score.

Representative means the awkward merchandise, too — translucent glass, white shoes on a white backdrop, products held by a model, and a batch containing several views of one SKU. Those cases reveal whether cancellation and moderation preserve a coherent product set. A synthetic square with a clean foreground edge won't tell an operator what happens when three of eight derivatives finish before the abort request and the remaining five stop later. The numbers here describe a test fixture, not a benchmark; use a fixture shaped like the catalog the store actually receives.

The queue can remain an internal detail. Outsource the undifferentiated media operation if that improves shipping cadence, but own the state machine, identifier typing, moderation rule, and publication gate. Those pieces encode the product's promise to merchants.

Where does this choice stop working?

Batch cancellation is the wrong default when the unit users understand is one independent image and cancelling neighboring images would be surprising. In that case, use a per-image job boundary and expose that boundary consistently in the UI and audit record. Likewise, stick with the video-specific path when the asset is a product clip; routing it through the image-batch abstraction would erase the media lifecycle distinction the system needs.

There is another catch: cancellation is not rollback. If an approved derivative was already published before the abort request, the cancellation command alone should not be treated as evidence that the storefront copy disappeared. Removal from publication needs its own explicit state transition and verification. Keep those commands separate even if one operator action initiates both.

The practical decision rule is firm. Default to image-batch cancellation for grouped background-removal work, switch to a per-image boundary when partial abort is the actual product requirement, and use video cancellation only for video jobs. Preserve the original asset in every branch. Separate namespaces make the rule enforceable; moderation-aware publication makes it safe.

References

Top comments (0)