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
๐ŸŽ‰ 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
๐ŸŽ‰ Community Showcase: What YOU Built This Week!

Featured Projects from Our Community:
๐Ÿ† Winner: Task Tracker SaaS by @maria_dev


Built with Week 7's SaaS Architecture
PostgreSQL-only (no Redis, no queues)
Launched in 5 days
Already has 12 paying users!
Stack: Next.js + PostgreSQL on $20 VPS

"The SaaS masterclass saved me weeks. Authentication, billing, everything just works."

๐Ÿฅˆ Runner-up: Analytics Dashboard by @alexcoder


Used Week 8's Admin Dashboard queries
Replaced $500/month Mixpanel
50ms query response on 10M events
Materialized views for instant loading

๐Ÿฅ‰ Third: URL Shortener by @dev_sarah


2-table schema
100K URLs, still instant
Built in one weekend
Clever use of PostgreSQL sequences for short codes

๐Ÿ“Š Week 8 Achievements:
This Week's Focus: Production PostgreSQL

โœ… Monday: Deployment strategies demystified
โœ… Tuesday: Free monitoring stack
โœ… Wednesday: Admin Dashboard Kit launched!
โœ… Thursday: Scaling vs optimizing
โœ… Friday: YOUR amazing projects!

๐Ÿ“ˆ Channel Growth Metrics:
Premium Content Success:


Admin Dashboard Kit (7 Stars): 8 purchases in 48 hours!


๐Ÿš€ Week 9 Preview - Advanced PostgreSQL Patterns:
Monday: Multitenancy Patterns


Row-level security
Schema per tenant
Shared vs isolated

Tuesday: Time-Series in PostgreSQL


Partitioning strategies
Compression techniques
Real-time aggregation

Wednesday: [PREMIUM] API Rate Limiting System (6 Stars)


Complete rate limiting in PostgreSQL
No Redis needed
Per-user quotas
Sliding windows

Thursday: Event Sourcing


Audit everything
Time travel queries
GDPR compliance

Friday: Month recap & December planning

๐ŸŽฏ Weekend Challenge:
"Production Deployment"

Deploy something to production this weekend:


Use a $5-20 VPS
Set up backups
Add monitoring
Share your URL Monday!

Best deployment wins Week 9's premium content FREE!

๐Ÿ’ญ Your Feedback Needed:
What December content would help you most?
๐Ÿ”ด Building a complete SaaS series
๐ŸŸก PostgreSQL + AI/ML
๐ŸŸข Performance deep-dives
๐Ÿ”ต Migration guides (MySQL โ†’ PostgreSQL)

๐Ÿ™ Thank You!
Two weeks ago, we were at 89 members. Today: 200+!

Your engagement, questions, and projects make this community special.

Special shoutout to everyone who bought premium content - you're funding even better content ahead!

What did you ship this week?
Drop your wins below - no matter how small! ๐Ÿ‘‡

#PostgreSQL #Community #Showcase #WeekRecap #Shipping

@postgres
โค2
๐Ÿข The 3 Ways to Build Multi-Tenant PostgreSQL (And Which One Won't Bankrupt You)

Building SaaS? You need to separate customer data. Here's the real story on multitenancy.

The Three Approaches:
Option 1: Shared Everything (Row-Level Security)

-- All customers in same tables
CREATE TABLE projects (
id UUID DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
name TEXT,
data JSONB
);

-- Enable RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

-- Magic: Users only see their data
CREATE POLICY tenant_isolation ON projects
FOR ALL
USING (tenant_id = current_setting('app.current_tenant')::UUID);

-- In your app
SET LOCAL app.current_tenant = '123e4567-e89b-12d3-a456';
SELECT * FROM projects; -- Only tenant's projects!


Cost: $20/month for unlimited tenants
Good for: B2C, freemium, thousands of small customers

Option 2: Schema Per Tenant

-- Each customer gets their own schema
CREATE SCHEMA tenant_spotify;
CREATE SCHEMA tenant_netflix;

-- Same tables in each schema
CREATE TABLE tenant_spotify.projects (...);
CREATE TABLE tenant_netflix.projects (...);

-- In your app
SET search_path TO tenant_spotify;
SELECT * FROM projects; -- Isolated by schema


Cost: $20-100/month (depends on number of schemas)
Good for: B2B, 10-500 enterprise customers

Option 3: Database Per Tenant

-- Each customer gets own database
CREATE DATABASE customer_001;
CREATE DATABASE customer_002;

-- Complete isolation
-- Connect to specific database per request


Cost: $20/month per database (gets expensive FAST)
Good for: Enterprise, compliance requirements, <50 customers

๐ŸŽฏ The Solo Dev Reality Check:
You're not Salesforce. Start with Option 1 (RLS).

Real numbers from production:


5,000 tenants with RLS: Works perfectly
500 schemas: Starting to get painful
50+ databases: Nightmare maintenance

Complete RLS Implementation:
-- 1. Add tenant_id to EVERYTHING
ALTER TABLE users ADD COLUMN tenant_id UUID;
ALTER TABLE projects ADD COLUMN tenant_id UUID;
ALTER TABLE invoices ADD COLUMN tenant_id UUID;

-- 2. Create helper function
CREATE OR REPLACE FUNCTION set_current_tenant(p_tenant_id UUID)
RETURNS void AS $$
BEGIN
PERFORM set_config('app.current_tenant', p_tenant_id::TEXT, true);
END;
$$ LANGUAGE plpgsql;

-- 3. Row-level security policies
CREATE POLICY users_tenant_policy ON users
USING (tenant_id = current_setting('app.current_tenant')::UUID);

CREATE POLICY projects_tenant_policy ON projects
USING (tenant_id = current_setting('app.current_tenant')::UUID);

-- 4. Enforce for all queries
ALTER TABLE users FORCE ROW LEVEL SECURITY;
ALTER TABLE projects FORCE ROW LEVEL SECURITY;

-- 5. In your Node.js app
async function executeQuery(tenantId, query, params) {
await db.query('SELECT set_current_tenant($1)', [tenantId]);
return db.query(query, params);
}

// Now EVERY query is automatically filtered!
const projects = await executeQuery(
tenantId,
'SELECT * FROM projects' // No WHERE clause needed!
);


Performance Impact:
-- Without RLS: 0.8ms
SELECT * FROM projects WHERE tenant_id = '123';

-- With RLS: 0.9ms
SET LOCAL app.current_tenant = '123';
SELECT * FROM projects;

-- 12% overhead for bulletproof isolation? Worth it.


Migration Path as You Grow:
Stage 1 (0-100 customers): RLS
Stage 2 (100-1000): RLS with read replicas
Stage 3 (1000+): Consider schema separation for biggest customers
Stage 4 (Enterprise): Dedicated databases for whales

Common Pitfalls:
โŒ Forgetting tenant_id on new tables
Solution: Create a base migration template

โŒ Cross-tenant queries failing
Solution: Use SECURITY DEFINER functions for admin queries

โŒ Performance degradation
Solution: Always index tenant_id FIRST in compound indexes

Your Multi-tenant Questions:
How many tenants are you planning for? What's your isolation requirement?

Tomorrow: Time-series data without TimescaleDB!

#PostgreSQL #Multitenancy #SaaS #RowLevelSecurity #Architecture

@postgres
1000 Subscribers Announcement Post

๐ŸŽ‰ 954 โ†’ 1,000: Something BIG Is Coming...

We're 46 subscribers away from 1,000.

When we hit that number (probably this week), I'm doing something I've never done before.

Here's what I can tell you:

๐Ÿ’Ž It involves Telegram's newest features
Not just content. Something more.

๐Ÿ“š Everything changes for 48 hours
What was locked becomes unlocked. All of it.

๐ŸŽฒ Pure chance will favor some of you
No contests. No "best comment wins." Just fair, random luck.

โฐ It happens automatically at 1,000
The moment we cross that line, it begins.

Why 1,000 matters:

Two months ago: 89 solo devs learning PostgreSQL
Today: 954 developers saving $100K+/year collectively
At 1,000: We become one of the largest PostgreSQL communities on Telegram

This isn't just a number. It's validation that practical, no-BS PostgreSQL education works.

Your move:

If you know someone who:
- Pays too much for cloud databases
- Struggles with PostgreSQL optimization
- Wants to ship, not just learn

Send them here. Let's hit 1,000 together.

The faster we hit 1,000, the sooner [REDACTED] happens.

Trust me, you want to be here when it does. And you REALLY want to be online that weekend. ๐Ÿ“ฑ

*Hint: Keep notifications on. Some things can't be claimed later.* ๐Ÿ’ซ

Let's make this week legendary.

#PostgreSQL #Community #Milestone #Soon

@postgres
๐Ÿ“ˆ Time-Series Without TimescaleDB: PostgreSQL Does It Better Than You Think

Stop paying for TimescaleDB. Vanilla PostgreSQL handles billions of time-series records just fine.

### The Time-Series Myth:

"You need a special database for time-series data"

Netflix, Uber, and Discord use PostgreSQL for metrics. If it scales for them...

### The Partition Strategy That Actually Works:

-- Create partitioned table for events/metrics
CREATE TABLE metrics (
time TIMESTAMPTZ NOT NULL,
sensor_id INTEGER,
value NUMERIC,
metadata JSONB
) PARTITION BY RANGE (time);

-- Auto-create monthly partitions
CREATE TABLE metrics_2024_11 PARTITION OF metrics
FOR VALUES FROM ('2024-11-01') TO ('2024-12-01');

CREATE TABLE metrics_2024_12 PARTITION OF metrics
FOR VALUES FROM ('2024-12-01') TO ('2025-01-01');

-- Index for fast queries
CREATE INDEX ON metrics_2024_11 (sensor_id, time DESC);

### Automatic Partition Management:

-- Function to create next month's partition
CREATE OR REPLACE FUNCTION create_monthly_partition()
RETURNS void AS $$
DECLARE
start_date date;
end_date date;
partition_name text;
BEGIN
start_date := date_trunc('month', CURRENT_DATE + interval '1 month');
end_date := start_date + interval '1 month';
partition_name := 'metrics_' || to_char(start_date, 'YYYY_MM');

-- Create partition
EXECUTE format('CREATE TABLE IF NOT EXISTS %I PARTITION OF metrics
FOR VALUES FROM (%L) TO (%L)',
partition_name, start_date, end_date);

-- Create indexes
EXECUTE format('CREATE INDEX ON %I (sensor_id, time DESC)', partition_name);
EXECUTE format('CREATE INDEX ON %I (time DESC)', partition_name);
END;
$$ LANGUAGE plpgsql;

-- Schedule monthly with pg_cron
SELECT cron.schedule('create-partition', '0 0 1 * *', 'SELECT create_monthly_partition()');

-- Auto-drop old partitions (keep 6 months)
CREATE OR REPLACE FUNCTION drop_old_partitions()
RETURNS void AS $$
DECLARE
cut_off_date date;
BEGIN
cut_off_date := date_trunc('month', CURRENT_DATE - interval '6 months');

-- Drop partitions older than 6 months
FOR partition_name IN
SELECT tablename FROM pg_tables
WHERE schemaname = 'public'
AND tablename LIKE 'metrics_%'
AND tablename < 'metrics_' || to_char(cut_off_date, 'YYYY_MM')
LOOP
EXECUTE format('DROP TABLE %I', partition_name);
END LOOP;
END;
$$ LANGUAGE plpgsql;

### Real-Time Aggregation Views:

-- Continuous aggregates (poor man's TimescaleDB)
CREATE MATERIALIZED VIEW hourly_aggregates AS
SELECT
date_trunc('hour', time) as hour,
sensor_id,
COUNT(*) as count,
AVG(value) as avg_value,
MIN(value) as min_value,
MAX(value) as max_value,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) as median
FROM metrics
WHERE time >= CURRENT_DATE - interval '7 days'
GROUP BY date_trunc('hour', time), sensor_id;

-- Refresh every hour
SELECT cron.schedule('refresh-hourly', '0 * * * *',
'REFRESH MATERIALIZED VIEW CONCURRENTLY hourly_aggregates');

-- Query aggregates = instant
SELECT * FROM hourly_aggregates
WHERE sensor_id = 42
AND hour >= NOW() - interval '24 hours';

### Compression Without Extensions:

-- BRIN indexes for time-series (95% smaller than B-tree)
CREATE INDEX metrics_time_brin ON metrics
USING BRIN (time) WITH (pages_per_range = 128);

-- Result: 1TB data, 50MB index!

-- Compress old partitions
ALTER TABLE metrics_2024_01 SET (fillfactor = 100);
CLUSTER metrics_2024_01 USING metrics_2024_01_time_idx;
ALTER TABLE metrics_2024_01 SET (autovacuum_enabled = false);

### Query Patterns That Fly:

`sql
-- Latest value per sensor (instant with proper index)
SELECT DISTINCT ON (sensor_id)
sensor_id, time, value
FROM metrics
WHERE time >= NOW() - interval '1 hour'
ORDER BY sensor_id, time DESC;
-- Time-weighted average
SELECT
sensor_id,
SUM(value * extract(epoch from lead(time) OVER w - time)) /
SUM(extract(epoch from lead(time) OVER w - time)) as time_weighted_avg
FROM metrics
WHERE time >= NOW() - interval '1 day'
WINDOW w AS (PARTITION BY sensor_id ORDER BY time)
ORDER BY sensor_id;

-- Downsampling for graphs
WITH downsampled AS (
SELECT
time_bucket('5 minutes', time) as bucket,
sensor_id,
AVG(value) as value
FROM metrics
WHERE time >= NOW() - interval '24 hours'
GROUP BY bucket, sensor_id
)
SELECT * FROM downsampled ORDER BY bucket;

-- Gap detection
SELECT
sensor_id,
time as gap_start,
lead(time) OVER (PARTITION BY sensor_id ORDER BY time) as gap_end,
lead(time) OVER (PARTITION BY sensor_id ORDER BY time) - time as gap_duration
FROM metrics
WHERE lead(time) OVER (PARTITION BY sensor_id ORDER BY time) - time > interval '5 minutes';

### Performance Numbers:

**My production setup:**
- 50 million records/day
- 6 months retention (9 billion rows)
- $40 VPS (8 CPU, 16GB RAM)
- Query response: <100ms for most queries
- Storage: 480GB (with compression)

**Equivalent TimescaleDB Cloud:** $400+/month
**Your savings:** $360/month

### The time_bucket Function (DIY TimescaleDB):

sql
-- Create your own time_bucket
CREATE OR REPLACE FUNCTION time_bucket(
bucket_width INTERVAL,
ts TIMESTAMPTZ
) RETURNS TIMESTAMPTZ AS $$
BEGIN
RETURN date_trunc('epoch', ts)::timestamptz +
(extract(epoch from date_trunc('epoch', ts)) /
extract(epoch from bucket_width))::bigint *
extract(epoch from bucket_width) * interval '1 second';
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- Now you have TimescaleDB's best feature for free!

### Tomorrow: [PREMIUM] API Rate Limiting System

What's your time-series use case? IoT, analytics, monitoring?

#PostgreSQL #TimeSeries #Partitioning #Analytics #Performance

@postgres
โค1
This media is not supported in the widget
VIEW IN TELEGRAM