openlang

ON CONFLICT DO NOTHING and retried jobs

Answering every unique key conflict with "already accepted" hides three different situations.

Two servers, one PostgreSQL table keyed by a request identifier. A client times out and retries, maybe with a changed payload, maybe after the first attempt already triggered an external action. ON CONFLICT DO NOTHING keeps the second row out and compares nothing. Whatever the Go handler does with its context afterwards, the remote work has already happened.

What's the smallest durable record that tells a matching retry from changed input, and both from work whose outcome nobody knows yet?

后续讨论 5

顺着彼此的依据读下去
  1. 固定链接

    Put the client's deadline ahead of the external reply. The client stops waiting. The worker doesn't. When that late success shows up, it has to land on the same durable job, even if the retry was already told the outcome is unresolved.

  2. 固定链接

    Start from the invariant. Each identifier has one and only one accepted payload identity, and a conflict says nothing about whether the new request matches it or whether the job finished. Read the stored row and compare identities before you pick a response. Under Read Committed, a plain SELECT sees a snapshot taken when that command starts, so be explicit about which later query reads the state you report.

  3. 固定链接

    That seems manageable without adding much. Tag the late result with the original job identifier, and let one small status lookup report the job as running, unresolved or completed. Then the retry and the late success end up talking about the same job.

  4. 固定链接

    The worst spot is narrower than a timeout. The external service has accepted the action, and the result write never happens. Whoever picks the job up next finds no result and nothing that says whether submitting again is safe.

    So keep the accepted payload identity, the execution state, any downstream correlation identifier and the eventual response in the row with the request identifier. The next attempt decides its step from that row alone.

    Even then the remote success can land before the write. If the external service can look up an action by correlation identifier, reconcile. If it documents idempotency keys, give it the accepted job's key. If it offers neither, leave the outcome unresolved and settle the recovery policy before anything is resubmitted.

    A retry with different input gets a mismatch and leaves the accepted job alone. A matching retry gets the stored result, a running status or that unresolved outcome.

  5. 固定链接

    The two statement version is right, and the wait is what makes it work. A inserts the identifier inside an open transaction. B runs INSERT ... ON CONFLICT (request_id) DO NOTHING RETURNING id and waits, because the conflicting row isn't committed yet. A commits, B gets zero rows back, and B's next SELECT under Read Committed sees A's row with its payload identity. If A rolls back instead, B's insert goes through and returns the new id.

    So the empty RETURNING is the signal to read. The docs note the DO NOTHING case works this way only under Read Committed, so I wouldn't assume the same steps under Repeatable Read.

同一个问题的其他角度

  1. Workers stuck on the results send after the context is canceled

    Ran into a simple thing and spent longer on it than I'd like. A pool of eight workers reads tasks from jobs and writes result…

    回复 8最近活动 go
  2. Eight queue slots can still leave 88 goroutines waiting

    A buffered channel bounds the jobs sitting in it. It does nothing about the goroutines blocked trying to send. Say a server g…

    回复 8最近活动 go
  3. Marking the filter async did nothing for the freeze

    Search filters a list of around forty thousand rows, and the UI freezes for a moment on every query. I figured async was the…

    回复 8最近活动 dart