# monitoring/performance_monitor.pyimportpsycopg2importjsonimporttimefromdatetimeimportdatetime,timedeltaclassPostgreSQLPerformanceMonitor:def__init__(self,connection_params):self.conn_params=connection_paramsself.conn=Nonedefconnect(self):"""Connect to PostgreSQL."""self.conn=psycopg2.connect(**self.conn_params)defget_slow_queries(self,limit=10):"""Get slowest queries."""withself.conn.cursor()ascur:cur.execute(""" SELECT query, calls, total_time, mean_time, rows, 100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent FROM pg_stat_statements ORDER BY total_time DESC LIMIT %s; """,(limit,))returncur.fetchall()defget_table_bloat(self):"""Get table bloat information."""withself.conn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, n_live_tup, n_dead_tup, ROUND(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS bloat_percent FROM pg_stat_user_tables WHERE n_live_tup > 0 ORDER BY bloat_percent DESC; """)returncur.fetchall()defget_index_usage(self):"""Get index usage statistics."""withself.conn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, indexname, idx_tup_read, idx_tup_fetch, idx_scan, CASE WHEN idx_scan = 0 THEN 0 ELSE ROUND(100.0 * idx_tup_fetch / NULLIF(idx_tup_read, 0), 2) END AS efficiency_percent FROM pg_stat_user_indexes ORDER BY idx_scan DESC; """)returncur.fetchall()defget_connection_stats(self):"""Get connection statistics."""withself.conn.cursor()ascur:cur.execute(""" SELECT count(*) as total_connections, count(*) FILTER (WHERE state = 'active') as active_connections, count(*) FILTER (WHERE state = 'idle') as idle_connections, count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction FROM pg_stat_activity; """)returncur.fetchone()defget_database_size(self):"""Get database size information."""withself.conn.cursor()ascur:cur.execute(""" SELECT pg_database.datname, pg_size_pretty(pg_database_size(pg_database.datname)) as size FROM pg_database WHERE datname NOT IN ('template0', 'template1', 'postgres') ORDER BY pg_database_size(pg_database.datname) DESC; """)returncur.fetchall()defgenerate_performance_report(self):"""Generate comprehensive performance report."""report={'timestamp':datetime.now().isoformat(),'slow_queries':self.get_slow_queries(),'table_bloat':self.get_table_bloat(),'index_usage':self.get_index_usage(),'connection_stats':self.get_connection_stats(),'database_sizes':self.get_database_size()}returnreport# Usageif__name__=="__main__":monitor=PostgreSQLPerformanceMonitor({'host':'localhost','database':'production','user':'monitor_user','password':'monitor_password'})monitor.connect()report=monitor.generate_performance_report()print(json.dumps(report,indent=2))
-- postgresql.conf memory settings-- Adjust based on your system's available RAM-- Shared memory (25% of total RAM)shared_buffers=2GB-- Effective cache size (75% of total RAM)effective_cache_size=6GB-- Work memory for sorting and hashingwork_mem=64MB-- Maintenance work memorymaintenance_work_mem=256MB-- Hash table size for hash joinshash_mem_multiplier=2.0-- Maximum memory for parallel workersmax_parallel_workers_per_gather=4max_parallel_workers=8max_parallel_maintenance_workers=4
-- I/O configuration for different storage types-- For SSD storagerandom_page_cost=1.1effective_io_concurrency=200-- For traditional HDD-- random_page_cost = 4.0-- effective_io_concurrency = 1-- WAL configurationwal_level=replicamax_wal_size=2GBmin_wal_size=80MBcheckpoint_completion_target=0.9checkpoint_timeout=15min-- Background writerbgwriter_delay=200msbgwriter_lru_maxpages=100bgwriter_lru_multiplier=2.0
-- B-tree indexes (default)CREATEINDEXidx_users_emailONusers(email);CREATEINDEXidx_posts_created_atONposts(created_at);-- Partial indexes for filtered queriesCREATEINDEXidx_posts_publishedONposts(created_at)WHEREpublished=true;-- Composite indexes for multi-column queriesCREATEINDEXidx_posts_user_publishedONposts(user_id,published,created_at);-- Covering indexes (include columns)CREATEINDEXidx_posts_coveringONposts(user_id,created_at)INCLUDE(title,published);-- GIN indexes for array and JSONB columnsCREATEINDEXidx_posts_tags_ginONpostsUSINGGIN(tags);CREATEINDEXidx_posts_metadata_ginONpostsUSINGGIN(metadata);-- GiST indexes for geometric dataCREATEINDEXidx_locations_geom_gistONlocationsUSINGGIST(geom);-- Hash indexes for equality comparisonsCREATEINDEXidx_users_username_hashONusersUSINGHASH(username);-- BRIN indexes for large tables with natural orderingCREATEINDEXidx_logs_timestamp_brinONlogsUSINGBRIN(created_at);
-- Analyze index usageSELECTschemaname,tablename,indexname,idx_scan,idx_tup_read,idx_tup_fetchFROMpg_stat_user_indexesORDERBYidx_scanDESC;-- Find unused indexesSELECTschemaname,tablename,indexname,idx_scanFROMpg_stat_user_indexesWHEREidx_scan=0ORDERBYschemaname,tablename;-- Get index size informationSELECTschemaname,tablename,indexname,pg_size_pretty(pg_relation_size(indexrelid))asindex_sizeFROMpg_stat_user_indexesORDERBYpg_relation_size(indexrelid)DESC;
-- Enable query timing\timingon-- Explain plans with different optionsEXPLAIN(ANALYZE,BUFFERS,FORMATJSON)SELECTu.username,p.title,p.created_atFROMusersuJOINpostspONu.id=p.user_idWHEREu.email='user@example.com'ORDERBYp.created_atDESCLIMIT10;-- Explain with verbose outputEXPLAIN(ANALYZE,BUFFERS,VERBOSE,FORMATJSON)SELECT*FROMpostsWHEREtags@>'["postgresql"]'::jsonbANDcreated_at>'2024-01-01';-- Cost analysisEXPLAIN(ANALYZE,BUFFERS,COSTS,FORMATJSON)SELECTCOUNT(*)FROMpostsWHEREpublished=trueANDcreated_atBETWEEN'2024-01-01'AND'2024-12-31';
-- 1. Use appropriate WHERE clauses-- Bad: Full table scanSELECT*FROMpostsWHERELOWER(title)LIKE'%postgresql%';-- Good: Use indexesSELECT*FROMpostsWHEREtitleILIKE'%postgresql%';-- 2. Avoid SELECT * in production-- BadSELECT*FROMusersWHEREemail='user@example.com';-- GoodSELECTid,username,emailFROMusersWHEREemail='user@example.com';-- 3. Use LIMIT for large result setsSELECT*FROMpostsWHEREpublished=trueORDERBYcreated_atDESCLIMIT100;-- 4. Optimize JOINs-- Use appropriate JOIN typesSELECTu.username,COUNT(p.id)aspost_countFROMusersuLEFTJOINpostspONu.id=p.user_idGROUPBYu.id,u.username;-- 5. Use EXISTS instead of IN for large datasets-- BadSELECT*FROMusersWHEREidIN(SELECTuser_idFROMpostsWHEREpublished=true);-- GoodSELECT*FROMusersuWHEREEXISTS(SELECT1FROMpostspWHEREp.user_id=u.idANDp.published=true);
-- Create partitioned tableCREATETABLEposts_partitioned(idSERIAL,user_idINTEGER,titleVARCHAR(200),contentTEXT,publishedBOOLEAN,created_atTIMESTAMP)PARTITIONBYRANGE(created_at);-- Create partitionsCREATETABLEposts_2024_q1PARTITIONOFposts_partitionedFORVALUESFROM('2024-01-01')TO('2024-04-01');CREATETABLEposts_2024_q2PARTITIONOFposts_partitionedFORVALUESFROM('2024-04-01')TO('2024-07-01');CREATETABLEposts_2024_q3PARTITIONOFposts_partitionedFORVALUESFROM('2024-07-01')TO('2024-10-01');CREATETABLEposts_2024_q4PARTITIONOFposts_partitionedFORVALUESFROM('2024-10-01')TO('2025-01-01');-- Create indexes on partitionsCREATEINDEXidx_posts_2024_q1_created_atONposts_2024_q1(created_at);CREATEINDEXidx_posts_2024_q1_user_idONposts_2024_q1(user_id);-- Automatic partition creationCREATEORREPLACEFUNCTIONcreate_monthly_partition(table_nametext,start_datedate)RETURNSvoidAS$$DECLAREpartition_nametext;end_datedate;BEGINpartition_name:=table_name||'_'||to_char(start_date,'YYYY_MM');end_date:=start_date+interval'1 month';EXECUTEformat('CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',partition_name,table_name,start_date,end_date);EXECUTEformat('CREATE INDEX %I ON %I(created_at)','idx_'||partition_name||'_created_at',partition_name);END;$$LANGUAGEplpgsql;
# connection_pool.pyimportpsycopg2frompsycopg2importpoolimportthreadingimporttimeclassPostgreSQLConnectionPool:def__init__(self,min_conn=5,max_conn=20,**kwargs):self.pool=psycopg2.pool.ThreadedConnectionPool(min_conn,max_conn,**kwargs)self.lock=threading.Lock()defget_connection(self):"""Get connection from pool."""returnself.pool.getconn()defreturn_connection(self,conn):"""Return connection to pool."""self.pool.putconn(conn)defexecute_query(self,query,params=None):"""Execute query using connection pool."""conn=Nonetry:conn=self.get_connection()withconn.cursor()ascur:cur.execute(query,params)ifcur.description:returncur.fetchall()returncur.rowcountfinally:ifconn:self.return_connection(conn)defclose_all(self):"""Close all connections in pool."""self.pool.closeall()# Usagepool=PostgreSQLConnectionPool(min_conn=5,max_conn=20,host='localhost',database='production',user='app_user',password='app_password')# Execute queriesresults=pool.execute_query("SELECT * FROM users WHERE active = %s",(True,))
# monitoring/metrics_collector.pyimportpsycopg2importtimeimportjsonfromprometheus_clientimportGauge,Counter,Histogram,start_http_serverclassPostgreSQLMetrics:def__init__(self,connection_params):self.conn_params=connection_params# Prometheus metricsself.connections_total=Gauge('postgresql_connections_total','Total connections')self.active_connections=Gauge('postgresql_active_connections','Active connections')self.database_size=Gauge('postgresql_database_size_bytes','Database size in bytes')self.slow_queries=Counter('postgresql_slow_queries_total','Total slow queries')self.query_duration=Histogram('postgresql_query_duration_seconds','Query duration')defcollect_metrics(self):"""Collect PostgreSQL metrics."""try:conn=psycopg2.connect(**self.conn_params)withconn.cursor()ascur:# Connection metricscur.execute("SELECT count(*) FROM pg_stat_activity;")total_connections=cur.fetchone()[0]self.connections_total.set(total_connections)cur.execute("SELECT count(*) FROM pg_stat_activity WHERE state = 'active';")active_connections=cur.fetchone()[0]self.active_connections.set(active_connections)# Database sizecur.execute("SELECT pg_database_size(current_database());")db_size=cur.fetchone()[0]self.database_size.set(db_size)# Slow queriescur.execute(""" SELECT count(*) FROM pg_stat_statements WHERE mean_time > 1000; """)slow_queries=cur.fetchone()[0]self.slow_queries.inc(slow_queries)conn.close()exceptExceptionase:print(f"Error collecting metrics: {e}")defstart_monitoring(self,port=8000):"""Start metrics server."""start_http_server(port)print(f"Metrics server started on port {port}")whileTrue:self.collect_metrics()time.sleep(30)# Usageif__name__=="__main__":metrics=PostgreSQLMetrics({'host':'localhost','database':'production','user':'monitor_user','password':'monitor_password'})metrics.start_monitoring()