-- Create time series tables with appropriate designCREATETABLEsensor_readings(idBIGSERIAL,sensor_idINTEGERNOTNULL,timestampTIMESTAMPTZNOTNULL,valueDOUBLEPRECISIONNOTNULL,unitVARCHAR(20)NOTNULL,quality_scoreNUMERIC(3,2)DEFAULT1.0,metadataJSONB,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id,timestamp))PARTITIONBYRANGE(timestamp);-- Create monthly partitionsCREATETABLEsensor_readings_2024_01PARTITIONOFsensor_readingsFORVALUESFROM('2024-01-01')TO('2024-02-01');CREATETABLEsensor_readings_2024_02PARTITIONOFsensor_readingsFORVALUESFROM('2024-02-01')TO('2024-03-01');CREATETABLEsensor_readings_2024_03PARTITIONOFsensor_readingsFORVALUESFROM('2024-03-01')TO('2024-04-01');-- Create indexes on partitionsCREATEINDEXidx_sensor_readings_2024_01_sensor_timestampONsensor_readings_2024_01(sensor_id,timestamp);CREATEINDEXidx_sensor_readings_2024_01_timestampONsensor_readings_2024_01(timestamp);-- Create BRIN indexes for time series dataCREATEINDEXidx_sensor_readings_2024_01_timestamp_brinONsensor_readings_2024_01USINGBRIN(timestamp);CREATEINDEXidx_sensor_readings_2024_01_sensor_brinONsensor_readings_2024_01USINGBRIN(sensor_id);
-- Create tables for different time series use casesCREATETABLEfinancial_data(idBIGSERIAL,symbolVARCHAR(10)NOTNULL,timestampTIMESTAMPTZNOTNULL,open_priceNUMERIC(10,4)NOTNULL,high_priceNUMERIC(10,4)NOTNULL,low_priceNUMERIC(10,4)NOTNULL,close_priceNUMERIC(10,4)NOTNULL,volumeBIGINTNOTNULL,adjusted_closeNUMERIC(10,4),created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id,timestamp))PARTITIONBYRANGE(timestamp);CREATETABLEuser_events(idBIGSERIAL,user_idINTEGERNOTNULL,event_typeVARCHAR(50)NOTNULL,timestampTIMESTAMPTZNOTNULL,session_idVARCHAR(100),propertiesJSONB,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id,timestamp))PARTITIONBYRANGE(timestamp);CREATETABLEsystem_metrics(idBIGSERIAL,hostnameVARCHAR(100)NOTNULL,metric_nameVARCHAR(100)NOTNULL,timestampTIMESTAMPTZNOTNULL,valueDOUBLEPRECISIONNOTNULL,tagsJSONB,created_atTIMESTAMPTZDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id,timestamp))PARTITIONBYRANGE(timestamp);
-- Create function for 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);-- Create indexes on new partitionEXECUTEformat('CREATE INDEX %I ON %I (timestamp)','idx_'||partition_name||'_timestamp',partition_name);EXECUTEformat('CREATE INDEX %I ON %I USING BRIN (timestamp)','idx_'||partition_name||'_timestamp_brin',partition_name);END;$$LANGUAGEplpgsql;-- Create function to create partitions for the next 12 monthsCREATEORREPLACEFUNCTIONcreate_future_partitions(table_nameTEXT)RETURNSVOIDAS$$DECLAREcurrent_dateDATE:=CURRENT_DATE;iINTEGER;BEGINFORiIN0..11LOOPPERFORMcreate_monthly_partition(table_name,current_date+(i||' months')::interval);ENDLOOP;END;$$LANGUAGEplpgsql;-- Create future partitionsSELECTcreate_future_partitions('sensor_readings');SELECTcreate_future_partitions('financial_data');SELECTcreate_future_partitions('user_events');SELECTcreate_future_partitions('system_metrics');
-- Create function to drop old partitionsCREATEORREPLACEFUNCTIONdrop_old_partitions(table_nameTEXT,retention_monthsINTEGERDEFAULT12)RETURNSINTEGERAS$$DECLAREpartition_nameTEXT;partition_dateDATE;dropped_countINTEGER:=0;BEGINFORpartition_nameINSELECTschemaname||'.'||tablenameFROMpg_tablesWHEREtablenameLIKEtable_name||'_%'ANDschemaname='public'LOOP-- Extract date from partition namepartition_date:=to_date(regexp_replace(partition_name,'.*_(\d{4}_\d{2})','\1'),'YYYY_MM');-- Drop partition if older than retention periodIFpartition_date<CURRENT_DATE-(retention_months||' months')::intervalTHENEXECUTE'DROP TABLE '||partition_name;dropped_count:=dropped_count+1;ENDIF;ENDLOOP;RETURNdropped_count;END;$$LANGUAGEplpgsql;-- Create function to archive old partitionsCREATEORREPLACEFUNCTIONarchive_old_partitions(table_nameTEXT,archive_table_nameTEXT,retention_monthsINTEGERDEFAULT6)RETURNSINTEGERAS$$DECLAREpartition_nameTEXT;partition_dateDATE;archived_countINTEGER:=0;BEGINFORpartition_nameINSELECTschemaname||'.'||tablenameFROMpg_tablesWHEREtablenameLIKEtable_name||'_%'ANDschemaname='public'LOOP-- Extract date from partition namepartition_date:=to_date(regexp_replace(partition_name,'.*_(\d{4}_\d{2})','\1'),'YYYY_MM');-- Archive partition if older than retention periodIFpartition_date<CURRENT_DATE-(retention_months||' months')::intervalTHENEXECUTEformat('INSERT INTO %I SELECT * FROM %I',archive_table_name,partition_name);EXECUTE'DROP TABLE '||partition_name;archived_count:=archived_count+1;ENDIF;ENDLOOP;RETURNarchived_count;END;$$LANGUAGEplpgsql;
-- Create comprehensive indexes for time series dataCREATEINDEXidx_sensor_readings_sensor_timestampONsensor_readings(sensor_id,timestampDESC);CREATEINDEXidx_sensor_readings_timestamp_valueONsensor_readings(timestampDESC,value);CREATEINDEXidx_sensor_readings_sensor_timestamp_valueONsensor_readings(sensor_id,timestampDESC,value);-- Create partial indexes for active dataCREATEINDEXidx_sensor_readings_activeONsensor_readings(sensor_id,timestampDESC)WHEREtimestamp>CURRENT_DATE-INTERVAL'30 days';-- Create indexes for specific time rangesCREATEINDEXidx_sensor_readings_recentONsensor_readings(timestampDESC)WHEREtimestamp>CURRENT_DATE-INTERVAL'7 days';-- Create indexes for aggregation queriesCREATEINDEXidx_sensor_readings_hourly_aggONsensor_readings(sensor_id,date_trunc('hour',timestamp));
-- Create BRIN indexes for time series dataCREATEINDEXidx_sensor_readings_timestamp_brinONsensor_readingsUSINGBRIN(timestamp);CREATEINDEXidx_sensor_readings_sensor_brinONsensor_readingsUSINGBRIN(sensor_id);CREATEINDEXidx_sensor_readings_value_brinONsensor_readingsUSINGBRIN(value);-- Create BRIN indexes on partitionsCREATEINDEXidx_sensor_readings_2024_01_timestamp_brinONsensor_readings_2024_01USINGBRIN(timestamp);CREATEINDEXidx_sensor_readings_2024_01_sensor_brinONsensor_readings_2024_01USINGBRIN(sensor_id);
-- Create materialized views for time series aggregationsCREATEMATERIALIZEDVIEWsensor_readings_hourlyASSELECTsensor_id,date_trunc('hour',timestamp)ashour,COUNT(*)asreading_count,AVG(value)asavg_value,MIN(value)asmin_value,MAX(value)asmax_value,STDDEV(value)asstddev_value,PERCENTILE_CONT(0.5)WITHINGROUP(ORDERBYvalue)asmedian_value,PERCENTILE_CONT(0.95)WITHINGROUP(ORDERBYvalue)asp95_valueFROMsensor_readingsWHEREtimestamp>CURRENT_DATE-INTERVAL'30 days'GROUPBYsensor_id,date_trunc('hour',timestamp);-- Create index on materialized viewCREATEINDEXidx_sensor_readings_hourly_sensor_hourONsensor_readings_hourly(sensor_id,hour);-- Create function to refresh materialized viewCREATEORREPLACEFUNCTIONrefresh_sensor_aggregations()RETURNSVOIDAS$$BEGINREFRESHMATERIALIZEDVIEWCONCURRENTLYsensor_readings_hourly;END;$$LANGUAGEplpgsql;
-- Create function for time series aggregationCREATEORREPLACEFUNCTIONget_sensor_statistics(p_sensor_idINTEGER,p_start_timeTIMESTAMPTZ,p_end_timeTIMESTAMPTZ,p_intervalINTERVALDEFAULT'1 hour')RETURNSTABLE(time_bucketTIMESTAMPTZ,reading_countBIGINT,avg_valueDOUBLEPRECISION,min_valueDOUBLEPRECISION,max_valueDOUBLEPRECISION,stddev_valueDOUBLEPRECISION)AS$$BEGINRETURNQUERYSELECTdate_trunc('hour',timestamp)astime_bucket,COUNT(*)asreading_count,AVG(value)asavg_value,MIN(value)asmin_value,MAX(value)asmax_value,STDDEV(value)asstddev_valueFROMsensor_readingsWHEREsensor_id=p_sensor_idANDtimestampBETWEENp_start_timeANDp_end_timeGROUPBYdate_trunc('hour',timestamp)ORDERBYtime_bucket;END;$$LANGUAGEplpgsql;-- Create function for time series interpolationCREATEORREPLACEFUNCTIONinterpolate_sensor_values(p_sensor_idINTEGER,p_start_timeTIMESTAMPTZ,p_end_timeTIMESTAMPTZ,p_intervalINTERVALDEFAULT'1 minute')RETURNSTABLE(timestampTIMESTAMPTZ,interpolated_valueDOUBLEPRECISION)AS$$DECLAREcurrent_timeTIMESTAMPTZ;prev_valueDOUBLEPRECISION;next_valueDOUBLEPRECISION;prev_timeTIMESTAMPTZ;next_timeTIMESTAMPTZ;interpolation_factorDOUBLEPRECISION;BEGINcurrent_time:=p_start_time;WHILEcurrent_time<=p_end_timeLOOP-- Find previous and next valuesSELECTvalue,timestampINTOprev_value,prev_timeFROMsensor_readingsWHEREsensor_id=p_sensor_idANDtimestamp<=current_timeORDERBYtimestampDESCLIMIT1;SELECTvalue,timestampINTOnext_value,next_timeFROMsensor_readingsWHEREsensor_id=p_sensor_idANDtimestamp>=current_timeORDERBYtimestampASCLIMIT1;-- Interpolate valueIFprev_valueISNOTNULLANDnext_valueISNOTNULLTHENinterpolation_factor:=EXTRACT(EPOCHFROM(current_time-prev_time))/EXTRACT(EPOCHFROM(next_time-prev_time));interpolated_value:=prev_value+(next_value-prev_value)*interpolation_factor;ELSIFprev_valueISNOTNULLTHENinterpolated_value:=prev_value;ELSIFnext_valueISNOTNULLTHENinterpolated_value:=next_value;ELSEinterpolated_value:=NULL;ENDIF;RETURNNEXT;current_time:=current_time+p_interval;ENDLOOP;END;$$LANGUAGEplpgsql;
-- Create function for time series compressionCREATEORREPLACEFUNCTIONcompress_sensor_data(p_sensor_idINTEGER,p_start_timeTIMESTAMPTZ,p_end_timeTIMESTAMPTZ,p_compression_ratioNUMERICDEFAULT0.1)RETURNSTABLE(compressed_timestampTIMESTAMPTZ,compressed_valueDOUBLEPRECISION,compression_methodTEXT)AS$$DECLAREtotal_pointsINTEGER;target_pointsINTEGER;step_sizeINTEGER;current_timeTIMESTAMPTZ;current_valueDOUBLEPRECISION;BEGIN-- Calculate compression parametersSELECTCOUNT(*)INTOtotal_pointsFROMsensor_readingsWHEREsensor_id=p_sensor_idANDtimestampBETWEENp_start_timeANDp_end_time;target_points:=GREATEST(1,FLOOR(total_points*p_compression_ratio));step_size:=GREATEST(1,FLOOR(total_points/target_points));-- Return compressed dataRETURNQUERYSELECTtimestampascompressed_timestamp,valueascompressed_value,'uniform_sampling'::TEXTascompression_methodFROM(SELECTtimestamp,value,ROW_NUMBER()OVER(ORDERBYtimestamp)asrnFROMsensor_readingsWHEREsensor_id=p_sensor_idANDtimestampBETWEENp_start_timeANDp_end_time)tWHERErn%step_size=1ORDERBYtimestamp;END;$$LANGUAGEplpgsql;
-- Create function for data retentionCREATEORREPLACEFUNCTIONapply_retention_policy(table_nameTEXT,retention_daysINTEGERDEFAULT365)RETURNSINTEGERAS$$DECLAREdeleted_countINTEGER;cutoff_dateTIMESTAMPTZ;BEGINcutoff_date:=CURRENT_TIMESTAMP-(retention_days||' days')::interval;EXECUTEformat('DELETE FROM %I WHERE timestamp < %L',table_name,cutoff_date);GETDIAGNOSTICSdeleted_count=ROW_COUNT;RETURNdeleted_count;END;$$LANGUAGEplpgsql;-- Create function for data archivingCREATEORREPLACEFUNCTIONarchive_old_data(source_tableTEXT,archive_tableTEXT,retention_daysINTEGERDEFAULT365)RETURNSINTEGERAS$$DECLAREarchived_countINTEGER;cutoff_dateTIMESTAMPTZ;BEGINcutoff_date:=CURRENT_TIMESTAMP-(retention_days||' days')::interval;EXECUTEformat('INSERT INTO %I SELECT * FROM %I WHERE timestamp < %L',archive_table,source_table,cutoff_date);GETDIAGNOSTICSarchived_count=ROW_COUNT;EXECUTEformat('DELETE FROM %I WHERE timestamp < %L',source_table,cutoff_date);RETURNarchived_count;END;$$LANGUAGEplpgsql;
# monitoring/timeseries_monitor.pyimportpsycopg2importjsonfromdatetimeimportdatetime,timedeltaimportloggingclassTimeSeriesMonitor: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_partition_statistics(self):"""Get partition statistics."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size, pg_total_relation_size(schemaname||'.'||tablename) as size_bytes, n_tup_ins as inserts, n_tup_upd as updates, n_tup_del as deletes, n_live_tup as live_tuples, n_dead_tup as dead_tuples FROM pg_stat_user_tables WHERE tablename LIKE '%_2024_%' ORDER BY size_bytes DESC """)partition_stats=cur.fetchall()returnpartition_statsexceptExceptionase:self.logger.error(f"Error getting partition statistics: {e}")return[]finally:conn.close()defget_time_series_performance(self):"""Get time series query performance."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:# Test time series query performancestart_time=datetime.now()cur.execute(""" SELECT COUNT(*) FROM sensor_readings WHERE timestamp > CURRENT_DATE - INTERVAL '1 day' """)daily_count_time=(datetime.now()-start_time).total_seconds()start_time=datetime.now()cur.execute(""" SELECT sensor_id, AVG(value) FROM sensor_readings WHERE timestamp > CURRENT_DATE - INTERVAL '1 hour' GROUP BY sensor_id """)hourly_agg_time=(datetime.now()-start_time).total_seconds()start_time=datetime.now()cur.execute(""" SELECT * FROM sensor_readings WHERE sensor_id = 1 AND timestamp > CURRENT_DATE - INTERVAL '1 day' ORDER BY timestamp DESC LIMIT 1000 """)recent_data_time=(datetime.now()-start_time).total_seconds()return{'daily_count_time':daily_count_time,'hourly_agg_time':hourly_agg_time,'recent_data_time':recent_data_time}exceptExceptionase:self.logger.error(f"Error getting time series performance: {e}")return{}finally:conn.close()defget_data_retention_status(self):"""Get data retention status."""conn=psycopg2.connect(**self.conn_params)try:withconn.cursor()ascur:cur.execute(""" SELECT 'sensor_readings' as table_name, MIN(timestamp) as oldest_data, MAX(timestamp) as newest_data, COUNT(*) as total_records, COUNT(*) FILTER (WHERE timestamp > CURRENT_DATE - INTERVAL '30 days') as recent_records FROM sensor_readings UNION ALL SELECT 'financial_data' as table_name, MIN(timestamp) as oldest_data, MAX(timestamp) as newest_data, COUNT(*) as total_records, COUNT(*) FILTER (WHERE timestamp > CURRENT_DATE - INTERVAL '30 days') as recent_records FROM financial_data """)retention_status=cur.fetchall()returnretention_statusexceptExceptionase:self.logger.error(f"Error getting data retention status: {e}")return[]finally:conn.close()defgenerate_timeseries_report(self):"""Generate comprehensive time series report."""partition_stats=self.get_partition_statistics()performance_metrics=self.get_time_series_performance()retention_status=self.get_data_retention_status()report={'report_timestamp':datetime.now().isoformat(),'partition_statistics':partition_stats,'performance_metrics':performance_metrics,'retention_status':retention_status}returnreport# Usageif__name__=="__main__":monitor=TimeSeriesMonitor({'host':'localhost','database':'production','user':'monitor_user','password':'monitor_password'})report=monitor.generate_timeseries_report()print(json.dumps(report,indent=2))
-- 1. Create time series table with partitioningCREATETABLEsensor_readings(idBIGSERIAL,sensor_idINTEGERNOTNULL,timestampTIMESTAMPTZNOTNULL,valueDOUBLEPRECISIONNOTNULL,PRIMARYKEY(id,timestamp))PARTITIONBYRANGE(timestamp);-- 2. Create partitionsCREATETABLEsensor_readings_2024_01PARTITIONOFsensor_readingsFORVALUESFROM('2024-01-01')TO('2024-02-01');-- 3. Create indexesCREATEINDEXidx_sensor_readings_sensor_timestampONsensor_readings(sensor_id,timestampDESC);CREATEINDEXidx_sensor_readings_timestamp_brinONsensor_readingsUSINGBRIN(timestamp);-- 4. Insert dataINSERTINTOsensor_readings(sensor_id,timestamp,value)VALUES(1,CURRENT_TIMESTAMP,25.5);-- 5. Query time series dataSELECTsensor_id,AVG(value)asavg_valueFROMsensor_readingsWHEREtimestamp>CURRENT_DATE-INTERVAL'1 day'GROUPBYsensor_id;