PostgreSQL Pro | Database Mastery
1.32K subscribers
1 photo
28 links
🐘 PostgreSQL Mastery Hub

🎯 What you get:
- Daily optimization tips
- Performance guides
- Real-world solutions
- Query debugging help
- Production best practices

πŸ“ˆ Join 500+ developers improving their PostgreSQL skills
Download Telegram
πŸ—ΊοΈ PostGIS: Turn PostgreSQL into a Geographic Information System

Uber, Lyft, and every delivery app use this. Here's why.

The $1M Question: "Find all restaurants within 1km"
Without PostGIS (nightmare):

-- Haversine formula hell
SELECT *,
6371 * acos(
cos(radians(user_lat)) * cos(radians(restaurant_lat)) *
cos(radians(restaurant_lng) - radians(user_lng)) +
sin(radians(user_lat)) * sin(radians(restaurant_lat))
) AS distance
FROM restaurants
WHERE -- Even more complex math here
ORDER BY distance;
-- Time: 2.3 seconds, inaccurate near poles


With PostGIS (magic):

-- Install once
CREATE EXTENSION postgis;

-- Store locations properly
ALTER TABLE restaurants
ADD COLUMN location GEOGRAPHY(POINT);

UPDATE restaurants
SET location = ST_MakePoint(longitude, latitude);

-- Find nearby restaurants
SELECT name, ST_Distance(location, user_location) as distance
FROM restaurants
WHERE ST_DWithin(location, user_location, 1000) -- 1km
ORDER BY location <-> user_location;
-- Time: 0.03 seconds, accurate everywhere


76x faster. 100% accurate.

🎯 Real-World PostGIS Powers:
Delivery Zone Check:

-- Draw delivery zones
CREATE TABLE delivery_zones (
id SERIAL PRIMARY KEY,
name TEXT,
zone GEOGRAPHY(POLYGON)
);

-- Check if address is in delivery area
SELECT name
FROM delivery_zones
WHERE ST_Contains(zone, customer_location);


Route Optimization:

-- Find shortest path between points
WITH RECURSIVE route AS (
SELECT location, 0 as total_distance
FROM locations WHERE id = start_id
UNION ALL
SELECT l.location,
r.total_distance + ST_Distance(r.location, l.location)
FROM locations l, route r
WHERE l.id = next_stop_id
)
SELECT * FROM route;


Geofencing Alerts:

-- Trigger when user enters area
CREATE OR REPLACE FUNCTION check_geofence()
RETURNS TRIGGER AS $
BEGIN
IF ST_DWithin(NEW.location, store_location, 100) THEN
INSERT INTO notifications (user_id, message)
VALUES (NEW.user_id, 'Welcome! You are near our store!');
END IF;
RETURN NEW;
END;
$ LANGUAGE plpgsql;


πŸ’‘ The Game Changer:
-- Spatial indexes = instant geographic queries
CREATE INDEX idx_restaurants_location
ON restaurants USING GIST (location);

-- Now this is instant on millions of points
SELECT * FROM restaurants
WHERE ST_DWithin(location, user_point, 5000)
ORDER BY location <-> user_point
LIMIT 10;


PostGIS turned our $5K/year Google Maps API bill into $0.

What geographic problem could PostGIS solve for you? 🌍

#PostgreSQL #PostGIS #Geospatial #LocationData

@postgres
πŸ‘1
⏰ pg_cron: Your Database Scheduler That Never Forgets

Stop using external cron jobs that fail silently. Run everything inside PostgreSQL.

The Problem with External Schedulers:
# Crontab - looks simple, fails mysteriously
0 2 * * * /usr/bin/psql -d mydb -c "VACUUM ANALYZE;"
# Connection fails? Silent failure
# Database down? Silent failure
# Wrong permissions? Silent failure


Enter pg_cron (Built-in Reliability):
-- Install once
CREATE EXTENSION pg_cron;

-- Schedule directly in database
SELECT cron.schedule('nightly-vacuum', '0 2 * * *', 'VACUUM ANALYZE;');
SELECT cron.schedule('hourly-refresh', '0 * * * *', 'REFRESH MATERIALIZED VIEW dashboard;');
SELECT cron.schedule('weekly-cleanup', '0 0 * * 0', 'DELETE FROM logs WHERE created < NOW() - INTERVAL ''30 days'';');

-- See all jobs
SELECT * FROM cron.job;

-- Check execution history
SELECT * FROM cron.job_run_details
ORDER BY start_time DESC
LIMIT 10;


🎯 Real Production Schedules:
Smart Partition Management:

-- Auto-create next month's partition
SELECT cron.schedule(
'create-next-partition',
'0 0 25 * *', -- 25th of each month
$
DO $
DECLARE
next_month DATE := DATE_TRUNC('month', CURRENT_DATE + INTERVAL '1 month');
partition_name TEXT := 'orders_' || TO_CHAR(next_month, 'YYYY_MM');
BEGIN
EXECUTE format('CREATE TABLE IF NOT EXISTS %I PARTITION OF orders
FOR VALUES FROM (%L) TO (%L)',
partition_name, next_month, next_month + INTERVAL '1 month');
RAISE NOTICE 'Created partition: %', partition_name;
END $;
$
);


Incremental Statistics Update:

-- Update stats on frequently changing tables
SELECT cron.schedule(
'update-hot-stats',
'*/15 * * * *', -- Every 15 minutes
'ANALYZE orders, users, sessions;'
);


Data Aggregation Pipeline:

-- Build hourly rollups
SELECT cron.schedule(
'hourly-aggregates',
'5 * * * *', -- 5 minutes past each hour
$
INSERT INTO hourly_stats (hour, metric, value)
SELECT
DATE_TRUNC('hour', created_at) as hour,
'orders' as metric,
COUNT(*) as value
FROM orders
WHERE created_at >= DATE_TRUNC('hour', NOW() - INTERVAL '1 hour')
AND created_at < DATE_TRUNC('hour', NOW())
GROUP BY DATE_TRUNC('hour', created_at)
ON CONFLICT (hour, metric) DO UPDATE
SET value = EXCLUDED.value;
$
);


πŸ’‘ Advanced Patterns:
-- Conditional execution
SELECT cron.schedule(
'business-hours-only',
'*/10 9-17 * * 1-5', -- Every 10 min, 9-5, Mon-Fri
$
PERFORM process_pending_orders()
WHERE EXISTS (SELECT 1 FROM pending_orders);
$
);

-- Error handling built-in
SELECT cron.schedule(
'safe-cleanup',
'0 3 * * *',
$
BEGIN
DELETE FROM old_data WHERE created < NOW() - INTERVAL '90 days';
INSERT INTO audit_log (action, rows_affected)
VALUES ('cleanup', ROW_COUNT);
EXCEPTION WHEN OTHERS THEN
INSERT INTO error_log (error_message)
VALUES (SQLERRM);
END;
$
);


🚨 Monitoring Your Jobs:
-- Failed jobs alert
CREATE OR REPLACE VIEW failing_jobs AS
SELECT
job.jobname,
COUNT(*) FILTER (WHERE status = 'failed') as failures,
COUNT(*) as total_runs,
MAX(return_message) as last_error
FROM cron.job_run_details d
JOIN cron.job ON job.jobid = d.jobid
WHERE d.start_time > NOW() - INTERVAL '24 hours'
GROUP BY job.jobname
HAVING COUNT(*) FILTER (WHERE status = 'failed') > 0;


Never write another crontab. Never miss another scheduled task.

Tomorrow: Build a complete SaaS backend in PostgreSQL - perfect for solo developers!

#PostgreSQL #pgcron #Automation #Scheduling

@postgres
πŸ‘2
This media is not supported in the widget
VIEW IN TELEGRAM
PostgreSQL Pro | Database Mastery pinned Β«πŸš€ [PREMIUM] Build Your Complete SaaS Backend in PostgreSQL For Solo Developers & Small Teams: Everything You Need, Nothing You Don't Tired of juggling Redis, queues, webhooks, and 10 other services? Build your entire SaaS backend with just PostgreSQL. What…»
πŸ’¬ Extension Thursday: Your PostgreSQL Superpower Questions!

Amazing week exploring extensions! Let's solve your implementation challenges.

πŸ†˜ From Lisa: "PostGIS is slow on my 5M location dataset"
-- Lisa's slow query (8 seconds):
SELECT * FROM locations
WHERE ST_DWithin(point::geography, user_location, 5000);


The fix: Right data type + right index

-- 1. Use geometry for local data (faster than geography)
ALTER TABLE locations
ADD COLUMN geom GEOMETRY(POINT, 4326);

UPDATE locations
SET geom = ST_Transform(point::geometry, 4326);

-- 2. Spatial index
CREATE INDEX idx_locations_geom
ON locations USING GIST (geom);

-- 3. Bounding box pre-filter
SELECT * FROM locations
WHERE geom && ST_Expand(user_location, 0.05) -- Rough filter
AND ST_DWithin(geom, user_location, 5000); -- Precise filter
-- Now: 0.2 seconds!


πŸ†˜ From Carlos: "pg_cron jobs randomly fail"
Common cause: Connection limits

-- Check if pg_cron is exhausting connections
SELECT COUNT(*) as cron_connections
FROM pg_stat_activity
WHERE application_name = 'pg_cron';

-- Fix: Adjust pg_cron settings
ALTER SYSTEM SET cron.max_running_jobs = 5; -- Default is 32!
SELECT pg_reload_conf();

-- Better: Combine multiple small jobs
-- Instead of 50 individual jobs:
SELECT cron.schedule('combined-maintenance', '0 * * * *', $
PERFORM cleanup_old_sessions();
PERFORM update_statistics();
PERFORM refresh_caches();
$);


πŸ†˜ From Ahmed: "How do I handle Stripe webhooks in PostgreSQL?"
Here's a preview from yesterday's masterclass:

-- Webhook handler table
CREATE TABLE webhook_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source TEXT NOT NULL, -- 'stripe', 'github', etc
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
processed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);

-- Process webhooks with pg_cron
SELECT cron.schedule('process-webhooks', '* * * * *', $
WITH next_event AS (
SELECT id, source, event_type, payload
FROM webhook_events
WHERE NOT processed
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED
)
UPDATE webhook_events
SET processed = TRUE
WHERE id = (
SELECT id FROM next_event
-- Process based on type
-- Handle subscription updates, payments, etc
);
$);


πŸ“Š Solo Dev Power Combos:
PostGIS + pg_cron = Location-based notifications

-- Alert users about nearby events
SELECT cron.schedule('nearby-alerts', '*/5 * * * *', $
INSERT INTO notifications (user_id, message)
SELECT u.id, 'New event near you: ' || e.name
FROM users u
JOIN events e ON ST_DWithin(u.location, e.location, 1000)
WHERE e.created_at > NOW() - INTERVAL '5 minutes';
$);


Your question about building with PostgreSQL? Drop it below!

Tomorrow: Week recap + December planning!

#PostgreSQL #Extensions #Community #SoloDevs

@postgres
❀1
πŸŽ‰ 100+ SUBSCRIBERS MILESTONE CELEBRATION! πŸŽ‰
Wow! When we started this journey with 89 followers, I never imagined we'd hit 100+ so quickly. This community is amazing, and I want to celebrate with YOU!

Here's what we're doing:

One of our paid masterclasses will be completely FREE this weekend (Saturday-Sunday) for EVERYONE - including members who haven't joined yet.

But I want YOU to decide which one!
Vote below:
πŸ“Š Which masterclass should be free this weekend?
πŸ”Ή Performance Audit Blueprint (normally 1 Star)

πŸ”Ή Table Partitioning Masterclass (normally 5 Stars)

πŸ”Ή High Availability & Replication Masterclass (normally 10 Stars)

πŸ”Ή Full-text search and ElasticSearch replacement (normally 15 Stars)



Why we're doing this:

To thank our loyal community for helping us grow
To let new members experience our premium content quality
To celebrate hitting 100+ PostgreSQL enthusiasts together!

Vote in the poll below! ⬇️


Thank you for being part of @postgres. Here's to the next 1,000 members! πŸš€
#PostgreSQL #CommunityFirst #Milestone
🎁 WEEKEND SPECIAL: Free PostgreSQL Full-Text Search Masterclass!
You voted, and here it is - completely FREE for the next 48 hours! πŸŽ‰

πŸ“₯ Download the PDF attached to this post ⬆️

What's Inside (45 pages):
βœ… Full-Text Search Fundamentals

tsvector & tsquery explained
Search configurations & dictionaries
Ranking & relevance tuning

βœ… Trigram Magic (pg_trgm)

Fuzzy search implementation
LIKE queries that actually scale
Similarity scoring

βœ… Production-Ready Patterns

Multi-language search setup
Autocomplete implementation
Search across multiple tables

βœ… Performance Optimization

GIN vs GiST indexes
Query optimization techniques
Real benchmarks & comparisons

βœ… Bonus Scripts

Ready-to-use search implementations
Migration from Elasticsearch
Monitoring queries

Why This Matters:

One member implemented this last week: "Replaced our Elasticsearch cluster with PostgreSQL FTS. Saved $800/month, searches are faster, and maintenance is 10x easier."

🎯 This Weekend Only (Saturday-Sunday)
Normally this is paid content. After Sunday 23:59, it goes back behind the paywall.

Download it now. Implement it Monday. Never need external search again.
Thank you for helping us reach 100+ members! This is OUR celebration. πŸš€

Questions? Drop them in comments - I'll be here all weekend helping you implement!

#PostgreSQL #FullTextSearch #FreeMasterclass #Community
@postgres

P.S. - If you find value in this, consider checking out our other masterclasses. Your support keeps this community growing! ⭐
❀3
πŸ’° Cloud PostgreSQL: When It Makes Sense (and When It Doesn't)

As a solo dev, every dollar matters. Let's do the math that AWS doesn't want you to see.

The Sales Pitch vs Reality:
AWS says: "Managed PostgreSQL from $15/month!"

Reality check:

AWS RDS db.t3.micro (20GB):      $15/month
+ EBS storage (100GB): $10/month
+ Backup storage (100GB): $10/month
+ Read replica (optional): $15/month
+ Multi-AZ (optional): $30/month
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Actual cost: $80/month

Self-hosted on $20 VPS:

Hetzner CPX21 (3vCPU, 4GB):     $20/month
+ Backups: $0 (included)
+ PostgreSQL: $0 (open source)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total: $20/month
Annual savings: $720/year

🎯 When to Use RDS (Solo Dev Edition):
βœ… Use RDS when:


You're making $10K+/month (time > money)
You're AWS-heavy already (EC2, Lambda, etc.)
You need point-in-time recovery without ops work
Client requires "managed service" for compliance

❌ Skip RDS when:


You're pre-revenue or bootstrapping
You're comfortable with SSH and basic Linux
You want to learn PostgreSQL deeply
You have < 1M rows (self-hosting is easy)

πŸ’‘ The Hybrid Approach (Best for Most):
Development: Local Docker
Staging: $5 VPS with backups
Production: Managed service when revenue > $5K/month

Real Solo Dev Scenarios:
Scenario 1: SaaS MVP


Users: < 1,000
Data: < 10GB
Traffic: < 100 req/min
β†’ Use $20 VPS with automated backups
Monthly cost: $20 vs $80 (save $720/year)

Scenario 2: API Service


Users: 5,000-10,000
Data: 50-100GB
Traffic: 1,000 req/min
β†’ Still self-hosted, upgrade to $40 VPS
Monthly cost: $40 vs $200 (save $1,920/year)

Scenario 3: Growing Startup


Users: 50,000+
Data: 500GB+
Traffic: 10,000 req/min
β†’ NOW consider managed (RDS/Aurora)
Time saved > money spent

πŸ› οΈ The $20 Production Setup:
# On Hetzner/DigitalOcean/Vultr
# 1. Install PostgreSQL 16
apt install postgresql-16

# 2. Configure automated backups
cat > /etc/cron.daily/pg-backup << 'EOF'
#!/bin/bash
pg_dump -U postgres mydb | gzip > /backup/mydb_$(date +%Y%m%d).sql.gz
# Upload to S3/Backblaze
rclone sync /backup remote:backups
# Keep last 30 days
find /backup -name "*.sql.gz" -mtime +30 -delete
EOF

# 3. Set up monitoring
apt install prometheus-postgres-exporter

# Done! Production-ready for $20/month

πŸ“Š 3-Year Total Cost Comparison:



Setup
Year 1
Year 2
Year 3
Total




Self-hosted
$240
$480
$720
$1,440


RDS Basic
$960
$1,920
$2,880
$5,760


RDS + HA
$1,920
$3,840
$5,760
$11,520



Savings over 3 years: $4,320 - $10,080

πŸš€ When You SHOULD Migrate to Managed:
Signals it's time to pay for managed:


βœ… Revenue > $10K/month
βœ… More than 2 hours/month on DB ops
βœ… Team > 1 person
βœ… Customer SLAs requiring 99.9% uptime
βœ… Multi-region needs

Rule of thumb: When DB downtime costs more than $80/hour, use managed.

Tomorrow's Preview:
Wednesday's premium masterclass: "Ship Your SaaS with PostgreSQL" - Complete production setup, authentication, multi-tenancy, and billing. Everything you need to launch.

Question: Are you self-hosting or using managed? What's your monthly DB cost?

#PostgreSQL #CloudComputing #SoloDev #CostOptimization #Bootstrapping

@postgres
πŸš€ Zero to Production PostgreSQL in 30 Minutes

Yesterday we covered the economics. Today: How to actually set up production PostgreSQL that won't embarrass you.

The One-Command Setup Nobody Teaches:
Most tutorials stop at apt install postgresql. That's not production. Here's production:

#!/bin/bash
# production-pg-setup.sh - Production PostgreSQL in one script

# 1. Install PostgreSQL 16
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt update && apt install -y postgresql-16

# 2. Configure for production
cat >> /etc/postgresql/16/main/postgresql.conf << EOF
# Connection settings
max_connections = 100
shared_buffers = 1GB
effective_cache_size = 3GB
work_mem = 10MB
maintenance_work_mem = 256MB

# WAL settings
wal_buffers = 16MB
checkpoint_completion_target = 0.9
max_wal_size = 2GB

# Query planning
random_page_cost = 1.1
effective_io_concurrency = 200

# Monitoring
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
EOF

# 3. Set up automated backups to S3
pip3 install wal-g
cat > /root/.walrc << EOF
export WALG_S3_PREFIX="s3://my-backups/postgres"
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
EOF

echo "0 2 * * * . /root/.walrc && wal-g backup-push /var/lib/postgresql/16/main" | crontab -

# 4. Set up monitoring
apt install -y prometheus-postgres-exporter
systemctl enable prometheus-postgres-exporter

# 5. Basic security
ufw allow 5432/tcp
ufw enable

echo "βœ… Production PostgreSQL ready!"


πŸ”’ Security Essentials (10 Minutes):
-- 1. Create app user (NOT postgres superuser!)
CREATE USER myapp WITH PASSWORD 'strong-random-password';
CREATE DATABASE myapp_db OWNER myapp;

-- 2. Restrict permissions
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE ON SCHEMA public TO myapp;

-- 3. Enable SSL
-- In postgresql.conf:
-- ssl = on
-- ssl_cert_file = '/etc/ssl/certs/server.crt'
-- ssl_key_file = '/etc/ssl/private/server.key'

-- 4. Configure pg_hba.conf properly
-- hostssl myapp_db myapp 0.0.0.0/0 scram-sha-256


πŸ“Š Essential Monitoring (5 Minutes):
-- Install pg_stat_statements
CREATE EXTENSION pg_stat_statements;

-- Daily health check query
CREATE VIEW health_check AS
SELECT
'DB Size' as metric,
pg_size_pretty(pg_database_size(current_database())) as value
UNION ALL
SELECT
'Active Connections',
count(*)::text
FROM pg_stat_activity
WHERE state = 'active'
UNION ALL
SELECT
'Cache Hit Ratio',
round(100.0 * sum(blks_hit) / nullif(sum(blks_hit + blks_read), 0), 2)::text || '%'
FROM pg_stat_database;

-- Check it daily
SELECT * FROM health_check;


πŸ”„ Automated Backup Verification:
#!/bin/bash
# backup-verify.sh - Run weekly

# 1. Take backup
pg_dump myapp_db > /tmp/test_backup.sql

# 2. Restore to test database
createdb test_restore
psql test_restore < /tmp/test_backup.sql

# 3. Run sanity checks
psql test_restore -c "SELECT count(*) FROM users;" > /tmp/user_count.txt

# 4. Compare with production
if [ "$(cat /tmp/user_count.txt)" == "$(psql myapp_db -c 'SELECT count(*) FROM users;')" ]; then
echo "βœ… Backup verified!"
else
echo "❌ Backup verification FAILED!"
# Send alert
fi

# 5. Cleanup
dropdb test_restore



πŸ’° Cost Breakdown (Self-Hosted):
VPS (Hetzner CPX21):             $20/month
S3 backup storage (50GB): $1/month
Monitoring (self-hosted): $0/month
SSL cert (Let's Encrypt): $0/month
Your time (2 hours/month): Priceless learning
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total: $21/month

vs AWS RDS equivalent: $150/month
Annual savings: $1,548/year



#PostgreSQL #Production #SelfHosted #SoloDev #DevOps

@postgres
πŸ’―2❀1
This media is not supported in the widget
VIEW IN TELEGRAM
❀1πŸ‘1
PostgreSQL Pro | Database Mastery pinned Β«πŸ”’ PostgreSQL-First SaaS Architecture Build complete SaaS with just PostgreSQL. Kill your $300/month service bills. PDF Download includes: β€’ Auth, billing, queues, real-time β€’ Replace 10 services with 1 β€’ Copy-paste Node.js code ⭐ 8 Stars - Scan QR to download…»
πŸ’¬ Thursday Q&A: Building Your SaaS with PostgreSQL

Amazing response on yesterday's masterclass! Let's answer your questions:

πŸ†˜ From X: "How do I handle different subscription tiers?"
-- Simple tier system
CREATE TABLE plans (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
price_monthly INTEGER, -- cents
max_users INTEGER,
max_storage_gb INTEGER,
features JSONB
);

INSERT INTO plans VALUES
('free', 'Free', 0, 5, 1, '["basic"]'),
('pro', 'Pro', 2900, 25, 50, '["basic","advanced","api"]'),
('enterprise', 'Enterprise', 9900, NULL, NULL, '["basic","advanced","api","support"]');

-- Check feature access
CREATE FUNCTION has_feature(tenant_id UUID, feature TEXT)
RETURNS BOOLEAN AS $$
SELECT features @> to_jsonb(feature)
FROM plans p
JOIN tenants t ON t.plan = p.id
WHERE t.id = tenant_id;
$$ LANGUAGE SQL;

-- Use in queries
SELECT * FROM premium_content
WHERE has_feature(current_setting('app.tenant_id')::UUID, 'advanced');


πŸ†˜ From Y: "How to track usage for usage-based pricing?"
-- Usage tracking table
CREATE TABLE usage_events (
id BIGSERIAL PRIMARY KEY,
tenant_id UUID REFERENCES tenants(id),
event_type TEXT NOT NULL, -- 'api_call', 'storage_mb', 'email_sent'
quantity INTEGER DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Index for fast aggregation
CREATE INDEX ON usage_events(tenant_id, event_type, created_at);

-- Monthly usage view
CREATE VIEW monthly_usage AS
SELECT
tenant_id,
event_type,
date_trunc('month', created_at) as month,
sum(quantity) as total
FROM usage_events
GROUP BY tenant_id, event_type, date_trunc('month', created_at);

-- Check if tenant exceeded quota
SELECT total > 1000 as exceeded_quota
FROM monthly_usage
WHERE tenant_id = 'xxx'
AND event_type = 'api_call'
AND month = date_trunc('month', NOW());


πŸ†˜ From Z: "Background job failures - how to retry?"
-- Job queue with retry logic
CREATE TABLE jobs (
id BIGSERIAL PRIMARY KEY,
job_type TEXT NOT NULL,
payload JSONB NOT NULL,
status TEXT DEFAULT 'pending', -- pending, running, failed, completed
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 3,
next_run_at TIMESTAMPTZ DEFAULT NOW(),
error TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Process job with exponential backoff
CREATE OR REPLACE FUNCTION process_jobs() RETURNS void AS $$
DECLARE
job RECORD;
BEGIN
FOR job IN
SELECT * FROM jobs
WHERE status IN ('pending', 'failed')
AND attempts < max_attempts
AND next_run_at <= NOW()
ORDER BY next_run_at
LIMIT 10
FOR UPDATE SKIP LOCKED
LOOP
BEGIN
-- Update status
UPDATE jobs SET status = 'running', attempts = attempts + 1
WHERE id = job.id;

-- Execute job (your logic here)
-- PERFORM execute_job(job.job_type, job.payload);

-- Mark completed
UPDATE jobs SET status = 'completed' WHERE id = job.id;

EXCEPTION WHEN OTHERS THEN
-- Failed, schedule retry with exponential backoff
UPDATE jobs SET
status = 'failed',
error = SQLERRM,
next_run_at = NOW() + (POWER(2, attempts) || ' minutes')::INTERVAL
WHERE id = job.id;
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Schedule with pg_cron
SELECT cron.schedule('process-jobs', '* * * * *', 'SELECT process_jobs()');



πŸ’‘ Your Questions:
Drop your SaaS architecture questions below! Tomorrow: Week recap + next week preview!

#PostgreSQL #SaaS #Community #SoloDev

@postgres
❀3
πŸ“Š Week 7 Complete: Production PostgreSQL Mastered!

This Week's Focus: Cloud vs Self-Hosted & SaaS Architecture
βœ… Monday: Cloud costs demystified ($720-10K/year savings)
βœ… Tuesday: Production setup in 30 minutes
βœ… Wednesday: SaaS Masterclass launched!
βœ… Thursday: Architecture Q&A

πŸ“ˆ Community Impact This Week:
Cost savings:


23 developers switched to self-hosting β†’ $16,740/year saved collectively
8 developers consolidated services β†’ $18,000/year saved
Total annual community savings this week: $34,740!

SaaS Masterclass:


14 purchases in first 48 hours! πŸŽ‰
Average feedback: "Worth 50x the price"
3 developers already implementing it


πŸ’° Premium Content Momentum:

Performance Blueprint (1 Star): 47 purchases
Partitioning Guide (5 Stars): 31 purchases
HA Masterclass (10 Stars): 19 purchases
SaaS Architecture (8 Stars): 14 purchases

Conversion improving: Up from 10% to 13% this week!


πŸš€ Week 8 Preview - PostgreSQL in Production:
Monday: Deployment Strategies


Docker vs bare metal
Blue-green deployments
Zero-downtime migrations
CI/CD for database changes

Tuesday: Monitoring & Observability


Essential metrics for solo devs
Free monitoring setup (no paid services)
Alert fatigue prevention
Performance baselines

Wednesday: [PREMIUM] Complete Admin Dashboard Kit (7 Stars)


Pre-built queries for every metric
User analytics
System health
Business KPIs
Revenue tracking
Copy-paste ready

Thursday: Scaling Decisions


When to optimize vs when to scale
Vertical vs horizontal scaling
Read replicas for solo devs
Cost-effective scaling

Friday: Community showcase


Show off what you built
Learn from each other
Week 8 recap

🎯 Weekend Challenge:
"Ship Something Small"

Build a mini-project using PostgreSQL this weekend:


Authentication + CRUD
Deploy it somewhere
Share the link

Best project wins Week 8's premium content FREE!

Ideas:


URL shortener
Note-taking app
Todo list with sharing
API rate limiter
Bookmark manager

πŸ’­ Week 8 Premium Content Vote:
What do you need most?
πŸ”΄ Admin dashboard queries & metrics
🟑 Advanced search implementation
🟒 API rate limiting & quotas
πŸ”΅ Webhooks & integrations

Vote below! Most popular wins Wednesday's premium slot!


🎁 Special Announcement:
Next Friday (Week 8): We're doing our first community showcase!

Show off:


What you've built with PostgreSQL
Problems you've solved
Optimizations you've made
Your SaaS if you launched

Best showcases get featured + free premium content!


Three weeks of November left. Let's keep building!

Have an incredible weekend! Monday: Deployment strategies! πŸš€

#PostgreSQL #Week7 #Community #SoloDev #Production

@postgres
πŸš€ Docker vs Bare Metal: The Real Production Choice

Everyone says "use Docker!" But should you? Let's be honest about deployment for solo devs.

The Deployment Reality Check:
Docker Lovers Say:


"Consistent environments!"
"Easy scaling!"
"Portable everywhere!"

Your Actual Needs (Solo Dev):


One server
PostgreSQL + your app
Backups that work
Monitoring that doesn't lie

Real Deployment Comparison:
Option 1: Bare Metal ($20 VPS)

# 10 minutes to production
apt install postgresql-16
systemctl enable postgresql
# Done. It just works.

Pros:
βœ… Dead simple
βœ… Max performance
βœ… Direct access to logs
βœ… No Docker overhead

Cons:
❌ Manual updates
❌ No container isolation


Option 2: Docker Compose

# docker-compose.yml
services:
db:
image: postgres:16
volumes:
- ./data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
restart: always

Pros:
βœ… Version locked
βœ… Easy local dev parity
βœ… One-command updates

Cons:
❌ Volume permission hell
❌ 10-15% performance hit
❌ Backup complexity


Option 3: Docker + Managed (Best of Both)

# App in Docker, DB managed
services:
app:
build: .
environment:
DATABASE_URL: ${RDS_URL}


🎯 The Solo Dev Deployment Strategy:
Stage 1 (0-1K users): Bare metal


Simple, fast, cheap
Learn PostgreSQL deeply

Stage 2 (1K-10K users): Docker for app, bare metal DB


Easy app updates
Database stays stable

Stage 3 (10K+ users): Containers + managed DB


Now complexity pays off

Blue-Green Deployments (Without K8s):
# Simple zero-downtime deployment
# 1. Deploy to new port
docker run -d -p 3001:3000 app:new

# 2. Test
curl localhost:3001/health

# 3. Switch nginx
sed -i 's/3000/3001/g' /etc/nginx/sites-enabled/app
nginx -s reload

# 4. Stop old version
docker stop app:old


CI/CD for Database Changes:
# .github/workflows/deploy.yml
- name: Run migrations
run: |
# Always forward, never back
psql $DATABASE_URL -f migrations/$(date +%Y%m%d).sql

# Test rollback locally first!
# psql local_db -f migrations/rollback.sql


Real Developer Experiences:
"Spent 2 days fixing Docker networking. Switched to bare metal. Been running 14 months, zero issues." - Mike T.

"Docker-compose for dev/staging. Bare PostgreSQL for production. Perfect balance." - Sarah K.

Tomorrow: Monitoring That Actually Helps
What monitoring setup do you use? Docker or bare metal?

#PostgreSQL #Docker #Deployment #DevOps #SoloDev

@postgres
❀1
πŸ“Š Stop Flying Blind: PostgreSQL Monitoring for $0

The Only 5 Metrics That Matter:
-- 1. Slow queries killing performance
CREATE VIEW monitoring.slow_queries AS
SELECT
SUBSTRING(query, 1, 50) as query_start,
calls,
mean_exec_time::numeric(10,2) as avg_ms,
total_exec_time::numeric(10,2)/1000 as total_sec,
100.0 * total_exec_time / sum(total_exec_time) OVER() as percentage
FROM pg_stat_statements
WHERE query NOT LIKE '%pg_stat%'
ORDER BY mean_exec_time DESC
LIMIT 10;

-- 2. Table bloat eating disk
CREATE VIEW monitoring.table_bloat AS
SELECT
schemaname || '.' || tablename as table_name,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size,
n_dead_tup,
n_live_tup,
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) as dead_percent
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

-- 3. Connection pool exhaustion
CREATE VIEW monitoring.connections AS
SELECT
state,
COUNT(*) as count,
COUNT(*) * 100.0 / current_setting('max_connections')::int as percentage
FROM pg_stat_activity
GROUP BY state
UNION ALL
SELECT
'total' as state,
COUNT(*) as count,
COUNT(*) * 100.0 / current_setting('max_connections')::int as percentage
FROM pg_stat_activity;

-- 4. Replication lag (if using replicas)
CREATE VIEW monitoring.replication_status AS
SELECT
client_addr,
state,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) as lag_size,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::int as lag_seconds
FROM pg_stat_replication;

-- 5. Cache hit ratio (should be >99%)
CREATE VIEW monitoring.cache_performance AS
SELECT
sum(heap_blks_read) as disk_reads,
sum(heap_blks_hit) as cache_hits,
CASE
WHEN sum(heap_blks_hit) + sum(heap_blks_read) = 0 THEN 0
ELSE round(100.0 * sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)), 2)
END as cache_hit_ratio
FROM pg_statio_user_tables;


Free Monitoring Stack (Better than Paid):
1. Grafana + Prometheus (Local)

# 5-minute setup
docker run -d -p 9090:9090 prom/prometheus
docker run -d -p 9187:9187 wrouesnel/postgres_exporter
docker run -d -p 3000:3000 grafana/grafana

# Import dashboard: 9628
# Done. Professional monitoring.


2. pg_stat_statements (Built-in)

-- Enable it once
CREATE EXTENSION pg_stat_statements;

-- Find query killers
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 5;



Alert Fatigue Prevention:
DON'T Alert On:


CPU at 80% for 1 minute
Single slow query
Temporary connection spike

DO Alert On:


Disk space < 10%
Replication lag > 5 minutes
Connection pool > 90%
Same query slow > 10 times

Performance Baselines:
-- Save weekly baselines
CREATE TABLE monitoring.baselines (
week_of DATE,
avg_query_time NUMERIC,
total_connections INTEGER,
cache_hit_ratio NUMERIC,
largest_table_size BIGINT
);

-- Weekly snapshot
INSERT INTO monitoring.baselines
SELECT
date_trunc('week', NOW()),
(SELECT AVG(mean_exec_time) FROM pg_stat_statements),
(SELECT COUNT(*) FROM pg_stat_activity),
(SELECT round(100.0 * sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)), 2) FROM pg_statio_user_tables),
(SELECT MAX(pg_total_relation_size(schemaname||'.'||tablename)) FROM pg_stat_user_tables);



Tomorrow: [PREMIUM] Complete Admin Dashboard
Your biggest monitoring pain point?

#PostgreSQL #Monitoring #Observability #DevOps #Performance

@postgres
❀1
This media is not supported in the widget
VIEW IN TELEGRAM
PostgreSQL Pro | Database Mastery pinned Β«πŸ”’ PostgreSQL Admin Dashboard Kit Every query you need to understand your database. Copy-paste ready. PDF Download includes: β€’ 35 dashboard queries β€’ User analytics & KPIs β€’ Revenue tracking SQL β€’ System health monitors ⭐ 7 Stars - Scan QR to download …»
🎯 When to Scale vs When to Optimize (The $10K Difference)

Most scaling advice is enterprise BS. Here's scaling for solo devs and small teams.

The Uncomfortable Truth:
You probably don't need to scale. You need to optimize.

Real numbers from production:


Unoptimized: 100 requests/second β†’ server dying
After optimization: 5,000 requests/second β†’ same server
Cost difference: $0
Time invested: 4 hours

The Optimization Checklist (Before Scaling):
-- 1. Missing indexes (90% of problems)
SELECT
schemaname,
tablename,
attname,
n_distinct,
correlation
FROM pg_stats
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
AND n_distinct > 100
AND correlation < 0.1
ORDER BY n_distinct DESC;

-- Create indexes for high-cardinality, low-correlation columns

-- 2. N+1 queries (check your ORM)
SELECT
query,
calls,
mean_exec_time
FROM pg_stat_statements
WHERE calls > 1000
AND mean_exec_time < 1
ORDER BY calls DESC;
-- These are N+1 candidates

-- 3. Lock contention
SELECT
pg_locks.pid,
pg_stat_activity.query,
pg_locks.mode,
age(now(), pg_stat_activity.query_start) AS duration
FROM pg_locks
JOIN pg_stat_activity ON pg_locks.pid = pg_stat_activity.pid
WHERE NOT pg_locks.granted
ORDER BY duration DESC;


When You ACTUALLY Need to Scale:
Vertical Scaling (Bigger Server)
Good for:


CPU-bound queries
Complex analytics
Large working sets

When: Cache hit ratio < 95% after optimization

# From $20 to $40 server
# 2x CPU, 2x RAM
# Solves 90% of scaling needs


Horizontal Scaling (Read Replicas)
Good for:


Read-heavy workloads (90%+ reads)
Geographic distribution
Report queries

-- Simple read/write split
-- Writes go to primary
await db.query('INSERT INTO users...', [], { target: 'primary' });

-- Reads go to replica
await db.query('SELECT * FROM users...', [], { target: 'replica' });


The Solo Dev Scaling Path:
Stage 1: Optimize queries


Add missing indexes
Fix N+1 queries
VACUUM regularly
Cost: $0
Handles: 0-10K users

Stage 2: Bigger server


Upgrade from $20 to $40-80 VPS
More CPU cores
More RAM for cache
Cost: +$20-60/month
Handles: 10K-100K users

Stage 3: Read replica


Add single read replica
Split read/write in app
Cost: +$40/month
Handles: 100K-500K users

Stage 4: Now consider managed


RDS/Aurora
Multi-region
Cost: +$200+/month
Handles: 500K+ users

Real Scaling Stories:
"Spent 2 weeks on microservices. Rolled back, added 3 indexes, 100x performance gain." - David M.

"Was quoted $50K for 'scaling consultation'. Read pg_stat_statements, found the slow query, fixed in 1 hour." - Lisa R.

Cost-Effective Scaling Tricks:
-- 1. Materialized views for heavy queries
CREATE MATERIALIZED VIEW dashboard_stats AS
SELECT [expensive query here]
WITH DATA;

-- Refresh hourly
CREATE EXTENSION pg_cron;
SELECT cron.schedule('refresh-stats', '0 * * * *',
'REFRESH MATERIALIZED VIEW CONCURRENTLY dashboard_stats');

-- 2. Partitioning for time-series
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

-- Old partitions can go to cheaper storage


Your Scaling Questions:
What's your current bottleneck? CPU, RAM, or I/O?

Tomorrow: First Community Showcase! πŸŽ‰

#PostgreSQL #Scaling #Optimization #Performance #SoloDev

@postgres
❀2