Skip to content

Implement Known Endpoints persistence with EF Core insert-only tables#5625

Merged
johnsimons merged 7 commits into
masterfrom
john/ingestion_part2
Jul 21, 2026
Merged

Implement Known Endpoints persistence with EF Core insert-only tables#5625
johnsimons merged 7 commits into
masterfrom
john/ingestion_part2

Conversation

@johnsimons

Copy link
Copy Markdown
Member

Introduce KnownEndpointEntity and KnownEndpointInsertOnlyEntity tables for efficient, eventual-consistent storage of discovered endpoints. A background reconciler processes new endpoint entries from the insert-only table into the main table, handling duplicates and maintaining existing 'Monitored' state.

This completes the EF Core integration for the IMonitoringDataStore.

Updates test contexts for database teardown and draining of insert-only tables.

@johnsimons johnsimons self-assigned this Jul 20, 2026
@johnsimons
johnsimons force-pushed the john/ingestion_part2 branch 2 times, most recently from e45b529 to 58b2c1f Compare July 21, 2026 01:38
{
var sql = """
WITH lock_check AS (
SELECT pg_try_advisory_xact_lock(hashtext('known_endpoints_sync')) AS acquired

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need locks so that if the instance is scalled out for some reason, the code still works correctly

// Call base first to apply entity configurations
base.OnModelCreating(modelBuilder);

// Apply snake_case naming convention for PostgreSQL

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using snake_case is the official de facto standard for PostgreSQL database objects because the system automatically folds all unquoted SQL identifiers to lowercase

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason for not using EFCore.NamingConventions to do this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, I have modified it to use it

<ItemGroup>
<!-- Private=false & ExcludeAssets=runtime prevent repeatedly including binary dependencies of ServiceControl.Persistence and its dependencies in each persister directory -->
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<!-- Private=false & ExcludeAssets=runtime prevent repeatedly including binary dependencies of ServiceControl.Persistence

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is important otherwise dotnet ef migrations does not work, It can't find dependencies

{
var sql = """
DECLARE @lockResult INT;
EXEC @lockResult = sp_getapplock @Resource = 'known_endpoints_sync', @LockMode = 'Exclusive', @LockOwner = 'Transaction', @LockTimeout = 0;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as PostgreSQL lock but this time for SqlServer


public class KnownEndpointEntity
{
public Guid Id { get; set; }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is ok to use Guid here, given that we are setting it explicitly


public class KnownEndpointInsertOnlyEntity
{
public long Id { get; set; }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is the insert only table, so I want things to go fast

using var scope = host.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<PostgreSqlServiceControlDbContext>();
await db.Database.EnsureCreatedAsync();
await db.Database.MigrateAsync();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using migrations now, this ensures that side is also correct.

@johnsimons
johnsimons marked this pull request as ready for review July 21, 2026 01:57
@johnsimons
johnsimons force-pushed the john/ingestion_part2 branch from 42dc604 to 885d4d0 Compare July 21, 2026 03:39
// Call base first to apply entity configurations
base.OnModelCreating(modelBuilder);

// Apply snake_case naming convention for PostgreSQL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason for not using EFCore.NamingConventions to do this?

Host NVARCHAR(MAX)
);

DELETE TOP (@batchSize) FROM KnownEndpointsInsertOnly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you avoid the temp table by just putting the Delete/Output into the CTE directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data-modifying CTEs are a PostgreSQL feature. SQL Server doesn't allow DELETE/INSERT/UPDATE inside a WITH clause at all.

@@ -9,19 +8,19 @@ namespace ServiceControl.Persistence.EFCore.Implementation.UnitOfWork;
public class EFIngestionUnitOfWork : IIngestionUnitOfWork

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this and the other persistence interface implementations be internal?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can fine tune that later, but the TL;DR; is that it doesn't really matter, this is not an API

}
catch (DbUpdateException)
{
// A concurrent insert with the same deterministic id may have won the race

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this explicitly check for the key violation error type?
should it also log a debug logline?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have modified this to confirm that is a key violation by revalidating the data.

@johnsimons
johnsimons force-pushed the john/ingestion_part2 branch from 885d4d0 to 3362d69 Compare July 21, 2026 04:34
Introduce `KnownEndpointEntity` and `KnownEndpointInsertOnlyEntity` tables for
efficient, eventual-consistent storage of discovered endpoints. A background
reconciler processes new endpoint entries from the insert-only table into the
main table, handling duplicates and maintaining existing 'Monitored' state.

This completes the EF Core integration for the `IMonitoringDataStore`.

Updates test contexts for database teardown and draining of insert-only tables.
Refactor `InsertOnlyTableReconciler` to be non-generic, simplifying its declaration.
Integrate `TimeProvider` for precise control over delays during testing.
Add a `ReconcileNow` method to immediately trigger reconciliation cycles,
bypassing the background service's periodic timers. This enables tests to
drain insert-only tables without waiting for real time.
Update persistence test contexts to leverage `FakeTimeProvider` and the new
`ReconcileNow` method, reducing test setup complexity and coupling to specific
reconciler implementations.
Ensures that all properties of `MessageBodyFileResult` are explicitly
initialized upon object creation, preventing nullability issues and
improving the integrity of message body data.
@johnsimons
johnsimons force-pushed the john/ingestion_part2 branch from 3362d69 to 510ed9c Compare July 21, 2026 05:22
Comment thread src/ServiceControl.Persistence.EFCore/Entities/KnownEndpointEntity.cs Outdated
Comment thread src/ServiceControl.Persistence.Tests.SqlServer/PersistenceTestsContext.cs Outdated
Co-authored-by: Warwick Schroeder <warwick.schroeder@gmail.com>
@johnsimons
johnsimons enabled auto-merge July 21, 2026 21:24
@johnsimons
johnsimons merged commit 2f66be6 into master Jul 21, 2026
33 checks passed
@johnsimons
johnsimons deleted the john/ingestion_part2 branch July 21, 2026 21:39
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.

3 participants