GH-3683: make conjoined tenant partition bucketing actually work - #3688
Merged
Conversation
…H-3683) Tenant "bucketing" -- registering several small tenants against one partition suffix so they share a physical partition -- is documented on the EF Core multi-tenancy page and exposed via ConjoinedTenancyOptions.PartitionPerTenant(p => p.AllowPartitionSharing = true), but it did not work on either engine. It had no test coverage, which is why this went unnoticed. Both defects were reproduced against real PostgreSQL and SQL Server. The root cause was in Weasel and is fixed by JasperFx/weasel#392 (9.20.0): - PostgreSQL emitted one single-value ListPartition per tenant, so the second member of a bucket was swallowed by CREATE TABLE IF NOT EXISTS and its first write failed with 23514. Widening an existing partition needs DETACH + re-ATTACH, which IF NOT EXISTS cannot express. - SQL Server's ordinal registry had no column for the bucket, so a tenant joining an existing bucket in a later call could not discover its ordinal. Wolverine changes here: PostgresqlTenantPartitioning needs no change at all -- it hands Weasel the tenant -> suffix map and the fixed Weasel does the right thing. SqlServerTenantPartitioning was resolving the bucket's ordinal itself out of the tenant -> ordinal map, which is blind to buckets: for a brand new tenant the lookup matched nothing and it allocated a fresh ordinal, so sequentially registered members silently never shared. It now delegates to Weasel's bucket-aware overload, and its AllowPartitionSharing guard spans calls via the persisted bucket map, matching what PostgreSQL already did. Compliance coverage on BOTH engines for the acceptance criteria on GH-3683: members registered together and separately land in one physical partition, and dropping one member leaves the others reading, writing, and holding their data. That last one covers GH-3686, the co-tenant data loss found alongside this. Docs: the bucketing sample showed the sequential pattern without enabling AllowPartitionSharing, so as written it would have thrown. The config sample now enables it, and a new Tenant Bucketing section covers when to reach for it, the drop semantics, and the 9.20.0 requirement. Verified: EfCoreTests.MultiTenancy 193/193, SqlServerTests 388/390 (2 skipped), full wolverine.slnx Release build clean at 0 warnings. NOTE: requires Weasel 9.20.0 on nuget.org; verified locally against a packed build.
jeremydmiller
marked this pull request as ready for review
July 28, 2026 17:29
Member
Author
|
Unblocked — Weasel 9.20.0 is published (JasperFx/weasel#392 merged; publish workflow completed 16:36 UTC). Verified against the published package, with no local feed overlay in play:
Marking ready for review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3683. Also closes #3686 (the co-tenant data loss found alongside it).
Important
Draft — blocked on Weasel 9.20.0 reaching nuget.org. The root cause is in Weasel, fixed by
JasperFx/weasel#392. CI will fail restore until that version is published. Everything here was
verified locally against a packed 9.20.0 build.
The problem
Tenant bucketing — registering several small tenants against one partition suffix so they share a physical
partition — is documented on the EF Core multi-tenancy page and exposed through
PartitionPerTenant(p => p.AllowPartitionSharing = true). It did not work on either engine. It had no testcoverage, which is why it went unnoticed; the doc sample demonstrating it is compile-only and never executed.
Both defects were reproduced against real PostgreSQL and SQL Server:
ListPartitionper tenant, so the second member of a bucket wasswallowed by
CREATE TABLE IF NOT EXISTSand its first write failed with23514 no partition of relation found for row.shape, and the one the docs showed. The registry stored only
tenant_id -> ordinal, so a brand-new tenantcould not discover which ordinal its bucket already owned and quietly got its own partition. The
15,000-partition ceiling that bucketing exists to dodge was not mitigated at all.
Neither was fixable inside Wolverine, hence JasperFx/weasel#392.
Plus a data-loss defect found along the way (#3686)
Dropping one member of a bucket destroyed every co-tenant's rows: the by-value drop resolved the tenant
to its suffix and then dropped by suffix, deleting the whole bucket's registry rows and
DROPping the sharedpartition table. Also fixed in weasel#392, and covered here.
Changes
PostgresqlTenantPartitioningneeds no change — it hands Weasel the tenant → suffix map, and the fixedWeasel does the right thing.
SqlServerTenantPartitioningwas resolving the bucket's ordinal itself out of the tenant → ordinal map, whichis blind to buckets: for a brand-new tenant the lookup matched nothing and it allocated a fresh ordinal. It now
delegates to Weasel's bucket-aware overload, and its
AllowPartitionSharingguard spans calls via thepersisted bucket map — matching what PostgreSQL already did.
Compliance coverage on both engines for #3683's acceptance criteria: members registered together and
separately land in one physical partition, and dropping one member leaves the others reading, writing, and
holding their data.
Docs. The bucketing sample showed the sequential pattern without enabling
AllowPartitionSharing, so aswritten it would have thrown. The config sample now enables it, and a new Tenant Bucketing section covers
when to reach for it, the drop semantics, the isolation trade-off, and the 9.20.0 requirement.
Verification
Real databases throughout.
EfCoreTests.MultiTenancySqlServerTestsPostgresqlTestswolverine.slnxRelease buildUpstream weasel#392 carries 5 new PostgreSQL tests (all 5 red on 9.19.1) and 5 new SQL Server tests, with
both full Weasel suites green.
Merge order
Directory.Packages.propsbump to 9.20.0 is already in this branch