diff --git a/priv/repo/migrations/20260103191858_create_snmp_sensor_readings.exs b/priv/repo/migrations/20260103191858_create_snmp_sensor_readings.exs index 320d8289..c6a7ba7e 100644 --- a/priv/repo/migrations/20260103191858_create_snmp_sensor_readings.exs +++ b/priv/repo/migrations/20260103191858_create_snmp_sensor_readings.exs @@ -2,11 +2,9 @@ defmodule Towerops.Repo.Migrations.CreateSnmpSensorReadings do use Ecto.Migration def up do - # Create the table with composite primary key (id, checked_at) - # This is required for TimescaleDB hypertables - create table(:snmp_sensor_readings, primary_key: false) do - add :id, :binary_id, null: false - + # Note: TimescaleDB features removed due to Apache license restrictions + # Using standard PostgreSQL table instead + create table(:snmp_sensor_readings) do add :sensor_id, references(:snmp_sensors, type: :binary_id, on_delete: :delete_all), null: false @@ -17,33 +15,11 @@ defmodule Towerops.Repo.Migrations.CreateSnmpSensorReadings do timestamps(type: :utc_datetime, updated_at: false) end - # Add composite primary key - execute("ALTER TABLE snmp_sensor_readings ADD PRIMARY KEY (id, checked_at)") - - # Convert to TimescaleDB hypertable (partitioned by checked_at) - execute("SELECT create_hypertable('snmp_sensor_readings', 'checked_at')") - - # Create indexes + # Create indexes for query performance create index(:snmp_sensor_readings, [:sensor_id]) create index(:snmp_sensor_readings, [:sensor_id, :checked_at]) create index(:snmp_sensor_readings, [:status]) - - # Add retention policy: automatically drop data older than 90 days - execute(""" - SELECT add_retention_policy('snmp_sensor_readings', INTERVAL '90 days') - """) - - # Add compression policy: compress chunks older than 7 days - execute(""" - ALTER TABLE snmp_sensor_readings SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'sensor_id' - ) - """) - - execute(""" - SELECT add_compression_policy('snmp_sensor_readings', INTERVAL '7 days') - """) + create index(:snmp_sensor_readings, [:checked_at]) end def down do diff --git a/priv/repo/migrations/20260103191859_create_snmp_interface_stats.exs b/priv/repo/migrations/20260103191859_create_snmp_interface_stats.exs index 2e246667..3c19f245 100644 --- a/priv/repo/migrations/20260103191859_create_snmp_interface_stats.exs +++ b/priv/repo/migrations/20260103191859_create_snmp_interface_stats.exs @@ -2,11 +2,9 @@ defmodule Towerops.Repo.Migrations.CreateSnmpInterfaceStats do use Ecto.Migration def up do - # Create the table with composite primary key (id, checked_at) - # This is required for TimescaleDB hypertables - create table(:snmp_interface_stats, primary_key: false) do - add :id, :binary_id, null: false - + # Note: TimescaleDB features removed due to Apache license restrictions + # Using standard PostgreSQL table instead + create table(:snmp_interface_stats) do add :interface_id, references(:snmp_interfaces, type: :binary_id, on_delete: :delete_all), null: false @@ -21,32 +19,10 @@ defmodule Towerops.Repo.Migrations.CreateSnmpInterfaceStats do timestamps(type: :utc_datetime, updated_at: false) end - # Add composite primary key - execute("ALTER TABLE snmp_interface_stats ADD PRIMARY KEY (id, checked_at)") - - # Convert to TimescaleDB hypertable (partitioned by checked_at) - execute("SELECT create_hypertable('snmp_interface_stats', 'checked_at')") - - # Create indexes + # Create indexes for query performance create index(:snmp_interface_stats, [:interface_id]) create index(:snmp_interface_stats, [:interface_id, :checked_at]) - - # Add retention policy: automatically drop data older than 90 days - execute(""" - SELECT add_retention_policy('snmp_interface_stats', INTERVAL '90 days') - """) - - # Add compression policy: compress chunks older than 7 days - execute(""" - ALTER TABLE snmp_interface_stats SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'interface_id' - ) - """) - - execute(""" - SELECT add_compression_policy('snmp_interface_stats', INTERVAL '7 days') - """) + create index(:snmp_interface_stats, [:checked_at]) end def down do diff --git a/priv/repo/migrations/20260103192153_create_snmp_sensor_aggregates.exs b/priv/repo/migrations/20260103192153_create_snmp_sensor_aggregates.exs index 469bf6c2..f5633679 100644 --- a/priv/repo/migrations/20260103192153_create_snmp_sensor_aggregates.exs +++ b/priv/repo/migrations/20260103192153_create_snmp_sensor_aggregates.exs @@ -1,70 +1,16 @@ defmodule Towerops.Repo.Migrations.CreateSnmpSensorAggregates do use Ecto.Migration - # Disable DDL transaction for TimescaleDB continuous aggregates - @disable_ddl_transaction true - @disable_migration_lock true + # Note: TimescaleDB continuous aggregates require commercial license + # This migration is disabled - aggregates are computed in real-time instead def up do - # Hourly continuous aggregate for sensor readings - execute(""" - CREATE MATERIALIZED VIEW snmp_sensor_readings_hourly - WITH (timescaledb.continuous) AS - SELECT - sensor_id, - time_bucket('1 hour', checked_at) AS bucket, - COUNT(*) AS total_readings, - AVG(value) AS avg_value, - MIN(value) AS min_value, - MAX(value) AS max_value, - COUNT(*) FILTER (WHERE status = 'ok') AS ok_readings, - COUNT(*) FILTER (WHERE status = 'warning') AS warning_readings, - COUNT(*) FILTER (WHERE status = 'critical') AS critical_readings - FROM snmp_sensor_readings - GROUP BY sensor_id, bucket - """) - - # Add refresh policy for hourly aggregate (refresh every hour) - execute(""" - SELECT add_continuous_aggregate_policy('snmp_sensor_readings_hourly', - start_offset => INTERVAL '3 hours', - end_offset => INTERVAL '1 hour', - schedule_interval => INTERVAL '1 hour') - """) - - # Daily continuous aggregate for sensor readings - execute(""" - CREATE MATERIALIZED VIEW snmp_sensor_readings_daily - WITH (timescaledb.continuous) AS - SELECT - sensor_id, - time_bucket('1 day', checked_at) AS bucket, - COUNT(*) AS total_readings, - AVG(value) AS avg_value, - MIN(value) AS min_value, - MAX(value) AS max_value, - COUNT(*) FILTER (WHERE status = 'ok') AS ok_readings, - COUNT(*) FILTER (WHERE status = 'warning') AS warning_readings, - COUNT(*) FILTER (WHERE status = 'critical') AS critical_readings - FROM snmp_sensor_readings - GROUP BY sensor_id, bucket - """) - - # Add refresh policy for daily aggregate (refresh every day) - execute(""" - SELECT add_continuous_aggregate_policy('snmp_sensor_readings_daily', - start_offset => INTERVAL '3 days', - end_offset => INTERVAL '1 day', - schedule_interval => INTERVAL '1 day') - """) - - # Create indexes on the materialized views - create index(:snmp_sensor_readings_hourly, [:sensor_id, :bucket]) - create index(:snmp_sensor_readings_daily, [:sensor_id, :bucket]) + # TimescaleDB continuous aggregates disabled due to license restrictions + # Metrics are calculated on-demand in the application + :ok end def down do - execute("DROP MATERIALIZED VIEW IF EXISTS snmp_sensor_readings_daily") - execute("DROP MATERIALIZED VIEW IF EXISTS snmp_sensor_readings_hourly") + :ok end end diff --git a/priv/repo/migrations/20260103192154_create_snmp_interface_aggregates.exs b/priv/repo/migrations/20260103192154_create_snmp_interface_aggregates.exs index cdc6b95d..1ca20d51 100644 --- a/priv/repo/migrations/20260103192154_create_snmp_interface_aggregates.exs +++ b/priv/repo/migrations/20260103192154_create_snmp_interface_aggregates.exs @@ -1,74 +1,16 @@ defmodule Towerops.Repo.Migrations.CreateSnmpInterfaceAggregates do use Ecto.Migration - # Disable DDL transaction for TimescaleDB continuous aggregates - @disable_ddl_transaction true - @disable_migration_lock true + # Note: TimescaleDB continuous aggregates require commercial license + # This migration is disabled - aggregates are computed in real-time instead def up do - # Hourly continuous aggregate for interface stats - execute(""" - CREATE MATERIALIZED VIEW snmp_interface_stats_hourly - WITH (timescaledb.continuous) AS - SELECT - interface_id, - time_bucket('1 hour', checked_at) AS bucket, - COUNT(*) AS total_samples, - AVG(if_in_octets) AS avg_in_octets, - MAX(if_in_octets) AS max_in_octets, - AVG(if_out_octets) AS avg_out_octets, - MAX(if_out_octets) AS max_out_octets, - SUM(if_in_errors) AS total_in_errors, - SUM(if_out_errors) AS total_out_errors, - SUM(if_in_discards) AS total_in_discards, - SUM(if_out_discards) AS total_out_discards - FROM snmp_interface_stats - GROUP BY interface_id, bucket - """) - - # Add refresh policy for hourly aggregate (refresh every hour) - execute(""" - SELECT add_continuous_aggregate_policy('snmp_interface_stats_hourly', - start_offset => INTERVAL '3 hours', - end_offset => INTERVAL '1 hour', - schedule_interval => INTERVAL '1 hour') - """) - - # Daily continuous aggregate for interface stats - execute(""" - CREATE MATERIALIZED VIEW snmp_interface_stats_daily - WITH (timescaledb.continuous) AS - SELECT - interface_id, - time_bucket('1 day', checked_at) AS bucket, - COUNT(*) AS total_samples, - AVG(if_in_octets) AS avg_in_octets, - MAX(if_in_octets) AS max_in_octets, - AVG(if_out_octets) AS avg_out_octets, - MAX(if_out_octets) AS max_out_octets, - SUM(if_in_errors) AS total_in_errors, - SUM(if_out_errors) AS total_out_errors, - SUM(if_in_discards) AS total_in_discards, - SUM(if_out_discards) AS total_out_discards - FROM snmp_interface_stats - GROUP BY interface_id, bucket - """) - - # Add refresh policy for daily aggregate (refresh every day) - execute(""" - SELECT add_continuous_aggregate_policy('snmp_interface_stats_daily', - start_offset => INTERVAL '3 days', - end_offset => INTERVAL '1 day', - schedule_interval => INTERVAL '1 day') - """) - - # Create indexes on the materialized views - create index(:snmp_interface_stats_hourly, [:interface_id, :bucket]) - create index(:snmp_interface_stats_daily, [:interface_id, :bucket]) + # TimescaleDB continuous aggregates disabled due to license restrictions + # Metrics are calculated on-demand in the application + :ok end def down do - execute("DROP MATERIALIZED VIEW IF EXISTS snmp_interface_stats_daily") - execute("DROP MATERIALIZED VIEW IF EXISTS snmp_interface_stats_hourly") + :ok end end