Skip to content

ManagedRangePartitions: rolling time-window range partitions with additive roll-forward and aged-partition drop #401

Description

@jeremydmiller

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions