Skip to content

ReadProjectionProgressAsync reports the persisted heartbeat and agent status (#5172) - #5180

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/5172-read-progress-extended-columns
Aug 4, 2026
Merged

ReadProjectionProgressAsync reports the persisted heartbeat and agent status (#5172)#5180
jeremydmiller merged 1 commit into
masterfrom
fix/5172-read-progress-extended-columns

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #5172.

The bug

Both ReadProjectionProgressAsync overloads hardcoded AgentStatus and LastHeartbeat to null and never selected the columns. The XML docs justified it:

ProjectionProgressRow.AgentStatus and ProjectionProgressRow.LastHeartbeat are always null: Marten models the columns but no daemon path writes them (#519).

That was true when it was written. Since #537 the daemon does write them — ExtendedProgressionWriterWriteExtendedProgressionAsync populates heartbeat and agent_status on every flush, and AllProjectionProgress reads them back correctly through ProjectionProgressStatement + ShardStateSelector. Only these read paths were never updated.

The sting: #435 added the targeted per-cell read specifically so a monitor polling one visible cell wouldn't have to pull every projection × tenant row on each tick. A caller taking that supported path to ask "is this shard's agent alive" got NULL, with no way to tell a placeholder from a fact — while the row-scan path they were told to avoid returned the real value.

The fix

Both queries select agent_status, heartbeat when EnableExtendedProgressionTracking is on, and hydrate them into ProjectionProgressRow. With extended tracking off the columns don't exist on mt_event_progression, so the narrow column list is kept and both fields stay null — that's a fact ("this store doesn't track it"), not a placeholder.

In the version-collapsing overload the telemetry is captured from the row that wins the newest-version/highest-sequence contest, not from whichever candidate happened to be read first.

Kept minimal per the issue: ProjectionProgressRow has no fields for pause_reason, running_on_node or the failure_* columns, and monitors wanting those still have AllProjectionProgress.

Not in this PR

ProjectionProgressRow's own doc comment in JasperFx (src/JasperFx.Events/ProjectionProgressRow.cs:21-25) carries the same stale claim ("Neither Marten nor Polecat writes it today"). That's a JasperFx change and is left as a hand-off.

Tests

DaemonTests/Bugs/Bug_5172_read_progress_extended_columns.cs — 5 cases:

  • the projection-name overload reads back the persisted status + heartbeat
  • the exact-ShardName overload does too, on a tenant-bearing identity
  • the winning version supplies the telemetry (an older version's stale Stopped/heartbeat must not leak through)
  • an undecorated row still reports nulls
  • with extended tracking off, both overloads still work and report nulls (no undefined-column failure)

All 5 pass, along with the existing 8 read_projection_progress tests (net10.0).

🤖 Generated with Claude Code

https://claude.ai/code/session_017CTtw2kVRSZKp1p5RTxgAy

… status (#5172)

Both overloads hardcoded `LastHeartbeat` and `AgentStatus` to null and never selected the
columns, on the rationale that Marten modelled them but no daemon path wrote them
(#519). That stopped being true at #537 — `ExtendedProgressionWriter`
populates both on every flush, and `AllProjectionProgress` reads them back correctly.

So the targeted per-cell read that #435 added *specifically* as the monitoring
alternative to scanning every row returned NULL for exactly the two fields a monitor calls
it for, with nothing to distinguish a placeholder from a fact — while the expensive path it
was meant to replace returned the truth.

Both queries now select `agent_status` and `heartbeat` when
`EnableExtendedProgressionTracking` is on and hydrate them into the record. With extended
tracking off the columns are not on the table, so the narrow column list is kept and both
fields stay null. In the version-collapsing overload the telemetry follows the winning row
rather than whichever candidate was read first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CTtw2kVRSZKp1p5RTxgAy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReadProjectionProgressAsync hardcodes LastHeartbeat/AgentStatus to null on a stale rationale

1 participant