Skip to content

server: idempotency keys (or natural at-most-once) for retried non-idempotent writes — /branches/merge foremost #416

Description

@FlorianSeemann

Summary

Retries are the default behavior of production HTTP clients (timeouts, connection resets, 5xx). For most Omnigraph routes that's safe — /load --mode merge upserts by slug, /query is a read. But /branches/merge is not idempotent under concurrency: a retry that races the still-executing first request double-applies the merge and produces duplicate rows for the same @key value on the target (observed in production on v0.8.1 — details and impact in #415).

Today the only safe client policy is "never retry a merge," which has to be discovered the hard way and re-implemented in every client.

Ask

Either (or both):

  • Idempotency-Key request header on non-idempotent writes (/branches/merge; arguably /mutate for callers whose mutations aren't naturally idempotent). Server stores the key with the operation's outcome for a bounded window; a replay with the same key returns the stored outcome instead of re-executing. This is the established pattern (Stripe-style) and makes client retry policy uniformly safe.
  • Natural at-most-once for merge: key the merge on (source branch, source head version, target). If that source head has already been merged into the target — or a merge job for it is currently in flight — return the prior/attached outcome (already_up_to_date) instead of computing and applying a second diff. Git semantics already imply this; it just needs to hold under concurrent requests, not only sequential ones.

The second option needs no client changes at all and closes the double-apply hole even for clients that retry naively.

Related: #415 (async merge job API — the two compose: job dedup by key), #384 (slow merges are what make client timeouts + retries common).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions