Skip to content

Widhian Bramantya

coding is an art form

Menu
  • About Me
Menu
postgresql

Smart Automation in PostgreSQL: Managing Time-Based Data with pg_partman and pg_cron

Posted on October 9, 2025October 8, 2025 by admin

Modern systems often deal with continuous data streams: logs, metrics, transactions, or sensor readings. Over time, these tables grow huge and slow down queries and backups. To keep performance high and storage clean, we need automation both in how data is stored (partitioning) and maintained (scheduling).

That’s where pg_partman and pg_cron come in. Together, they turn PostgreSQL into a self-managing time-series database, automatically creating partitions, cleaning up old data, and keeping everything in order.

Why Partitioning Matters

Partitioning splits a large table into smaller chunks (partitions) based on time or another key. Instead of one massive table, you get multiple smaller tables like:

  • sensor_readings_p2025_01_01
  • sensor_readings_p2025_01_02
  • sensor_readings_p2025_01_03

Queries become faster because PostgreSQL can skip partitions outside the time range.

Example:

SELECT * FROM sensor_readings
WHERE reading_time >= now() - interval '1 day';

Now PostgreSQL only scans the recent partition, not the whole dataset.

Automating Partition Management with pg_partman

pg_partman is a PostgreSQL extension that automates partition creation and cleanup. It supports both time-based and serial-based partitioning.

With pg_partman, you can:

  • Automatically create new partitions in advance (premake)
  • Drop old partitions based on retention
  • Define your own template for partitions (structure + indexes)
  • Run maintenance easily via SQL or cron jobs

Practical Template: Daily Time-Based Partitioning

Here’s a complete Goose migration template to automate daily partitions for a simple sensor_readings table.

-- +goose Up
-- +goose StatementBegin

-- Create schemas for organization
CREATE SCHEMA IF NOT EXISTS partman;
CREATE SCHEMA IF NOT EXISTS iot;

-- Install pg_partman in partman schema (safe for all environments)
DO $do$
BEGIN
    CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman;
    RAISE NOTICE 'pg_partman installed successfully';
EXCEPTION 
    WHEN undefined_file THEN
        RAISE NOTICE 'pg_partman not available - skipping (development mode)';
END
$do$;

-- Grant privileges to main user
GRANT ALL ON SCHEMA partman TO postgres;

-- ============================================================
-- 1. Create a TEMPLATE table for partitions
-- ============================================================
-- The template defines structure & indexes that each partition will copy.
CREATE TABLE iot.sensor_readings_template (
    id bigserial PRIMARY KEY,
    device_id text NOT NULL,
    reading_value numeric(10,2) NOT NULL,
    reading_time timestamptz NOT NULL DEFAULT now(),
    created_at timestamptz NOT NULL DEFAULT now()
);

-- Indexes to improve query performance
CREATE INDEX idx_sensor_template_device_id ON iot.sensor_readings_template(device_id);
CREATE INDEX idx_sensor_template_reading_time ON iot.sensor_readings_template(reading_time);

-- ============================================================
-- 2. Create the MAIN partitioned table
-- ============================================================
CREATE TABLE iot.sensor_readings (
    id bigserial PRIMARY KEY,
    device_id text NOT NULL,
    reading_value numeric(10,2) NOT NULL,
    reading_time timestamptz NOT NULL DEFAULT now(),
    created_at timestamptz NOT NULL DEFAULT now()
) PARTITION BY RANGE (reading_time);

-- ============================================================
-- 3. Register with pg_partman and configure
-- ============================================================
DO $do$
BEGIN
    PERFORM partman.create_parent(
        p_parent_table => 'iot.sensor_readings',
        p_control => 'reading_time',
        p_type => 'range',
        p_interval => '1 day',
        p_premake => 3,
        p_template_table => 'iot.sensor_readings_template'
    );

    UPDATE partman.part_config
    SET retention = '30 days',
        retention_keep_table = false,
        retention_keep_index = false,
        infinite_time_partitions = true,
        datetime_string = 'YYYY-MM-DD'
    WHERE parent_table = 'iot.sensor_readings';

    RAISE NOTICE 'sensor_readings registered with pg_partman successfully';
END
$do$;

-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin

DROP TABLE IF EXISTS iot.sensor_readings CASCADE;
DROP TABLE IF EXISTS iot.sensor_readings_template CASCADE;

DO $do$
BEGIN
    DROP EXTENSION IF EXISTS pg_partman CASCADE;
    DROP SCHEMA IF EXISTS partman CASCADE;
EXCEPTION 
    WHEN OTHERS THEN
        RAISE NOTICE 'Cleanup failed (may not exist): %', SQLERRM;
END
$do$;

-- +goose StatementEnd

Why Use a Template Table?

The template table is the base model for all partitions that pg_partman creates later. Every new partition is cloned from this template, including columns, indexes, constraints, and defaults.

See also  Understanding PostgreSQL WAL, Slot, Publication, LSN, and Replication Lag

Without a template, each new partition would be empty and missing indexes, which could hurt query performance badly.

Benefits of Using a Template

PurposeExplanation
ConsistencyEvery partition has the same structure and indexes
PerformanceIndexes are pre-created automatically
AutomationYou don’t need to manually modify partitions
SafetyAvoids schema drift between partitions

The template table ensures every new partition behaves like the original table.

Related posts:

PostgreSQL Replication Deep Dive: From High Availability to Multi-Master Clusters

Understanding PostgreSQL WAL, Slot, Publication, LSN, and Replication Lag

PostgreSQL Write-Ahead Log (WAL): Durability, Performance Tuning, and Recovery Explained

Pages: 1 2
Category: PostgreSQL

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Linkedin

Widhian Bramantya

Recent Posts

  • Smart Automation in PostgreSQL: Managing Time-Based Data with pg_partman and pg_cron
  • Understanding PostgreSQL WAL, Slot, Publication, LSN, and Replication Lag
  • PostgreSQL Write-Ahead Log (WAL): Durability, Performance Tuning, and Recovery Explained
  • PostgreSQL Replication Deep Dive: From High Availability to Multi-Master Clusters
  • Finding Nearby Merchants in a Ride-Hailing App Using Elasticsearch Polygon Search
  • Advanced Text Search in Elasticsearch: N-Gram, Reverse, Fuzzy, and Search-as-you-type
  • Understanding and Customizing Analyzers in Elasticsearch
  • Log Management at Scale: Integrating Elasticsearch with Beats, Logstash, and Kibana
  • Index Lifecycle Management (ILM) in Elasticsearch: Automatic Data Control Made Simple
  • Blue-Green Deployment in Elasticsearch: Safe Reindexing and Zero-Downtime Upgrades
  • Maintaining Super Large Datasets in Elasticsearch
  • Elasticsearch Best Practices for Beginners
  • Implementing the Outbox Pattern with Debezium
  • Production-Grade Debezium Connector with Kafka (Postgres Outbox Example – E-Commerce Orders)
  • Connecting Debezium with Kafka for Real-Time Streaming
  • Debezium Architecture – How It Works and Core Components
  • What is Debezium? – An Introduction to Change Data Capture
  • Offset Management and Consumer Groups in Kafka
  • Partitions, Replication, and Fault Tolerance in Kafka
  • Delivery Semantics in Kafka: At Most Once, At Least Once, Exactly Once

Recent Comments

No comments to show.

Archives

  • October 2025
  • September 2025
  • August 2025
  • November 2021
  • October 2021
  • August 2021
  • July 2021
  • June 2021
  • March 2021
  • January 2021

Categories

  • Debezium
  • Devops
  • ElasticSearch
  • Golang
  • Kafka
  • Lua
  • NATS
  • PostgreSQL
  • Programming
  • RabbitMQ
  • Redis
  • VPC
© 2026 Widhian Bramantya | Powered by Minimalist Blog WordPress Theme