AAP-87549 — Add management command to create JobHostSummary composite index (#16586)

Adds `create_host_summary_index` management command that creates the
composite index (host_id, id DESC) on main_jobhostsummary using
CREATE INDEX CONCURRENTLY.

The command is idempotent: it checks for existing valid/invalid indexes,
drops invalid indexes from failed prior attempts, and skips if the index
already exists. Disables statement_timeout for the session to avoid
killing the index build on large tables where ALTER ROLE timeout is set.

Forward-port of tower#7893 (stable-2.6).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dirk Julich
2026-08-13 13:41:48 -04:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 7242fe89ae
commit 587b816793
@@ -0,0 +1,59 @@
from django.core.management.base import BaseCommand
from django.db import connection
class Command(BaseCommand):
help = 'Creates composite index (host_id, id DESC) on main_jobhostsummary to improve host list query performance.'
INDEX_NAME = 'main_jobhostsumm_host_id_desc'
TABLE_NAME = 'main_jobhostsummary'
def _index_status(self):
"""Check whether the index exists in pg_class and whether it is valid.
Returns:
'valid' - index exists and is usable; no action needed.
'invalid' - index exists but was left in an invalid state by a
previous failed CREATE INDEX CONCURRENTLY; must be
dropped before retrying.
None - index does not exist; safe to create.
"""
with connection.cursor() as cursor:
cursor.execute(
"SELECT pg_index.indisvalid " "FROM pg_class JOIN pg_index ON pg_class.oid = pg_index.indexrelid " "WHERE pg_class.relname = %s",
[self.INDEX_NAME],
)
row = cursor.fetchone()
if row is None:
return None
return 'valid' if row[0] else 'invalid'
def handle(self, *args, **options):
status = self._index_status()
if status == 'valid':
self.stdout.write(f"Index {self.INDEX_NAME} already exists, nothing to do.")
return
if status == 'invalid':
self.stdout.write(f"Dropping invalid index {self.INDEX_NAME} from a previous failed attempt...")
with connection.cursor() as cursor:
cursor.execute(f"DROP INDEX IF EXISTS {self.INDEX_NAME}")
self.stdout.write(f"Creating index {self.INDEX_NAME} on {self.TABLE_NAME} (host_id, id DESC) concurrently...")
# CREATE INDEX CONCURRENTLY cannot run inside a transaction.
connection.ensure_connection()
old_autocommit = connection.connection.autocommit
connection.connection.autocommit = True
try:
with connection.cursor() as cursor:
# Disable statement_timeout for this session — the customer may
# have set ALTER ROLE ... SET statement_timeout which would kill
# the index build on large tables.
cursor.execute("SET statement_timeout = 0")
cursor.execute(f"CREATE INDEX CONCURRENTLY {self.INDEX_NAME} " f"ON {self.TABLE_NAME} (host_id, id DESC)")
finally:
connection.connection.autocommit = old_autocommit
self.stdout.write(f"Index {self.INDEX_NAME} created successfully.")