Skip to content

Extended progression: running_on_node stays NULL under managed distribution (AssignedNodeNumber never stamped on published ShardState) #5001

Description

@jeremydmiller

Summary

The extended-progression running_on_node column stays NULL forever, even under genuine
Balanced multi-node managed distribution where the projection agents are assigned to a real node.
The sibling telemetry columns (heartbeat, agent_status, last_updated) write correctly on the
same code path and in the same row — only running_on_node never populates.

This is the last open gap from the extended-progression write path wired in marten#4990
(MartenDatabase.WriteExtendedProgressionAsync) / #537#538 (ExtendedProgressionWriter).
Core #750 is otherwise confirmed live on 9.17.1.

Root cause (traced end to end)

The write op consumes state.RunningOnNode:

// Marten: MartenDatabase.EventStorage.cs — WriteExtendedProgressionAsync
.With("node", (object?)state.RunningOnNode ?? DBNull.Value, NpgsqlDbType.Integer)

The JasperFx.Events observer that drives it tries to source that value from the agent's assigned
node number:

// JasperFx.Events: ExtendedProgressionWriter.OnNext (release/2.33.0, commit 33a2f46)
if (value.RunningOnNode == null && value.AssignedNodeNumber != 0)
{
    value.RunningOnNode = value.AssignedNodeNumber;
}

…but nothing ever stamps AssignedNodeNumber onto a runtime-published ShardState. A grep of
the entire JasperFx.Events tree finds AssignedNodeNumber = … in exactly one place — Marten's
ShardStateSelector, which populates it only when reading a row back from the DB (the
assigned_node column). The telemetry ShardState the daemon publishes on the heartbeat timer /
status transitions never has it set, so AssignedNodeNumber == 0, the carry block is dead, and the
write persists NULL.

So the carry logic is correct but its input is never supplied on the runtime publish path. The fix
is to stamp the agent's actual assigned node number onto the ShardState a subscription/projection
agent publishes (the ones ShardStateTracker broadcasts and ExtendedProgressionWriter observes).
Under Wolverine-managed subscription distribution the assignment lives in
wolverine_node_assignments / is known to the coordinator, so this likely spans JasperFx.Events (the
daemon publish surface) and the distribution layer that owns the assignment — happy to split this to
a jasperfx issue if that's the better home.

Reproduction (already written, green, pinning the gap)

CritterWatch src/EventStoreCoordinationTests/RunningOnNodeMultiNodeTests.cs boots two
MartenProjectionService nodes
, Balanced + UseWolverineManagedEventSubscriptionDistribution,
extended progression forced on via the CritterWatch #321 instrumentation path. The leader distributes
the Trip/Day/Distance agents across both nodes (logs show node-2 join and the distance agent
redistributing node 1 → node 2). After the daemon advances the Trip shard and a heartbeat tick lands,
projection_tests.mt_event_progression for the Trip row reads:

running_on_node = NULL,  agent_status = Running,  heartbeat = <set>,  last_seq_id = 30

heartbeat + agent_status write under genuine managed distribution; running_on_node stays NULL in
the same telemetry row → this is a write-path/publish gap, not a "single-node, nothing to stamp"
expected-NULL (that interpretation was ruled out — the agents are assigned to node_number 1, confirmed
via wolverine_node_assignments + wolverine_nodes).

The test is a characterization guard: it asserts the working columns and pins the gap with a
// FLIP-WHEN-FIXED marker on runningOnNode.ShouldBeNull(...). When this is fixed, that assertion
flips to ShouldBe(assignedNodeNumber).

Acceptance

running_on_node reflects the projection agent's assigned node number under Balanced managed
distribution, populated on the same heartbeat/transition publications that already write
heartbeat/agent_status.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions