#!/usr/bin/env python3 """Drop and recreate idx_detect_job_items_release_node_job on mainland.""" from __future__ import annotations import os import time from pathlib import Path import psycopg2 from psycopg2 import sql ENV_FILE = Path("/etc/default/domaincheck-worker") INDEX_NAME = "idx_detect_job_items_release_node_job" def load_env(path: Path) -> None: if not path.exists(): return for raw in path.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) os.environ[key] = value def log(message: str) -> None: stamp = time.strftime("%Y-%m-%d %H:%M:%S") print(f"[{stamp}] {message}", flush=True) def fetch_index_state(cur: psycopg2.extensions.cursor, index_name: str) -> tuple[bool, bool, bool] | None: cur.execute( """ SELECT i.indisvalid, i.indisready, i.indislive FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid WHERE c.relname = %s """, (index_name,), ) row = cur.fetchone() if not row: return None return bool(row[0]), bool(row[1]), bool(row[2]) def fetch_conflicting_backends(cur: psycopg2.extensions.cursor) -> list[tuple[int, str, str]]: cur.execute( """ SELECT pid, query, coalesce(wait_event_type, '') FROM pg_stat_activity WHERE state <> 'idle' AND ( query ILIKE %s OR query ILIKE %s ) ORDER BY backend_start """, ( "%idx_detect_job_items_release_node_job%", "%idx_detect_job_items_stalled_job_activity%", ), ) rows = [] for pid, query, wait_event_type in cur.fetchall(): snippet = " ".join((query or "").split())[:160] rows.append((pid, wait_event_type, snippet)) return rows def main() -> int: load_env(ENV_FILE) conn = psycopg2.connect( host="127.0.0.1", port=5432, user=os.getenv("DB_USER", "postgres"), password=os.getenv("DB_PASSWORD"), dbname="domain", ) conn.autocommit = True cur = conn.cursor() try: conflicts = fetch_conflicting_backends(cur) if conflicts: log(f"preflight conflicting_backends={conflicts}") log("drop start") cur.execute(sql.SQL("DROP INDEX CONCURRENTLY IF EXISTS {}").format(sql.Identifier(INDEX_NAME))) log("drop done") residual_state = fetch_index_state(cur, INDEX_NAME) if residual_state is not None: raise RuntimeError( f"index shell still present after drop: name={INDEX_NAME} " f"state={residual_state}" ) log("create start") cur.execute( sql.SQL( """ CREATE INDEX CONCURRENTLY {} ON public.detect_job_items (claimed_by, job_id, status, id) WHERE claimed_by <> '' AND status IN ('claimed', 'running') """ ).format(sql.Identifier(INDEX_NAME)) ) log("create done") log(f"final state={fetch_index_state(cur, INDEX_NAME)}") finally: cur.close() conn.close() return 0 if __name__ == "__main__": raise SystemExit(main())