adsb-1090/sql/03_convert_partition_avec_donnees.sql

142 lines
5.8 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================================================
-- 03_convert_partition_avec_donnees.sql — migre UNE partition contenant des
-- données, de HASH×16 vers RANGE simple, sans perte.
--
-- À lancer une partition à la fois, en passant son nom :
-- docker exec -i wordpress-postgres psql -U pgz_admin -d wpz_postgres \
-- -v ON_ERROR_STOP=1 -v part=positions_2026_w31 \
-- < 03_convert_partition_avec_donnees.sql
--
-- PRÉALABLE OBLIGATOIRE — sauvegarde de la partition concernée :
-- docker exec wordpress-postgres pg_dump -U pgz_admin -d wpz_postgres \
-- -t "adsb.<partition>*" -Fc -f /tmp/<partition>.dump
-- docker cp wordpress-postgres:/tmp/<partition>.dump /data/adsb/backups/
--
-- Principe :
-- 1. DETACH de l'ancienne partition (les données restent, hors du parent)
-- 2. CREATE de la nouvelle partition RANGE simple, attachée
-- 3. INSERT ... SELECT depuis l'ancienne vers le parent
-- 4. Contrôle des comptes AVANT/APRÈS — DROP uniquement si identiques
--
-- Le tout dans UNE transaction : en cas de problème, ROLLBACK ramène l'état
-- initial. Pendant l'opération, adsb2pg peut continuer d'insérer : ses lignes
-- arrivent dans la nouvelle partition, déjà attachée.
--
-- NOTE : sur la partition COURANTE, faire l'opération à faible trafic. Les
-- lignes insérées entre le DETACH et le CREATE (quelques millisecondes)
-- provoqueraient une erreur « no partition of relation found » côté adsb2pg,
-- qui les réessaiera au cycle suivant.
-- =============================================================================
\timing on
\set ON_ERROR_STOP on
\if :{?part}
\else
\echo 'ERREUR : passer -v part=<nom_partition>'
\quit 1
\endif
BEGIN;
-- Verrou explicite : empêche toute modification concurrente de la structure
LOCK TABLE adsb.positions IN SHARE UPDATE EXCLUSIVE MODE;
-- Transmission du nom de partition au bloc PL/pgSQL (les variables psql ne
-- sont pas visibles depuis un DO $$ : il faut passer par un paramètre de
-- session).
SET LOCAL my.part = :'part';
DO $$
DECLARE
p_old text := current_setting('my.part');
p_tmp text := current_setting('my.part') || '_old';
bornes text;
d0 date;
d1 date;
n_avant bigint;
n_apres bigint;
cols text;
BEGIN
SELECT pg_get_expr(c.relpartbound, c.oid) INTO bornes
FROM pg_class c JOIN pg_namespace ns ON ns.oid = c.relnamespace
WHERE ns.nspname = 'adsb' AND c.relname = p_old;
IF bornes IS NULL THEN
RAISE EXCEPTION 'Partition adsb.% introuvable ou non attachée', p_old;
END IF;
d0 := (regexp_match(bornes, 'FROM \(''([0-9-]+)'))[1]::date;
d1 := (regexp_match(bornes, 'TO \(''([0-9-]+)'))[1]::date;
EXECUTE format('SELECT count(*) FROM adsb.%I', p_old) INTO n_avant;
RAISE NOTICE 'Partition % : % ligne(s) à migrer (bornes % → %)', p_old, n_avant, d0, d1;
-- 1. Détacher et renommer l'ancienne
EXECUTE format('ALTER TABLE adsb.positions DETACH PARTITION adsb.%I', p_old);
EXECUTE format('ALTER TABLE adsb.%I RENAME TO %I', p_old, p_tmp);
-- 2. Créer la nouvelle, en RANGE simple
EXECUTE format(
'CREATE TABLE adsb.%I PARTITION OF adsb.positions FOR VALUES FROM (%L) TO (%L)',
p_old, d0, d1);
-- 3. Recopier les données.
-- geom est GENERATED ALWAYS : PostgreSQL refuse toute valeur explicite
-- (« cannot insert a non-DEFAULT value into column geom »). Un SELECT *
-- la remonterait, il faut donc énumérer les colonnes réelles et l'exclure.
-- Elle sera recalculée automatiquement depuis lat/lon à l'insertion.
SELECT string_agg(quote_ident(a.attname), ', ' ORDER BY a.attnum)
INTO cols
FROM pg_attribute a
WHERE a.attrelid = format('adsb.%I', p_tmp)::regclass
AND a.attnum > 0
AND NOT a.attisdropped
AND a.attgenerated = ''; -- exclut les colonnes générées
IF cols IS NULL OR cols = '' THEN
RAISE EXCEPTION 'Impossible de déterminer les colonnes de adsb.%', p_tmp;
END IF;
RAISE NOTICE 'Colonnes copiées (geom exclue, recalculée) : %', cols;
EXECUTE format('INSERT INTO adsb.%I (%s) SELECT %s FROM adsb.%I',
p_old, cols, cols, p_tmp);
EXECUTE format('SELECT count(*) FROM adsb.%I', p_old) INTO n_apres;
-- 4. Contrôle : on ne supprime QUE si le compte correspond exactement.
IF n_apres <> n_avant THEN
RAISE EXCEPTION 'ÉCART DE COMPTE : % avant, % après — transaction annulée, aucune donnée perdue',
n_avant, n_apres;
END IF;
RAISE NOTICE 'Copie vérifiée : % ligne(s) — suppression de l''ancienne structure', n_apres;
EXECUTE format('DROP TABLE adsb.%I', p_tmp);
-- 5. Index sur la nouvelle partition
EXECUTE format('CREATE INDEX %I ON adsb.%I USING GIST (geom)', p_old||'_geom_gix', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (ts DESC)', p_old||'_ts_idx', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (icao, ts DESC)', p_old||'_icao_ts_idx', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (inserted_at DESC)', p_old||'_inserted_at_idx', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (callsign) WHERE callsign IS NOT NULL',
p_old||'_callsign_idx', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (snap_id)', p_old||'_snap_id_idx', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (receiver_id)', p_old||'_receiver_id_idx', p_old);
EXECUTE format('CREATE INDEX %I ON adsb.%I (aircraft_wtc)', p_old||'_aircraft_wtc_idx', p_old);
EXECUTE format(
'ALTER TABLE adsb.%I ADD CONSTRAINT %I FOREIGN KEY (receiver_id) '
'REFERENCES adsb.receivers(id) ON DELETE SET NULL',
p_old, p_old||'_receiver_fk');
RAISE NOTICE 'Partition % convertie en RANGE simple avec ses index', p_old;
END $$;
COMMIT;
-- ANALYZE hors transaction : indispensable, la nouvelle partition n'a aucune
-- statistique et le planner ferait de mauvais choix.
ANALYZE adsb.positions;
\timing off