-- Create B-tree indexes for equality and range queriesCREATETABLEusers(idSERIALPRIMARYKEY,usernameVARCHAR(50)UNIQUENOTNULL,emailVARCHAR(100)UNIQUENOTNULL,first_nameVARCHAR(50),last_nameVARCHAR(50),ageINTEGER,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP,is_activeBOOLEANDEFAULTTRUE);-- Single column B-tree indexesCREATEINDEXidx_users_usernameONusers(username);CREATEINDEXidx_users_emailONusers(email);CREATEINDEXidx_users_ageONusers(age);CREATEINDEXidx_users_created_atONusers(created_at);CREATEINDEXidx_users_is_activeONusers(is_active);-- Composite B-tree indexes (order by selectivity)CREATEINDEXidx_users_name_compositeONusers(last_name,first_name);CREATEINDEXidx_users_active_createdONusers(is_active,created_at);CREATEINDEXidx_users_age_activeONusers(age,is_active);-- Partial B-tree indexesCREATEINDEXidx_users_active_usersONusers(username)WHEREis_active=TRUE;CREATEINDEXidx_users_recent_usersONusers(created_at)WHEREcreated_at>'2024-01-01';
-- Create GIN indexes for array and JSONB columnsCREATETABLEarticles(idSERIALPRIMARYKEY,titleVARCHAR(200)NOTNULL,contentTEXTNOTNULL,tagsTEXT[],metadataJSONB,search_vectorTSVECTOR,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP);-- GIN indexes for arraysCREATEINDEXidx_articles_tags_ginONarticlesUSINGGIN(tags);-- GIN indexes for JSONBCREATEINDEXidx_articles_metadata_ginONarticlesUSINGGIN(metadata);-- GIN indexes for full-text searchCREATEINDEXidx_articles_search_vector_ginONarticlesUSINGGIN(search_vector);-- GIN indexes for specific JSONB pathsCREATEINDEXidx_articles_metadata_author_ginONarticlesUSINGGIN((metadata->'author'));CREATEINDEXidx_articles_metadata_category_ginONarticlesUSINGGIN((metadata->'category'));
-- Create GiST indexes for geometric and range dataCREATETABLElocations(idSERIALPRIMARYKEY,nameVARCHAR(100)NOTNULL,geomGEOMETRY(POINT,4326),bboxBOX,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP);-- GiST index for geometric dataCREATEINDEXidx_locations_geom_gistONlocationsUSINGGIST(geom);-- GiST index for range dataCREATEINDEXidx_locations_bbox_gistONlocationsUSINGGIST(bbox);-- GiST index for text search (alternative to GIN)CREATEINDEXidx_locations_name_gistONlocationsUSINGGIST(namegist_trgm_ops);
-- Create hash indexes for equality-only queriesCREATETABLEsessions(idSERIALPRIMARYKEY,session_tokenVARCHAR(255)UNIQUENOTNULL,user_idINTEGERNOTNULL,expires_atTIMESTAMPTZNOTNULL,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP);-- Hash index for session token lookupsCREATEINDEXidx_sessions_token_hashONsessionsUSINGHASH(session_token);-- Note: Hash indexes only support equality operations-- Use B-tree for range queries or sorting
-- Create BRIN indexes for large tables with natural orderingCREATETABLEsensor_readings(idBIGSERIALPRIMARYKEY,sensor_idINTEGERNOTNULL,reading_valueDOUBLEPRECISIONNOTNULL,reading_timeTIMESTAMPTZNOTNULL,location_idINTEGER);-- BRIN index for time-series dataCREATEINDEXidx_sensor_readings_time_brinONsensor_readingsUSINGBRIN(reading_time);-- BRIN index for sensor ID (if data is ordered by sensor)CREATEINDEXidx_sensor_readings_sensor_brinONsensor_readingsUSINGBRIN(sensor_id);-- BRIN index for location (if data is ordered by location)CREATEINDEXidx_sensor_readings_location_brinONsensor_readingsUSINGBRIN(location_id);
-- Create covering indexes to avoid table lookupsCREATETABLEorders(idSERIALPRIMARYKEY,customer_idINTEGERNOTNULL,order_dateDATENOTNULL,total_amountDECIMAL(10,2)NOTNULL,statusVARCHAR(20)NOTNULL,shipping_addressTEXT,billing_addressTEXT);-- Covering index for order lookupsCREATEINDEXidx_orders_customer_date_coveringONorders(customer_id,order_date)INCLUDE(total_amount,status);-- Covering index for status queriesCREATEINDEXidx_orders_status_date_coveringONorders(status,order_date)INCLUDE(customer_id,total_amount);
-- Create indexes on expressionsCREATETABLEproducts(idSERIALPRIMARYKEY,nameVARCHAR(200)NOTNULL,descriptionTEXT,priceDECIMAL(10,2)NOTNULL,categoryVARCHAR(50),created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP);-- Expression index for case-insensitive searchesCREATEINDEXidx_products_name_lowerONproducts(LOWER(name));-- Expression index for date functionsCREATEINDEXidx_products_created_yearONproducts(EXTRACT(YEARFROMcreated_at));-- Expression index for concatenated fieldsCREATEINDEXidx_products_name_categoryONproducts(name||' '||category);-- Expression index for JSONB operationsCREATEINDEXidx_products_metadata_priceONproducts((metadata->>'price')::numeric);
-- Create partial indexes for filtered dataCREATETABLEevents(idSERIALPRIMARYKEY,event_typeVARCHAR(50)NOTNULL,user_idINTEGERNOTNULL,event_dataJSONB,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP,processedBOOLEANDEFAULTFALSE);-- Partial index for unprocessed eventsCREATEINDEXidx_events_unprocessedONevents(created_at)WHEREprocessed=FALSE;-- Partial index for specific event typesCREATEINDEXidx_events_login_eventsONevents(user_id,created_at)WHEREevent_type='login';-- Partial index for recent eventsCREATEINDEXidx_events_recentONevents(event_type,created_at)WHEREcreated_at>CURRENT_DATE-INTERVAL'30 days';
-- Analyze table statistics for better index usageANALYZEusers;ANALYZEarticles;ANALYZEorders;-- Reindex specific indexesREINDEXINDEXidx_users_username;REINDEXINDEXidx_articles_search_vector_gin;-- Reindex all indexes on a tableREINDEXTABLEusers;-- Reindex all indexes in a databaseREINDEXDATABASEproduction;-- Update table statisticsUPDATEpg_stat_user_tablesSETlast_autoanalyze=NULLWHERErelname='users';
# monitoring/index_monitor.pyimportpsycopg2importjsonfromdatetimeimportdatetimeimportloggingclassIndexMonitor:def__init__(self,connection_params):self.conn_params=connection_paramsself.setup_logging()defsetup_logging(self):"""Setup logging."""logging.basicConfig(level=logging.INFO)self.logger=logging.getLogger(__name__)defget_index_usage_stats(self):"""Get index usage statistics."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch, pg_size_pretty(pg_relation_size(indexrelid)) as index_size, pg_relation_size(indexrelid) as index_size_bytes FROM pg_stat_user_indexes ORDER BY idx_scan DESC """)stats=cur.fetchall()returnstatsexceptExceptionase:self.logger.error(f"Error getting index usage stats: {e}")return[]finally:conn.close()deffind_unused_indexes(self):"""Find unused indexes."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(indexrelid)) as index_size, pg_relation_size(indexrelid) as index_size_bytes FROM pg_stat_user_indexes WHERE idx_scan = 0 ORDER BY pg_relation_size(indexrelid) DESC """)unused_indexes=cur.fetchall()returnunused_indexesexceptExceptionase:self.logger.error(f"Error finding unused indexes: {e}")return[]finally:conn.close()deffind_duplicate_indexes(self):"""Find duplicate indexes."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT t1.schemaname, t1.tablename, t1.indexname as index1, t2.indexname as index2, pg_get_indexdef(t1.indexrelid) as index_def FROM pg_stat_user_indexes t1 JOIN pg_stat_user_indexes t2 ON t1.schemaname = t2.schemaname AND t1.tablename = t2.tablename AND t1.indexname < t2.indexname WHERE pg_get_indexdef(t1.indexrelid) = pg_get_indexdef(t2.indexrelid) """)duplicate_indexes=cur.fetchall()returnduplicate_indexesexceptExceptionase:self.logger.error(f"Error finding duplicate indexes: {e}")return[]finally:conn.close()defget_index_bloat_info(self):"""Get index bloat information."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(indexrelid)) as index_size, pg_relation_size(indexrelid) as index_size_bytes, n_tup_ins as inserts, n_tup_upd as updates, n_tup_del as deletes FROM pg_stat_user_indexes ORDER BY pg_relation_size(indexrelid) DESC """)bloat_info=cur.fetchall()returnbloat_infoexceptExceptionase:self.logger.error(f"Error getting index bloat info: {e}")return[]finally:conn.close()defgenerate_index_report(self):"""Generate comprehensive index report."""usage_stats=self.get_index_usage_stats()unused_indexes=self.find_unused_indexes()duplicate_indexes=self.find_duplicate_indexes()bloat_info=self.get_index_bloat_info()report={'report_timestamp':datetime.now().isoformat(),'total_indexes':len(usage_stats),'unused_indexes_count':len(unused_indexes),'duplicate_indexes_count':len(duplicate_indexes),'usage_stats':usage_stats,'unused_indexes':unused_indexes,'duplicate_indexes':duplicate_indexes,'bloat_info':bloat_info}returnreport# Usageif__name__=="__main__":monitor=IndexMonitor({'host':'localhost','database':'production','user':'monitor_user','password':'monitor_password'})report=monitor.generate_index_report()print(json.dumps(report,indent=2))
-- Design composite indexes based on query patternsCREATETABLEorders(idSERIALPRIMARYKEY,customer_idINTEGERNOTNULL,order_dateDATENOTNULL,statusVARCHAR(20)NOTNULL,total_amountDECIMAL(10,2)NOTNULL,regionVARCHAR(50),created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP);-- Composite index for customer order queriesCREATEINDEXidx_orders_customer_date_statusONorders(customer_id,order_date,status);-- Composite index for regional sales analysisCREATEINDEXidx_orders_region_date_amountONorders(region,order_date,total_amount);-- Composite index for status-based queriesCREATEINDEXidx_orders_status_date_customerONorders(status,order_date,customer_id);-- Covering index for order summariesCREATEINDEXidx_orders_customer_summaryONorders(customer_id,order_date)INCLUDE(status,total_amount,region);
-- Create indexes for JSONB query patternsCREATETABLEuser_profiles(idSERIALPRIMARYKEY,user_idINTEGERNOTNULL,profile_dataJSONBNOTNULL,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP);-- GIN index for full JSONB contentCREATEINDEXidx_user_profiles_data_ginONuser_profilesUSINGGIN(profile_data);-- B-tree index for specific JSONB valuesCREATEINDEXidx_user_profiles_email_btreeONuser_profiles((profile_data->>'email'));-- GIN index for specific JSONB pathsCREATEINDEXidx_user_profiles_preferences_ginONuser_profilesUSINGGIN((profile_data->'preferences'));-- Expression index for JSONB operationsCREATEINDEXidx_user_profiles_age_btreeONuser_profiles(((profile_data->>'age')::integer));
# maintenance/index_maintenance.pyimportpsycopg2importloggingfromdatetimeimportdatetime,timedeltaclassIndexMaintenance:def__init__(self,connection_params):self.conn_params=connection_paramsself.setup_logging()defsetup_logging(self):"""Setup logging."""logging.basicConfig(level=logging.INFO)self.logger=logging.getLogger(__name__)defanalyze_tables(self,table_names=None):"""Analyze table statistics."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:iftable_names:fortable_nameintable_names:cur.execute(f"ANALYZE {table_name}")self.logger.info(f"Analyzed table: {table_name}")else:cur.execute("ANALYZE")self.logger.info("Analyzed all tables")conn.commit()exceptExceptionase:self.logger.error(f"Error analyzing tables: {e}")conn.rollback()finally:conn.close()defreindex_table(self,table_name):"""Reindex all indexes on a table."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(f"REINDEX TABLE {table_name}")self.logger.info(f"Reindexed table: {table_name}")exceptExceptionase:self.logger.error(f"Error reindexing table {table_name}: {e}")finally:conn.close()defreindex_index(self,index_name):"""Reindex a specific index."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(f"REINDEX INDEX {index_name}")self.logger.info(f"Reindexed index: {index_name}")exceptExceptionase:self.logger.error(f"Error reindexing index {index_name}: {e}")finally:conn.close()defdrop_unused_indexes(self,dry_run=True):"""Drop unused indexes."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, indexname FROM pg_stat_user_indexes WHERE idx_scan = 0 AND indexname NOT LIKE '%_pkey' AND indexname NOT LIKE '%_unique_%' """)unused_indexes=cur.fetchall()forschema,table,indexinunused_indexes:ifdry_run:self.logger.info(f"Would drop unused index: {schema}.{index}")else:cur.execute(f"DROP INDEX {schema}.{index}")self.logger.info(f"Dropped unused index: {schema}.{index}")ifnotdry_run:conn.commit()exceptExceptionase:self.logger.error(f"Error dropping unused indexes: {e}")conn.rollback()finally:conn.close()defoptimize_indexes(self):"""Run comprehensive index optimization."""self.logger.info("Starting index optimization")# Analyze all tablesself.analyze_tables()# Reindex critical tablescritical_tables=['users','orders','products','articles']fortableincritical_tables:try:self.reindex_table(table)exceptExceptionase:self.logger.error(f"Error reindexing {table}: {e}")# Drop unused indexes (dry run first)self.drop_unused_indexes(dry_run=True)self.logger.info("Index optimization completed")# Usageif__name__=="__main__":maintenance=IndexMaintenance({'host':'localhost','database':'production','user':'maintenance_user','password':'maintenance_password'})maintenance.optimize_indexes()
-- 1. Create B-tree indexes for equality and range queriesCREATEINDEXidx_users_emailONusers(email);CREATEINDEXidx_users_created_atONusers(created_at);-- 2. Create GIN indexes for arrays and JSONBCREATEINDEXidx_articles_tags_ginONarticlesUSINGGIN(tags);CREATEINDEXidx_articles_metadata_ginONarticlesUSINGGIN(metadata);-- 3. Create composite indexes for complex queriesCREATEINDEXidx_orders_customer_dateONorders(customer_id,order_date);-- 4. Create partial indexes for filtered dataCREATEINDEXidx_events_unprocessedONevents(created_at)WHEREprocessed=FALSE;