Skip to content

avoid very small amount of alloc by using ValueTask.CompletedTask#486

Merged
jeremydmiller merged 1 commit into
JasperFx:mainfrom
SimonCropp:remove-new-ValueTask
Aug 11, 2023
Merged

avoid very small amount of alloc by using ValueTask.CompletedTask#486
jeremydmiller merged 1 commit into
JasperFx:mainfrom
SimonCropp:remove-new-ValueTask

Conversation

@SimonCropp

Copy link
Copy Markdown
Contributor

No description provided.

@jeremydmiller

Copy link
Copy Markdown
Member

@SimonCropp I'd read somewhere that this wasn't actually helpful. Something to do w/ heap vs stack mechanics

@SimonCropp

Copy link
Copy Markdown
Contributor Author

@jeremydmiller news to me. can u find a reference?

@jeremydmiller jeremydmiller added this to the 1.6.0 milestone Aug 10, 2023
@jeremydmiller
jeremydmiller merged commit a228d5c into JasperFx:main Aug 11, 2023
@SimonCropp
SimonCropp deleted the remove-new-ValueTask branch August 12, 2023 01:00
jeremydmiller added a commit that referenced this pull request Jul 7, 2026
…urn/failover e2e, #4862 regression pin (#486) (#3334)

* Consume Marten 9.13.0-alpha.3 + JasperFx 2.21.0 (epic #486 WS0/WS1)

Marten 9.13.0-alpha.3 carries the per-tenant distribution work this PR's
e2e tests exercise: marten#4864 (broadened DistributesAgentsPerTenant gate
+ single-DB descriptor TenantIds), marten#4865 (ExternallyManaged honored),
marten#4869 (per-tenant high-water advancement — unblocks the failover
floor-continuity test), and marten#4870 (native per-tenant distribution).

JasperFx/JasperFx.Events pins move 2.20.0 -> 2.21.0 to match what Marten
alpha.3 already pulls transitively — keeps the explicit pins from drifting
below the resolved graph. No envelope-operation contract drift from
marten#4872 (Weasel 9.12 metadata binders): Wolverine.Marten's
StoreIncomingEnvelope/StoreOutgoingEnvelope compile unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* E2E: runtime tenant churn under Wolverine-managed distribution (#486 WS1)

Sharded + UseTenantPartitionedEvents, single node: with tenant-a's per-tenant
agent already projecting, add tenant-b to the same shard database at RUNTIME via
Advanced.AddTenantToShardAsync. Within the assignment-evaluation window
(CheckAssignmentPeriod = 1s) the leader re-enumerates the store usage, fans out
the ":all/tenant-b" agent, tenant-b's fresh seq-1.. events project, its
HighWaterMark-independent PartitionedCounter:All:tenant-b progression row lands
in the shard database, and no store-global (tenant-less) agent ever runs
alongside the per-tenant agents (the #3328 stale-agent-retirement edge).

Tenant REMOVAL is deliberately not covered end-to-end: Marten 9.13.0-alpha.2
exposes RemoveTenantAsync/DisableTenantAsync (ShardedTenancy.cs:378/:470), but a
running store's usage never shrinks - MartenDatabase.TenantIds is add-only
(Fill at ShardedTenancy.cs:211/:364) and DescribeDatabasesAsync copies that
in-memory list, so Wolverine keeps enumerating the removed tenant's agent until
a process restart. The Wolverine half of the removal transition stays pinned by
the running_per_tenant_agent_is_stopped_when_its_tenant_is_removed unit test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* E2E (pinned/skipped): failover preserves per-tenant progression floors (#486 WS1)

Two nodes, sharded + UseTenantPartitionedEvents, two tenants co-located on one
shard database: append N events per tenant, record each tenant's progression
floor, kill the node owning the affine agent group, assert the survivor picks
both agents up and resumes tenant-b exactly from its floor (doc == N+M, no
skips, no rewind) after M more post-failover events.

SKIPPED, pinning a Marten 9.13.0-alpha.2 product bug found by this test: the
failover mechanics work (survivor restarts both agents, floors never rewind,
tenant-a intact), but per-tenant high-water detection FREEZES at the first
persisted HighWaterMark:<tenant> row -
HighWaterDetector.loadPerTenantStatistics
(src/Marten/Events/Daemon/HighWater/HighWaterDetector.cs:260):
    var currentMark = lastSeqId > 0 ? lastSeqId : lastValue;
Once the row exists, CurrentMark is pinned to it and max(seq_id) is never
consulted again, so ANY second batch of events per tenant never projects (not
failover-specific; sibling tests all append one batch per tenant and never see
it). Empirically confirmed: tenant-b at max seq_id 11 committed, store-global
HighWaterMark advanced to 11 (global detection + per-tenant poll trigger both
firing), HighWaterMark:tenant-b frozen at 6. Unskip on the Marten-side fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Unskip the failover floor-continuity test — fixed by marten#4869 in 9.13.0-alpha.3

The skip pinned marten#4867: per-tenant high-water detection froze at the first
persisted HighWaterMark:<tenant> row, so the M post-failover tenant-b events
never projected. Marten 9.13.0-alpha.3 advances per-tenant CurrentMark past the
persisted row (marten#4869), which makes the final leg pass. The class comment
keeps a condensed history of the pinned bug.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Regression test for marten#4862: single-DB tenant-partitioned per-tenant distribution (#486 WS1)

Promotes the WS1 single-database fact-finding probe (found the bug 2026-07-06)
into the marten#4862 regression test. Against Marten 9.13.0-alpha.2 a
single-database store reported DistributesAgentsPerTenant = false, so managed
distribution ran ONE store-global agent whose progression floor (driven to the
leading tenant's max per-tenant seq) permanently skipped a lagging tenant's
later events at per-tenant seq 1..N — the wolverine#3280 failure mode on one
database. Marten 9.13.0-alpha.3 (marten#4864) broadens the gate to any
tenant-partitioned store, so the startup expectation flips to per-tenant
fan-out (2 tenants -> 2 agents) and the lagging-tenant projection assertions
now pass unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Flip single-DB distribution granularity tests to per-tenant fan-out (marten#4862/#4864)

With Marten 9.13.0-alpha.3, DistributesAgentsPerTenant is true for ANY
tenant-partitioned store, so one async projection over a single
tenant-partitioned database now fans out one subscription agent per
managed tenant instead of a single store-global "/all" agent whose lone
progression floor could silently skip a lagging tenant.

- tenant_partitioned_distribution_granularity: 1 agent -> 3 (one per
  registered tenant, default tenant included), agent URIs carry the
  tenant in the shard path
- tenant_partitioned_distribution_multinode: 2 store-global agents -> 6
  per-tenant agents (2 projections x 3 tenants), spread 3+3 across the
  cluster and all reassigned to the survivor on node loss
- AssignmentWaiter.WritePersistedActualsAsync no longer throws
  KeyNotFoundException when the database holds a node record the waiter
  isn't tracking (ghost rows from a hard-stopped prior run); it reports
  the untracked node instead so the TimeoutException diagnostic survives

Part of #486 WS1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Ancillary stores: dispatch message-store shape on database cardinality, not tenancy type name

Marten 9.13.0-alpha.3 (marten#4864) gives a single-database store with
UseTenantPartitionedEvents a TenantPartitionedSingleDatabaseTenancy — a
DefaultTenancy subclass, chosen precisely so 'is DefaultTenancy' dispatch
keeps working. The ancillary IntegrateWithWolverine seam compared
Tenancy.GetType().Name == "DefaultTenancy" instead, so such a store was
misrouted to the multi-tenanted message-database path and host startup
failed with "no configured connectivity for the required master PostgreSQL
message database" after 20 broker retries (the CIMarten failure on this PR).

Both the Marten and Polecat ancillary seams now dispatch on
Tenancy.Cardinality == DatabaseCardinality.Single, mirroring the main-store
integration (WolverineOptionsMartenExtensions), which is why only ancillary
stores were affected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 7, 2026
#492/#493) (#3335)

JasperFx 2.21.1 drives the vectorized per-tenant high-water poll from the
daemon's own SlowPollingTime timer instead of solely from store-global mark
changes, so a lagging tenant appending below the global max converges without
unrelated tenant activity. Epic #486 (WS1 residue).

Full wolverine.slnx Release build + MartenTests.Distribution folder green
locally (31 tests, net9.0).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 7, 2026
#494-#496) (#3336)

JasperFx 2.22.0 bounds the async daemon's database connection footprint per
database: MaxConcurrentEventLoadsPerDatabase (event-load throttle) and
MaxConcurrentBatchWritesPerDatabase (projection batch-write governor), both
default 4 and on by default, plus a bounded CrossTenantRebuild default.
Measured at 200 per-tenant agents on one database: peak connections drop
from 47-99 to a deterministic 12-14 with unchanged throughput and catch-up
(epic #486 WS2+WS3; numbers on #494).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 7, 2026
…tabase rebuild cap, #420/#463, epic #486 WS3) (#3338)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 14, 2026
…3422)

Connections are a resource of the database server, not of a logical database.
That distinction is invisible when an application talks to one database, and
load-bearing in the sharded-tenancy deployment #3397 comes from: hundreds of
tenant databases spread over a handful of Postgres clusters, all drawing on the
same finite pool, with the failures showing up as diffuse things like a schema
apply job that cannot get a connection.

This is phase 1 only: measure and surface. Wolverine reports connection pressure;
it does not yet react to it. The adaptive back-off half needs hooks that live in
JasperFx.Events' DaemonSettings (the polling knobs and the #486 governors are not
Wolverine's), and it needs #3376 to resolve first since that changes the baseline
any tuning would work against.

- DatabaseServerId keys the budget by (engine, host, port). DatabaseDescriptor's
  ServerName is host-only, so two clusters co-hosted on one box would otherwise
  collide onto a single budget. SQL Server leaves Port null, since its Data Source
  already carries the port or named instance.
- IConnectionBudgetProbe is an optional store capability, implemented for
  PostgreSQL and SQL Server. Providers without a cheap server-wide connection
  count simply do not implement it and drop out of the sweep.
- The probe rides the existing metrics sweeper and is deduped by server: one query
  per server per pass, however many tenant databases the node owns. Probing per
  database would multiply the very pressure the number exists to reveal.
- The max side of the budget is explicit configuration, not the server's
  max_connections. Behind a pooler, the server's limit describes what the pooler
  may open, not what the application is entitled to take; charting utilization
  against it would be reassuring and wrong. A probed limit is the labelled fallback
  when nothing is declared, and "unknown" is reported honestly when neither exists
  (SQL Server without VIEW SERVER STATE degrades to one warning, not a failed sweep).
- Surfaced as OTel gauges tagged by server, and as IWolverineObserver.ConnectionBudget
  for CritterWatch. The observer member is a default no-op, so existing observers are
  unaffected.

Active by default only for the statically-known-multiple-databases shape (Marten's
MultiTenantedWithShardedDatabases), where a per-server number tells you something a
per-database count cannot. Declaring a budget is itself an opt-in.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants