From c12baa5aef9eea1861aff9a9209ba8ef186cd805 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Tue, 11 Aug 2026 12:56:00 -0300 Subject: [PATCH] perf(server): drop three unused indexes on the sessions table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sessions_username_idx, sessions_type_idx and sessions_closed_started_idx back no filter, no sort and no constraint, while costing an index insert on every session row written. Over 66 days of production counters they served 2, 0 and 0 scans, against 204,400 on sessions_started_at_idx. username and type are unreachable by construction rather than merely unused: the session list accepts exactly three filter fields (device_uid, closed, active) and rejects anything else at the route, its sort is hardcoded to started_at with no user-selectable alternative, and cloud adds no session filters of its own. The type predicates that do exist are all on session_events, a different table, already served by its session_id and seat indexes. sessions_closed_started_idx is redundant rather than unused, which is why its 0 scans needed explaining before trusting them: the recording-conversion worker runs WHERE closed AND recorded AND NOT converted ORDER BY started_at DESC, which looks tailor-made for (closed, started_at). But closed is a near-constant — a session is closed for all but the minutes it is live — so the index is a strictly fatter duplicate of sessions_started_at_idx, 42 MB against 26 MB, and loses to it on cost for every shape that exists. If that worker ever shows up as a cost, what it wants is a partial index over the unconverted backlog alone. sessions_namespace_id_idx stays despite its 63 scans: deleting a namespace cascades to sessions, and without it that is a sequential scan over 1.19 M rows. There is no read-path trade here, so nothing gets slower. Fixes: shellhub-io/team#199 --- ...17_drop_unused_session_indexes.tx.down.sql | 10 +++++++ .../017_drop_unused_session_indexes.tx.up.sql | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.down.sql create mode 100644 server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.up.sql diff --git a/server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.down.sql b/server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.down.sql new file mode 100644 index 00000000000..54270f30026 --- /dev/null +++ b/server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.down.sql @@ -0,0 +1,10 @@ +-- Restores the index set from 001. +CREATE INDEX IF NOT EXISTS sessions_username_idx ON sessions USING btree (username); + +--bun:split + +CREATE INDEX IF NOT EXISTS sessions_type_idx ON sessions USING btree (type); + +--bun:split + +CREATE INDEX IF NOT EXISTS sessions_closed_started_idx ON sessions USING btree (closed, started_at); diff --git a/server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.up.sql b/server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.up.sql new file mode 100644 index 00000000000..08d58ca939d --- /dev/null +++ b/server/api/store/pg/migrations/017_drop_unused_session_indexes.tx.up.sql @@ -0,0 +1,27 @@ +-- Three indexes from 001 that no query in either repo can reach, maintained on every session +-- insert and update for nothing. IF EXISTS because operators may already have dropped them by +-- hand, and an error here fails the boot. +-- +-- There is no read-path trade to weigh: these back no filter, no sort and no constraint, so +-- nothing gets slower. + +-- The session list accepts exactly three filter fields (device_uid, closed, active) and +-- rejects anything else at the route, and its sort is hardcoded to started_at — username is +-- reachable by neither. +DROP INDEX IF EXISTS sessions_username_idx; + +--bun:split + +-- Same for type. The type predicates that do exist are all on session_events, a different +-- table, and are already served by its session_id/seat indexes. +DROP INDEX IF EXISTS sessions_type_idx; + +--bun:split + +-- Redundant rather than merely unused: closed is a near-constant (a session is closed for all +-- but the few minutes it is live), so (closed, started_at) is a strictly fatter duplicate of +-- sessions_started_at_idx and loses to it on cost for every shape that exists — including the +-- one it looks tailor-made for, the recording-conversion worker's +-- "WHERE closed AND recorded AND NOT converted ORDER BY started_at DESC". If that worker ever +-- shows up as a cost, the index it wants is a partial one on the backlog alone, not this. +DROP INDEX IF EXISTS sessions_closed_started_idx;