Remove all TimescaleDB features from migrations

- Convert snmp_sensor_readings to standard table
- Convert snmp_interface_stats to standard table
- Disable snmp_sensor_aggregates (continuous aggregates)
- Disable snmp_interface_aggregates (continuous aggregates)

All TimescaleDB commercial features (hypertables, continuous aggregates,
retention/compression policies) are disabled. Using standard PostgreSQL
tables with proper indexing instead.
This commit is contained in:
Graham McIntire 2026-01-03 16:39:50 -06:00
parent 94ce596319
commit 4216a5b7bf
No known key found for this signature in database
4 changed files with 22 additions and 182 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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