@@ -17,7 +17,7 @@ relationnelle. Conçu pour une cible de **500 000 utilisateurs actifs**.
1717
1818## Philosophie
1919
20- Ce projet applique le paradigme ** SoA** (_ Structure of Arrays _ ), inspiré de l'ECS
20+ Ce projet applique le paradigme ** SoA** (* Structure of Arrays * ), inspiré de l'ECS
2121des moteurs de jeu et du DOD bas niveau :
2222
2323- ** Entity** — un identifiant entier pur, sans donnée métier.
@@ -41,34 +41,36 @@ l'interface [schema.org](https://schema.org) par-dessus les composants fragment
4141.
4242├── master_schema_ddl.pgsql # Blueprint immuable — DDL pur
4343├── master_schema_dml.pgsql # Seed data — dev / CI uniquement
44- ├── architecture_decision_records.md # 25 arbitrages architecturaux
44+ ├── architecture_decision_records.md # 28 arbitrages architecturaux
4545├── README.md
4646└── tests/
4747 ├── 01_schema_and_security.sql # Types physiques, BRIN, RBAC, SECURITY DEFINER
4848 ├── 02_identity_logic.sql # Comptes, slugs, bitmask, connexions
4949 ├── 03_content_logic.sql # Documents, révisions, commentaires ltree
5050 ├── 04_commerce_logic.sql # Stock, snapshots de prix, agrégats
51- └── 05_tag_hierarchy.sql # Mots clefs hiérarchisés
51+ ├── 05_tag_hierarchy.sql # Closure Table, profondeur, breadcrumb
52+ └── 06_rls_policies.sql # RLS GUC stateless, SELECT/UPDATE isolation
5253```
5354
5455### ` master_schema_ddl.pgsql `
5556
5657Le schéma complet en un fichier autonome. Contient dans l'ordre d'exécution :
5758
58- | Section | Contenu |
59- | --------------------- | ------------------------------------------------------------------------------------------------------------ |
60- | 0 — Initialisation | Création de l'utilisateur, de la base, connexion |
61- | 1 — Extensions | ` unaccent ` , ` ltree ` , ` pg_trgm ` , ` postgis ` |
62- | 2 — Schémas | ` identity ` , ` geo ` , ` org ` , ` commerce ` , ` content ` |
63- | 3 — Spines | ` identity.entity ` , ` org.entity ` , ` content.document ` |
64- | 4 — Fondation | ` geo.place_core/content ` , ` identity.permission_bit ` , ` identity.role ` |
65- | 5–8 — Composants | Toutes les tables physiques par domaine |
66- | 9 — Fonctions | ` fn_update_modified_at ` , ` fn_slug_deduplicate ` , ` fn_revision_num ` , ` has_permission ` |
67- | 10 — Triggers | Triggers ` modified_at ` , déduplication de slug, numérotation des révisions |
68- | 11 — Procédures | ` create_account ` , ` create_document ` , ` create_comment ` , ` create_transaction ` , ` create_transaction_item ` , etc. |
69- | 12 — Vues | Toutes les vues sémantiques schema.org |
70- | 13 — Permissions | ` GRANT SELECT + EXECUTE ` sur ` marius_user ` · calibrage autovacuum |
71- | 14 — Verrouillage ECS | ` marius_admin ` · révocation DML globale · ` SECURITY DEFINER ` (ADR-020) |
59+ | Section | Contenu |
60+ | ---| ---|
61+ | 0 — Initialisation | Création de l'utilisateur, de la base, connexion |
62+ | 1 — Extensions | ` unaccent ` , ` ltree ` , ` pg_trgm ` , ` postgis ` |
63+ | 2 — Schémas | ` identity ` , ` geo ` , ` org ` , ` commerce ` , ` content ` |
64+ | 3 — Spines | ` identity.entity ` , ` org.entity ` , ` content.document ` |
65+ | 4 — Fondation | ` geo.place_core/content ` , ` identity.permission_bit ` , ` identity.role ` |
66+ | 5–8 — Composants | Toutes les tables physiques par domaine |
67+ | 9 — Fonctions | ` fn_update_modified_at ` , ` fn_slug_deduplicate ` , ` fn_revision_num ` , ` has_permission ` |
68+ | 10 — Triggers | Triggers ` modified_at ` , déduplication de slug, numérotation des révisions |
69+ | 11 — Procédures | ` create_account ` , ` create_document ` , ` create_comment ` , ` create_transaction ` , ` create_transaction_item ` , etc. |
70+ | 12 — Vues | Toutes les vues sémantiques schema.org |
71+ | 13 — Permissions | ` GRANT SELECT + EXECUTE ` sur ` marius_user ` · calibrage autovacuum |
72+ | 14 — Verrouillage ECS | ` marius_admin ` · révocation DML globale · ` SECURITY DEFINER ` · ` BYPASSRLS ` (ADR-020/028) |
73+ | 15 — RLS | Politiques stateless GUC sur ` content.core ` , ` transaction_core ` , ` account_core ` (ADR-028) |
7274
7375### ` master_schema_dml.pgsql `
7476
@@ -82,7 +84,7 @@ d'écriture que la production.
8284
8385### ` architecture_decision_records.md `
8486
85- 25 arbitrages architecturaux, ordonnés par importance décroissante. Chaque
87+ 28 arbitrages architecturaux, ordonnés par importance décroissante. Chaque
8688entrée documente ce qui ** n'est pas déductible de la lecture du code** : le
8789raisonnement derrière la décision, les alternatives écartées et leurs coûts.
8890
@@ -98,32 +100,36 @@ psql -U postgres -d marius -f tests/01_schema_and_security.sql
98100psql -U postgres -d marius -f tests/02_identity_logic.sql
99101psql -U postgres -d marius -f tests/03_content_logic.sql
100102psql -U postgres -d marius -f tests/04_commerce_logic.sql
103+ psql -U postgres -d marius -f tests/05_tag_hierarchy.sql
104+ psql -U postgres -d marius -f tests/06_rls_policies.sql
101105```
102106
103- | Fichier | Périmètre | Tests |
104- | ---------------------------- | ---------------------------------------------------------------------- | ----- |
105- | ` 01_schema_and_security.sql ` | Types INT8, absence de bpchar, BRIN, SECURITY DEFINER, RBAC, triggers | 35 |
106- | ` 02_identity_logic.sql ` | ` create_account ` , slugs, bitmask ` has_permission ` , ` record_login ` | 9 |
107- | ` 03_content_logic.sql ` | ` create_document ` , snapshot complet (ADR-021), ltree, ` create_comment ` | 17 |
108- | ` 04_commerce_logic.sql ` | Stock, snapshot de prix, sur-vente, agrégats ` v_transaction ` | 6 |
107+ | Fichier | Périmètre | Tests |
108+ | ---| ---| ---|
109+ | ` 01_schema_and_security.sql ` | Types INT8, absence de bpchar, BRIN, SECURITY DEFINER, RBAC, triggers | 42 |
110+ | ` 02_identity_logic.sql ` | ` create_account ` , slugs, bitmask ` has_permission ` , ` record_login ` | 12 |
111+ | ` 03_content_logic.sql ` | ` create_document ` , snapshot complet (ADR-021), ltree, ` create_comment ` | 17 |
112+ | ` 04_commerce_logic.sql ` | Stock, snapshot de prix, sur-vente, agrégats ` v_transaction ` | 6 |
113+ | ` 05_tag_hierarchy.sql ` | Closure Table, create_tag, sous-arbre, profondeur max, breadcrumb | 10 |
114+ | ` 06_rls_policies.sql ` | RLS stateless GUC, SELECT/UPDATE sur content, commerce, identity | 15 |
109115
110116---
111117
112118## Prérequis
113119
114- | Composant | Version minimale |
115- | ---------- | ---------------------------------- |
120+ | Composant | Version minimale |
121+ | ---| ---|
116122| PostgreSQL | ** 18** (async I/O, EXPLAIN MEMORY) |
117- | PostGIS | 3.x |
123+ | PostGIS | 3.x |
118124
119125### Extensions PostgreSQL requises
120126
121- | Extension | Usage |
122- | ---------- | ------------------------------------------------------------------------------ |
123- | ` unaccent ` | Normalisation des accents pour les index de recherche texte |
124- | ` ltree ` | Chemins matérialisés pour la taxonomie des tags et les threads de commentaires |
125- | ` pg_trgm ` | Index trigrammes pour la recherche partielle sur les noms et titres |
126- | ` postgis ` | Type ` geometry(Point, 4326) ` , index GiST géospatiaux, opérateur KNN ` <-> ` |
127+ | Extension | Usage |
128+ | ---| ---|
129+ | ` unaccent ` | Normalisation des accents pour les index de recherche texte |
130+ | ` ltree ` | Chemins matérialisés pour la taxonomie des tags et les threads de commentaires |
131+ | ` pg_trgm ` | Index trigrammes pour la recherche partielle sur les noms et titres |
132+ | ` postgis ` | Type ` geometry(Point, 4326) ` , index GiST géospatiaux, opérateur KNN ` <-> ` |
127133
128134Vérification de disponibilité sur le serveur cible :
129135
@@ -253,10 +259,10 @@ SELECT * FROM identity.v_role WHERE id = 1;
253259
254260### Vues de listing vs page complète (isolation TOAST, ADR-016)
255261
256- | Vue | Usage | Charge TOAST |
257- | ------------------------ | -------------------------- | ------------------- |
258- | ` content.v_article_list ` | Listings, flux, navigation | Zéro |
259- | ` content.v_article ` | Page article complète | Oui (` articleBody ` ) |
262+ | Vue | Usage | Charge TOAST |
263+ | ---| ---| ---|
264+ | ` content.v_article_list ` | Listings, flux, navigation | Zéro |
265+ | ` content.v_article ` | Page article complète | Oui (` articleBody ` ) |
260266
261267Toujours utiliser ` v_article_list ` pour les listings. Ne projeter ` articleBody `
262268que sur les lectures de page complète.
@@ -268,24 +274,24 @@ tables physiques. Toute mutation passe par les procédures stockées, déclarée
268274` SECURITY DEFINER ` : elles s'exécutent avec les droits du propriétaire (` postgres ` )
269275indépendamment des droits de l'appelant.
270276
271- | Procédure | Usage |
272- | ----------------------------------------------- | ------------------------------------------------------------------------ |
273- | ` identity.create_account(...) ` | Créer un compte (entity + auth + account_core) |
274- | ` identity.create_person(...) ` | Créer un profil public |
275- | ` identity.record_login(entity_id) ` | Enregistrer une connexion (hot path) |
276- | ` content.create_document(...) ` | Créer un article/page |
277- | ` content.publish_document(document_id) ` | Publier un brouillon |
277+ | Procédure | Usage |
278+ | ---| ---|
279+ | ` identity.create_account(...) ` | Créer un compte (entity + auth + account_core) |
280+ | ` identity.create_person(...) ` | Créer un profil public |
281+ | ` identity.record_login(entity_id) ` | Enregistrer une connexion (hot path) |
282+ | ` content.create_document(...) ` | Créer un article/page |
283+ | ` content.publish_document(document_id) ` | Publier un brouillon |
278284| ` content.save_revision(document_id, author_id) ` | Snapshot éditorial complet (name, slug, alt_headline, description, body) |
279- | ` content.create_comment(...) ` | Insérer un commentaire (zéro dead tuple, ADR-012) |
280- | ` commerce.create_transaction_item(...) ` | Ligne de commande avec snapshot du prix en centimes (INT8, ADR-014) |
285+ | ` content.create_comment(...) ` | Insérer un commentaire (zéro dead tuple, ADR-012) |
286+ | ` commerce.create_transaction_item(...) ` | Ligne de commande avec snapshot du prix en centimes (INT8, ADR-014) |
281287
282288### Rôles PostgreSQL
283289
284- | Rôle | Droits | Usage |
285- | -------------- | --------------------------------------------- | -------------------------------- |
286- | ` marius_user ` | ` SELECT ` + ` EXECUTE ` | Runtime applicatif |
290+ | Rôle | Droits | Usage |
291+ | ---| ---| ---|
292+ | ` marius_user ` | ` SELECT ` + ` EXECUTE ` | Runtime applicatif |
287293| ` marius_admin ` | ` SELECT ` + ` EXECUTE ` + ` INSERT/UPDATE/DELETE ` | Maintenance, migrations, CI seed |
288- | ` postgres ` | Superutilisateur | Déploiement DDL, installation |
294+ | ` postgres ` | Superutilisateur | Déploiement DDL, installation |
289295
290296` marius_admin ` hérite de ` marius_user ` via ` GRANT ... WITH INHERIT TRUE ` .
291297En environnement hautement sécurisé, désactiver le ` LOGIN ` direct sur
@@ -295,8 +301,8 @@ En environnement hautement sécurisé, désactiver le `LOGIN` direct sur
295301
296302## Cas d'usage : de la frugalité à l'extreme scale
297303
298- L'architecture Marius n'est pas dimensionnée _ pour _ 500 000 utilisateurs —
299- elle est dimensionnée _ par _ les contraintes physiques de PostgreSQL. Cette
304+ L'architecture Marius n'est pas dimensionnée * pour * 500 000 utilisateurs —
305+ elle est dimensionnée * par * les contraintes physiques de PostgreSQL. Cette
300306rigueur mécanique la rend pertinente à n'importe quelle échelle.
301307
302308---
@@ -327,7 +333,7 @@ gonfleront jamais les tables hot path, quelle que soit leur longueur.
327333massifs, catalogues produits dynamiques.
328334
329335À ce volume, le coût dominant n'est plus le I/O disque mais la ** fragmentation
330- progressive du stockage** (_ bloat _ ) et la pression sur l'autovacuum.
336+ progressive du stockage** (* bloat * ) et la pression sur l'autovacuum.
331337
332338** Zéro dead tuple structurel** (ADR-012) : ` content.create_comment() ` effectue
333339une seule écriture heap par commentaire. Sur 10 000 commentaires/jour, l'absence
@@ -392,12 +398,12 @@ ORDER BY n_dead_tup DESC
392398LIMIT 20 ;
393399```
394400
395- | Table | Source de dead tuples | fillfactor |
396- | ----------------------- | ----------------------------------------- | ---------- |
397- | ` identity.auth ` | ` last_login_at ` à chaque connexion | 70 |
398- | ` commerce.product_core ` | ` stock ` à chaque vente | 80 |
399- | ` content.core ` | ` status ` à chaque changement de cycle | 75 |
400- | ` content.comment ` | Suppressions de modération (` status = 9 ` ) | défaut |
401+ | Table | Source de dead tuples | fillfactor |
402+ | ---| ---| ---|
403+ | ` identity.auth ` | ` last_login_at ` à chaque connexion | 70 |
404+ | ` commerce.product_core ` | ` stock ` à chaque vente | 80 |
405+ | ` content.core ` | ` status ` à chaque changement de cycle | 75 |
406+ | ` content.comment ` | Suppressions de modération (` status = 9 ` ) | défaut |
401407
402408### Vérification de l'isolation TOAST
403409
@@ -455,4 +461,4 @@ ORDER BY usename, state;
455461
456462---
457463
458- _ Architecture ECS/DOD · PostgreSQL 18 · Projet Marius _
464+ * Architecture ECS/DOD · PostgreSQL 18 · Projet Marius *
0 commit comments