-- ============================================================================= -- 02_convert_partitions_vides.sql — supprime les partitions HASH VIDES et les -- recrée en RANGE simple, avec leurs index. -- -- Sans risque de perte : chaque partition est recomptée juste avant sa -- suppression, et toute partition non vide est IGNORÉE (jamais supprimée). -- C'est le garde-fou principal de ce script. -- -- Gain attendu : ~2000 sous-partitions HASH en moins, donc un temps de -- planification qui retombe de ~1500 ms à quelques millisecondes. L'exécution -- des requêtes, elle, était déjà à ~5 ms — c'est bien la planification qui -- coûte, car le planner doit ouvrir toutes les partitions pour les élaguer. -- -- Usage OBLIGATOIRE avec ON_ERROR_STOP : sans ce flag, psql poursuit après une -- erreur et pourrait exécuter un DROP alors que le contrôle a échoué. -- docker exec -i wordpress-postgres psql -U pgz_admin -d wpz_postgres \ -- -v ON_ERROR_STOP=1 < 02_convert_partitions_vides.sql -- ============================================================================= \timing on \set ON_ERROR_STOP on DO $$ DECLARE r record; n bigint; d0 date; d1 date; tbl text; converties int := 0; ignorees int := 0; BEGIN FOR r IN SELECT c.oid, c.relname::text AS partition, pg_get_expr(c.relpartbound, c.oid) AS bornes FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE i.inhparent = 'adsb.positions'::regclass AND c.relkind = 'p' ORDER BY pg_get_expr(c.relpartbound, c.oid) LOOP -- GARDE-FOU : comptage réel immédiatement avant toute action destructive. EXECUTE format('SELECT count(*) FROM adsb.%I', r.partition) INTO n; IF n > 0 THEN RAISE NOTICE 'IGNORÉE % : % ligne(s) — migration avec copie requise (script 03)', r.partition, n; ignorees := ignorees + 1; CONTINUE; END IF; -- Extraction des bornes réelles depuis relpartbound : on ne se fie pas au -- nom, qui peut être erroné (bug EXTRACT(YEAR) vs ISOYEAR). d0 := (regexp_match(r.bornes, 'FROM \(''([0-9-]+)'))[1]::date; d1 := (regexp_match(r.bornes, 'TO \(''([0-9-]+)'))[1]::date; tbl := r.partition; EXECUTE format('DROP TABLE adsb.%I', tbl); EXECUTE format( 'CREATE TABLE adsb.%I PARTITION OF adsb.positions FOR VALUES FROM (%L) TO (%L)', tbl, d0, d1); -- Index identiques à ceux posés par adsb.create_week_partition EXECUTE format('CREATE INDEX %I ON adsb.%I USING GIST (geom)', tbl||'_geom_gix', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (ts DESC)', tbl||'_ts_idx', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (icao, ts DESC)', tbl||'_icao_ts_idx', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (inserted_at DESC)', tbl||'_inserted_at_idx', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (callsign) WHERE callsign IS NOT NULL', tbl||'_callsign_idx', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (snap_id)', tbl||'_snap_id_idx', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (receiver_id)', tbl||'_receiver_id_idx', tbl); EXECUTE format('CREATE INDEX %I ON adsb.%I (aircraft_wtc)', tbl||'_aircraft_wtc_idx', tbl); EXECUTE format( 'ALTER TABLE adsb.%I ADD CONSTRAINT %I FOREIGN KEY (receiver_id) ' 'REFERENCES adsb.receivers(id) ON DELETE SET NULL', tbl, tbl||'_receiver_fk'); converties := converties + 1; END LOOP; RAISE NOTICE '─────────────────────────────────────────────'; RAISE NOTICE '% partition(s) converties en RANGE simple', converties; RAISE NOTICE '% partition(s) ignorées (contiennent des données)', ignorees; END $$; -- Résultat SELECT c.relkind, count(*) AS nb FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE i.inhparent = 'adsb.positions'::regclass GROUP BY 1 ORDER BY 1; SELECT count(*) AS total_partitions_et_sous_partitions FROM pg_class c JOIN pg_inherits i ON i.inhrelid = c.oid WHERE i.inhparent = 'adsb.positions'::regclass OR i.inhparent IN (SELECT inhrelid FROM pg_inherits WHERE inhparent = 'adsb.positions'::regclass); \timing off