The gap
Weasel has a managed partition manager for the LIST strategy — ManagedListPartitions (Postgres) keeps its partition set in a side table, adds partitions at runtime, and deliberately sits outside the generic table delta so it "needs no IgnorePartitionsInMigration toggling." That is exactly the right shape for a partition set that changes over the life of the application.
The RANGE strategy has no equivalent. RangePartitioning.CreateDelta compares a declared list of ranges against actual:
- missing declared ranges →
PartitionDelta.Additive (good — Weasel can add partitions)
- actual has ranges the declaration no longer contains →
PartitionDelta.Rebuild
- actual has more ranges than declared →
PartitionDelta.Rebuild
For a time-series table those last two are the normal steady state, not an anomaly. The declared window moves forward every month; last year's partitions are still on disk and are supposed to be dropped per a retention policy, not treated as drift. On a multi-gigabyte table a Rebuild is catastrophic, so the only viable options today are IgnorePartitionsInMigration or an "externally managed" mode — both of which mean the application hand-writes CREATE TABLE … PARTITION OF and DROP TABLE itself.
The SQL Server side has the same gap in its own idiom: Weasel.SqlServer.Tables.Partitioning.RangePartitioning takes a static list of boundary values, so rolling a new period forward (ALTER PARTITION SCHEME … NEXT USED + ALTER PARTITION FUNCTION … SPLIT) and retiring an aged one (MERGE) is left to the application.
Why it matters beyond convenience
Applications that opt out don't just take on DDL authorship — they lose Weasel's ordering and dependency management. Concrete case from JasperFx/CritterWatch#886: a hand-rolled rebuild of a partitioned table re-created it in a schema where Marten's mt_immutable_timestamptz helper hadn't been created, so a perfectly ordinary computed index over a DateTimeOffset member failed with 42883: function … does not exist. The index was fine; bypassing Weasel was the bug. Any application doing time-series partitioning today is exposed to that class of failure.
Proposal
A ManagedRangePartitions analogous to ManagedListPartitions, driven by a policy rather than a static list:
- Window policy — a period (month/week/day), how many periods to provision ahead of now, and how many to keep behind before an aged partition is dropped.
- Roll-forward — create missing periods additively at startup and on demand; never rebuild because "now" moved.
- Retention drop — aged partitions beyond the keep-behind window are dropped (Postgres
DROP TABLE, SQL Server MERGE RANGE), which is what makes range partitioning worth having: reclaim is O(1) instead of a mass DELETE.
- A DEFAULT/overflow partition so rows outside the provisioned window are never rejected.
- Both engines: Postgres (
PARTITION OF … FOR VALUES FROM … TO …) and SQL Server (partition function + scheme, SPLIT/MERGE, NEXT USED filegroup).
Consumers then declare intent — "monthly, 12 ahead, drop past 45 days" — and Weasel owns every DDL statement.
Acceptance
- No consumer needs
IgnorePartitionsInMigration or an externally-managed escape hatch to run a rolling time-partitioned table.
- Rolling the window forward is
Additive, never Rebuild.
- Dropping an aged partition is a policy outcome, not drift.
- Idempotent and safe to run on every startup, including concurrently from multiple nodes.
Downstream consumers to unblock: JasperFx/marten (ByExternallyManagedRangePartitions), JasperFx/polecat (ByExternallyManagedRange), and JasperFx/CritterWatch, which currently hand-writes all of this for its metrics table in both flavors.
The gap
Weasel has a managed partition manager for the LIST strategy —
ManagedListPartitions(Postgres) keeps its partition set in a side table, adds partitions at runtime, and deliberately sits outside the generic table delta so it "needs noIgnorePartitionsInMigrationtoggling." That is exactly the right shape for a partition set that changes over the life of the application.The RANGE strategy has no equivalent.
RangePartitioning.CreateDeltacompares a declared list of ranges against actual:PartitionDelta.Additive(good — Weasel can add partitions)PartitionDelta.RebuildPartitionDelta.RebuildFor a time-series table those last two are the normal steady state, not an anomaly. The declared window moves forward every month; last year's partitions are still on disk and are supposed to be dropped per a retention policy, not treated as drift. On a multi-gigabyte table a
Rebuildis catastrophic, so the only viable options today areIgnorePartitionsInMigrationor an "externally managed" mode — both of which mean the application hand-writesCREATE TABLE … PARTITION OFandDROP TABLEitself.The SQL Server side has the same gap in its own idiom:
Weasel.SqlServer.Tables.Partitioning.RangePartitioningtakes a static list of boundary values, so rolling a new period forward (ALTER PARTITION SCHEME … NEXT USED+ALTER PARTITION FUNCTION … SPLIT) and retiring an aged one (MERGE) is left to the application.Why it matters beyond convenience
Applications that opt out don't just take on DDL authorship — they lose Weasel's ordering and dependency management. Concrete case from JasperFx/CritterWatch#886: a hand-rolled rebuild of a partitioned table re-created it in a schema where Marten's
mt_immutable_timestamptzhelper hadn't been created, so a perfectly ordinary computed index over aDateTimeOffsetmember failed with42883: function … does not exist. The index was fine; bypassing Weasel was the bug. Any application doing time-series partitioning today is exposed to that class of failure.Proposal
A
ManagedRangePartitionsanalogous toManagedListPartitions, driven by a policy rather than a static list:DROP TABLE, SQL ServerMERGE RANGE), which is what makes range partitioning worth having: reclaim is O(1) instead of a massDELETE.PARTITION OF … FOR VALUES FROM … TO …) and SQL Server (partition function + scheme,SPLIT/MERGE,NEXT USEDfilegroup).Consumers then declare intent — "monthly, 12 ahead, drop past 45 days" — and Weasel owns every DDL statement.
Acceptance
IgnorePartitionsInMigrationor an externally-managed escape hatch to run a rolling time-partitioned table.Additive, neverRebuild.Downstream consumers to unblock: JasperFx/marten (
ByExternallyManagedRangePartitions), JasperFx/polecat (ByExternallyManagedRange), and JasperFx/CritterWatch, which currently hand-writes all of this for its metrics table in both flavors.