diff --git a/README.md b/README.md index 8aef4f1..e83f7d2 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ One row per connection event ingested from any node. | `banner_len` | `INT UNSIGNED` | Length of the banner in bytes | | `created_at` | `DATETIME` | Row insertion time | -**Indexes:** `(node_id, occurred_at)` for cursor lookups and per-node queries; `src_ip` and `dst_port` for the top-N aggregations. +**Indexes:** `(node_id, occurred_at)` for cursor lookups and per-node queries; `occurred_at` for time-window queries, the recent-connections sort, and the purge; `(src_ip, occurred_at)` for the frequent-IP aggregation; `dst_port` for the top-port aggregation. --- @@ -507,7 +507,7 @@ If you deployed before `last_event_at` was added to the schema, run `setup.php` php /var/www/portspoof_concentrator/setup.php ``` -It will add the missing column and drop the legacy `uq_event` unique key if present. No data is lost. +It will add the missing column, drop the legacy `uq_event` unique key, and add the `occurred_at` / `(src_ip, occurred_at)` indexes if present. No data is lost. --- diff --git a/schema.sql b/schema.sql index 8807e43..186b69a 100644 --- a/schema.sql +++ b/schema.sql @@ -27,7 +27,8 @@ CREATE TABLE IF NOT EXISTS connections ( created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_node_occurred (node_id, occurred_at), - KEY idx_src_ip (src_ip), + KEY idx_occurred (occurred_at), + KEY idx_src_ip_occ (src_ip, occurred_at), KEY idx_dst_port (dst_port), CONSTRAINT fk_conn_node FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/setup.php b/setup.php index 6dc24d7..8f565cc 100644 --- a/setup.php +++ b/setup.php @@ -41,6 +41,43 @@ if ($row > 0) { echo "Dropped legacy uq_event unique key.\n"; } +// Migration: add occurred_at index if upgrading from an older schema. +// Time-window queries (dashboard, JSON API, purge) full-scanned the table +// without it, so page loads slowed down as the table grew. +$row = $pdo->query( + "SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'connections' + AND INDEX_NAME = 'idx_occurred'" +)->fetchColumn(); +if ($row == 0) { + $pdo->exec("ALTER TABLE connections ADD KEY idx_occurred (occurred_at)"); + echo "Added idx_occurred index.\n"; +} + +// Migration: replace the standalone src_ip index with (src_ip, occurred_at) +// so frequent_ips() resolves as a covering index scan. +$row = $pdo->query( + "SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'connections' + AND INDEX_NAME = 'idx_src_ip_occ'" +)->fetchColumn(); +if ($row == 0) { + $pdo->exec("ALTER TABLE connections ADD KEY idx_src_ip_occ (src_ip, occurred_at)"); + echo "Added idx_src_ip_occ index.\n"; +} +$row = $pdo->query( + "SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'connections' + AND INDEX_NAME = 'idx_src_ip'" +)->fetchColumn(); +if ($row > 0) { + $pdo->exec("ALTER TABLE connections DROP INDEX idx_src_ip"); + echo "Dropped redundant idx_src_ip index.\n"; +} + // Migration: create settings table and seed default retention if upgrading $pdo->exec( "CREATE TABLE IF NOT EXISTS settings (