Skip to content
Back to skills

Range Partitioning Growth Tables

ASecurity

Range Partitioning for Growth Tables

  • 17 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 2, 2026
ai-agentsgosqlazureperformance

Security analysis

A100/100

Scanned September 2, 2026

npx -y skills add CarlosCaPe/octorato --skill range-partitioning-growth-tables --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Range Partitioning Growth Tables?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Range Partitioning Growth Tables
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/carloscape-range-partitioning-growth-tables/badge)](https://www.skillsdirectory.com/skills/carloscape-range-partitioning-growth-tables)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: range-partitioning-growth-tables
description: "Range Partitioning for Growth Tables"
metadata:
  short-description: "Range Partitioning for Growth Tables"
  original-index: 34
---

# Range Partitioning for Growth Tables

> Source: [PostgreSQL Best Practices](../DOCUMENTS/PostgreSQL_BestPractices_Azure.md)
> -- Backlog #4, 

## What

Splitting large, continuously-growing tables (audit logs, error logs,
appointment history) into **range-partitioned children** by date. This uses
PostgreSQL's native declarative partitioning (`PARTITION BY RANGE`) to keep
query performance stable as the table grows.

## Why

Tables like `AuditLog`, `ErrorLog`, and `AppointmentInfo` grow without
bound. Without partitioning:
- Sequential scans grow linearly with table size
- Index B-tree depth increases, slowing lookups
- VACUUM must process the entire table (hours on large tables)
- Archival requires scanning and deleting millions of rows

With range partitioning:
- Queries with a date filter benefit from **partition pruning** (only
  relevant partitions are scanned)
- VACUUM runs per-partition (smaller, faster)
- Archival = `DROP TABLE` on old partitions (instant, no bloat)
- New partitions can be created in advance (no schema change needed)

## How

### Create a partitioned parent table

```sql
-- New table (greenfield)
CREATE TABLE public."AuditLog" (
    "AuditLogId"    bigint GENERATED BY DEFAULT AS IDENTITY,
    "Action"        text NOT NULL,
    "CreatedAt"     timestamptz NOT NULL DEFAULT NOW(),
    "Payload"       jsonb
) PARTITION BY RANGE ("CreatedAt");
```

### Create partition children (monthly)

```sql
-- January 2025
CREATE TABLE public."AuditLog_2025_01" PARTITION OF public."AuditLog"
    FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');

-- February 2025
CREATE TABLE public."AuditLog_2025_02" PARTITION OF public."AuditLog"
    FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');

-- etc.
```

### Partition naming convention

```text
<parent_table>_<YYYY>_<MM>     -- monthly
<parent_table>_<YYYY>_Q<n>     -- quarterly
<parent_table>_<YYYY>          -- yearly

Examples:
  AuditLog_2025_01
  AuditLog_2025_Q1
  ErrorLog_2024
```

### Automate future partition creation with pg_cron

```sql
-- Create next month's partition on the 25th of each month
SELECT cron.schedule(
    'create_auditlog_partition',
    '0 3 25 * *',   -- 3:00 AM on the 25th
    $$
    DO $$
    DECLARE
        v_start date := date_trunc('month', NOW() + INTERVAL '1 month');
        v_end   date := v_start + INTERVAL '1 month';
        v_name  text := 'AuditLog_' || to_char(v_start, 'YYYY_MM');
    BEGIN
        EXECUTE format(
            'CREATE TABLE IF NOT EXISTS public.%I PARTITION OF public."AuditLog"
             FOR VALUES FROM (%L) TO (%L)',
            v_name, v_start, v_end
        );
        RAISE NOTICE 'Created partition %', v_name;
    END $$;
    $$
);
```

### Retroactively partition an existing table

This is a multi-step migration:

```text
1. Create new partitioned parent (e.g., AuditLog_new)
2. Create partition children for existing date ranges
3. INSERT INTO AuditLog_new SELECT * FROM AuditLog (batch by date)
4. Rename: AuditLog -> AuditLog_legacy, AuditLog_new -> AuditLog
5. Recreate indexes, FKs, and dependent views
6. Drop AuditLog_legacy after validation
```

This is an **offline migration** that requires downtime or a shadow-write
strategy. See Skill #04 (Idempotent Migrations) for safe scripting patterns.

### Query with partition pruning

```sql
-- This query only scans AuditLog_2025_01 (pruning in action)
SELECT *
FROM public."AuditLog"
WHERE "CreatedAt" >= '2025-01-01'
  AND "CreatedAt" <  '2025-02-01';

-- Verify pruning with EXPLAIN
EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM public."AuditLog"
WHERE "CreatedAt" >= '2025-01-01'
  AND "CreatedAt" <  '2025-02-01';
-- Look for: "Partitions removed: N" or only one child in the plan
```

### Archive old partitions

```sql
-- Detach (keeps the data, removes from parent routing)
ALTER TABLE public."AuditLog"
    DETACH PARTITION public."AuditLog_2023_01";

-- Optionally move to archive schema
ALTER TABLE public."AuditLog_2023_01"
    SET SCHEMA archive;

-- Or drop entirely
DROP TABLE public."AuditLog_2023_01";
```

## Decision Matrix: When to Partition

| Signal | Partition? | Notes |
|--------|-----------|-------|
| Table > 10M rows and growing | Yes | Clear benefit |
| Table > 1M rows, date-filtered queries | Yes | Pruning helps |
| Table < 1M rows | No | Overhead not justified |
| No date column in WHERE clauses | No | Pruning cannot help |
| Need to archive/purge old data | Yes | DROP partition = instant |
| Heavily updated (not append-only) | Maybe | Updates across partitions are complex |

## When to Use

- Audit log tables that grow daily
- Error/event log tables
- Appointment history tables with date-range queries
- Any append-mostly table exceeding 10M rows

## Where We Used It

- ****: Partitioning analysis for AppointmentInfo, AuditLog, ErrorLog
  in the scheduling DB

## Related Skills

- **Skill #04** (Idempotent Migrations) -- scripting the multi-step migration
- **Skill #10** (Index CONCURRENTLY) -- indexes on partitioned children
- **Skill #16** (pg_cron Scheduling) -- automated partition creation
- **Skill #26** (timestamptz) -- partition key should be timestamptz
- **Skill #30** (Autovacuum & Bloat) -- per-partition VACUUM is faster

## References

- [PostgreSQL Best Practices](../DOCUMENTS/PostgreSQL_BestPractices_Azure.md)
  -- Backlog #4
- [scheduling Audit TDD](../DOCUMENTS/scheduling_DB_Audit_TDD.md)
  -- Growth table analysis

## Gotchas

- **Primary keys** on partitioned tables must include the partition key
  column -- `(AuditLogId, CreatedAt)` not just `(AuditLogId)`
- **Foreign keys** pointing TO a partitioned table are supported only in
  PostgreSQL 12+ and require the partition key in the referenced columns
- **FK NOT VALID limitation**: Foreign key constraints on partitioned
  tables may NOT be declared `NOT VALID` (PG 16 docs: sql-createtable.html).
  This means the standard NOT VALID + VALIDATE pattern (Skill #09) does
  not apply -- FK creation on a partitioned table scans ALL partitions.
- **Unique indexes** must include the partition key column
- If no partition exists for an inserted row's date, the INSERT **fails** --
  always create partitions in advance
- `CREATE INDEX` on the parent creates indexes on ALL children (PG 11+) --
  but `CONCURRENTLY` is not supported on partitioned parents.
  **Workaround** (PG 16 docs: ddl-partitioning.html):
  1. `CREATE INDEX ON ONLY parent_table (col)` -- marks parent index as invalid
  2. `CREATE INDEX CONCURRENTLY ON each_partition (col)` -- non-blocking per child
  3. `ALTER INDEX parent_idx ATTACH PARTITION child_idx` -- links them
  4. Once all children are attached, parent index becomes valid automatically
- Partition pruning requires `enable_partition_pruning = on` (default) and
  the WHERE clause must use the partition key directly (not wrapped in a
  function)
- Retroactive partitioning of an existing table requires data migration --
  there is no `ALTER TABLE ... PARTITION BY` command
  (PG 16 docs: ddl-partitioning.html)

---

*Category: DDL | Origin: *

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…