The project is in a healthy, maintained state
Runtime maintenance for PostgreSQL declarative partitions — archive / current / future zones, heat-driven splits, mandatory default drain, hot-switch migrations, and strategy templates.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

Partition Gardener

Gem Version Test Status codecov

PostgreSQL partition lifecycle: archive, current, and future zones, heat-driven splits inside the active window, cursor-based rebalance, mandatory default drain, and hot-switch migration helpers. Rails-integrated by default; works standalone with pg and a JSON registry.

Requirements

  • Ruby >= 3.2
  • PostgreSQL with declarative partitioning
  • pg gem (runtime dependency)
  • Rails >= 7.1 optional — railtie loads when Rails is present and sets Active Record connection plus Time.zone.today

Complements migration gems (e.g. pg_party) — use those for creation DDL; use this gem for runtime maintenance (scheduled or indicator-driven) and cutover migrations. See docs/related_postgres_tooling.md for how Gardener pairs with PgHero, Dexter, pgsync, and pgslice.

Documentation

Choosing and configuring

Operations

Application and migration

Schemas

Quick start

# Gemfile
gem "partition_gardener"
# config/initializers/partition_gardener.rb — see docs/configuration.md for all options
PartitionGardener.configure do |config|
  config.notifier = ->(message, context: {}) { Rails.logger.info(message) }
  config.today_resolver = -> { Time.zone.today }
end

PartitionGardener::Registry.register_template(
  :sliding_window_monthly,
  table_name: "events",
  partition_key_column: "occurred_on",
  conflict_key: %w[id occurred_on],
  active_months: 12
)
# app/jobs/partition_maintenance_job.rb — see docs/background_job.md
class PartitionMaintenanceJob < ApplicationJob
  def perform
    PartitionGardener.run!(job_class_name: self.class.name)
  end
end
# Dry-run plan from the app directory
bundle exec partition_gardener --rails plan events --pretty

Standalone (no Rails)

Set DATABASE_URL and point the CLI at a JSON registry:

export DATABASE_URL=postgres://postgres@127.0.0.1:5432/mydb
bundle exec partition_gardener --registry config/partition_garden.json plan events --pretty

Or configure in Ruby:

PartitionGardener.configure do |config|
  config.connection_resolver = -> { PartitionGardener::PgConnection.connect(ENV.fetch("DATABASE_URL")) }
  config.today_resolver = -> { Date.today }
end
PartitionGardener::ConfigDocument.load_registry_file!("config/partition_garden.json")
PartitionGardener.run!

Default layout

Default layout: monthly sliding window (register_template :sliding_window_monthly in the Rails example above). Three-area layout with planner-enforced non-overlapping ranges, default drained last, and keyset moves on (partition_key, conflict_key).

See docs/decision_flow.md for when to pick other templates.

Templates

Templates.sliding_window_monthly (layout :sliding_window, bucket :month) — RANGE (date) monthly time-series.

Templates.sliding_window_daily (bucket :day) — daily buckets for short retention telemetry.

Templates.sliding_window_weekly (bucket :week) — ISO-week buckets.

Templates.sliding_window_quarterly (bucket :quarter) — calendar-quarter buckets.

Templates.calendar_year (layout :calendar_year) — RANGE (date) yearly buckets.

Templates.rolling_current_monthly (layout :rolling_current) — monthly sliding window without heat splits.

Templates.integer_window (layout :integer_window) — RANGE (bigint) id bands.

Templates.list_split (layout :list_split) — fixed LIST branches.

Templates.composite_list_hash (layout :composite) — LIST parent plus HASH sub-trees.

Templates.composite_list_range / Templates.list_range — LIST parent plus RANGE sliding-window sub-trees.

Templates.composite_range_hash — RANGE parent plus HASH child tables.

Templates.composite_range_list — RANGE parent plus LIST child tables.

Templates.hash_branches (layout :hash_branches) — HASH remainders.

Templates.premake_monthly (layout :premake_monthly) — cron-style premake bridge; migrate to sliding window.

See docs/partition_landscape.md for the template catalog, Rails sharding, composite keys, partition pruning, UI and product surfaces, aggregate snapshots, and materialized view limits. Operations: operations.md, cutover.md, monitoring.md.

Hot-switch migrations

PartitionGardener::Migration::HotSwitchConcern (alias HotSwitchPartitionedTable) consolidates cutover migration helpers.

class PartitionEventsHotSwitch < ActiveRecord::Migration[8.0]
  include PartitionGardener::Migration::HotSwitchConcern

  HOT_SWITCH_CONFIG = {
    current_table: "events",
    partitioned_table: "p_events",
    partition_key_column: "occurred_on",
    conflict_key: %w[id occurred_on],
    partition_config: PartitionGardener::Registry.hot_switch_partition_config("events")
  }.freeze

  def up
    ensure_future_partitions_exist(months_ahead: 1)
    hot_switch_tables
  end
end

partition_config may also be an inline hash for tables not yet registered.

Runtime guarantees

  • Per-table advisory lock during maintenance (hashtext namespace plus table name)
  • continue_on_error: true by default — one failing table does not block others (PartitionGardener::RunFailed aggregates errors)
  • Batch moves use composite keyset cursors and delete source rows by batch keys
  • run! returns RunSummary with per-table duration, plan_signature, and rows_moved
  • maintenance_backend: :pg_partman skips gardener for partman-owned tables

Tests

App-agnostic PostgreSQL integration specs live under spec/integration/. CI and local full-suite runs use polyrun to shard specs across parallel workers (each worker uses its own PostgreSQL database when DATABASE_URL is set).

# Full suite (unit + integration; requires PostgreSQL)
make test

# Same as make test
INTEGRATION=1 DATABASE_URL=postgres://postgres@127.0.0.1:5432/partition_gardener_test ./bin/polyrun parallel-rspec --workers 5

# Unit specs only
bundle exec rspec --exclude-pattern "spec/integration/**/*_spec.rb"

# Integration only (requires PostgreSQL)
INTEGRATION=1 DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/partition_gardener_test bundle exec rake spec:integration

License

The gem is available as open source under the terms of the MIT License.

Sponsors

Sponsored by Kisko Labs.

Sponsored by Kisko Labs