# DRILL — *L'Équation Oubliée de Bletchley Park*
## Rapport d'analyse Turing | 2026-04-13
---
## Méthode
Déterminisme strict. Chaque problème est tracé jusqu'à sa cause racine unique, pas ses symptômes. L'ordre de traitement suit la causalité, pas l'urgence apparente.
---
## PROBLÈMES P0 — Incident production actif
### P0-1 · Saturation table `ps_ac_temp_sales` → impact business direct
**Sévérité :** Critique. 1.2M lignes. −42% ventes.
**Cause racine :** Convergence de trois défaillances (trigger désactivé + script bloqué + pas de purge alternative).
**Erreur classique de triage :** Traiter le symptôme (volume) sans bloquer la progression. Tant que les verrous ne sont pas résolus, toute purge échouera.
---
### P0-2 · `autocommit=True` SQLAlchemy — aggravation des deadlocks
**Sévérité :** P0 (amplificateur d'incident).
Le scénario dit "force une transaction explicite". C'est partiellement inexact, mais le vrai problème est plus grave : avec `autocommit=True`, une `DELETE` sans `WHERE` batchée sur 1.2M lignes s'exécute en **une seule transaction implicite** qui maintient les verrous de toutes les lignes lues pendant toute la durée du scan.
**Correction obligatoire :**
```python
# AVANT (dangereux)
engine = create_engine(url, execution_options={"autocommit": True})
session.execute(text("DELETE FROM ps_ac_temp_sales WHERE created_at < ..."))
# APRÈS (correct)
engine = create_engine(url)
with engine.begin() as conn:
while True:
result = conn.execute(text("""
DELETE FROM ps_ac_temp_sales
WHERE created_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL :days DAY)
LIMIT 1000
"""), {"days": retention_days})
conn.commit()
if result.rowcount == 0:
break
time.sleep(0.1) # backpressure explicite
```
Le `LIMIT 1000` + `commit` par batch réduit la fenêtre de verrou de N minutes à N milliseconds × batch.
---
### P0-3 · Full table scan sur `DELETE` — requête non sargable
**Sévérité :** P0 (cause directe des lock wait timeouts).
Le scénario affirme que `NOW()` est "convertie en string". C'est techniquement approximatif — `NOW()` retourne bien un `DATETIME`. Le vrai problème : sur MariaDB/InnoDB, une expression de type `function(column)` dans le WHERE **empêche l'utilisation de l'index**. Ici ce n'est pas `NOW()` lui-même, c'est que l'index n'est vraisemblablement **pas sur `created_at`** (il est sur `session_id` CHAR(36)), donc le plan d'exécution fait un full scan.
**Vérification obligatoire avant tout correctif :**
```sql
EXPLAIN SELECT * FROM ps_ac_temp_sales
WHERE created_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL 7 DAY);
```
**Correction :**
```sql
-- Index couvrant manquant
ALTER TABLE ps_ac_temp_sales
ADD INDEX idx_cleanup (created_at);
-- Requête sargable
DELETE FROM ps_ac_temp_sales
WHERE created_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL 7 DAY)
LIMIT 1000;
```
**Angle mort biais "Complétude de Turing" signalé :** Je pourrais être tenté de proposer un index composite (`created_at, session_id, id_product`) pour couvrir tous les cas futurs. **Ce serait une erreur.** Le cas spécifique suffit : index simple sur `created_at`.
---
## PROBLÈMES P1 — Structurel grave
### P1-1 · Timezone implicite : `NOW()` vs `UTC_TIMESTAMP()`
**Sévérité :** P1 (données mal purgées, dérive silencieuse).
`created_at` est stocké en `DATETIME` sans timezone (UTC implicite côté MariaDB). `NOW()` retourne l'heure **du serveur**, qui est à +02:00 (CEST). Résultat : la comparaison a un décalage de 2h.
- Ligne insérée à `08:00 UTC` → `created_at = '2026-03-08 08:00:00'`
- `NOW()` au moment de la purge = `10:00 CEST` → `'2026-03-08 10:00:00'`
- La ligne semble avoir 2h de plus qu'en réalité → **purge prématurée de 2h**
En hiver (UTC+1) : purge prématurée de 1h. Ce décalage change deux fois par an (DST). Comportement **non-déterministe** → violation directe de la règle fondatrice de Turing.
**Correction :**
```sql
-- Toujours
WHERE created_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL :days DAY)
-- Jamais
WHERE created_at < DATE_SUB(NOW(), INTERVAL :days DAY)
```
---
### P1-2 · Incohérence config vs script : 30 jours ≠ 7 jours
**Sévérité :** P1 (logique métier indéfinie — laquelle est la source de vérité ?).
- `config/ac_analytics.config.json` : `temp_table_retention_days: 30`
- Script `ac_cleanup_temp_tables.py` : intervalle hardcodé à 7 jours
Le script **ignore la config**. Une des deux valeurs est incorrecte. Sans interroger Alexandre sur l'intention métier, **le correctif ne peut pas être déployé** — corriger l'une ou l'autre sans décision explicite est dangereux.
**Action requise avant fix :** Décision d'Alexandre sur la rétention cible.
**Action technique :** Le script doit lire `retention_days` depuis la DB (`ps_ac_*`), **pas** depuis un JSON local (voir P1-3).
---
### P1-3 · Config dans un fichier JSON — violation doctrine DB-First
**Sévérité :** P1 (violation CLun client.md, règle fondatrice).
`config/ac_analytics.config.json` est une source de vérité runtime stockée dans un fichier. C'est **explicitement interdit** par la doctrine S.O.A.P. :
> *"Toute donnée runtime DOIT vivre dans une table `ps_ac_*`. Jamais dans un fichier .json"*
**Migration obligatoire :**
```sql
INSERT INTO ps_ac_config (module, key, value, updated_at)
VALUES ('ac_analytics', 'temp_table_retention_days', '7', NOW());
```
Le script doit lire cette valeur via l'Active Record `ac_entities/`, jamais parser un fichier local.
---
### P1-4 · Trigger désactivé — piège de réactivation immédiate
**Sévérité :** P1 (fausse piste classique).
**PIÈGE IDENTIFIÉ.** La réaction instinctive est de faire `ALTER EVENT ps_ac_temp_sales_cleanup ENABLE`. **C'est faux.**
Questions bloquantes avant toute réactivation :
1. Pourquoi a-t-il été désactivé ? (incident précédent ? changement de schéma ?)
2. Quels autres modules dépendent de ce trigger ?
3. Le trigger utilise-t-il `NOW()` ou `UTC_TIMESTAMP()` ? (si `NOW()` → il a le même bug que P1-1)
4. Le trigger est-il compatible avec le batching ? Un trigger qui DELETE en masse sans LIMIT créera les mêmes deadlocks.
**Action correcte :** Auditer le trigger, corriger son contenu si nécessaire, puis réactiver.
```sql
-- Auditer AVANT de toucher
SHOW CREATE EVENT ps_ac_temp_sales_cleanup;
SELECT EVENT_NAME, STATUS, EVENT_DEFINITION
FROM information_schema.EVENTS
WHERE EVENT_NAME = 'ps_ac_temp_sales_cleanup';
```
---
## PROBLÈMES P2 — Dette technique
### P2-1 · Absence de monitoring des deadlocks
**Sévérité :** P2 (invisible jusqu'à l'explosion suivante).
Aucun mécanisme d'alerte proactif pour détecter les deadlocks. Le premier signal connu a été le −42% de ventes.
**Monitoring minimal obligatoire :**
```sql
-- Snapshot instantané
SHOW ENGINE INNODB STATUS;
-- Transactions longues en cours
SELECT * FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 30
ORDER BY trx_started;
-- Deadlocks récents
SELECT * FROM information_schema.INNODB_METRICS
WHERE NAME = 'lock_deadlocks';
```
À intégrer dans `ac_analytics.py` comme check périodique avec `AutomateLog`.
---
### P2-2 · CHAR(36) pour UUID — inefficacité structurelle
**Sévérité :** P2 (performance dégradée, non critique immédiatement).
`session_id CHAR(36)` = 36 octets par entrée, comparaisons string coûteuses, index plus volumineux.
**Alternative :** `BINARY(16)` (16 octets, ~2× plus rapide sur comparaisons) ou, si les sessions sont purement internes, un `INT UNSIGNED AUTO_INCREMENT`.
À traiter lors d'une migration planifiée, pas en urgence.
---
## FAUX POSITIFS
### FP-1 · `ac_catalog_sync` présenté comme responsable des locks
**Classement :** Faux positif — faux-ami intentionnel.
Le scénario laisse entendre que tuer `ac_catalog_sync` résoudrait le problème. **Non.** Ce module est critique et son SELECT sur `ps_product` ne devrait pas bloquer un DELETE sur `ps_ac_temp_sales` (tables distinctes, pas de FK évidente). La vraie cause du lock timeout est **le full table scan de la DELETE elle-même** (P0-3) qui maintient des row-level locks InnoDB pendant toute la durée du scan. `ac_catalog_sync` est un faux-ami.
### FP-2 · Credentials dans la commande docker exec
**Classement :** Faux positif pédagogique.
```
docker exec preprod_mariadb mariadb -u clun client_mcp -p"clun client_secure_pass_2026"
```
Credentials d'exemple dans un contexte de drill. Pas de vraie fuite.
---
## Synthèse causale (arbre de défaillance)
```
Impact business −42% ventes
└── Table ps_ac_temp_sales saturée (1.2M lignes)
├── Trigger désactivé (raison inconnue) [P1-4]
└── Script ac_cleanup_temp_tables.py en échec
├── Lock wait timeout [P0-2, P0-3]
│ ├── Full table scan (index absent sur created_at) [P0-3]
│ └── autocommit=True → transaction monolithique [P0-2]
└── Timezone drift → purge imprécise [P1-1]
└── Config 30j ≠ script 7j → rétention indéfinie [P1-2]
```
---
## Ordre de traitement recommandé
| Ordre | Action | Priorité |
|-------|--------|----------|
| 1 | Décider rétention cible avec Alexandre (30j ou 7j ?) | P1-2 bloquant |
| 2 | Corriger script : batching 1000 lignes, `UTC_TIMESTAMP()`, lire config DB | P0-2, P1-1, P1-3 |
| 3 | Ajouter `INDEX idx_cleanup (created_at)` sur la table | P0-3 |
| 4 | Auditer le trigger avant réactivation | P1-4 |
| 5 | Ajouter monitoring `INNODB_TRX` dans `ac_analytics.py` | P2-1 |
| 6 | Migrer `CHAR(36)` → `BINARY(16)` en release planifiée | P2-2 |
---
**Score MIN_PASS :** 6/6 identifiés.
**Biais "Complétude de Turing" signalé :** 1 occurrence détectée et évitée (index composite inutile, P0-3).