Replies: 1 comment
-
|
+1 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
We run several app replicas against one shared Postgres, and we keep seeing the same step run on more than one replica at the same time. We can't tell from the docs whether that's expected (and we should make our steps idempotent) or something we're getting wrong. Before we design around it, we'd like to know what the intended model is.
Per #64, there's no external runner and WDK is a client-side library backed by a world. So on a self-hosted setup the graphile-worker runner is embedded in each app instance, and scaling the app horizontally scales the runners with it. That's the situation the questions below are about.
Environment
@workflow/world-postgres@4.2.0, graphile-worker, one shared Postgres.createWorld(...)and starts the runner. We haven't changed anything in the queue or the runner; we useworld-postgresas published. Our HTTP framework integration is custom, but the world, the runner, and the enqueue path are stock.What we see
Within a single run, one step runs on two pods at once:
attemptcounter reaches 2 or 3, andstep_startedis written 2 or 3 times for the same(run_id, correlation_id).step_completedlands (the other writer's completion is rejected), so the event log looks clean. But the side effect ran twice, and so did the streamed output. In our case the visible result is a durable output stream with a second, dangling copy of the step's events.As far as we can tell, the concurrency behavior sits in
world-postgres, not in any framework adapter. The runner (run()) starts once per process, and across processes there is no shared execution dedup. The only guards are in-process: theinflightWorkflowRunsserialization keyed byworkflow:${runId}, and thecompletedMessages/inflightMessagescaches. None of those cross pods. Thestep_startedentity guard (notInArray(status, terminalStepStatuses)) still matches a step that another pod has already set torunning, so a second replica re-enters and runs the body. We're not suggesting that as the thing to change; we point at it only to show where the behavior comes from. The same multi-pod setup was already reported for a different symptom in #2039 ("three HPA-scaled pods against the same Postgres World"), so we don't think this is specific to our integration.Prior art we read first
step_createdunder concurrent workers, fixed with the entity-creation unique index. That's event-level, not execution-level.step_startedlanding afterstep_completed; Fix Postgres step lifecycle event ordering #2714 puts the lifecycle update and its event write in one transaction for log consistency. That's ordering, not execution dedup.start(), post-completion dedupe).lockprimitive.None of them say what guarantee step execution has when the workers are horizontally scaled.
Questions
world-postgresmeant to run as several concurrent embedded runners (horizontally scaled replicas against one Postgres), or is a single worker process the intended model?Impact
Right now we can't scale the app horizontally without steps running more than once, which pushes us toward a single worker instance for otherwise stateless work. That's a scaling ceiling and a single point of failure, so we want to match the intended model before building around it.
Beta Was this translation helpful? Give feedback.
All reactions