April 18, 2026

Taming Billions of Rows: PostgreSQL Table Partitioning in Laravel

By Paresh Prajapati • Lead Architect

Taming Billions of Rows: PostgreSQL Table Partitioning in Laravel

The Limitations of a Single Table

When architecting B2B SaaS platforms at Smart Tech Devs, we frequently deal with high-velocity data. Think of IoT sensor readings, API request logs, or detailed financial audit trails. For the first few million rows, standard PostgreSQL indexing handles this effortlessly. But what happens when that activity_logs table hits 50 million, 100 million, or 500 million rows?

At this scale, the B-Tree indexes become massive and no longer fit into RAM. Write latency spikes because updating the index on every insert becomes an expensive disk operation. Deleting old data (data pruning) becomes a nightmare, causing massive database locks and transaction log bloat. When generic indexing fails, the enterprise solution is Table Partitioning.

Declarative Partitioning in PostgreSQL

Partitioning splits one massive logical table into several smaller physical tables (partitions), usually based on a date range (e.g., one partition per month). Your application still queries the main activity_logs table exactly as it always did, but PostgreSQL routes the query only to the relevant physical partition, ignoring the rest of the massive dataset entirely.

Implementing Partitioning in Laravel Migrations

Because Laravel's standard blueprint doesn't natively support creating partitioned tables, we leverage raw SQL statements within our migrations to instruct PostgreSQL.


use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class CreateActivityLogsTable extends Migration
{
    public function up(): void
    {
        // 1. Create the parent logical table. 
        // Notice we do NOT use Schema::create, and we specify 'PARTITION BY RANGE'
        DB::statement('
            CREATE TABLE activity_logs (
                id BIGSERIAL,
                tenant_id BIGINT NOT NULL,
                action VARCHAR(255) NOT NULL,
                payload JSONB,
                created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                PRIMARY KEY (id, created_at) -- The partition key must be part of the PK
            ) PARTITION BY RANGE (created_at);
        ');

        // 2. Create the physical partitions (e.g., by month)
        DB::statement("
            CREATE TABLE activity_logs_2026_04 
            PARTITION OF activity_logs 
            FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
        ");

        DB::statement("
            CREATE TABLE activity_logs_2026_05 
            PARTITION OF activity_logs 
            FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
        ");

        // 3. Create indexes on the parent (they cascade to partitions)
        DB::statement('CREATE INDEX activity_logs_tenant_id_idx ON activity_logs (tenant_id);');
    }

    public function down(): void
    {
        DB::statement('DROP TABLE activity_logs CASCADE;');
    }
}

The Operational ROI

Transitioning high-volume tables to partitioned architecture provides massive, durable benefits:

  1. Blazing Fast Writes: Inserts hit small, highly active partitions where the indexes easily fit into memory, keeping write latency incredibly low.
  2. Surgical Queries: When querying logs for "last week", PostgreSQL entirely skips (prunes) older partitions. It scans millions of rows instead of billions.
  3. Zero-Cost Deletes: Need to delete data older than one year to save disk space? Instead of running an expensive DELETE FROM activity_logs WHERE created_at < '2025-01-01', you simply run DROP TABLE activity_logs_2025_01;. It reclaims gigabytes of disk space instantly with zero database locking.

Conclusion

Durable software requires anticipating the weight of scale. If you are logging millions of events in a single B2B application, do not wait for the database to choke. Implementing PostgreSQL declarative partitioning in your Laravel architecture ensures your platform remains highly performant, regardless of how much historical data you accumulate.

Paresh Prajapati
Lead Architect, Smart Tech Devs