Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions docs/events/projections/async-daemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -642,9 +642,8 @@ that just emits and update every time that Marten has to "skip" stale events.

## Extended Progression Tracking

Extended progression tracking adds ten monitoring columns (`heartbeat`,
`agent_status`, `pause_reason`, `running_on_node`, `warning_behind_threshold`,
`critical_behind_threshold`, `failure_category`, `failure_event_sequence`,
Extended progression tracking adds eight monitoring columns (`heartbeat`,
`agent_status`, `pause_reason`, `running_on_node`, `failure_category`, `failure_event_sequence`,
`failure_event_type`, `failure_event_tenant_id`) to `mt_event_progression`. The async daemon writes
them from existing runtime state and the shard-state selector reads them back
into `ShardState` so monitoring tooling such as CritterWatch can display
Expand Down
63 changes: 63 additions & 0 deletions src/DaemonTests/Bugs/Bug_5173_behind_threshold_columns_removed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JasperFx.Events;
using Marten.Testing;
using Marten.Testing.Harness;
using Npgsql;
using Shouldly;
using Weasel.Postgresql;
using Xunit;

namespace DaemonTests.Bugs;

/// <summary>
/// #5173 — <c>warning_behind_threshold</c> and <c>critical_behind_threshold</c> were created in DDL,
/// listed in every extended-tracking SELECT, and hydrated onto <c>ShardState</c> — and written by
/// nothing, in any repo. Every read returned NULL, always. They are gone; what is left is a pin that
/// removing two columns from the middle of a positional selector did not shift the ordinals of the
/// <c>failure_*</c> columns that follow them.
/// </summary>
public class Bug_5173_behind_threshold_columns_removed: OneOffConfigurationsContext
{
[Fact]
public async Task the_threshold_columns_are_no_longer_created()
{
StoreOptions(x => x.Events.EnableExtendedProgressionTracking = true);
await theStore.EnsureStorageExistsAsync(typeof(IEvent));

var columns = await progressionColumnsAsync();

columns.ShouldNotContain("warning_behind_threshold");
columns.ShouldNotContain("critical_behind_threshold");

// ...and the eight columns that carry something are untouched.
foreach (var expected in new[]
{
"heartbeat", "agent_status", "pause_reason", "running_on_node", "failure_category",
"failure_event_sequence", "failure_event_type", "failure_event_tenant_id"
})
{
columns.ShouldContain(expected);
}
}

private async Task<List<string>> progressionColumnsAsync()
{
await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString);
await conn.OpenAsync(TestContext.Current.CancellationToken);

await using var reader = await conn
.CreateCommand(
"select column_name from information_schema.columns where table_schema = :schema and table_name = 'mt_event_progression'")
.With("schema", theStore.Events.DatabaseSchemaName, NpgsqlTypes.NpgsqlDbType.Varchar)
.ExecuteReaderAsync(TestContext.Current.CancellationToken);

var names = new List<string>();
while (await reader.ReadAsync(TestContext.Current.CancellationToken))
{
names.Add(reader.GetString(0));
}

return names;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected override void configure(ICommandBuilder builder)
// #5048 / jasperfx#565: the failure_* columns trail the existing extended block so the ordinals
// ShardStateSelector walks stay stable.
const string extendedColumns =
"heartbeat, agent_status, pause_reason, running_on_node, warning_behind_threshold, critical_behind_threshold, failure_category, failure_event_sequence, failure_event_type, failure_event_tenant_id";
"heartbeat, agent_status, pause_reason, running_on_node, failure_category, failure_event_sequence, failure_event_type, failure_event_tenant_id";

if (_events.UseOptimizedProjectionRebuilds && _events.EnableExtendedProgressionTracking)
{
Expand Down
14 changes: 3 additions & 11 deletions src/Marten/Events/Daemon/Progress/ShardStateSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,9 @@ public async Task<ShardState> ResolveAsync(DbDataReader reader, CancellationToke
}
nextIndex++;

if (!await reader.IsDBNullAsync(nextIndex, token).ConfigureAwait(false))
{
state.WarningBehindThreshold = await reader.GetFieldValueAsync<long>(nextIndex, token).ConfigureAwait(false);
}
nextIndex++;

if (!await reader.IsDBNullAsync(nextIndex, token).ConfigureAwait(false))
{
state.CriticalBehindThreshold = await reader.GetFieldValueAsync<long>(nextIndex, token).ConfigureAwait(false);
}
nextIndex++;
// #5173: WarningBehindThreshold / CriticalBehindThreshold were hydrated here from two
// columns nothing ever wrote. The properties remain on JasperFx's ShardState (unassigned
// and unread there too) until they are removed upstream.

// #5048 / jasperfx#565: rehydrate the classified failure so a consumer polling the database
// (CritterWatch when the publishing node is down) gets the same shape as a live ShardState
Expand Down
10 changes: 8 additions & 2 deletions src/Marten/Events/Schema/EventProgressionTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,14 @@ public EventProgressionTable(EventGraph eventGraph): base(new PostgresqlObjectNa
AddColumn("agent_status", "varchar(20)").AllowNulls();
AddColumn("pause_reason", "text").AllowNulls();
AddColumn("running_on_node", "integer").AllowNulls();
AddColumn("warning_behind_threshold", "bigint").AllowNulls();
AddColumn("critical_behind_threshold", "bigint").AllowNulls();

// #5173: warning_behind_threshold / critical_behind_threshold used to be created here.
// They were created, selected and hydrated onto ShardState -- and written by nothing, in
// any repo, so every read of them returned NULL. Two columns of storage, two entries in
// every extended-tracking SELECT and two selector ordinals, carrying nothing. Removed
// rather than wired: nothing ever owned the value. Existing deployments get an
// `alter table ... drop column` on their next apply, which is lossless because the
// columns were provably always NULL.

// #5048 / jasperfx#565: the classified reason this shard is paused or stopped, so a consumer
// polling the database (CritterWatch when the publishing node is DOWN, which is exactly when
Expand Down
Loading