perf(central): set-based ingest, aligned partition purge, KPI query shapes, EF hygiene

This commit is contained in:
Joseph Doherty
2026-08-14 21:07:12 -04:00
parent ee193cd2bb
commit 5db2a810c0
29 changed files with 3790 additions and 266 deletions
@@ -26,6 +26,22 @@ public static class ServiceCollectionExtensions
// registers IDataProtectionProvider as a singleton; resolving it here does not recurse
// because key-ring loading is lazy (first Protect/Unprotect), not triggered by
// CreateProtector during model building.
//
// POOLING IS DELIBERATELY NOT USED (WP2.2 — verified, not overlooked).
// AddDbContextPool requires a context with a SINGLE public constructor taking
// only DbContextOptions<TContext>; EF Core constructs pooled instances through
// its own activator and cannot supply anything else. ScadaBridgeDbContext has
// two public constructors and the runtime one takes IDataProtectionProvider,
// because the encrypting value converter for secret-bearing columns is built
// during OnModelCreating from that provider. Worse, the model itself DIFFERS
// between the two constructors (no provider ⇒ no encrypting converter), so a
// pooled activator would silently produce a context that reads secret columns
// as ciphertext. Making this poolable means moving the protector out of the
// constructor and into a DbContextOptions extension — a change to the
// secrets-at-rest path, which is not a performance refactor. The registration
// below (a scoped factory overriding AddDbContext's activator) is what makes
// the provider reach the context at all, and it also bypasses pooling by
// construction. Revisit only alongside a deliberate secrets-plumbing change.
services.AddDbContext<ScadaBridgeDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(