By Sagar Shankaran, Founder of CallSphere
AI call logs grow 100GB+ per month per tenant. Native Postgres range partitioning + pg_partman keeps queries fast and retention painless. A working schema, retention job, and pruning checklist.
Key takeaways
TL;DR — A flat
call_logstable dies around 200M rows. Range partitioning bycreated_atkeeps inserts fast, queries pruned, and retention a singleDROP. pg_partman automates the whole lifecycle.
A weekly-partitioned call_logs table with automatic future-partition creation and 90-day retention, capable of >10k inserts/sec while keeping point-in-time queries under 50ms.
CREATE TABLE call_logs (
id BIGSERIAL,
tenant_id UUID NOT NULL,
call_id UUID NOT NULL,
started_at TIMESTAMPTZ NOT NULL,
ended_at TIMESTAMPTZ,
duration_sec INT,
transcript TEXT,
metadata JSONB DEFAULT '{}',
PRIMARY KEY (id, started_at)
) PARTITION BY RANGE (started_at);
-- Initial weekly partitions
CREATE TABLE call_logs_2026w19 PARTITION OF call_logs
FOR VALUES FROM ('2026-05-04') TO ('2026-05-11');
CREATE TABLE call_logs_2026w20 PARTITION OF call_logs
FOR VALUES FROM ('2026-05-11') TO ('2026-05-18');
CREATE INDEX ON call_logs (tenant_id, started_at DESC);
flowchart LR
W[App writes] --> ROOT[call_logs partitioned root]
ROOT --> P1[2026w18]
ROOT --> P2[2026w19]
ROOT --> P3[2026w20]
P1 --> COLD[Detach + S3 archive at 90d]
P2 --> HOT[Hot reads]
P3 --> WRITES[Active writes]
PM[pg_partman cron] --> ROOT
CREATE EXTENSION pg_partman;
SELECT partman.create_parent(
p_parent_table => 'public.call_logs',
p_control => 'started_at',
p_type => 'native',
p_interval => 'weekly',
p_premake => 4
);
p_premake = 4 keeps 4 weekly partitions ready ahead of now() so inserts never miss a partition.
UPDATE partman.part_config SET
retention = '90 days',
retention_keep_table = false,
retention_keep_index = false,
optimize_constraint = 30
WHERE parent_table = 'public.call_logs';
# Cron every 15 min
psql -d app -c "CALL partman.run_maintenance_proc();"
await prisma.$executeRaw`
INSERT INTO call_logs (tenant_id, call_id, started_at, ended_at, duration_sec, transcript)
VALUES (${tenantId}::uuid, ${callId}::uuid,
${startedAt}, ${endedAt}, ${duration}, ${transcript})
`;
Postgres routes the row to the correct partition automatically.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
Always pin the partition key:
SELECT call_id, transcript
FROM call_logs
WHERE tenant_id = $1
AND started_at >= now() - interval '7 days' -- prunes!
AND started_at < now()
ORDER BY started_at DESC LIMIT 100;
EXPLAIN should show only the relevant partitions in the plan.
ALTER TABLE call_logs DETACH PARTITION call_logs_2026w05 CONCURRENTLY;
COPY call_logs_2026w05 TO PROGRAM 'aws s3 cp - s3://archive/call_logs_2026w05.csv';
DROP TABLE call_logs_2026w05;
CONCURRENTLY (PG14+) avoids the access-exclusive lock.
SELECT n.nspname, c.relname, pg_size_pretty(pg_total_relation_size(c.oid)) AS size,
(SELECT count(*) FROM pg_inherits WHERE inhparent = 'call_logs'::regclass) AS partitions
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname LIKE 'call_logs_%'
ORDER BY c.relname DESC LIMIT 10;
WHERE date_trunc('day', started_at) = ... disables pruning. Use raw >= / <.CallSphere's call_logs table partitions weekly across 115+ DB tables, holding 6+ months of voice transcripts per tenant. Healthcare's HIPAA workload (Prisma healthcare_voice) keeps a 7-year retention horizon via S3 archival; OneRoof retains 90 days hot under RLS; UrackIT mirrors metadata into Supabase. Across 37 agents · 90+ tools · 6 verticals, partition pruning keeps p95 transcript fetches under 35 ms. Plans: $149/$499/$1,499 — 14-day trial, 22% affiliate.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Q: Daily, weekly, or monthly partitions? Daily for >100M rows/day, weekly for the typical 1–10M/day, monthly for archives.
Q: What's the partition limit? Postgres handles thousands gracefully; >10k starts hurting plan time.
Q: Can I have a global unique constraint? Only if it includes the partition key. Otherwise enforce uniqueness in app or via deferred trigger.
Q: Does logical replication work with partitions?
Yes — use publish_via_partition_root so the subscriber sees a single logical stream.
Q: pg_partman vs hand-rolled? pg_partman every time. Hand-rolled cron jobs forget to premake at exactly the wrong moment.
Written by
Sagar Shankaran· Founder, CallSphere
Sagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Personalizing agents for one user is easy. Personalizing them for a million users is a memory-tier problem. The hot/warm/cold split and what each tier optimizes for.
Your agent's memory, embeddings, and conversation state all live in Postgres. Backups must include vector data and survive a full-region loss. Here's how CallSphere does PITR for 115+ tables.
Embeddings, vector storage, graph nodes, and recall API calls all add up faster than expected. The cost model for serving 100k users with agent memory at scale.
Sharding patterns that hold up beyond 100M vectors. The 2026 designs for partition keys, replication, and rebalancing.
Per-vector cost economics matter at scale. The 2026 numbers for storage, compute, egress, and how to model TCO.
At 100K minutes/month, Vapi customers spend $30K+ on direct vendors alone. CallSphere's Scale tier covers it flat. Here is the math.
© 2026 CallSphere LLC. All rights reserved.
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI