|
| 1 | +# Outillage `meta` — Mode d'emploi |
| 2 | +**Projet Marius · Architecture ECS/DOD · PostgreSQL 18** |
| 3 | + |
| 4 | +--- |
| 5 | + |
| 6 | +## Contexte |
| 7 | + |
| 8 | +Le schéma `meta` héberge trois outils d'ingénierie complémentaires. Ils s'appuient sur `meta.containment_intent` (registre AOT des composants) et sur `pg_catalog`. Aucun n'est destiné au runtime applicatif — accès réservé à `marius_admin` et `postgres`. |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## 1. `meta.f_generate_dod_template` — Générateur de DDL aligné CPU |
| 13 | + |
| 14 | +### Raison d'être |
| 15 | + |
| 16 | +Dans une architecture DOD, l'ordre des colonnes dans une table n'est pas cosmétique : il détermine le padding d'alignement CPU entre chaque champ. Un ordre naïf (tel que dicté par la logique métier) peut insérer plusieurs octets de padding par tuple, ce qui réduit la densité et augmente la pression cache. |
| 17 | + |
| 18 | +Cette fonction prend une liste de colonnes brutes et produit trois livrables en un seul appel : |
| 19 | + |
| 20 | +- le `CREATE TABLE` avec les colonnes triées dans l'ordre optimal (8 B → 4 B → 2 B → 1 B → varlena), |
| 21 | +- un bloc de commentaires SQL détaillant le layout mémoire colonne par colonne (offset, taille, padding), |
| 22 | +- l'`INSERT INTO meta.containment_intent` pré-calculé pour armer le registre d'audit. |
| 23 | + |
| 24 | +### Utilisation |
| 25 | + |
| 26 | +```sql |
| 27 | +SELECT meta.f_generate_dod_template( |
| 28 | + 'commerce.my_table', |
| 29 | + ARRAY[ |
| 30 | + 'id int8 GENERATED ALWAYS AS IDENTITY', |
| 31 | + 'created_at timestamptz NOT NULL', |
| 32 | + 'status smallint NOT NULL DEFAULT 0', |
| 33 | + 'is_active boolean NOT NULL DEFAULT true', |
| 34 | + 'label varchar(64)' |
| 35 | + ] |
| 36 | +); |
| 37 | +``` |
| 38 | + |
| 39 | +La fonction retourne un bloc TEXT à copier-coller directement dans le DDL de migration. Exemple de sortie : |
| 40 | + |
| 41 | +```sql |
| 42 | +-- ============================================================ |
| 43 | +-- DOD TEMPLATE : commerce.my_table |
| 44 | +-- ------------------------------------------------------------ |
| 45 | +-- Fixed-Length Header : 24 B |
| 46 | +-- (23 B base + 1 B null bitmap [ceil(5 cols / 8)] -> MAXALIGN 8) |
| 47 | +-- ------------------------------------------------------------ |
| 48 | +-- Layout memoire (Ordre DOD) : |
| 49 | +-- id (int8 ) : offset 24 B, size 8 B, padding_before 0 B |
| 50 | +-- created_at (timestamptz ) : offset 32 B, size 8 B, padding_before 0 B |
| 51 | +-- status (smallint ) : offset 40 B, size 2 B, padding_before 0 B |
| 52 | +-- is_active (boolean ) : offset 42 B, size 1 B, padding_before 0 B |
| 53 | +-- label (varchar(64) ) : offset 44 B, size 4 B, padding_before 1 B [varlena -- 4 B pre-ANALYZE] |
| 54 | +-- ------------------------------------------------------------ |
| 55 | +-- Ordre naif (input) : 56 B / tuple |
| 56 | +-- Ordre DOD optimise : 48 B / tuple |
| 57 | +-- Padding economise : 8 B / tuple |
| 58 | +-- ============================================================ |
| 59 | +CREATE TABLE commerce.my_table ( |
| 60 | + id int8 GENERATED ALWAYS AS IDENTITY, |
| 61 | + created_at timestamptz NOT NULL, |
| 62 | + status smallint NOT NULL DEFAULT 0, |
| 63 | + is_active boolean NOT NULL DEFAULT true, |
| 64 | + label varchar(64) |
| 65 | +); |
| 66 | + |
| 67 | +-- Armement du registre AOT |
| 68 | +INSERT INTO meta.containment_intent |
| 69 | + (component_id, intent_density_bytes) |
| 70 | +VALUES |
| 71 | + ('commerce.my_table', 48) |
| 72 | +ON CONFLICT (component_id) |
| 73 | + DO UPDATE SET intent_density_bytes = EXCLUDED.intent_density_bytes; |
| 74 | +``` |
| 75 | + |
| 76 | +### Points d'attention |
| 77 | + |
| 78 | +- Les types custom (PostGIS `geometry`, `ltree`, etc.) sont supportés à condition que l'extension soit installée. |
| 79 | +- Les colonnes varlena (`text`, `varchar`, `jsonb`...) sont toujours reléguées en fin de table (Fixed-Length Prefixing) et leur taille est fixée à 4 B dans la simulation. La valeur `intent_density_bytes` doit être réévaluée après `ANALYZE` via `meta.v_extended_containment_security_matrix`. |
| 80 | +- Le format `p_table_name` doit respecter `schema.table` en snake_case, lettre initiale obligatoire — conforme au `CHECK` constraint de `meta.containment_intent`. |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +## 2. `meta.f_compile_entity_profile` — Compilateur AOT de la vue de profil ECS |
| 85 | + |
| 86 | +### Raison d'être |
| 87 | + |
| 88 | +Dans une architecture ECS, une entité est un identifiant entier pur. Ses données sont fragmentées dans des tables de composants indépendantes (`identity.auth`, `content.core`, etc.). Il n'existe aucune table centrale listant quels composants sont actifs pour un identifiant donné. |
| 89 | + |
| 90 | +Cette fonction génère et exécute dynamiquement la vue `meta.v_entity_profile`, qui permet de répondre à la question de debugging : *"Quels composants sont peuplés pour l'entité #N ?"* |
| 91 | + |
| 92 | +Le principe AOT (Ahead-Of-Time) est central : l'introspection du catalogue (`pg_catalog`, `meta.containment_intent`) n'a lieu qu'une seule fois — à la compilation. La vue résultante est un `UNION ALL` pur, sans aucun accès catalogue au runtime. |
| 93 | + |
| 94 | +### Utilisation |
| 95 | + |
| 96 | +**Compiler (ou recompiler après ajout d'un composant) :** |
| 97 | + |
| 98 | +```sql |
| 99 | +SELECT meta.f_compile_entity_profile(); |
| 100 | +``` |
| 101 | + |
| 102 | +La fonction retourne le DDL compilé pour inspection, puis l'exécute. Exemple de sortie : |
| 103 | + |
| 104 | +```sql |
| 105 | +CREATE OR REPLACE VIEW meta.v_entity_profile AS |
| 106 | +SELECT |
| 107 | + spine_id, |
| 108 | + spine_type, |
| 109 | + array_agg(component_name ORDER BY component_name) AS active_components |
| 110 | +FROM ( |
| 111 | + SELECT entity_id AS spine_id, 'entity_id' AS spine_type, 'identity.auth' AS component_name FROM identity.auth |
| 112 | + UNION ALL |
| 113 | + SELECT document_id AS spine_id, 'document_id' AS spine_type, 'content.core' AS component_name FROM content.core |
| 114 | +) AS raw_components |
| 115 | +GROUP BY spine_id, spine_type |
| 116 | +``` |
| 117 | + |
| 118 | +**Interroger la vue une fois compilée :** |
| 119 | + |
| 120 | +```sql |
| 121 | +-- Tous les composants actifs pour l'entité #42 |
| 122 | +SELECT spine_id, spine_type, active_components |
| 123 | +FROM meta.v_entity_profile |
| 124 | +WHERE spine_id = 42; |
| 125 | +``` |
| 126 | + |
| 127 | +``` |
| 128 | + spine_id | spine_type | active_components |
| 129 | +----------+------------+-------------------------------- |
| 130 | + 42 | entity_id | {identity.auth} |
| 131 | + 42 | document_id| {content.core} |
| 132 | +``` |
| 133 | + |
| 134 | +### Points d'attention |
| 135 | + |
| 136 | +- **Recompiler après chaque ajout de composant** dans `meta.containment_intent`. La vue n'est pas auto-rafraîchie. |
| 137 | +- La colonne `spine_type` est obligatoire pour lever l'ambiguïté : `identity.entity` et `content.document` ont des séquences d'identifiants indépendantes. Un `spine_id = 7` peut désigner simultanément une entité et un document — le `spine_type` les distingue. |
| 138 | +- Seuls les composants possédant une colonne de liaison directe vers une spine (`entity_id`, `document_id`, ou `id` avec FK vérifiée via `pg_constraint`) sont inclus. Les composants sans liaison spine directe (`commerce.transaction_item`, `content.tag_hierarchy`, etc.) sont silencieusement exclus — ils ne portent pas d'identifiant d'entité au sens ECS. |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## 3. `meta.v_performance_sentinel` — Sentinel de performance DOD |
| 143 | + |
| 144 | +### Raison d'être |
| 145 | + |
| 146 | +Les invariants de performance du projet (densité DOD, HOT-eligibility, corrélation BRIN) peuvent être dégradés silencieusement par une modification de schéma : ajout d'un index sur une colonne fréquemment mise à jour, réorganisation physique des lignes, ou dérive de densité après une vague d'insertions imprévues. |
| 147 | + |
| 148 | +Cette vue croise les statistiques runtime (`pg_stat_user_tables`, `pg_stats`) avec le registre d'intention (`meta.containment_intent`) pour lever trois types d'alertes booléennes par composant enregistré. |
| 149 | + |
| 150 | +### Les trois alertes |
| 151 | + |
| 152 | +| Alerte | Condition | Cause typique | |
| 153 | +|---|---|---| |
| 154 | +| `hot_blocker_alert` | `n_tup_upd > 0` ET colonne indexée non-immutable | Ajout d'un index sur une colonne mise à jour fréquemment | |
| 155 | +| `brin_drift_alert` | `abs(brin_correlation) < 0.90` | Insertions non séquentielles sur une table avec index BRIN | |
| 156 | +| `bloat_alert` | `pg_relation_size / n_live_tup > (intent_density / fillfactor) * 1.20` | Padding structurel, varlena plus large que prévu, dead tuples | |
| 157 | + |
| 158 | +### Utilisation |
| 159 | + |
| 160 | +```sql |
| 161 | +-- Vue d'ensemble : tous les composants avec au moins une alerte active |
| 162 | +SELECT component_id, |
| 163 | + hot_blocker_alert, hot_blocker_cols, |
| 164 | + brin_drift_alert, brin_worst_col, brin_correlation, |
| 165 | + bloat_alert, observed_bytes_per_tuple, bloat_threshold_bytes, |
| 166 | + fillfactor_pct, n_live_tup |
| 167 | +FROM meta.v_performance_sentinel |
| 168 | +WHERE hot_blocker_alert |
| 169 | + OR brin_drift_alert |
| 170 | + OR bloat_alert = TRUE; |
| 171 | + |
| 172 | +-- Détail d'un composant spécifique |
| 173 | +SELECT * FROM meta.v_performance_sentinel |
| 174 | +WHERE component_id = 'content.core'; |
| 175 | +``` |
| 176 | + |
| 177 | +### Colonnes de diagnostic |
| 178 | + |
| 179 | +| Colonne | Usage | |
| 180 | +|---|---| |
| 181 | +| `hot_blocker_cols` | Noms des colonnes qui bloquent HOT (à dé-indexer ou déclarer immutable) | |
| 182 | +| `brin_worst_col` | Colonne BRIN la plus dégradée (candidat à un `CLUSTER` ou `VACUUM FULL`) | |
| 183 | +| `brin_correlation` | Valeur abs de corrélation (1.0 = parfait, < 0.9 = drift) | |
| 184 | +| `observed_bytes_per_tuple` | Densité observée brute (pg_relation_size / n_live_tup) | |
| 185 | +| `bloat_threshold_bytes` | Seuil effectif après correction fillfactor (pour comprendre le calcul) | |
| 186 | +| `fillfactor_pct` | Fillfactor lu depuis `pg_class.reloptions` (100 si absent) | |
| 187 | +| `n_live_tup` | Tuples vivants selon `pg_stat_user_tables` — NULL ou 0 = ANALYZE requis | |
| 188 | + |
| 189 | +### Points d'attention |
| 190 | + |
| 191 | +- **Prérequis : `ANALYZE`** sur les tables auditées. Sans statistiques, `brin_correlation` est NULL (pas d'alerte BRIN) et `n_live_tup` peut être sous-estimé (alertes bloat silencieuses ou erronées). |
| 192 | +- `bloat_alert = NULL` (et non FALSE) indique une table vide ou sans statistiques — à distinguer d'une absence de dérive. |
| 193 | +- La correction fillfactor est automatique : les tables `identity.auth` (ff=70) et `commerce.product_core` (ff=80) n'afficheront pas de faux positif bloat sur un layout sain. |
| 194 | +- La vue est en lecture seule, sans effet de bord. Elle peut être interrogée à tout moment en session `marius_admin`. |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## Workflow recommandé |
| 199 | + |
| 200 | +``` |
| 201 | +Nouveau composant |
| 202 | + │ |
| 203 | + ▼ |
| 204 | +1. meta.f_generate_dod_template(...) |
| 205 | + └─ Obtenir le DDL trié + intent_density_bytes |
| 206 | + │ |
| 207 | + ▼ |
| 208 | +2. Appliquer le DDL (CREATE TABLE) + INSERT meta.containment_intent |
| 209 | + │ |
| 210 | + ▼ |
| 211 | +3. meta.f_compile_entity_profile() |
| 212 | + └─ Recompiler meta.v_entity_profile si le composant porte entity_id / document_id |
| 213 | + │ |
| 214 | + ▼ |
| 215 | +4. ANALYZE <schema>.<table> |
| 216 | + │ |
| 217 | + ▼ |
| 218 | +5. SELECT * FROM meta.v_performance_sentinel WHERE component_id = '<schema>.<table>' |
| 219 | + └─ Vérifier : hot_blocker_alert, brin_drift_alert, bloat_alert tous à FALSE |
| 220 | +``` |
0 commit comments