142 lines
5.8 KiB
PL/PgSQL
142 lines
5.8 KiB
PL/PgSQL
-- =============================================================================
|
||
-- 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
|