{"index.json":{"schemaVersion":2,"app":"DBAtlas","updated":"2026-07-04","engines":"engines.json","rosetta":["rosetta/sessions.json","rosetta/storage.json","rosetta/performance.json","rosetta/backup.json","rosetta/security.json","rosetta/maintenance.json","rosetta/schema.json","rosetta/replication.json","rosetta/connect.json","rosetta/objects.json","rosetta/datamove.json","rosetta/monitoring.json","rosetta/automation.json","rosetta/lifecycle.json","rosetta/migration.json","rosetta/tuning.json","rosetta/tuning-adv.json","rosetta/advanced-features.json","rosetta/sql-syntax.json"],"cloud":["cloud/cloud-ops-1.json","cloud/cloud-ops-2.json"],"guide":{"bestPractices":["guide/best-practices.json","guide/best-practices-2.json","guide/best-practices-3.json","guide/best-practices-4.json"],"glossary":["guide/glossary.json","guide/glossary-2.json","guide/glossary-3.json"]},"rosettaOverlays":["rosetta/overlay-elasticsearch.json"],"types":"types/types-functions.json","errors":["errors/oracle.json","errors/postgres.json","errors/mysql.json","errors/mssql.json","errors/mongodb.json","errors/redis.json","errors/elasticsearch.json","errors/oracle-more.json","errors/postgres-more.json","errors/mysql-more.json","errors/mssql-more.json","errors/mongodb-more.json","errors/redis-more.json","errors/elasticsearch-more.json","errors/oracle-more2.json","errors/postgres-more2.json","errors/mysql-more2.json","errors/mssql-more2.json","errors/mongodb-more2.json","errors/redis-more2.json","errors/elasticsearch-more2.json","errors/oracle-more3.json","errors/postgres-more3.json","errors/mysql-more3.json","errors/mssql-more3.json","errors/mongodb-more3.json","errors/redis-more3.json","errors/elasticsearch-more3.json","errors/oracle-more4.json","errors/postgres-more4.json","errors/mysql-more4.json","errors/mssql-more4.json","errors/mongodb-more4.json","errors/redis-more4.json","errors/elasticsearch-more4.json"],"health":["health/oracle.json","health/postgres.json","health/mysql.json","health/mssql.json","health/mongodb.json","health/redis.json","health/elasticsearch.json"],"recipes":["recipes/oracle.json","recipes/oracle-ha.json","recipes/oracle-ops.json","recipes/postgres.json","recipes/postgres-ha.json","recipes/postgres-ops.json","recipes/mysql.json","recipes/mysql-ext.json","recipes/mssql.json","recipes/mssql-ext.json","recipes/mongodb.json","recipes/mongodb-ext.json","recipes/redis.json","recipes/redis-ext.json","recipes/elasticsearch.json","recipes/elasticsearch-ext.json"],"playbooks":["playbooks/playbooks.json","playbooks/hadr.json","playbooks/security.json","playbooks/cloud.json","playbooks/automation.json"],"solver":["solver/slow-query.json","solver/deadlock.json","solver/disk-full.json","solver/connection-storm.json","solver/replication-lag.json","solver/ora-01555.json","solver/backup-restore.json","solver/upgrade-wizard.json","solver/migration-wizard.json","solver/high-cpu.json","solver/memory-pressure.json","solver/install-setup.json","solver/corruption-recovery.json","solver/failover-drill.json","solver/security-incident.json","solver/audit-setup.json","solver/decide-ha.json","solver/decide-isolation.json","solver/decide-sqlnosql.json","solver/decide-shard-replica.json","solver/decide-backup.json","solver/decide-index.json","solver/decide-partition.json","solver/decide-pooling.json","solver/decide-cache.json","solver/decide-datatype.json","solver/decide-capacity.json","solver/slow-write.json","solver/io-bottleneck.json","solver/undo-temp-pressure.json","solver/long-transaction.json","solver/plan-regression.json","solver/replica-resync.json","solver/cache-hit-low.json","solver/arch-oltp.json","solver/arch-dwh.json","solver/arch-microservice.json","solver/arch-eventdriven.json","solver/arch-multiregion.json","solver/arch-highscale.json"],"reference":{"params":["reference/params-oracle.json","reference/params-postgres.json","reference/params-mysql.json","reference/params-mssql.json","reference/params-mongodb.json","reference/params-redis.json","reference/params-elasticsearch.json"],"waits":["reference/waits-oracle.json","reference/waits-postgres.json","reference/waits-mysql.json","reference/waits-mssql.json","reference/waits-mongodb.json","reference/waits-redis.json","reference/waits-elasticsearch.json"],"alerts":["reference/alerts-oracle.json","reference/alerts-postgres.json","reference/alerts-mysql.json","reference/alerts-mssql.json","reference/alerts-mongodb.json","reference/alerts-redis.json","reference/alerts-elasticsearch.json"]},"install":"install/index.json"},"engines.json":{"schemaVersion":1,"updated":"2026-07-03","engines":[{"id":"oracle","name":"Oracle","full":"Oracle Database","color":"#C74634","family":"sql","cli":"sqlplus","docs":"https://docs.oracle.com/en/database/"},{"id":"postgres","name":"PostgreSQL","full":"PostgreSQL","color":"#3461B5","family":"sql","cli":"psql","docs":"https://www.postgresql.org/docs/"},{"id":"mysql","name":"MySQL","full":"MySQL / MariaDB","color":"#00758F","family":"sql","cli":"mysql","docs":"https://dev.mysql.com/doc/"},{"id":"mssql","name":"SQL Server","full":"Microsoft SQL Server","color":"#6E3FC4","family":"sql","cli":"sqlcmd","docs":"https://learn.microsoft.com/en-us/sql/"},{"id":"mongodb","name":"MongoDB","full":"MongoDB","color":"#2E7D32","family":"document","cli":"mongosh","docs":"https://www.mongodb.com/docs/"},{"id":"redis","name":"Redis","full":"Redis","color":"#B45309","family":"keyvalue","cli":"redis-cli","docs":"https://redis.io/docs/"},{"id":"elasticsearch","name":"Elasticsearch","full":"Elasticsearch","color":"#BE2A6E","family":"search","cli":"curl / Kibana","docs":"https://www.elastic.co/guide/"}]},"rosetta/sessions.json":{"category":"Sessions et activité","categoryId":"sessions","order":1,"concepts":[{"id":"active-sessions","title":"Afficher les sessions actives / requêtes en cours","aliases":["active sessions","running queries","who is connected","current activity","active session","running query","who is connected now","processlist","current op"],"engines":{"oracle":{"command":"SELECT sid, serial#, username, status, sql_id, machine, program\nFROM v$session\nWHERE type = 'USER' AND status = 'ACTIVE';","note":"v$session donne les sessions actuellement actives. Pour le texte SQL complet, joignez v$sql sur sql_id.","danger":"safe"},"postgres":{"command":"SELECT pid, usename, state, wait_event_type, query\nFROM pg_stat_activity\nWHERE state <> 'idle';","note":"state = 'active' affiche celles qui exécutent une requête à cet instant. Calculez les durées à partir de backend_start / query_start.","danger":"safe"},"mysql":{"command":"SELECT id, user, host, db, command, time, state, info\nFROM information_schema.processlist\nWHERE command <> 'Sleep';\n-- Shortcut: SHOW FULL PROCESSLIST;","note":"Sur MySQL 8 / MariaDB, performance_schema.processlist pose moins de verrous.","danger":"safe"},"mssql":{"command":"SELECT r.session_id, s.login_name, r.status, r.command,\n       r.cpu_time, r.total_elapsed_time, t.text AS sql_text\nFROM sys.dm_exec_requests r\nJOIN sys.dm_exec_sessions s ON r.session_id = s.session_id\nCROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE s.is_user_process = 1;","note":"Pour un aperçu rapide : EXEC sp_who2 'active';","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"active\": true })","note":"Pour filtrer les opérations longues : db.currentOp({ active: true, secs_running: { $gt: 5 } })","danger":"safe"},"redis":{"command":"CLIENT LIST\n# For a summary: INFO clients","note":"Redis étant mono-thread, ce sont les clients connectés qui sont listés et non les « requêtes en cours ». Pour les commandes lentes, utilisez SLOWLOG GET.","danger":"safe"}}},{"id":"blocking-sessions","title":"Trouver les sessions bloquantes / en attente de verrou","aliases":["blocking","locks","lock wait","deadlock source","blocking session","lock waiting","who is blocking","who is blocking me","blocked","lock holder","what am I waiting on"],"engines":{"oracle":{"command":"SELECT blocking_session, sid, serial#, username,\n       sql_id, seconds_in_wait, event\nFROM v$session\nWHERE blocking_session IS NOT NULL;","note":"Une session dont blocking_session est renseigné est bloquée par le SID indiqué dans la valeur blocking_session.","danger":"safe"},"postgres":{"command":"SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by,\n       wait_event, query\nFROM pg_stat_activity\nWHERE cardinality(pg_blocking_pids(pid)) > 0;","note":"Le tableau blocked_by donne les PID qui bloquent ce pid.","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.innodb_lock_waits\\G\n-- MySQL 8: SELECT * FROM performance_schema.data_lock_waits;","note":"Le schéma sys présente sous forme lisible les trx_id / threads en attente et bloquants.","danger":"safe"},"mssql":{"command":"SELECT r.session_id, r.blocking_session_id,\n       r.wait_type, r.wait_time, r.wait_resource\nFROM sys.dm_exec_requests r\nWHERE r.blocking_session_id <> 0;","note":"Les requêtes avec blocking_session_id <> 0 sont bloquées. Une requête récursive permet de suivre la chaîne.","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"waitingForLock\": true })","note":"WiredTiger verrouille au niveau document/collection. Vous pouvez terminer les opérations longues avec killOp.","danger":"safe"},"redis":{"command":"# Not applicable: Redis commands run atomically one at a time; there are no row locks.\n# Closest diagnostic: SLOWLOG GET 10","note":"Le blocage provient généralement d'une seule commande longue (par ex. KEYS *, un SORT volumineux) ; SLOWLOG les affiche.","danger":"safe"}}},{"id":"kill-session","title":"Terminer (kill) une session / requête","aliases":["kill session","terminate","cancel query","terminate session","cancel the query","kill query","killop"],"engines":{"oracle":{"command":"ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;\n-- To cancel only the running statement (18c+):\n-- ALTER SYSTEM CANCEL SQL 'sid,serial#';","note":"Récupérez les valeurs sid et serial# dans v$session. IMMEDIATE lance le rollback immédiatement.","danger":"danger"},"postgres":{"command":"-- Cancel only the running query (the session stays):\nSELECT pg_cancel_backend(pid);\n-- Close the session entirely:\nSELECT pg_terminate_backend(pid);","note":"Essayez d'abord pg_cancel_backend ; si cela ne fonctionne pas, utilisez pg_terminate_backend.","danger":"danger"},"mysql":{"command":"-- Kill only the query:\nKILL QUERY <id>;\n-- Close the connection entirely:\nKILL CONNECTION <id>;","note":"La valeur <id> correspond à la colonne Id du processlist.","danger":"danger"},"mssql":{"command":"KILL <session_id>;\n-- To see rollback progress:\n-- KILL <session_id> WITH STATUSONLY;","note":"KILL annule la transaction ouverte ; pour les transactions volumineuses, le rollback peut être long.","danger":"danger"},"mongodb":{"command":"db.killOp(<opid>)","note":"Prenez la valeur opid dans le champ opid de la sortie de db.currentOp().","danger":"danger"},"redis":{"command":"CLIENT KILL ID <client-id>\n# or by address: CLIENT KILL ADDR 10.0.0.5:6379","note":"Utilisez CLIENT LIST pour obtenir le client-id. Une commande unique déjà en cours ne peut pas être interrompue (sauf un script Lua : SCRIPT KILL) ; il faut attendre sa fin. Pour libérer un client en attente sur une commande bloquante telle que BLPOP/XREAD BLOCK sans couper sa connexion, utilisez CLIENT UNBLOCK <client-id> [TIMEOUT|ERROR]. CLIENT UNPAUSE reprend uniquement les clients mis en pause avec CLIENT PAUSE.","danger":"caution"}}},{"id":"connection-count","title":"Afficher le nombre de connexions et la limite de connexions","aliases":["connections","max connections","connection limit","connection count","how many connections","is the connection pool full"],"engines":{"oracle":{"command":"SELECT COUNT(*) AS current_sessions FROM v$session WHERE type = 'USER';\nSELECT name, value FROM v$parameter WHERE name IN ('processes','sessions');","note":"Les limites sont les paramètres processes/sessions. Pour le taux d'utilisation, vous pouvez aussi consulter la vue resource_limit.","danger":"safe"},"postgres":{"command":"SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';\nSHOW max_connections;","note":"pg_stat_activity incluant aussi les processus d'arrière-plan, seules les connexions client (backend_type = 'client backend') sont comptées. Les emplacements superuser_reserved_connections sont réservés aux administrateurs.","danger":"safe"},"mysql":{"command":"SHOW STATUS LIKE 'Threads_connected';\nSHOW VARIABLES LIKE 'max_connections';","note":"Le statut Max_used_connections indique le pic.","danger":"safe"},"mssql":{"command":"SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process = 1;\nSELECT @@MAX_CONNECTIONS AS configured_max;","note":"@@MAX_CONNECTIONS est le plafond théorique ; la limite pratique dépend de la mémoire et des worker threads.","danger":"safe"},"mongodb":{"command":"db.serverStatus().connections","note":"Renvoie les champs current, available et totalCreated. La limite est définie par net.maxIncomingConnections.","danger":"safe"},"redis":{"command":"INFO clients\nCONFIG GET maxclients","note":"connected_clients est le nombre actuel ; maxclients est le plafond configuré (10000 par défaut).","danger":"safe"}}}]},"rosetta/storage.json":{"category":"Espace et stockage","categoryId":"storage","order":2,"concepts":[{"id":"database-size","title":"Afficher la taille totale de la base","aliases":["database size","db size","database size","how much space does it use","disk usage","total size"],"engines":{"oracle":{"command":"SELECT ROUND(SUM(bytes)/1024/1024/1024, 2) AS total_gb\nFROM dba_data_files;","note":"Pour les segments utilisés uniquement, faites la somme de dba_segments. Ajoutez dba_temp_files pour TEMP.","danger":"safe"},"postgres":{"command":"SELECT pg_size_pretty(pg_database_size(current_database()));","note":"Pour toutes les bases : SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY 2 DESC;","danger":"safe"},"mysql":{"command":"SELECT table_schema AS db,\n       ROUND(SUM(data_length + index_length)/1024/1024, 1) AS mb\nFROM information_schema.tables\nGROUP BY table_schema\nORDER BY mb DESC;","note":"Pour InnoDB, cette valeur est une estimation ; pour la taille exacte des fichiers, vérifiez ibdata/les fichiers au niveau de l'OS.","danger":"safe"},"mssql":{"command":"EXEC sp_spaceused;\n-- All databases:\nSELECT DB_NAME(database_id) AS db,\n       CAST(SUM(size) * 8.0 / 1024 AS DECIMAL(10,1)) AS mb\nFROM sys.master_files\nGROUP BY database_id;","note":"La colonne size est exprimée en pages de 8 KB ; * 8 / 1024 la convertit en MB.","danger":"safe"},"mongodb":{"command":"db.stats(1024*1024)\n// returns the dataSize, storageSize, indexSize fields in MB","note":"storageSize est la taille compressée sur disque ; dataSize est la taille logique non compressée.","danger":"safe"},"redis":{"command":"INFO memory\n# the used_memory_human line gives the total size in memory\nDBSIZE  # number of keys","note":"Redis fonctionne en mémoire ; la « taille » désigne l'utilisation de la RAM. La taille du fichier RDB/AOF correspond à l'état persisté sur disque.","danger":"safe"}}},{"id":"largest-tables","title":"Lister les plus grandes tables / collections","aliases":["largest tables","biggest tables","top tables by size","largest table","tables using the most space","bigkeys"],"engines":{"oracle":{"command":"SELECT segment_name, segment_type,\n       ROUND(bytes/1024/1024, 1) AS mb\nFROM dba_segments\nWHERE segment_type IN ('TABLE','TABLE PARTITION')\nORDER BY bytes DESC\nFETCH FIRST 10 ROWS ONLY;","note":"Supprimez le filtre segment_type pour voir aussi les index.","danger":"safe"},"postgres":{"command":"SELECT relname AS table,\n       pg_size_pretty(pg_total_relation_size(relid)) AS total\nFROM pg_catalog.pg_statio_user_tables\nORDER BY pg_total_relation_size(relid) DESC\nLIMIT 10;","note":"pg_total_relation_size est la somme table + index + TOAST ; utilisez pg_relation_size pour la table seule.","danger":"safe"},"mysql":{"command":"SELECT table_name,\n       ROUND((data_length + index_length)/1024/1024, 1) AS mb\nFROM information_schema.tables\nWHERE table_schema = DATABASE()\nORDER BY (data_length + index_length) DESC\nLIMIT 10;","note":"Pour un schéma précis, utilisez WHERE table_schema = 'name'.","danger":"safe"},"mssql":{"command":"SELECT TOP 10 OBJECT_NAME(object_id) AS table_name,\n       SUM(reserved_page_count) * 8 / 1024 AS mb\nFROM sys.dm_db_partition_stats\nGROUP BY object_id\nORDER BY mb DESC;","note":"reserved_page_count couvre toutes les pages réservées (données + index).","danger":"safe"},"mongodb":{"command":"db.getCollectionNames()\n  .map(c => ({ name: c, mb: +(db[c].stats().storageSize/1048576).toFixed(1) }))\n  .sort((a, b) => b.mb - a.mb)\n  .slice(0, 10)","note":"storageSize est la taille compressée sur disque. Le coût de stats() reste faible même sur de grandes collections.","danger":"safe"},"redis":{"command":"redis-cli --bigkeys\n# To rank by memory (bytes): redis-cli --memkeys","note":"Les deux parcourent tout le keyspace avec SCAN (sans bloquer le serveur, mais en ajoutant de la charge) ; la différence tient à la métrique : --bigkeys signale les plus grandes clés par nombre d'éléments/longueur pour chaque type, --memkeys par consommation mémoire via MEMORY USAGE. Pour une seule clé, utilisez MEMORY USAGE <key>.","danger":"caution"}}},{"id":"free-space","title":"Vérifier l'espace libre / le taux d'occupation","aliases":["free space","disk full","free space","usage","space left","tablespace usage","disk is full"],"engines":{"oracle":{"command":"SELECT df.tablespace_name,\n       ROUND(SUM(df.bytes)/1024/1024) AS total_mb,\n       ROUND(NVL(fs.free, 0)/1024/1024) AS free_mb,\n       ROUND((1 - NVL(fs.free,0)/SUM(df.bytes)) * 100, 1) AS pct_used\nFROM dba_data_files df\nLEFT JOIN (SELECT tablespace_name, SUM(bytes) free\n           FROM dba_free_space GROUP BY tablespace_name) fs\n  ON df.tablespace_name = fs.tablespace_name\nGROUP BY df.tablespace_name, fs.free;","note":"Pour les fichiers en AUTOEXTEND, la limite réelle est maxbytes ; vérifiez dba_data_files.maxbytes pour MAXSIZE.","danger":"safe"},"postgres":{"command":"-- Datafile usage is at the OS level (df -h). Tablespace sizes from inside the DB:\nSELECT spcname, pg_size_pretty(pg_tablespace_size(spcname))\nFROM pg_tablespace;","note":"PostgreSQL s'appuie sur l'OS pour l'espace disque libre ; la supervision au niveau du système d'exploitation (df) est indispensable pour les seuils critiques.","danger":"safe"},"mysql":{"command":"SELECT table_schema,\n       ROUND(SUM(data_free)/1024/1024, 1) AS reclaimable_mb\nFROM information_schema.tables\nGROUP BY table_schema;","note":"data_free est l'espace récupérable (fragmenté) dans la table ; l'espace disque libre se situe au niveau de l'OS.","danger":"safe"},"mssql":{"command":"SELECT name AS file_name,\n       size/128.0 AS size_mb,\n       size/128.0 - CAST(FILEPROPERTY(name,'SpaceUsed') AS int)/128.0 AS free_mb\nFROM sys.database_files;","note":"SpaceUsed est en pages ; diviser par 128 donne des MB (pages de 8 KB).","danger":"safe"},"mongodb":{"command":"var s = db.stats();\n({ fsTotalGB: +(s.fsTotalSize/1073741824).toFixed(1),\n   fsUsedGB:  +(s.fsUsedSize/1073741824).toFixed(1) })","note":"Avec WiredTiger, fsTotalSize / fsUsedSize donnent la taille totale et utilisée du disque au niveau de l'OS.","danger":"safe"},"redis":{"command":"INFO memory\nCONFIG GET maxmemory\n# Usage = used_memory / maxmemory","note":"Si maxmemory vaut 0, il n'y a pas de limite et, lorsque la RAM est saturée, l'OOM killer de l'OS intervient. maxmemory-policy détermine le comportement d'éviction.","danger":"safe"}}},{"id":"row-count","title":"Obtenir le nombre de lignes / documents (rapide)","aliases":["row count","count rows","row count","how many records","document count","how many rows"],"engines":{"oracle":{"command":"-- Statistics-based (fast, estimated):\nSELECT num_rows FROM dba_tables WHERE table_name = 'CUSTOMER';\n-- Exact: SELECT COUNT(*) FROM customer;","note":"num_rows reflète la dernière exécution d'ANALYZE/DBMS_STATS ; utilisez COUNT(*) pour une valeur exacte.","danger":"safe"},"postgres":{"command":"-- Estimated (instant):\nSELECT reltuples::bigint FROM pg_class WHERE relname = 'customer';\n-- Exact: SELECT count(*) FROM customer;","note":"reltuples est une estimation datant du dernier ANALYZE/VACUUM ; un COUNT complet est coûteux sur les grandes tables.","danger":"safe"},"mysql":{"command":"-- Estimated (InnoDB):\nSELECT table_rows FROM information_schema.tables\nWHERE table_name = 'customer';\n-- Exact: SELECT COUNT(*) FROM customer;","note":"Dans InnoDB, table_rows est une estimation et peut s'écarter jusqu'à 50 %.","danger":"safe"},"mssql":{"command":"SELECT SUM(row_count) AS rows\nFROM sys.dm_db_partition_stats\nWHERE object_id = OBJECT_ID('dbo.customer')\n  AND index_id IN (0, 1);","note":"index_id 0 (heap) ou 1 (clustered) donne le nombre de lignes ; bien plus rapide qu'un parcours COUNT(*).","danger":"safe"},"mongodb":{"command":"// Fast (from metadata):\ndb.customer.estimatedDocumentCount()\n// Exact (can count with a filter): db.customer.countDocuments({})","note":"estimatedDocumentCount lit les métadonnées (instantané) ; countDocuments effectue un parcours réel.","danger":"safe"},"redis":{"command":"DBSIZE\n# Specific pattern: count roughly with SCAN (do not use KEYS * in production)","note":"DBSIZE renvoie en O(1) le nombre total de clés de la base sélectionnée.","danger":"safe"}}}]},"rosetta/performance.json":{"category":"Performances","categoryId":"performance","order":3,"concepts":[{"id":"top-slow-queries","title":"Trouver les requêtes les plus lentes / coûteuses","aliases":["slow queries","top queries","slowest queries","top resource consumers","expensive queries","slowlog"],"engines":{"oracle":{"command":"SELECT sql_id,\n       ROUND(elapsed_time/1e6, 1) AS elapsed_sec,\n       executions,\n       ROUND(elapsed_time/1e6/NULLIF(executions,0), 3) AS sec_per_exec,\n       sql_text\nFROM v$sql\nORDER BY elapsed_time DESC\nFETCH FIRST 10 ROWS ONLY;","note":"Pour l'analyse historique, utilisez AWR : dba_hist_sqlstat. Avec le sql_id, DBMS_XPLAN.DISPLAY_CURSOR affiche le plan.","danger":"safe"},"postgres":{"command":"SELECT calls,\n       ROUND(total_exec_time::numeric, 1) AS total_ms,\n       ROUND(mean_exec_time::numeric, 2) AS mean_ms,\n       query\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;","note":"Nécessite l'extension pg_stat_statements (shared_preload_libraries). Réinitialisez avec pg_stat_statements_reset().","danger":"safe"},"mysql":{"command":"SELECT digest_text,\n       count_star AS calls,\n       ROUND(sum_timer_wait/1e12, 2) AS total_sec,\n       ROUND(avg_timer_wait/1e9, 2) AS avg_ms\nFROM performance_schema.events_statements_summary_by_digest\nORDER BY sum_timer_wait DESC\nLIMIT 10;","note":"Alternative : le slow query log avec slow_query_log = ON et long_query_time.","danger":"safe"},"mssql":{"command":"SELECT TOP 10\n       qs.execution_count,\n       qs.total_elapsed_time/1000 AS total_ms,\n       qs.total_elapsed_time/qs.execution_count/1000 AS avg_ms,\n       SUBSTRING(t.text, 1, 200) AS sql_text\nFROM sys.dm_exec_query_stats qs\nCROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t\nORDER BY qs.total_elapsed_time DESC;","note":"Si Query Store est activé, utilisez sys.query_store_runtime_stats pour une analyse historique plus riche.","danger":"safe"},"mongodb":{"command":"// Enable the profiler for operations over 100ms:\ndb.setProfilingLevel(1, { slowms: 100 })\n// Then read the slowest ones:\ndb.system.profile.find().sort({ millis: -1 }).limit(10)","note":"Le profiler ajoute une surcharge en écriture ; désactivez-le avec db.setProfilingLevel(0) après le diagnostic.","danger":"caution"},"redis":{"command":"SLOWLOG GET 10\n# Set the threshold (microseconds): CONFIG SET slowlog-log-slower-than 10000","note":"slowlog-log-slower-than est en microsecondes (10000 = 10 ms). Videz-le avec SLOWLOG RESET.","danger":"safe"}}},{"id":"explain-plan","title":"Obtenir le plan d'exécution d'une requête (EXPLAIN)","aliases":["explain","execution plan","query plan","explain plan","show plan","explain analyze","why slow"],"engines":{"oracle":{"command":"EXPLAIN PLAN FOR\nSELECT * FROM customer WHERE city = 'Ankara';\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);\n-- For the actual executed plan:\n-- SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));","note":"EXPLAIN PLAN donne le plan estimé ; pour les nombres de lignes réels, utilisez DISPLAY_CURSOR + STATISTICS_LEVEL=ALL.","danger":"safe"},"postgres":{"command":"EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT * FROM customer WHERE city = 'Ankara';","note":"ANALYZE exécute RÉELLEMENT la requête (prudence avec INSERT/UPDATE/DELETE ; testez dans une transaction puis ROLLBACK).","danger":"caution"},"mysql":{"command":"EXPLAIN ANALYZE\nSELECT * FROM customer WHERE city = 'Ankara';\n-- Estimate only (does not execute): EXPLAIN FORMAT=JSON SELECT ...;","note":"EXPLAIN ANALYZE (MySQL 8.0.18+) exécute la requête ; FORMAT=JSON donne les détails sans l'exécuter.","danger":"caution"},"mssql":{"command":"SET STATISTICS PROFILE ON;\nSELECT * FROM dbo.customer WHERE city = 'Ankara';\nSET STATISTICS PROFILE OFF;\n-- Estimated plan only (does not execute):\n-- SET SHOWPLAN_XML ON;","note":"Dans SSMS, « Include Actual Execution Plan » (Ctrl+M) affiche le plan graphique.","danger":"caution"},"mongodb":{"command":"db.customer.find({ city: \"Ankara\" }).explain(\"executionStats\")","note":"executionStats indique le nombre réel de documents renvoyés et l'utilisation des index (IXSCAN vs COLLSCAN).","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no query planner.\n# The complexity of each command is documented (e.g. GET O(1), SORT O(N+M*log(M))).","note":"Les performances dépendent de la complexité des commandes et de la taille des clés/structures de données ; mesurez-les avec SLOWLOG.","danger":"safe"}}},{"id":"unused-indexes","title":"Détecter les index inutilisés","aliases":["unused indexes","unused index","redundant index","index usage","drop index candidate"],"engines":{"oracle":{"command":"-- Start monitoring:\nALTER INDEX customer_city_idx MONITORING USAGE;\n-- Check after a while (12c+, all schemas):\nSELECT owner, index_name, used FROM dba_object_usage;","note":"Si USED = NO, l'index n'a pas été utilisé pendant la période de surveillance. Désactivez la surveillance avec NOMONITORING USAGE. L'ancienne v$object_usage n'affiche que les index de l'utilisateur de la session et est dépréciée depuis 12c. En 12.2+, l'utilisation des index est suivie automatiquement dans DBA_INDEX_USAGE.","danger":"safe"},"postgres":{"command":"SELECT relname AS table, indexrelname AS index, idx_scan\nFROM pg_stat_user_indexes\nWHERE idx_scan = 0\nORDER BY relname;","note":"idx_scan = 0 signifie que l'index n'a pas été utilisé depuis la dernière réinitialisation des statistiques ; ne supprimez pas les index UNIQUE/PK.","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.schema_unused_indexes;","note":"performance_schema doit être activé et le serveur doit tourner depuis un certain temps ; les contraintes UNIQUE sont exclues.","danger":"safe"},"mssql":{"command":"SELECT OBJECT_NAME(i.object_id) AS table_name, i.name AS index_name\nFROM sys.indexes i\nLEFT JOIN sys.dm_db_index_usage_stats s\n  ON i.object_id = s.object_id AND i.index_id = s.index_id\n  AND s.database_id = DB_ID()\nWHERE i.type_desc = 'NONCLUSTERED'\n  AND COALESCE(s.user_seeks + s.user_scans + s.user_lookups, 0) = 0;","note":"usage_stats est réinitialisé au redémarrage du serveur ; prévoyez une fenêtre d'observation suffisante.","danger":"safe"},"mongodb":{"command":"db.customer.aggregate([{ $indexStats: {} }])\n// Indexes with low/0 accesses.ops are candidates","note":"accesses.ops compte les accès depuis l'horodatage accesses.since ; l'index _id ne peut pas être supprimé.","danger":"safe"},"redis":{"command":"# Not applicable: core Redis has no secondary indexes.\n# If the RediSearch module is used, check statistics with FT.INFO <index>.","note":"Le modèle de données étant clé-valeur, il n'existe pas de notion d'index traditionnelle.","danger":"safe"}}},{"id":"cache-hit-ratio","title":"Mesurer le taux de succès du cache","aliases":["cache hit ratio","buffer cache","cache hit","hit ratio","buffer pool hit"],"engines":{"oracle":{"command":"SELECT metric_name, ROUND(value, 2) AS pct\nFROM v$sysmetric\nWHERE metric_name = 'Buffer Cache Hit Ratio'\n  AND intsize_csec = (SELECT MAX(intsize_csec) FROM v$sysmetric);","note":"Un taux faible n'est pas toujours mauvais ; interprétez-le dans le contexte de la charge de travail, pas isolément.","danger":"safe"},"postgres":{"command":"SELECT ROUND(sum(blks_hit) * 100.0 / NULLIF(sum(blks_hit) + sum(blks_read), 0), 2) AS cache_hit_pct\nFROM pg_stat_database;","note":"La cible est généralement 99 %+. S'il est faible, revoyez shared_buffers et le cache de l'OS.","danger":"safe"},"mysql":{"command":"SELECT ROUND((1 - (\n    (SELECT variable_value FROM performance_schema.global_status WHERE variable_name='Innodb_buffer_pool_reads') /\n    (SELECT variable_value FROM performance_schema.global_status WHERE variable_name='Innodb_buffer_pool_read_requests')\n  )) * 100, 2) AS buffer_pool_hit_pct;","note":"reads correspond aux lectures disque, read_requests au total des lectures. S'il est faible, augmentez innodb_buffer_pool_size.","danger":"safe"},"mssql":{"command":"SELECT ROUND(\n  100.0 * (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Buffer cache hit ratio')\n        / NULLIF((SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Buffer cache hit ratio base'), 0), 2) AS pct;","note":"Sur un SQL Server moderne, Page Life Expectancy (PLE) est un indicateur plus pertinent de la pression mémoire.","danger":"safe"},"mongodb":{"command":"var c = db.serverStatus().wiredTiger.cache;\n({ configuredMB: +(c['maximum bytes configured']/1048576).toFixed(0),\n   inCacheMB:    +(c['bytes currently in the cache']/1048576).toFixed(0),\n   readIntoCache: c['pages read into cache'],\n   evictedPages:  c['unmodified pages evicted'] })","note":"Des valeurs élevées de « pages read into cache » et d'éviction indiquent que le cache WiredTiger est trop petit.","danger":"safe"},"redis":{"command":"INFO stats\n# keyspace_hits / (keyspace_hits + keyspace_misses) = hit ratio","note":"Un taux de succès durablement faible indique qu'il faut revoir les réglages TTL/éviction ou le schéma d'accès.","danger":"safe"}}}]},"rosetta/backup.json":{"category":"Sauvegarde et restauration","categoryId":"backup","order":4,"concepts":[{"id":"logical-dump","title":"Effectuer une sauvegarde logique (dump)","aliases":["backup","dump","export","take backup","logical dump","logical backup","pg_dump","mysqldump","mongodump"],"engines":{"oracle":{"command":"expdp system/password DIRECTORY=dpump_dir \\\n  SCHEMAS=hr DUMPFILE=hr_%U.dmp LOGFILE=hr_exp.log PARALLEL=4","note":"Utilise Data Pump. L'objet DIRECTORY doit être défini au préalable avec CREATE DIRECTORY. Utilisez RMAN pour les sauvegardes physiques.","danger":"safe"},"postgres":{"command":"pg_dump -U postgres -h localhost -Fc -f db.dump mydb\n# -Fc = compressed custom format (restored with pg_restore)","note":"-Fc permet des restaurations flexibles (tables sélectives/parallèle). Utilisez pg_dumpall pour tout le cluster (rôles compris).","danger":"safe"},"mysql":{"command":"mysqldump -u root -p --single-transaction \\\n  --routines --triggers --events mydb > db.sql","note":"--single-transaction donne une sauvegarde cohérente sans verrou sur InnoDB. Options parallèles pour les gros volumes : mydumper/myloader ou MySQL Shell util.dumpInstance()/dumpSchemas() (mysqlpump a été déprécié en 8.0.34 et supprimé en 8.4).","danger":"safe"},"mssql":{"command":"BACKUP DATABASE [MyDB]\nTO DISK = N'C:\\backup\\MyDB_full.bak'\nWITH COMPRESSION, CHECKSUM, INIT;","note":"INIT écrase le fichier existant ; utilisez NOINIT pour ajouter. Pour les sauvegardes de journal, utilisez BACKUP LOG (en mode de récupération FULL).","danger":"safe"},"mongodb":{"command":"mongodump --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --db=mydb","note":"Sur un cluster shardé, passez par mongos avec le balancer arrêté pour garantir la cohérence.","danger":"safe"},"redis":{"command":"BGSAVE\n# Takes an RDB snapshot in the background (dump.rdb).\n# Non-blocking copy: redis-cli --rdb /backup/dump.rdb","note":"BGSAVE s'exécute en arrière-plan via fork ; SAVE bloque le serveur (à ne pas utiliser en production). Avec AOF activé, moins de données sont perdues.","danger":"safe"}}},{"id":"restore-dump","title":"Restaurer depuis une sauvegarde","aliases":["restore","import","restore backup","restore from backup","recover","pg_restore","mongorestore"],"engines":{"oracle":{"command":"impdp system/password DIRECTORY=dpump_dir \\\n  SCHEMAS=hr DUMPFILE=hr_%U.dmp LOGFILE=hr_imp.log","note":"Utilisez REMAP_SCHEMA pour charger dans un autre schéma et REMAP_TABLESPACE pour un autre tablespace.","danger":"danger"},"postgres":{"command":"# custom format:\npg_restore -U postgres -d target_db --clean --if-exists db.dump\n# plain SQL: psql -U postgres -d target_db -f db.sql","note":"--clean supprime les objets existants. Utilisez -j <n> pour une restauration parallèle.","danger":"danger"},"mysql":{"command":"mysql -u root -p target_db < db.sql","note":"La base cible doit déjà exister (CREATE DATABASE). Pour les gros fichiers, la commande source est préférable.","danger":"danger"},"mssql":{"command":"RESTORE DATABASE [MyDB]\nFROM DISK = N'C:\\backup\\MyDB_full.bak'\nWITH REPLACE, RECOVERY;","note":"Pour une restauration à un instant donné, restaurez la sauvegarde complète WITH NORECOVERY, puis appliquez les sauvegardes de journal avec STOPAT.","danger":"danger"},"mongodb":{"command":"mongorestore --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --drop","note":"--drop supprime les collections cibles avant la restauration. Utilisez --nsInclude pour une restauration sélective.","danger":"danger"},"redis":{"command":"# 1) Stop Redis   2) Copy dump.rdb to the 'dir' location\n# 3) Start Redis (the RDB is loaded automatically at startup)\nredis-cli CONFIG GET dir   # find the target directory","note":"Avec AOF activé, c'est l'AOF qui est chargé et non le RDB ; pour restaurer depuis le RDB, démarrez avec appendonly no ou reconstruisez l'AOF.","danger":"danger"}}},{"id":"last-backup-status","title":"Vérifier l'état / l'heure de la dernière sauvegarde","aliases":["last backup","backup status","when was the last backup","backup state","backup history","rpo"],"engines":{"oracle":{"command":"SELECT session_key, input_type, status,\n       start_time, end_time,\n       output_bytes_display\nFROM v$rman_backup_job_details\nORDER BY start_time DESC\nFETCH FIRST 10 ROWS ONLY;","note":"L'historique RMAN est aussi consultable avec RMAN> LIST BACKUP SUMMARY.","danger":"safe"},"postgres":{"command":"-- WAL archiving health (for physical backup/PITR):\nSELECT last_archived_wal, last_archived_time,\n       failed_count, last_failed_time\nFROM pg_stat_archiver;","note":"Les sauvegardes logiques (pg_dump) ne sont pas enregistrées dans la base ; consultez les journaux de l'outil/du job de sauvegarde.","danger":"safe"},"mysql":{"command":"-- There is no built-in backup record; check the backup tool's log.\n-- For the binlog-based PITR window:\nSHOW BINARY LOGS;","note":"SHOW BINARY LOGS affiche les fichiers binlog disponibles (la fenêtre PITR) ; l'historique des sauvegardes se trouve dans le journal de l'outil (Xtrabackup, etc.).","danger":"safe"},"mssql":{"command":"SELECT database_name,\n       MAX(backup_finish_date) AS last_backup,\n       CASE type WHEN 'D' THEN 'Full' WHEN 'I' THEN 'Diff'\n                 WHEN 'L' THEN 'Log' END AS backup_type\nFROM msdb.dbo.backupset\nGROUP BY database_name, type\nORDER BY database_name;","note":"msdb.dbo.backupset enregistre chaque sauvegarde effectuée par SQL Server.","danger":"safe"},"mongodb":{"command":"// There is no built-in backup record. PITR window = oplog duration:\nrs.printReplicationInfo()  // oplog first/last event times","note":"La fenêtre de l'oplog détermine jusqu'où vous pouvez remonter en PITR depuis le replica set ; l'historique des sauvegardes se trouve dans le journal de l'outil.","danger":"safe"},"redis":{"command":"INFO persistence\n# check the rdb_last_save_time (epoch), rdb_last_bgsave_status,\n# aof_last_bgrewrite_status lines","note":"rdb_last_save_time donne l'heure de la dernière sauvegarde RDB réussie (epoch Unix) ; rdb_changes_since_last_save indique combien d'écritures ne sont pas encore sauvegardées.","danger":"safe"}}}]},"rosetta/security.json":{"category":"Utilisateurs et privilèges","categoryId":"security","order":5,"concepts":[{"id":"list-users","title":"Lister les utilisateurs / rôles","aliases":["list users","show users","list users","roles","who has access","accounts"],"engines":{"oracle":{"command":"SELECT username, account_status, default_tablespace, created\nFROM dba_users\nORDER BY username;","note":"account_status indique les états LOCKED/EXPIRED. Pour les rôles, utilisez dba_roles.","danger":"safe"},"postgres":{"command":"SELECT rolname, rolsuper, rolcanlogin, rolvaliduntil\nFROM pg_roles\nORDER BY rolname;\n-- Shortcut (psql): \\du","note":"Dans PostgreSQL, un utilisateur est un rôle doté de l'attribut LOGIN ; rolcanlogin permet de le distinguer.","danger":"safe"},"mysql":{"command":"SELECT user, host, account_locked, password_expired\nFROM mysql.user\nORDER BY user;","note":"Dans MySQL, une identité est un couple utilisateur + hôte ('app'@'%' et 'app'@'localhost' sont différents).","danger":"safe"},"mssql":{"command":"-- Server logins:\nSELECT name, type_desc, is_disabled FROM sys.server_principals\nWHERE type IN ('S','U','G');\n-- Database users:\nSELECT name, type_desc FROM sys.database_principals\nWHERE type IN ('S','U','G');","note":"Un login est de niveau serveur, un user de niveau base ; les deux sont associés par SID.","danger":"safe"},"mongodb":{"command":"// Users in the current database:\ndb.getUsers()\n// All users in the system: use admin; db.system.users.find({}, {user:1, db:1, roles:1})","note":"Les utilisateurs sont définis dans la base où ils ont été créés (authenticationDatabase).","danger":"safe"},"redis":{"command":"ACL LIST\n# Names only: ACL USERS\n# Your own identity: ACL WHOAMI","note":"Le système ACL de Redis 6+. Auparavant, il n'existait qu'un utilisateur « default » et requirepass.","danger":"safe"}}},{"id":"create-user-grant","title":"Créer un utilisateur et accorder des privilèges","aliases":["create user","grant","add user","create user","grant privilege","grant permission","new user"],"engines":{"oracle":{"command":"CREATE USER app IDENTIFIED BY \"Strong#Pass1\";\nGRANT CREATE SESSION TO app;\nGRANT SELECT, INSERT, UPDATE ON hr.employee TO app;","note":"Sans CREATE SESSION, l'utilisateur ne peut pas se connecter. Pour un quota : ALTER USER app QUOTA 100M ON users;","danger":"caution"},"postgres":{"command":"CREATE ROLE app LOGIN PASSWORD 'Strong#Pass1';\nGRANT CONNECT ON DATABASE mydb TO app;\nGRANT USAGE ON SCHEMA public TO app;\nGRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app;","note":"Pour les tables créées à l'avenir : ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app;","danger":"caution"},"mysql":{"command":"CREATE USER 'app'@'%' IDENTIFIED BY 'Strong#Pass1';\nGRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app'@'%';","note":"'%' autorise les connexions depuis n'importe quel hôte ; pour la sécurité, indiquez si possible une IP/un hôte précis. CREATE USER/GRANT prennent effet immédiatement ; FLUSH PRIVILEGES n'est nécessaire que si du DML a été exécuté directement sur les tables de droits.","danger":"caution"},"mssql":{"command":"CREATE LOGIN app WITH PASSWORD = 'Strong#Pass1';\nUSE mydb;\nCREATE USER app FOR LOGIN app;\nGRANT SELECT, INSERT, UPDATE ON SCHEMA::dbo TO app;","note":"Un login est valable pour tout le serveur ; un user est créé séparément dans chaque base. Pour un rôle : ALTER ROLE db_datareader ADD MEMBER app;","danger":"caution"},"mongodb":{"command":"db.createUser({\n  user: \"app\",\n  pwd: \"Strong#Pass1\",\n  roles: [ { role: \"readWrite\", db: \"mydb\" } ]\n})","note":"La base dans laquelle l'utilisateur est créé devient sa base d'authentification. Rôles intégrés : read, readWrite, dbAdmin, clusterAdmin...","danger":"caution"},"redis":{"command":"ACL SETUSER app on >Strong#Pass1 ~app:* +@read +@write\n# To persist: ACL SAVE if an aclfile is defined, otherwise CONFIG REWRITE (users are written to redis.conf)","note":"on = activé, >password ajoute un mot de passe, ~app:* est le motif de clés, +@read/+@write sont des catégories de commandes. Sans persistance, tout est perdu au redémarrage : ACL SAVE ne fonctionne que si un aclfile est configuré et renvoie une erreur sinon ; dans ce cas, utilisez CONFIG REWRITE.","danger":"caution"}}},{"id":"show-privileges","title":"Afficher les privilèges d'un utilisateur","aliases":["show grants","privileges","show privileges","who can access what","permissions","roles of user"],"engines":{"oracle":{"command":"SELECT privilege FROM dba_sys_privs WHERE grantee = 'APP'\nUNION ALL SELECT granted_role FROM dba_role_privs WHERE grantee = 'APP'\nUNION ALL SELECT privilege||' ON '||owner||'.'||table_name\n          FROM dba_tab_privs WHERE grantee = 'APP';","note":"Le grantee est stocké en majuscules. Pour les privilèges indirects accordés via des rôles, parcourez aussi role_role_privs.","danger":"safe"},"postgres":{"command":"SELECT grantee, table_schema, table_name, privilege_type\nFROM information_schema.role_table_grants\nWHERE grantee = 'app';\n-- Role memberships (direct): \\drg app (PG16+; earlier \\du app)","note":"Les appartenances aux rôles (héritage) sont stockées dans pg_auth_members ; \\du/\\drg n'affichent que les appartenances directes, utilisez pg_has_role('app', '<role>', 'MEMBER') pour l'appartenance indirecte.","danger":"safe"},"mysql":{"command":"SHOW GRANTS FOR 'app'@'%';","note":"Pour les rôles (MySQL 8), faites d'abord SET ROLE ALL, ou utilisez SHOW GRANTS FOR 'app'@'%' USING 'role_name';","danger":"safe"},"mssql":{"command":"SELECT dp.name AS principal, p.permission_name,\n       p.state_desc, o.name AS object\nFROM sys.database_permissions p\nJOIN sys.database_principals dp ON p.grantee_principal_id = dp.principal_id\nLEFT JOIN sys.objects o ON p.major_id = o.object_id\nWHERE dp.name = 'app';","note":"Pour les permissions accordées via des rôles, vérifiez aussi les appartenances dans sys.database_role_members.","danger":"safe"},"mongodb":{"command":"db.getUser(\"app\", { showPrivileges: true, showCredentials: false })","note":"showPrivileges liste aussi les privilèges développés (effectifs) des rôles.","danger":"safe"},"redis":{"command":"ACL GETUSER app","note":"Renvoie les commandes autorisées, les motifs de clés (~), les canaux pub/sub (&) et les hachages de mots de passe.","danger":"safe"}}},{"id":"change-password","title":"Modifier le mot de passe d'un utilisateur","aliases":["change password","reset password","change password","reset password","alter password"],"engines":{"oracle":{"command":"ALTER USER app IDENTIFIED BY \"New#Pass2\";","note":"Pour déverrouiller un compte verrouillé : ALTER USER app ACCOUNT UNLOCK; Si le mot de passe a expiré, la même commande le réinitialise.","danger":"caution"},"postgres":{"command":"ALTER ROLE app WITH PASSWORD 'New#Pass2';","note":"Pour que le mot de passe n'apparaisse pas dans les journaux, la méta-commande \\password app (psql) le hache côté client.","danger":"caution"},"mysql":{"command":"ALTER USER 'app'@'%' IDENTIFIED BY 'New#Pass2';","note":"Veillez à choisir le bon hôte ; 'app'@'localhost' est un compte distinct. ALTER USER prend effet immédiatement ; FLUSH PRIVILEGES n'est pas nécessaire.","danger":"caution"},"mssql":{"command":"ALTER LOGIN app WITH PASSWORD = 'New#Pass2';\n-- For a user changing their own password: add OLD_PASSWORD","note":"MUST_CHANGE peut imposer un changement à la première connexion : WITH PASSWORD='...' MUST_CHANGE, CHECK_POLICY=ON.","danger":"caution"},"mongodb":{"command":"db.changeUserPassword(\"app\", \"New#Pass2\")","note":"Assurez-vous d'être passé sur la base d'authentification de l'utilisateur (use mydb).","danger":"caution"},"redis":{"command":"# Clear old passwords and set the new one:\nACL SETUSER app resetpass >New#Pass2\n# Persistence: ACL SAVE if an aclfile is defined, otherwise CONFIG REWRITE","note":"resetpass supprime tous les mots de passe existants ; > en ajoute seulement un. Pour la persistance, exécutez ACL SAVE si un aclfile est utilisé, ou CONFIG REWRITE si les utilisateurs sont définis dans redis.conf (ACL SAVE renvoie une erreur sans aclfile).","danger":"caution"}}}]},"rosetta/maintenance.json":{"category":"Maintenance et administration","categoryId":"maintenance","order":6,"concepts":[{"id":"update-statistics","title":"Mettre à jour les statistiques (pour l'optimiseur)","aliases":["update statistics","analyze","update stats","gather stats","optimizer stats","analyze for planner"],"engines":{"oracle":{"command":"EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEE');\n-- Whole schema: EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR');","note":"La tâche AUTO les collecte généralement la nuit ; collectez-les manuellement en cas de problème. ESTIMATE_PERCENT=>AUTO_SAMPLE_SIZE est recommandé.","danger":"safe"},"postgres":{"command":"ANALYZE customer;\n-- Both cleanup and analyze: VACUUM ANALYZE customer;","note":"Autovacuum le fait automatiquement ; un ANALYZE manuel après un chargement massif de données donne des plans exacts.","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer;","note":"Pose un bref verrou en lecture sur InnoDB ; sur les grandes tables, exécutez-le en période de faible activité.","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Whole database: EXEC sp_updatestats;","note":"FULLSCAN est le plus précis mais le plus coûteux ; sur les grandes tables, l'échantillonnage (SAMPLE) peut être préférable.","danger":"safe"},"mongodb":{"command":"// There are no manual statistics; the planner measures at runtime.\n// To clear a stale plan cache:\ndb.customer.getPlanCache().clear()","note":"Au lieu d'une estimation de coût, MongoDB effectue des essais réels (plan racing) ; les statistiques manuelles ne sont pas nécessaires.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no query optimizer / statistics.","note":"La complexité des commandes est fixe ; les performances dépendent du choix de la structure de données.","danger":"safe"}}},{"id":"rebuild-index","title":"Reconstruire / réorganiser un index","aliases":["rebuild index","reindex","refresh index","reorganize","index maintenance","defrag index"],"engines":{"oracle":{"command":"ALTER INDEX customer_city_idx REBUILD ONLINE;","note":"ONLINE reconstruit sans verrouiller la table (Enterprise Edition). Pour le déplacer vers un autre tablespace : ... REBUILD TABLESPACE new_ts.","danger":"caution"},"postgres":{"command":"REINDEX INDEX CONCURRENTLY customer_city_idx;\n-- Whole table: REINDEX TABLE CONCURRENTLY customer;","note":"CONCURRENTLY (PG 12+) reconstruit sans bloquer les écritures sur la table ; plus lent mais sûr.","danger":"caution"},"mysql":{"command":"-- Rebuild InnoDB indexes + table:\nALTER TABLE customer ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;\n-- Alternative: OPTIMIZE TABLE customer;","note":"LOCK=NONE autorise les écritures concurrentes ; INPLACE n'est pas possible pour tous les ALTER.","danger":"caution"},"mssql":{"command":"-- Rebuild for high fragmentation:\nALTER INDEX ALL ON dbo.customer REBUILD WITH (ONLINE = ON);\n-- Lighter option for low fragmentation:\n-- ALTER INDEX ALL ON dbo.customer REORGANIZE;","note":"Seuil courant : 5-30 % de fragmentation REORGANIZE, >30 % REBUILD. ONLINE est une fonctionnalité Enterprise.","danger":"caution"},"mongodb":{"command":"db.customer.reIndex()","note":"Reconstruit tous les index au premier plan et verrouille la collection. En 6.0+, il ne s'exécute que sur un standalone (interdit sur les membres d'un replica set) ; sur un replica set, reconstruisez un index unique avec dropIndex + createIndex, nœud par nœud (rolling).","danger":"danger"},"redis":{"command":"# Not applicable (core). If RediSearch is used:\n# FT.ALTER / rebuilding is done per the module documentation.","note":"Le modèle clé-valeur n'a pas de maintenance d'index traditionnelle.","danger":"safe"}}},{"id":"reclaim-space","title":"Récupérer l'espace libre (shrink / vacuum / compact)","aliases":["reclaim space","vacuum","shrink","compact","free up space","clean up bloat","bloat"],"engines":{"oracle":{"command":"ALTER TABLE customer ENABLE ROW MOVEMENT;\nALTER TABLE customer SHRINK SPACE;","note":"SHRINK abaisse le high water mark (HWM). Pour réduire aussi les index, utilisez SHRINK SPACE CASCADE.","danger":"caution"},"postgres":{"command":"-- Online, light locking (does not return space to the OS):\nVACUUM (VERBOSE, ANALYZE) customer;\n-- Return space to the OS (locks the table!):\n-- VACUUM FULL customer;","note":"VACUUM FULL pose un AccessExclusiveLock et réécrit la table ; ne l'exécutez pas aux heures de pointe. Alternative : pg_repack.","danger":"caution"},"mysql":{"command":"OPTIMIZE TABLE customer;","note":"Sur InnoDB, reconstruit la table et restitue l'espace data_free ; long sur les grandes tables.","danger":"caution"},"mssql":{"command":"-- Generally NOT RECOMMENDED (causes fragmentation). If really needed:\nDBCC SHRINKFILE (N'MyDB_data', 1024);  -- target 1024 MB","note":"Réduire un fichier de données provoque une fragmentation des index ; faites ensuite un REBUILD des index. Pour le journal, effectuez d'abord une sauvegarde.","danger":"danger"},"mongodb":{"command":"db.runCommand({ compact: \"customer\" })","note":"Sur WiredTiger, tente de restituer l'espace libre au système d'exploitation (pas toujours possible). En 4.4+, ne bloque pas les lectures/écritures, seulement certaines opérations DDL sur la même collection ; comme il génère beaucoup d'IO, il est plus sûr de l'exécuter sur un secondaire et dans une fenêtre de maintenance.","danger":"danger"},"redis":{"command":"MEMORY PURGE\n# Tries to return jemalloc fragmentation to the OS","note":"Le réglage activedefrag yes effectue une défragmentation automatique en ligne ; MEMORY DOCTOR fournit des recommandations.","danger":"caution"}}},{"id":"version-uptime","title":"Afficher la version et l'uptime","aliases":["version","uptime","server version","how long has it been running","server version","days since startup"],"engines":{"oracle":{"command":"SELECT banner_full FROM v$version;\nSELECT startup_time, ROUND(SYSDATE - startup_time, 1) AS up_days\nFROM v$instance;","note":"v$instance fournit aussi des détails sur l'instance comme status et host_name.","danger":"safe"},"postgres":{"command":"SELECT version();\nSELECT pg_postmaster_start_time() AS started,\n       now() - pg_postmaster_start_time() AS uptime;","note":"server_version_num est pratique pour une comparaison numérique (par ex. 160002 = 16.2).","danger":"safe"},"mysql":{"command":"SELECT VERSION();\nSHOW GLOBAL STATUS LIKE 'Uptime';  -- seconds","note":"Uptime est en secondes ; divisez par 86400 pour obtenir des jours.","danger":"safe"},"mssql":{"command":"SELECT @@VERSION;\nSELECT sqlserver_start_time,\n       DATEDIFF(DAY, sqlserver_start_time, GETDATE()) AS up_days\nFROM sys.dm_os_sys_info;","note":"SERVERPROPERTY('ProductVersion') et ('Edition') donnent les détails de version/édition.","danger":"safe"},"mongodb":{"command":"db.version()\ndb.serverStatus().uptime   // seconds","note":"serverStatus() inclut aussi les informations host, process et connections.","danger":"safe"},"redis":{"command":"INFO server\n# redis_version and uptime_in_seconds / uptime_in_days lines","note":"INFO server fournit aussi les informations os, run_id et tcp_port.","danger":"safe"}}},{"id":"show-config-param","title":"Consulter un paramètre de configuration","aliases":["show config","parameter","show setting","config parameter","setting","memory setting","variable"],"engines":{"oracle":{"command":"SHOW PARAMETER sga_target;\n-- or: SELECT name, value, isdefault FROM v$parameter WHERE name = 'sga_target';","note":"Pour le modifier : ALTER SYSTEM SET ... SCOPE=BOTH; (certains uniquement avec SCOPE=SPFILE + redémarrage).","danger":"safe"},"postgres":{"command":"SHOW shared_buffers;\n-- Details: SELECT name, setting, unit, context FROM pg_settings WHERE name = 'shared_buffers';","note":"La colonne context indique si la modification nécessite un redémarrage ou un rechargement (SELECT pg_reload_conf()).","danger":"safe"},"mysql":{"command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","note":"Attention à la distinction GLOBAL vs SESSION : SHOW GLOBAL VARIABLES affiche la valeur à l'échelle du serveur.","danger":"safe"},"mssql":{"command":"SELECT name, value, value_in_use, is_dynamic\nFROM sys.configurations\nWHERE name LIKE '%max server memory%';","note":"Pour le modifier : sp_configure + RECONFIGURE ; les options avec is_dynamic=0 nécessitent un redémarrage.","danger":"safe"},"mongodb":{"command":"db.adminCommand({ getParameter: 1, cursorTimeoutMillis: 1 })\n// All startup options: db.adminCommand({ getCmdLineOpts: 1 })","note":"Utilisez setParameter pour ceux modifiables à chaud ; les réglages persistants se trouvent dans le fichier mongod.conf.","danger":"safe"},"redis":{"command":"CONFIG GET maxmemory\n# Wildcard: CONFIG GET max*","note":"Utilisez CONFIG SET pour modifier à chaud ; CONFIG REWRITE pour la persistance (écrit dans redis.conf).","danger":"safe"}}}]},"rosetta/schema.json":{"category":"Schéma et objets","categoryId":"schema","order":7,"concepts":[{"id":"list-tables","title":"Lister les tables / collections","aliases":["list tables","show tables","list all tables","table list","list collections","collections","which tables exist"],"engines":{"oracle":{"command":"SELECT table_name FROM user_tables ORDER BY table_name;\n-- All schemas: SELECT owner, table_name FROM dba_tables ORDER BY owner, table_name;","note":"user_tables renvoie les tables du schéma courant, all_tables celles qui sont accessibles.","danger":"safe"},"postgres":{"command":"SELECT table_schema, table_name\nFROM information_schema.tables\nWHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog','information_schema')\nORDER BY 1, 2;\n-- psql shortcut: \\dt *.*","note":"Pour le schéma public uniquement : WHERE table_schema = 'public'.","danger":"safe"},"mysql":{"command":"SHOW TABLES;\n-- Another schema: SHOW TABLES FROM mydb;","note":"Pour plus de détails : SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();","danger":"safe"},"mssql":{"command":"SELECT s.name AS schema_name, t.name AS table_name\nFROM sys.tables t\nJOIN sys.schemas s ON t.schema_id = s.schema_id\nORDER BY s.name, t.name;","note":"Raccourci : SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';","danger":"safe"},"mongodb":{"command":"show collections\n// or: db.getCollectionNames()","note":"Pour voir aussi les collections système : db.getCollectionInfos().","danger":"safe"},"redis":{"command":"SCAN 0 MATCH * COUNT 100\n# Continue with SCAN <cursor> ... using the returned cursor","note":"Redis n'a pas de « tables » ; ce sont les clés qui sont listées. N'utilisez JAMAIS KEYS * en production (il bloque) ; SCAN fonctionne par curseur.","danger":"caution"}}},{"id":"describe-table","title":"Afficher les colonnes / la structure d'une table (describe)","aliases":["describe","desc table","columns","table columns","table structure","show schema","field types","fields"],"engines":{"oracle":{"command":"SELECT column_name, data_type, data_length, nullable\nFROM user_tab_columns\nWHERE table_name = 'EMPLOYEE'\nORDER BY column_id;\n-- Shortcut (SQL*Plus): DESC employee","note":"Les noms de tables sont stockés en majuscules (s'ils ont été créés sans guillemets).","danger":"safe"},"postgres":{"command":"SELECT column_name, data_type, is_nullable, column_default\nFROM information_schema.columns\nWHERE table_name = 'customer'\nORDER BY ordinal_position;\n-- psql shortcut: \\d customer","note":"\\d affiche aussi les index, contraintes et clés étrangères.","danger":"safe"},"mysql":{"command":"DESCRIBE customer;\n-- or: SHOW COLUMNS FROM customer;","note":"Pour la définition complète, utilisez SHOW CREATE TABLE customer; (voir la notion « Générer le DDL » ci-dessous).","danger":"safe"},"mssql":{"command":"SELECT c.name, t.name AS type, c.max_length, c.is_nullable\nFROM sys.columns c\nJOIN sys.types t ON c.user_type_id = t.user_type_id\nWHERE c.object_id = OBJECT_ID('dbo.customer')\nORDER BY c.column_id;\n-- Shortcut: EXEC sp_help 'dbo.customer';","note":"sp_help liste les index et contraintes en plus des colonnes.","danger":"safe"},"mongodb":{"command":"// The schema is flexible; infer it from a sample document:\ndb.customer.findOne()\n// For field coverage: db.customer.aggregate([{ $sample: { size: 100 } }])","note":"MongoDB n'a pas de schéma fixe ; les champs peuvent varier d'un document à l'autre. Si un validateur $jsonSchema est défini, il est visible avec db.getCollectionInfos.","danger":"safe"},"redis":{"command":"TYPE mykey        # string | list | set | hash | zset | stream\nOBJECT ENCODING mykey   # internal encoding","note":"Il n'y a pas de schéma fixe ; le type et l'encodage de chaque clé sont interrogés séparément. Utilisez HGETALL pour les champs d'un hash.","danger":"safe"}}},{"id":"list-indexes","title":"Lister les index d'une table","aliases":["list indexes","show indexes","show table indexes","which indexes","table indexes","getindexes"],"engines":{"oracle":{"command":"SELECT index_name, column_name, column_position\nFROM user_ind_columns\nWHERE table_name = 'EMPLOYEE'\nORDER BY index_name, column_position;","note":"Joignez avec user_indexes pour le type/l'unicité de l'index.","danger":"safe"},"postgres":{"command":"SELECT indexname, indexdef\nFROM pg_indexes\nWHERE tablename = 'customer';","note":"indexdef donne l'instruction CREATE INDEX complète ; \\d customer dans psql les liste aussi.","danger":"safe"},"mysql":{"command":"SHOW INDEX FROM customer;","note":"Les colonnes Non_unique, Seq_in_index et Cardinality indiquent la structure et la sélectivité de l'index.","danger":"safe"},"mssql":{"command":"EXEC sp_helpindex 'dbo.customer';\n-- Details: join sys.indexes + sys.index_columns","note":"sp_helpindex renvoie le nom, le type et les colonnes de l'index ; utilisez les vues sys pour les colonnes filtrées/incluses.","danger":"safe"},"mongodb":{"command":"db.customer.getIndexes()","note":"Renvoie les clés de chaque index et ses options telles que l'unicité et TTL/partial.","danger":"safe"},"redis":{"command":"# Not applicable (core). If the RediSearch module is present:\n# FT._LIST        (all indexes)\n# FT.INFO idx     (details of one index)","note":"Le modèle clé-valeur n'a pas d'index secondaires ; ils n'existent qu'avec des modules comme RediSearch/RedisJSON.","danger":"safe"}}},{"id":"create-index","title":"Créer un index","aliases":["create index","add index","build index","new index","createindex"],"engines":{"oracle":{"command":"CREATE INDEX customer_city_idx ON customer(city) ONLINE;","note":"ONLINE le crée sans verrouiller la table. Utilisez CREATE UNIQUE INDEX pour un index unique ; (a, b) pour un index composite.","danger":"caution"},"postgres":{"command":"CREATE INDEX CONCURRENTLY customer_city_idx ON customer(city);","note":"CONCURRENTLY ne bloque pas les écritures sur la table, mais ne peut pas s'exécuter dans un bloc de transaction et est plus lent.","danger":"caution"},"mysql":{"command":"CREATE INDEX customer_city_idx ON customer(city);\n-- Lock-free: ... ALGORITHM=INPLACE, LOCK=NONE;","note":"InnoDB ajoute la plupart des index en ligne ; sur les grandes tables, autorisez les écritures avec LOCK=NONE.","danger":"caution"},"mssql":{"command":"CREATE NONCLUSTERED INDEX IX_customer_city\nON dbo.customer (city)\nWITH (ONLINE = ON);","note":"ONLINE est une fonctionnalité Enterprise. Pour les colonnes couvrantes, utilisez INCLUDE (col1, col2).","danger":"caution"},"mongodb":{"command":"db.customer.createIndex({ city: 1 })\n// Unique: { city: 1 }, { unique: true }\n// Built in the background without blocking (default behavior in 4.2+)","note":"1 correspond à l'ordre croissant, -1 à l'ordre décroissant. Sur les grandes collections, construire d'abord sur un secondaire réduit la charge.","danger":"caution"},"redis":{"command":"# Not applicable (core). If RediSearch is present:\n# FT.CREATE idx ON HASH PREFIX 1 customer: SCHEMA city TAG","note":"Les index secondaires n'existent qu'avec le module RediSearch ; dans Redis de base, l'accès se fait par clé.","danger":"safe"}}},{"id":"show-ddl","title":"Générer le DDL d'un objet (instruction CREATE)","aliases":["show ddl","get ddl","create statement","generate ddl","table definition","show create table","generate script"],"engines":{"oracle":{"command":"SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEE', 'HR') FROM dual;","note":"Utilisez 'INDEX' pour les index, 'PACKAGE' pour les packages, etc. Évitez la troncature des sorties longues avec SET LONG 100000.","danger":"safe"},"postgres":{"command":"-- There is no single function in SQL; from the shell:\npg_dump -t public.customer --schema-only mydb","note":"Dans psql, \\d customer affiche la structure de façon lisible ; pour le DDL complet, utilisez pg_dump --schema-only.","danger":"safe"},"mysql":{"command":"SHOW CREATE TABLE customer\\G","note":"Renvoie le CREATE TABLE complet (colonnes, index, moteur, charset) en une seule commande.","danger":"safe"},"mssql":{"command":"-- T-SQL has no single built-in command. Options:\n-- 1) SSMS: right-click > Script Table as > CREATE To\n-- 2) EXEC sp_help 'dbo.customer';  (structure summary)\n-- 3) mssql-scripter tool (command line)","note":"Pour le corps d'une procédure/vue : SELECT OBJECT_DEFINITION(OBJECT_ID('dbo.usp_name'));","danger":"safe"},"mongodb":{"command":"// There is no DDL (schemaless). View collection options/validator:\ndb.getCollectionInfos({ name: \"customer\" })","note":"Si options.validator ($jsonSchema) est défini, c'est la seule « définition de schéma » ; les index sont récupérés séparément avec getIndexes().","danger":"safe"},"redis":{"command":"# Not applicable: there is no schema/DDL concept.","note":"Les structures de données sont définies dans le code applicatif ; la persistance se fait dans les fichiers RDB/AOF.","danger":"safe"}}}]},"rosetta/replication.json":{"category":"Réplication et HA","categoryId":"replication","order":8,"concepts":[{"id":"replication-status","title":"Afficher l'état et le retard de la réplication","aliases":["replication status","replica lag","replication status","lag","slave lag","standby lag","how far behind"],"engines":{"oracle":{"command":"-- Role and mode:\nSELECT database_role, open_mode FROM v$database;\n-- Data Guard lag:\nSELECT name, value, unit FROM v$dataguard_stats\nWHERE name LIKE '%lag%';","note":"L'apply lag et le transport lag sont les principales métriques de retard de Data Guard ; exécutez ceci sur le standby.","danger":"safe"},"postgres":{"command":"-- On the PRIMARY:\nSELECT client_addr, state, sync_state,\n       write_lag, flush_lag, replay_lag\nFROM pg_stat_replication;\n-- On the STANDBY:\nSELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;","note":"replay_lag est le retard de rejeu ; sync_state = 'sync' indique un réplica synchrone.","danger":"safe"},"mysql":{"command":"SHOW REPLICA STATUS\\G\n-- Before 8.0.22: SHOW SLAVE STATUS\\G","note":"Seconds_Behind_Source donne le retard ; Replica_IO_Running et Replica_SQL_Running doivent valoir 'Yes'. SHOW SLAVE STATUS est un alias en 8.0.22–8.3 et a été supprimé en 8.4.","danger":"safe"},"mssql":{"command":"SELECT ar.replica_server_name, drs.synchronization_state_desc,\n       drs.log_send_queue_size, drs.redo_queue_size\nFROM sys.dm_hadr_database_replica_states drs\nJOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id;","note":"Pour Always On. Un redo_queue_size élevé signifie que le réplica secondaire prend du retard.","danger":"safe"},"mongodb":{"command":"rs.printSecondaryReplicationInfo()\n// Detailed status: rs.status()","note":"Indique de combien de secondes chaque secondaire est en retard sur le primaire ; les valeurs optimeDate sont comparées.","danger":"safe"},"redis":{"command":"INFO replication\n# Check the role, connected_slaves, master_repl_offset,\n# and slaveN:...,offset=... lines","note":"La différence entre master_repl_offset et l'offset du slave donne le retard ; master_link_status doit valoir 'up'.","danger":"safe"}}},{"id":"list-replicas","title":"Lister les réplicas / nœuds du cluster","aliases":["list replicas","show replicas","list replicas","nodes","cluster members","slaves","standby list"],"engines":{"oracle":{"command":"-- Standby destinations:\nSELECT dest_id, destination, status, target\nFROM v$archive_dest\nWHERE status <> 'INACTIVE';\n-- RAC instances: SELECT inst_id, instance_name, status FROM gv$instance;","note":"La configuration Data Guard est aussi consultable avec DGMGRL> SHOW CONFIGURATION.","danger":"safe"},"postgres":{"command":"SELECT application_name, client_addr, state, sync_state\nFROM pg_stat_replication;","note":"Liste les réplicas connectés uniquement sur le PRIMARY ; chaque ligne est un standby.","danger":"safe"},"mysql":{"command":"SHOW REPLICAS;\n-- Before 8.0.22: SHOW SLAVE HOSTS;","note":"À exécuter sur le serveur source ; affiche les réplicas ayant report-host défini. SHOW SLAVE HOSTS est un alias en 8.0.22–8.3 et a été supprimé en 8.4.","danger":"safe"},"mssql":{"command":"SELECT ar.replica_server_name, ars.role_desc,\n       ars.connected_state_desc, ars.synchronization_health_desc\nFROM sys.dm_hadr_availability_replica_states ars\nJOIN sys.availability_replicas ar ON ars.replica_id = ar.replica_id;","note":"role_desc distingue PRIMARY/SECONDARY ; synchronization_health_desc donne l'état de santé global.","danger":"safe"},"mongodb":{"command":"rs.status().members.map(m => ({ name: m.name, state: m.stateStr, health: m.health }))","note":"stateStr renvoie PRIMARY/SECONDARY/ARBITER ; health 1 signifie sain.","danger":"safe"},"redis":{"command":"INFO replication\n# If Sentinel is used:\n# SENTINEL replicas <master-name>","note":"Les lignes slave0, slave1... donnent l'ip/le port/l'offset de chaque réplica.","danger":"safe"}}},{"id":"promote-failover","title":"Promouvoir un réplica / basculer (promote)","aliases":["promote","failover","switchover","promote","role change","stepdown","make master","make primary"],"engines":{"oracle":{"command":"-- Data Guard broker (DGMGRL):\nSWITCHOVER TO 'standby_db';     -- planned, no data loss\n-- FAILOVER TO 'standby_db';     -- on loss of the primary, emergency","note":"SWITCHOVER est une transition de rôle planifiée ; FAILOVER s'effectue lorsque le primaire est injoignable et comporte un risque de perte de données.","danger":"danger"},"postgres":{"command":"-- Promote the standby to primary:\nSELECT pg_promote();\n-- or from the shell: pg_ctl promote -D /var/lib/pgsql/data","note":"Avant la promotion, assurez-vous que l'ancien primaire est réellement arrêté (risque de split-brain). Après la promotion, réintégrer l'ancien primaire comme réplica du nouveau primary nécessite pg_rewind ; pour cela, wal_log_hints = on ou les checksums de données doivent être activés.","danger":"danger"},"mysql":{"command":"-- On the replica (manual):\n-- 1) Stop receiving new events, wait until the received relay log is fully applied:\nSTOP REPLICA IO_THREAD;\nSELECT RECEIVED_TRANSACTION_SET FROM performance_schema.replication_connection_status;\nSELECT @@GLOBAL.gtid_executed;  -- the received set must be fully contained here\n-- 2) Remove replication and enable writes:\nSTOP REPLICA;\nRESET REPLICA ALL;\nSET GLOBAL read_only = OFF;  -- also turns off super_read_only\n-- 3) Fence the old source against writes, then redirect the application.","note":"Si RESET REPLICA ALL est exécuté avant que le relay log soit entièrement appliqué, les transactions reçues mais non appliquées sont perdues. Les réplicas tournent généralement avec read_only/super_read_only activés ; les écritures ne sont acceptées qu'après leur désactivation. Si l'ancienne source est encore accessible en écriture, il existe un risque de split-brain. La syntaxe est 8.0.22+ (auparavant STOP SLAVE / RESET SLAVE ALL ; l'ancienne forme a été supprimée en 8.4). Pour un failover automatique et sûr en production, préférez InnoDB Cluster/Group Replication, Orchestrator ou MHA.","danger":"danger"},"mssql":{"command":"-- On the target secondary replica (synchronous):\nALTER AVAILABILITY GROUP [AG_Name] FAILOVER;\n-- Force it, with risk of data loss:\n-- ALTER AVAILABILITY GROUP [AG_Name] FORCE_FAILOVER_ALLOW_DATA_LOSS;","note":"Un failover planifié vers un réplica synchrone n'entraîne aucune perte de données ; FORCE n'est utilisé qu'en scénario de sinistre.","danger":"danger"},"mongodb":{"command":"// Trigger an election on the current primary:\nrs.stepDown(60)   // step down from primary for 60 s","note":"stepDown déclenche une nouvelle élection ; pour favoriser un nœud précis, ajustez les valeurs priority des membres.","danger":"danger"},"redis":{"command":"# Make the replica an independent primary:\nREPLICAOF NO ONE\n# With Sentinel (recommended): SENTINEL FAILOVER <master-name>","note":"Si vous utilisez Sentinel/Cluster, utilisez SENTINEL FAILOVER au lieu d'un REPLICAOF manuel ; sinon il existe un risque de split-brain.","danger":"danger"}}},{"id":"open-transactions","title":"Trouver les transactions ouvertes / longues","aliases":["open transactions","long running transaction","open transaction","long transaction","idle in transaction","oldest transaction","undo"],"engines":{"oracle":{"command":"SELECT s.sid, s.serial#, s.username,\n       t.start_time, t.used_ublk AS undo_blocks\nFROM v$transaction t\nJOIN v$session s ON t.ses_addr = s.saddr\nORDER BY t.start_time;","note":"Un used_ublk élevé signifie qu'une grosse transaction consomme beaucoup d'undo ; elle attend peut-être un commit/rollback.","danger":"safe"},"postgres":{"command":"SELECT pid, usename, state,\n       now() - xact_start AS xact_age, query\nFROM pg_stat_activity\nWHERE xact_start IS NOT NULL\nORDER BY xact_start;","note":"Les transactions longues à l'état 'idle in transaction' bloquent VACUUM et le nettoyage des tuples ; surveillez-les.","danger":"safe"},"mysql":{"command":"SELECT trx_id, trx_state, trx_started,\n       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_sec,\n       trx_mysql_thread_id\nFROM information_schema.innodb_trx\nORDER BY trx_started;","note":"Utilisez trx_mysql_thread_id pour faire la corrélation avec PROCESSLIST et faites un KILL si nécessaire.","danger":"safe"},"mssql":{"command":"DBCC OPENTRAN;\n-- Detailed:\nSELECT st.session_id, dt.database_transaction_begin_time\nFROM sys.dm_tran_database_transactions dt\nJOIN sys.dm_tran_session_transactions st ON st.transaction_id = dt.transaction_id\nWHERE dt.database_id = DB_ID()\nORDER BY dt.database_transaction_begin_time;","note":"Les transactions ouvertes longues empêchent la troncature du journal et font gonfler le fichier journal.","danger":"safe"},"mongodb":{"command":"db.adminCommand({\n  currentOp: 1,\n  \"transaction\": { $exists: true }\n})","note":"Les transactions multi-documents longues sont annulées automatiquement après transactionLifetimeLimitSeconds (60 s par défaut).","danger":"safe"},"redis":{"command":"# Not applicable: MULTI/EXEC transactions are short and atomic; they do not stay open.","note":"Les transactions Redis ne posent aucun verrou ; pour les commandes uniques longues, consultez SLOWLOG.","danger":"safe"}}}]},"rosetta/connect.json":{"category":"Connexion et CLI","categoryId":"connect","order":9,"concepts":[{"id":"connect-cli","title":"Se connecter en ligne de commande (CLI / chaîne de connexion)","aliases":["connect","connection string","connect","how do i connect","cli connect","login","dsn","uri"],"engines":{"oracle":{"command":"sqlplus app_user/password@//host:1521/service_name\n# As SYSDBA: sqlplus sys/password@//host:1521/orcl AS SYSDBA","note":"//host:port/service est le format « Easy Connect » ; sinon, utilisez un alias TNS de tnsnames.ora.","danger":"safe"},"postgres":{"command":"psql -h host -p 5432 -U postgres -d mydb\n# With a URI: psql \"postgresql://app_user:password@host:5432/mydb?sslmode=require\"","note":"Pour ne pas saisir le mot de passe en ligne de commande, utilisez un fichier ~/.pgpass ou la variable d'environnement PGPASSWORD.","danger":"safe"},"mysql":{"command":"mysql -h host -P 3306 -u root -p mydb\n# Require TLS: --ssl-mode=REQUIRED","note":"Passez -p seul (le mot de passe vous est demandé) ; la forme 'mysql -pPassword' expose le mot de passe dans la liste des processus.","danger":"safe"},"mssql":{"command":"sqlcmd -S host,1433 -U sa -P password -d mydb\n# Windows authentication: sqlcmd -S host -E -d mydb","note":"Avec le nouveau go-sqlcmd, les options -C (faire confiance au certificat serveur) et -N (chiffrement) peuvent être nécessaires pour TLS.","danger":"safe"},"mongodb":{"command":"mongosh \"mongodb://app_user:password@host:27017/mydb?authSource=admin\"\n# Replica set: ...mongodb://h1,h2,h3/db?replicaSet=rs0","note":"authSource est généralement la base où l'utilisateur est défini (le plus souvent admin).","danger":"safe"},"redis":{"command":"redis-cli -h host -p 6379 -a password\n# With TLS: redis-cli -h host -p 6379 --tls -a password\n# Redis 6+ ACL: redis-cli -h host --user app --pass password","note":"-a passe le mot de passe en ligne de commande (affiche un avertissement) ; pour une connexion sécurisée, utilisez la variable d'environnement REDISCLI_AUTH.","danger":"safe"}}},{"id":"list-databases","title":"Lister les bases / schémas","aliases":["list databases","show databases","list databases","which databases","list schemas","pdbs","show dbs"],"engines":{"oracle":{"command":"-- Schemas (users):\nSELECT username FROM dba_users ORDER BY username;\n-- Multitenant (CDB) PDBs:\nSELECT name, open_mode FROM v$pdbs;","note":"Dans Oracle, une « base de données » correspond à l'instance ; la séparation se fait au niveau du schéma (utilisateur) ou de la PDB dans une CDB.","danger":"safe"},"postgres":{"command":"SELECT datname FROM pg_database WHERE datistemplate = false;\n-- psql shortcut: \\l","note":"Pour les schémas : SELECT schema_name FROM information_schema.schemata;","danger":"safe"},"mysql":{"command":"SHOW DATABASES;","note":"Dans MySQL, « database » et « schema » sont synonymes.","danger":"safe"},"mssql":{"command":"SELECT name, state_desc FROM sys.databases ORDER BY name;","note":"state_desc indique l'état de la base, par exemple ONLINE/OFFLINE/RESTORING.","danger":"safe"},"mongodb":{"command":"show dbs\n// or: db.adminCommand({ listDatabases: 1 })","note":"Les bases vides (aucune donnée encore écrite) n'apparaissent pas dans la liste.","danger":"safe"},"redis":{"command":"INFO keyspace\n# db0:keys=..., db1:keys=... lines show the logical DBs in use","note":"Redis dispose de DB logiques numérotées (0-15 par défaut) ; en mode Cluster, seule la DB 0 est utilisée.","danger":"safe"}}},{"id":"switch-database","title":"Changer de base / schéma actif","aliases":["switch database","use database","change database","switch","change schema","change schema","use db"],"engines":{"oracle":{"command":"-- Change the default schema:\nALTER SESSION SET CURRENT_SCHEMA = hr;\n-- Switch to a PDB (inside a CDB):\nALTER SESSION SET CONTAINER = pdb1;","note":"CURRENT_SCHEMA ne change que la résolution des noms d'objets, pas les privilèges.","danger":"safe"},"postgres":{"command":"-- Switch to another DB inside psql (reconnects):\n\\c mydb\n-- Schema search path (same connection):\nSET search_path TO hr, public;","note":"Dans PostgreSQL, une connexion est liée à une seule base ; changer de DB implique de se reconnecter.","danger":"safe"},"mysql":{"command":"USE mydb;","note":"Change le schéma actif sans couper la connexion.","danger":"safe"},"mssql":{"command":"USE mydb;","note":"Change le contexte du batch ; les commandes suivantes s'exécutent dans cette base.","danger":"safe"},"mongodb":{"command":"use mydb","note":"Bascule même si la base n'existe pas ; elle est réellement créée à l'écriture du premier document.","danger":"safe"},"redis":{"command":"SELECT 1\n# Switches to a logical DB index between 0 and 15","note":"En mode Cluster, SELECT ne prend en charge que 0 ; utilisez des préfixes de clés au lieu d'une séparation en DB logiques.","danger":"safe"}}}]},"rosetta/objects.json":{"category":"Gestion des objets","categoryId":"objects","order":10,"concepts":[{"id":"create-partition","title":"Créer / scinder / supprimer une partition","aliases":["partition","partition","split","split partition","drop partition","add partition","remove partition"],"engines":{"oracle":{"command":"-- Add a RANGE partition\nALTER TABLE sales ADD PARTITION p2026 VALUES LESS THAN (TO_DATE('2027-01-01','YYYY-MM-DD'));\n\n-- Split an existing partition\nALTER TABLE sales SPLIT PARTITION pmax AT (TO_DATE('2027-01-01','YYYY-MM-DD')) INTO (PARTITION p2026, PARTITION pmax);\n\n-- Drop a partition\nALTER TABLE sales DROP PARTITION p2020 UPDATE INDEXES;","note":"Prend en charge le partitionnement RANGE/LIST/HASH. DROP PARTITION supprime toutes les lignes de cette partition ; avec UPDATE INDEXES, les index globaux restent valides. Le partitionnement par intervalle peut ajouter des partitions automatiquement.","danger":"caution","priv":"ALTER sur la table","minver":"Option Partitioning"},"postgres":{"command":"-- Declarative partitioning: parent table\nCREATE TABLE sales (id bigint, created_at date, amount numeric) PARTITION BY RANGE (created_at);\n\n-- Add a new partition\nCREATE TABLE sales_2026 PARTITION OF sales FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');\n\n-- Attach a standalone (existing) table as a partition\nCREATE TABLE sales_2027 (LIKE sales INCLUDING ALL);\nALTER TABLE sales ATTACH PARTITION sales_2027 FOR VALUES FROM ('2027-01-01') TO ('2028-01-01');\n\n-- Detach a partition (the table is not dropped; it remains a standalone table)\nALTER TABLE sales DETACH PARTITION sales_2026;","note":"PostgreSQL de base n'a pas de SPLIT/MERGE PARTITION (à PG17) ; pour scinder, faites un DETACH de la partition, créez deux partitions avec les nouvelles bornes, déplacez les données puis faites un ATTACH. DETACH ... CONCURRENTLY (PG14+) réduit la durée du verrou.","danger":"caution","priv":"propriétaire de la table","minver":"PG10"},"mysql":{"command":"-- Partitioned table\nCREATE TABLE sales (id BIGINT, created_at DATE) PARTITION BY RANGE (YEAR(created_at)) (\n  PARTITION p2024 VALUES LESS THAN (2025),\n  PARTITION p2025 VALUES LESS THAN (2026),\n  PARTITION pmax VALUES LESS THAN MAXVALUE\n);\n\n-- Add / split / drop a partition\nALTER TABLE sales REORGANIZE PARTITION pmax INTO (\n  PARTITION p2026 VALUES LESS THAN (2027),\n  PARTITION pmax VALUES LESS THAN MAXVALUE\n);\nALTER TABLE sales DROP PARTITION p2024;","note":"La colonne de partitionnement doit faire partie de chaque PRIMARY KEY / clé UNIQUE. Pour RANGE, la scission/fusion se fait avec REORGANIZE PARTITION. DROP PARTITION supprime aussi les lignes (RANGE/LIST).","danger":"caution","priv":"ALTER, DROP sur la table","minver":"5.7"},"mssql":{"command":"-- 1) Partition function and scheme\nCREATE PARTITION FUNCTION pf_date (date) AS RANGE RIGHT FOR VALUES ('2025-01-01','2026-01-01');\nCREATE PARTITION SCHEME ps_date AS PARTITION pf_date ALL TO ([PRIMARY]);\n\n-- 2) Add a new boundary (split)\nALTER PARTITION SCHEME ps_date NEXT USED [PRIMARY];\nALTER PARTITION FUNCTION pf_date() SPLIT RANGE ('2027-01-01');\n\n-- 3) Remove a boundary (merge)\nALTER PARTITION FUNCTION pf_date() MERGE RANGE ('2025-01-01');","note":"On définit une partition function + scheme, puis la table est créée sur ce scheme avec CREATE TABLE ... ON ps_date(created_at). Le filegroup NEXT USED doit être défini avant SPLIT. Pour retirer des données, basculez-les vers une table de staging avec SWITCH PARTITION.","danger":"caution","priv":"ALTER sur la table, ALTER ANY DATASPACE","minver":"2016 SP1 (toutes éditions)"},"mongodb":{"command":"// MongoDB uses sharding instead of table partitioning\nsh.shardCollection(\"db.sales\", { created_at: 1 });\n\n// Native time-series collection for time series\ndb.createCollection(\"measurements\", { timeseries: { timeField: \"ts\", granularity: \"hours\" } });","note":"Il n'y a pas de partitionnement au sens classique. Le découpage horizontal se fait par chunks avec le sharding ; pour les données temporelles, les collections time-series assurent un bucketing automatique.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no table/partition concept; scaling is done with Redis Cluster hash slots (16384).","note":"Redis Cluster répartit les clés sur 16384 hash slots ; il s'agit d'un découpage au niveau applicatif, pas d'un partitionnement de table. Il n'existe pas de commande de partition manuelle.","danger":"safe"},"elasticsearch":{"command":"# Time-based automatic splitting (rollover) with an ILM policy + data stream\nPUT _ilm/policy/logs-policy\n{ \"policy\": { \"phases\": { \"hot\": { \"actions\": { \"rollover\": { \"max_size\": \"50gb\", \"max_age\": \"30d\" } } } } } }\n\nPUT _index_template/logs-tmpl\n{ \"index_patterns\": [\"logs*\"], \"data_stream\": {}, \"template\": { \"settings\": { \"index.lifecycle.name\": \"logs-policy\" } } }","note":"Dans Elasticsearch, le découpage s'obtient en divisant un index en shards et, pour les séries temporelles, en créant de nouveaux backing indices avec ILM + data streams (rollover). Les anciens indices peuvent être supprimés avec ILM.","danger":"safe"}}},{"id":"sequence","title":"Créer une séquence / valeur auto-incrémentée","aliases":["sequence","sequence","auto increment","identity","auto-increment","nextval","serial"],"engines":{"oracle":{"command":"-- Sequence\nCREATE SEQUENCE customer_seq START WITH 1 INCREMENT BY 1 NOCACHE;\nSELECT customer_seq.NEXTVAL FROM dual;\n\n-- or an identity column (12c+)\nCREATE TABLE customer (id NUMBER GENERATED ALWAYS AS IDENTITY, name VARCHAR2(100));","note":"La méthode classique est CREATE SEQUENCE + NEXTVAL/CURRVAL. Depuis 12c, les colonnes GENERATED AS IDENTITY sont aussi prises en charge. Une valeur CACHE améliore les performances mais des trous peuvent apparaître.","danger":"safe","priv":"CREATE SEQUENCE","minver":"12c pour IDENTITY"},"postgres":{"command":"-- Sequence\nCREATE SEQUENCE customer_seq START 1 INCREMENT 1;\nSELECT nextval('customer_seq');\n\n-- Modern method: identity column\nCREATE TABLE customer (id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text);\n-- Classic: serial (creates a sequence behind the scenes)\nCREATE TABLE logs (id serial PRIMARY KEY, message text);","note":"GENERATED ... AS IDENTITY est conforme au standard SQL et recommandé ; serial est l'ancienne forme abrégée. Accès manuel avec nextval/currval. La valeur peut être réinitialisée avec setval.","danger":"safe","priv":"CREATE sur le schéma","minver":"PG10 pour IDENTITY"},"mysql":{"command":"-- AUTO_INCREMENT column\nCREATE TABLE customer (id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100));\n\n-- Set the starting value\nALTER TABLE customer AUTO_INCREMENT = 1000;\n\n-- Get the last inserted value\nSELECT LAST_INSERT_ID();","note":"MySQL n'a pas d'objet séquence autonome (standard) ; on utilise une colonne AUTO_INCREMENT (une par table, obligatoirement indexée). MariaDB 10.3+ prend en charge CREATE SEQUENCE.","danger":"safe","priv":"CREATE, ALTER sur la table"},"mssql":{"command":"-- IDENTITY column\nCREATE TABLE customer (id INT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(100));\n\n-- Standalone sequence object\nCREATE SEQUENCE customer_seq AS INT START WITH 1 INCREMENT BY 1;\nSELECT NEXT VALUE FOR customer_seq;","note":"IDENTITY(seed,increment) est lié à une colonne de table. CREATE SEQUENCE (2012+) fournit un compteur indépendant des tables, partageable entre plusieurs tables ; les valeurs sont obtenues avec NEXT VALUE FOR.","danger":"safe","priv":"CREATE SEQUENCE / ALTER sur la table","minver":"2012 pour SEQUENCE"},"mongodb":{"command":"// MongoDB has no auto counter; counter pattern with findOneAndUpdate\ndb.counters.findOneAndUpdate(\n  { _id: \"customer\" },\n  { $inc: { seq: 1 } },\n  { upsert: true, returnDocument: \"after\" }\n);","note":"Le _id par défaut est un ObjectId (déjà unique et à peu près croissant). Si un entier séquentiel est nécessaire, générez-le de façon atomique avec $inc dans une collection counters distincte.","danger":"safe"},"redis":{"command":"# Atomic counter\nINCR customer:id\n# or with a specific step\nINCRBY customer:id 10","note":"INCR/INCRBY sont atomiques et idéaux pour générer des ID uniques croissants dans des environnements distribués. Si la clé n'existe pas, le compteur part de 0.","danger":"safe"},"elasticsearch":{"command":"# Not applicable: Elasticsearch does not offer a sequence object. Document IDs are assigned automatically;\n# the internal _seq_no and _primary_term fields are used for concurrency control.\nPOST logs/_doc\n{ \"message\": \"sample\" }","note":"Si vous faites un POST sans ID, ES attribue un _id unique aléatoire (non croissant). _seq_no est un numéro de séquence interne qui augmente à chaque écriture, mais il sert à la concurrence optimiste, pas de compteur applicatif.","danger":"safe"}}},{"id":"add-constraint","title":"Ajouter ou désactiver une contrainte (FK/CHECK/UNIQUE)","aliases":["constraint","constraint","foreign key","check","unique","foreign key","disable constraint"],"engines":{"oracle":{"command":"-- Add a foreign key\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\n-- CHECK\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n-- Disable / enable\nALTER TABLE orders DISABLE CONSTRAINT fk_customer;\nALTER TABLE orders ENABLE CONSTRAINT fk_customer;","note":"Les contraintes Oracle peuvent être désactivées temporairement avec DISABLE/ENABLE (utile pour les chargements massifs). ENABLE VALIDATE vérifie les lignes existantes ; NOVALIDATE ne le fait pas.","danger":"caution","priv":"ALTER sur la table, REFERENCES sur la cible"},"postgres":{"command":"-- Foreign key and CHECK\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n\n-- Instead of disabling the FK: NOT VALID + validate later\nALTER TABLE orders ADD CONSTRAINT fk2 FOREIGN KEY (c_id) REFERENCES customer(id) NOT VALID;\nALTER TABLE orders VALIDATE CONSTRAINT fk2;\nALTER TABLE orders DROP CONSTRAINT fk2;","note":"PostgreSQL ne permet pas de DISABLE sur une contrainte. Pour limiter le verrouillage sur une grande table, ajoutez la contrainte avec NOT VALID puis faites un VALIDATE plus tard. ALTER TABLE ... DISABLE TRIGGER ALL désactive aussi les triggers internes de la FK et nécessite superuser ; les données insérées pendant la désactivation ne sont jamais validées, et la réactivation ne vérifie pas rétroactivement (risque de perte d'intégrité silencieuse). À la place, faites un DROP de la contrainte, rajoutez-la avec NOT VALID, puis exécutez VALIDATE CONSTRAINT.","danger":"caution","priv":"propriétaire de la table, REFERENCES sur la cible"},"mysql":{"command":"-- Foreign key and CHECK\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n\n-- Temporarily disable FK checks for the session\nSET FOREIGN_KEY_CHECKS = 0;\n-- ... bulk operation ...\nSET FOREIGN_KEY_CHECKS = 1;","note":"Nécessite InnoDB. Au lieu de désactiver une contrainte précise, on utilise SET FOREIGN_KEY_CHECKS=0 au niveau session. Les contraintes CHECK ne sont réellement appliquées qu'à partir de MySQL 8.0.16 (elles étaient ignorées auparavant).","danger":"caution","priv":"ALTER, REFERENCES sur la table","minver":"8.0.16 pour CHECK"},"mssql":{"command":"-- Foreign key and CHECK\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n\n-- Disable / re-enable (validate with WITH CHECK)\nALTER TABLE orders NOCHECK CONSTRAINT fk_customer;\nALTER TABLE orders WITH CHECK CHECK CONSTRAINT fk_customer;","note":"Dans SQL Server, les contraintes FK/CHECK sont désactivées avec NOCHECK CONSTRAINT. Si WITH CHECK CHECK CONSTRAINT n'est pas utilisé lors de la réactivation, la contrainte reste « not trusted » et l'optimiseur de requêtes ne peut pas l'exploiter.","danger":"caution","priv":"ALTER sur la table, REFERENCES sur la cible"},"mongodb":{"command":"// Constraint-like checks with schema validation rules\ndb.runCommand({ collMod: \"product\",\n  validator: { $jsonSchema: { required: [\"price\"], properties: { price: { bsonType: \"number\", minimum: 0 } } } },\n  validationLevel: \"strict\"\n});\n// Unique index for uniqueness\ndb.customer.createIndex({ email: 1 }, { unique: true });","note":"Il n'y a pas de clés étrangères (appliquées dans la couche applicative). L'équivalent de UNIQUE est un index unique. Au lieu de CHECK, utilisez la validation $jsonSchema ; validationAction: 'warn' revient à la désactiver.","danger":"caution"},"redis":{"command":"# Not applicable: Redis has no FK/CHECK/UNIQUE constraints. Uniqueness is enforced at the application level with SET or\n# SETNX/conditional writes.\nSET customer:email:sample@x.com 1 NX","note":"Il n'existe pas de notion d'intégrité relationnelle ni de contraintes. L'unicité se gère côté application avec SET ... NX (écriture uniquement si absent) ou en vérifiant un SET/HASH distinct.","danger":"safe"},"elasticsearch":{"command":"# Not applicable: Elasticsearch has no FK/CHECK. Field types are enforced with the mapping,\n# and uniqueness with a fixed _id.\nPUT product/_doc/ABC-123\n{ \"price\": 100 }","note":"Les relations et contraintes n'existent pas dans le moteur. Le contrôle de type se fait via le mapping. Pour un effet UNIQUE, utilisez la clé naturelle comme _id (les écritures sur le même _id écrasent) ou rejetez les conflits avec op_type=create.","danger":"safe"}}},{"id":"create-view","title":"Créer / modifier une vue","aliases":["view","view","create view","alter view","or replace"],"engines":{"oracle":{"command":"CREATE OR REPLACE VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\n-- Drop\nDROP VIEW active_customer;","note":"CREATE OR REPLACE VIEW crée et modifie à la fois. Pour les vues modifiables sous certaines conditions, WITH CHECK OPTION peut être ajouté.","danger":"safe","readonly":true,"priv":"CREATE VIEW"},"postgres":{"command":"CREATE OR REPLACE VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\nDROP VIEW active_customer;","note":"Avec CREATE OR REPLACE, de nouvelles colonnes ne peuvent être ajoutées qu'à la fin ; changer le type/l'ordre d'une colonne nécessite d'abord un DROP. Les vues simples sont automatiquement modifiables.","danger":"safe","readonly":true,"priv":"CREATE sur le schéma"},"mysql":{"command":"CREATE OR REPLACE VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\n-- Modify\nALTER VIEW active_customer AS SELECT id, name FROM customer;\nDROP VIEW active_customer;","note":"La définition est mise à jour avec CREATE OR REPLACE VIEW ou ALTER VIEW. La méthode d'exécution se règle avec ALGORITHM = MERGE/TEMPTABLE ; WITH CHECK OPTION protège les vues modifiables.","danger":"safe","readonly":true,"priv":"CREATE VIEW sur la table"},"mssql":{"command":"CREATE OR ALTER VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\nDROP VIEW active_customer;","note":"CREATE OR ALTER VIEW (2016 SP1+) crée/modifie en une seule instruction. Dans les versions plus anciennes, utilisez d'abord CREATE, puis ALTER VIEW. L'instruction de vue doit être la première du batch.","danger":"safe","readonly":true,"priv":"CREATE VIEW","minver":"2016 SP1 pour OR ALTER"},"mongodb":{"command":"// Read-only view (on top of an aggregation pipeline)\ndb.createView(\"active_customer\", \"customer\", [ { $match: { status: \"ACTIVE\" } }, { $project: { name: 1, email: 1 } } ]);\n\ndb.active_customer.drop();","note":"db.createView crée une vue en lecture seule sur une collection source et un pipeline d'agrégation. On ne peut pas écrire dans les vues ; pour changer la définition, supprimez-la et recréez-la (ou utilisez collMod).","danger":"safe","readonly":true},"redis":{"command":"# Not applicable: Redis has no view concept; derived views are provided on the application\n# side or with RedisJSON/RediSearch indexes.","note":"Les vues définies sur des requêtes ne sont pas prises en charge. Pour un besoin similaire, conservez des clés précalculées ou construisez un index interrogeable avec RediSearch.","danger":"safe"},"elasticsearch":{"command":"# Filtered logical view with an alias\nPOST _aliases\n{ \"actions\": [ { \"add\": { \"index\": \"customer\", \"alias\": \"active_customer\",\n  \"filter\": { \"term\": { \"status\": \"ACTIVE\" } } } } ] }","note":"Il n'y a pas de vue SQL classique ; un alias filtré est l'équivalent le plus proche et applique automatiquement le filtre aux requêtes. Pour la sélection de colonnes, utilisez le filtrage _source dans la requête.","danger":"safe","readonly":true}}},{"id":"materialized-view","title":"Créer et rafraîchir une vue matérialisée","aliases":["materialized view","mview","refresh","refresh","cached view","materialize"],"engines":{"oracle":{"command":"-- FAST refresh first requires an MV log on the source table:\nCREATE MATERIALIZED VIEW LOG ON sales\n  WITH ROWID, SEQUENCE (region, amount) INCLUDING NEW VALUES;\n\nCREATE MATERIALIZED VIEW mv_revenue\nBUILD IMMEDIATE REFRESH FAST ON COMMIT\nAS SELECT region, SUM(amount) total, COUNT(*) cnt, COUNT(amount) amount_cnt\n   FROM sales GROUP BY region;\n\n-- Manual refresh\nEXEC DBMS_MVIEW.REFRESH('MV_REVENUE', 'C');","note":"REFRESH FAST nécessite un materialized view log ; COMPLETE ('C') recalcule entièrement, FAST ('F') est incrémental. ON COMMIT/ON DEMAND détermine le moment du rafraîchissement.","danger":"caution","priv":"CREATE MATERIALIZED VIEW"},"postgres":{"command":"CREATE MATERIALIZED VIEW mv_revenue AS\nSELECT region, SUM(amount) AS total FROM sales GROUP BY region;\n\n-- Refresh (CONCURRENTLY requires a unique index)\nCREATE UNIQUE INDEX ON mv_revenue (region);\nREFRESH MATERIALIZED VIEW CONCURRENTLY mv_revenue;","note":"REFRESH recalcule tout (il n'est pas incrémental). CONCURRENTLY ne bloque pas les lectures mais nécessite un index unique et est plus lent.","danger":"caution","priv":"propriétaire de la table","minver":"PG9.3 (CONCURRENTLY 9.4)"},"mysql":{"command":"-- MySQL/MariaDB has no built-in materialized views.\n-- Approach: summary table + refresh with a trigger or a scheduled event\nCREATE TABLE mv_revenue (region VARCHAR(50) PRIMARY KEY, total DECIMAL(18,2));\n\nCREATE EVENT ev_mv_revenue ON SCHEDULE EVERY 1 HOUR DO\n  REPLACE INTO mv_revenue SELECT region, SUM(amount) FROM sales GROUP BY region;","note":"Il n'y a pas de prise en charge native des vues matérialisées. Une table de synthèse est maintenue et réalimentée soit instantanément par des triggers, soit périodiquement avec l'EVENT SCHEDULER.","danger":"caution","priv":"CREATE, EVENT sur le schéma"},"mssql":{"command":"-- Indexed view (materialized view equivalent)\nCREATE VIEW dbo.mv_revenue WITH SCHEMABINDING AS\nSELECT region, SUM(ISNULL(amount, 0)) AS total, COUNT_BIG(*) AS cnt\nFROM dbo.sales GROUP BY region;\nGO\nCREATE UNIQUE CLUSTERED INDEX ix_mv_revenue ON dbo.mv_revenue (region);","note":"SQL Server utilise les vues indexées : WITH SCHEMABINDING est obligatoire ; dès qu'un index cluster unique est créé, les données sont stockées physiquement et maintenues automatiquement (aucun REFRESH séparé nécessaire). Avec GROUP BY, COUNT_BIG(*) est requis ; si la colonne dans SUM accepte NULL, écrivez SUM(ISNULL(amount, 0)) (sinon erreur 8662). La vue doit être créée avec QUOTED_IDENTIFIER et ANSI_NULLS à ON. La correspondance automatique des vues par l'optimiseur n'est disponible qu'en édition Enterprise (et Developer) ; en Standard, référencez la vue avec le hint WITH (NOEXPAND).","danger":"caution","priv":"CREATE VIEW, ALTER sur le schéma"},"mongodb":{"command":"// On-demand materialized view: write the result to a collection with $merge\ndb.sales.aggregate([\n  { $group: { _id: \"$region\", total: { $sum: \"$amount\" } } },\n  { $merge: { into: \"mv_revenue\", whenMatched: \"replace\", whenNotMatched: \"insert\" } }\n]);","note":"Une vue matérialisée à la demande s'obtient en écrivant le résultat de l'agrégation dans une collection persistante avec $out ou $merge. Pour rafraîchir, réexécutez le pipeline (manuellement ou via un planificateur).","danger":"caution"},"redis":{"command":"# Not applicable: Redis has no materialized views. The computed result is written to a key\n# and cached with a TTL.\nSET mv:revenue:region1 12345 EX 3600","note":"Il n'existe pas de notion de vue matérialisée ; l'approche la plus proche consiste à conserver des valeurs précalculées dans des clés (String/Hash/Sorted Set) et à les mettre en cache pour une durée limitée avec EX/TTL.","danger":"safe"},"elasticsearch":{"command":"# Continuous/materialized summary index with a transform\nPUT _transform/mv_revenue\n{ \"source\": { \"index\": \"sales\" },\n  \"dest\": { \"index\": \"mv_revenue\" },\n  \"pivot\": { \"group_by\": { \"region\": { \"terms\": { \"field\": \"region\" } } },\n            \"aggregations\": { \"total\": { \"sum\": { \"field\": \"amount\" } } } } }\nPOST _transform/mv_revenue/_start","note":"Un transform pivote l'index source et le matérialise dans un index de destination de synthèse. Cet exemple est un transform batch : il s'exécute une fois avec _start puis s'arrête. Pour un transform continu qui se met à jour automatiquement à l'arrivée de nouvelles données, ajoutez \"sync\": { \"time\": { \"field\": \"<time_field>\", \"delay\": \"60s\" } } avec un champ temporel à la configuration (il n'existe pas de réglage continuous: true).","danger":"caution"}}},{"id":"rename-object","title":"Renommer une table / un objet","aliases":["rename","rename","change name","rename table","sp_rename"],"engines":{"oracle":{"command":"-- Table\nALTER TABLE old_name RENAME TO new_name;\n-- or\nRENAME old_name TO new_name;\n-- Column\nALTER TABLE customer RENAME COLUMN name TO full_name;","note":"RENAME ou ALTER ... RENAME TO s'utilise pour les tables/vues/séquences. Les objets dépendants (vues, synonymes) peuvent devenir invalides et doivent être recompilés.","danger":"caution","priv":"ALTER sur l'objet"},"postgres":{"command":"ALTER TABLE old_name RENAME TO new_name;\nALTER TABLE customer RENAME COLUMN name TO full_name;\nALTER VIEW old_view RENAME TO new_view;","note":"ALTER ... RENAME TO fonctionne pour la plupart des types d'objets. Les références des clés étrangères et des index sont conservées automatiquement même après changement de nom (basé sur l'OID).","danger":"caution","priv":"propriétaire de la table"},"mysql":{"command":"RENAME TABLE old_name TO new_name;\n-- or\nALTER TABLE old_name RENAME TO new_name;\n-- Column (8.0+)\nALTER TABLE customer RENAME COLUMN name TO full_name;","note":"RENAME TABLE est atomique et peut déplacer plusieurs tables en une seule instruction. Pour renommer des colonnes, utilisez RENAME COLUMN en 8.0+ ; auparavant, il faut utiliser CHANGE en précisant aussi le type.","danger":"caution","priv":"ALTER, DROP sur l'ancien ; CREATE, INSERT sur le nouveau"},"mssql":{"command":"-- Table\nEXEC sp_rename 'dbo.old_name', 'new_name';\n-- Column\nEXEC sp_rename 'dbo.customer.name', 'full_name', 'COLUMN';","note":"SQL Server n'a pas d'instruction RENAME TABLE ; on utilise la procédure système sp_rename. Le préfixe de schéma figure dans le nom de la table mais pas dans le nouveau nom. Les vues/procédures dépendantes doivent être mises à jour manuellement.","danger":"caution","priv":"ALTER sur l'objet, CONTROL"},"mongodb":{"command":"// Rename a collection\ndb.old_name.renameCollection(\"new_name\");\n// or the admin command\ndb.adminCommand({ renameCollection: \"db.old_name\", to: \"db.new_name\" });","note":"renameCollection change le nom de la collection. Pour renommer un champ, utilisez l'opérateur $rename avec updateMany. Les collections shardées peuvent être renommées en 5.0+ ; les versions plus anciennes ne le permettent pas.","danger":"caution","priv":"renameCollectionSameDB / clusterManager"},"redis":{"command":"# Rename a key\nRENAME old_key new_key\n# Without overwriting if the target exists\nRENAMENX old_key new_key","note":"RENAME renomme la clé ; si la cible existe déjà, elle est écrasée. RENAMENX ne fonctionne que si la cible n'existe pas. Une erreur est renvoyée si la clé source n'existe pas.","danger":"caution"},"elasticsearch":{"command":"# An index cannot be renamed directly; reindex + alias approach\nPOST _reindex\n{ \"source\": { \"index\": \"old_name\" }, \"dest\": { \"index\": \"new_name\" } }\nDELETE old_name\n# or use an alias for a seamless switch\nPOST _aliases\n{ \"actions\": [ { \"add\": { \"index\": \"new_name\", \"alias\": \"customer\" } } ] }","note":"Il n'existe pas de commande pour renommer un index. Réindexez vers le nouveau nom avec _reindex et supprimez l'ancien. Il est recommandé de passer par un alias stable pour que l'application ne dépende pas des noms d'index.","danger":"caution"}}}]},"rosetta/datamove.json":{"category":"Déplacement de données","categoryId":"datamove","order":11,"concepts":[{"id":"bulk-import","title":"Chargement massif de données (CSV / depuis un fichier)","aliases":["import","bulk load","load csv","bulk insert","load data","copy from","import data"],"engines":{"oracle":{"command":"-- SQL*Loader (from the shell)\nsqlldr app_user/password control=customer.ctl log=load.log\n\n-- Contents of customer.ctl:\n-- LOAD DATA INFILE 'customer.csv'\n-- INTO TABLE customer FIELDS TERMINATED BY ','\n-- (id, name, email)\n\n-- Alternative: with an external table\nCREATE TABLE ext_customer (id NUMBER, name VARCHAR2(100))\nORGANIZATION EXTERNAL (TYPE ORACLE_LOADER DEFAULT DIRECTORY dir_csv\n  ACCESS PARAMETERS (FIELDS TERMINATED BY ',') LOCATION ('customer.csv'));","note":"L'outil le plus courant est SQL*Loader (avec un fichier de contrôle). Pour lire le fichier comme une requête, utilisez une table externe. Pour de gros volumes, le chargement direct path (DIRECT=TRUE) est rapide.","danger":"caution","priv":"INSERT sur la table, un objet DIRECTORY"},"postgres":{"command":"-- Server side (file on the server; requires superuser/pg_read_server_files)\nCOPY customer (id, name, email) FROM '/data/customer.csv' WITH (FORMAT csv, HEADER true);\n\n-- Client side (psql, file on the client)\n\\copy customer (id, name, email) FROM 'customer.csv' WITH (FORMAT csv, HEADER true)","note":"COPY lit un fichier sur le serveur et nécessite des privilèges ; psql \\copy lit un fichier client et ne requiert aucun privilège supplémentaire. C'est le moyen le plus rapide de charger du CSV.","danger":"caution","priv":"INSERT sur la table (pg_read_server_files pour COPY)"},"mysql":{"command":"-- Server side\nLOAD DATA INFILE '/data/customer.csv' INTO TABLE customer\nFIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n' IGNORE 1 LINES;\n\n-- From a client file\nLOAD DATA LOCAL INFILE 'customer.csv' INTO TABLE customer\nFIELDS TERMINATED BY ',' IGNORE 1 LINES;","note":"LOAD DATA INFILE est le moyen le plus rapide de charger en masse. LOCAL lit un fichier client et doit être activé à la fois sur le serveur (local_infile=ON) et sur le client. secure_file_priv restreint le chemin côté serveur.","danger":"caution","priv":"FILE (INSERT pour LOCAL)"},"mssql":{"command":"-- T-SQL BULK INSERT\nBULK INSERT customer FROM 'C:\\data\\customer.csv'\nWITH (FORMAT='CSV', FIRSTROW=2, FIELDTERMINATOR=',', ROWTERMINATOR='0x0a');\n\n-- bcp from the shell\nbcp mydb.dbo.customer in customer.csv -c -t, -S server -T","note":"BULK INSERT lit le fichier depuis un chemin accessible au compte de service SQL Server. bcp est un outil en ligne de commande ; -c est le mode caractère, -t le séparateur de champs. Pour une vitesse maximale, visez une journalisation minimale avec TABLOCK.","danger":"caution","priv":"INSERT, ADMINISTER BULK OPERATIONS"},"mongodb":{"command":"# Import CSV / JSON (from the shell)\nmongoimport --db=shop --collection=customer --type=csv --headerline --file=customer.csv\n\n# For a JSON array\nmongoimport --db=shop --collection=customer --file=customer.json --jsonArray","note":"mongoimport charge du CSV/TSV/JSON ; --headerline traite la première ligne comme noms de champs. Pour mettre à jour les documents existants, utilisez --mode=upsert --upsertFields. Pour de gros volumes, --numInsertionWorkers accélère le chargement.","danger":"caution","priv":"insert (readWrite)"},"redis":{"command":"# Fastest: feed commands in protocol format and pipe them\ncat commands.txt | redis-cli --pipe\n\n# Example lines in commands.txt:\n# SET customer:1 \"Alice\"\n# SET customer:2 \"Bob\"","note":"Pour le chargement massif, utilisez redis-cli --pipe (mass insert) ; il est très rapide car il envoie les commandes au format du protocole RESP sur une seule connexion. Sinon, les commandes sont regroupées par pipelining.","danger":"caution"},"elasticsearch":{"command":"# _bulk API (NDJSON: an action line + a source line per document)\ncurl -s -H 'Content-Type: application/x-ndjson' -XPOST 'localhost:9200/customer/_bulk' --data-binary @data.ndjson\n\n# Contents of data.ndjson:\n# { \"index\": {} }\n# { \"id\": 1, \"name\": \"Alice\" }\n# { \"index\": {} }\n# { \"id\": 2, \"name\": \"Bob\" }","note":"L'API _bulk accepte du NDJSON (une ligne d'action + une ligne de données par document ; le fichier doit se terminer par \\n). Pour le CSV, utilisez Logstash, Filebeat ou elasticdump. Désactiver refresh_interval pendant le chargement l'accélère.","danger":"caution","priv":"privilège index / create index"}}},{"id":"bulk-export","title":"Exporter des données (CSV / vers un fichier)","aliases":["export","export data","export csv","copy to","outfile","dump","extract data"],"engines":{"oracle":{"command":"-- CSV with SQL*Plus (spool)\nSET MARKUP CSV ON\nSPOOL customer.csv\nSELECT id, name, email FROM customer;\nSPOOL OFF\n\n-- Logical export with Data Pump (from the shell)\nexpdp app_user/password tables=customer directory=dir_dmp dumpfile=customer.dmp","note":"Pour le CSV, utilisez SQL*Plus SET MARKUP CSV ON + SPOOL (12.2+). Pour déplacer des schémas/tables, Data Pump (expdp) produit un .dmp binaire importé dans une autre base Oracle avec impdp.","danger":"safe","readonly":true,"priv":"SELECT sur la table","minver":"12.2 pour MARKUP CSV"},"postgres":{"command":"-- Server side\nCOPY (SELECT id, name, email FROM customer) TO '/data/customer.csv' WITH (FORMAT csv, HEADER true);\n\n-- Client side (psql)\n\\copy (SELECT id, name, email FROM customer) TO 'customer.csv' WITH (FORMAT csv, HEADER true)","note":"COPY ... TO écrit le résultat d'une requête dans un fichier (côté serveur, nécessite des privilèges) ; psql \\copy écrit dans un fichier client. HEADER true ajoute une ligne d'en-tête.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mysql":{"command":"-- To a file on the server\nSELECT id, name, email FROM customer\nINTO OUTFILE '/data/customer.csv'\nFIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n';\n\n-- To the client: mysql -e ... > file (tab-separated)\n-- mysql -e \"SELECT * FROM customer\" shop > customer.tsv","note":"SELECT ... INTO OUTFILE crée le fichier sur le serveur (nécessite le chemin secure_file_priv et le privilège FILE ; il ne peut pas écraser). Pour une sortie côté client, redirigez avec mysql -e.","danger":"safe","readonly":true,"priv":"SELECT, FILE"},"mssql":{"command":"-- Export with bcp (from the shell)\nbcp \"SELECT id, name, email FROM shop.dbo.customer\" queryout customer.csv -c -t, -S server -T\n\n-- or the whole table\nbcp shop.dbo.customer out customer.dat -n -S server -T","note":"bcp out/queryout écrit les données dans un fichier : -c mode caractère, -t, séparateur de champs, -n format binaire natif. Utilisez queryout pour le résultat d'une requête. « Results to File » dans SSMS est aussi une option.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mongodb":{"command":"# Export CSV (fields must be specified)\nmongoexport --db=shop --collection=customer --type=csv --fields=id,name,email --out=customer.csv\n\n# As JSON\nmongoexport --db=shop --collection=customer --out=customer.json","note":"mongoexport produit du CSV/JSON ; pour le CSV, les colonnes doivent être indiquées explicitement avec --fields. Utilisez --query pour filtrer. Pour une sauvegarde binaire complète, mongodump est préférable.","danger":"safe","readonly":true,"priv":"find (read)"},"redis":{"command":"# Scan keys (SCAN instead of KEYS in production)\nredis-cli --scan --pattern 'customer:*' > keys.txt\n\n# For a full data backup, download an RDB snapshot to the client side (non-blocking)\nredis-cli --rdb ./dump.rdb","note":"L'export CSV structuré n'est pas intégré. Utilisez --scan (non bloquant) pour une liste de clés et un fichier RDB pour l'ensemble des données : redis-cli --rdb télécharge dans un fichier local un snapshot généré en arrière-plan sur le serveur ; si vous voulez le fichier sur le serveur, utilisez BGSAVE. SAVE bloque le thread principal ; ne l'utilisez pas en production. Pour obtenir aussi les valeurs, lisez le résultat du scan dans une boucle.","danger":"safe","readonly":true},"elasticsearch":{"command":"# Export with elasticdump\nelasticdump --input=http://localhost:9200/customer --output=customer.json --type=data\n\n# For large data, paged reads with scroll / point-in-time\nGET customer/_search?scroll=1m\n{ \"size\": 1000, \"query\": { \"match_all\": {} } }","note":"Il n'y a pas d'export CSV direct. Pour récupérer toutes les données, utilisez elasticdump ou paginez _search avec scroll/PIT (point-in-time). La conversion en CSV nécessite la sortie csv de Logstash ou un outil externe.","danger":"safe","readonly":true,"priv":"read sur l'index"}}},{"id":"copy-between","title":"Copier des données entre tables / environnements","aliases":["copy","copy","insert select","ctas","reindex","move data","between tables"],"engines":{"oracle":{"command":"-- Into an existing table\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl WHERE status = 'ACTIVE';\n\n-- By creating a new table (CTAS)\nCREATE TABLE target_tbl AS SELECT * FROM source_tbl;\n\n-- From a remote database via a database link\nINSERT INTO target_tbl SELECT * FROM source_tbl@remote_db;","note":"Dans la même base, utilisez INSERT ... SELECT ou CREATE TABLE AS SELECT (CTAS). Pour les copies entre environnements, un database link (@remote_db) ou Data Pump est préférable. Le hint APPEND accélère avec le direct-path.","danger":"caution","priv":"SELECT sur la source, INSERT/CREATE sur la cible"},"postgres":{"command":"-- Same database\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl;\nCREATE TABLE target_tbl AS SELECT * FROM source_tbl;\n\n-- Between environments (psql pipe)\n-- pg_dump -t source_tbl sourcedb | psql targetdb\n-- or read the remote table with postgres_fdw and INSERT","note":"Dans la même DB, utilisez INSERT ... SELECT / CTAS. Entre serveurs, utilisez un pipeline pg_dump | psql ou copiez via une table étrangère avec postgres_fdw.","danger":"caution","priv":"SELECT sur la source, INSERT/CREATE sur la cible"},"mysql":{"command":"-- Same database\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl;\nCREATE TABLE target_tbl AS SELECT * FROM source_tbl;\n\n-- Between servers (mysqldump pipe)\n-- mysqldump sourcedb source_tbl | mysql -h targethost targetdb","note":"Dans la même DB, INSERT ... SELECT ou CREATE TABLE ... AS SELECT. Entre serveurs différents, rediriger la sortie de mysqldump vers le client mysql est courant. CTAS ne copie ni les index ni les contraintes.","danger":"caution","priv":"SELECT sur la source, INSERT/CREATE sur la cible"},"mssql":{"command":"-- Into an existing table\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl;\n\n-- By creating a new table\nSELECT * INTO target_tbl FROM source_tbl;\n\n-- Between servers: linked server\nINSERT INTO target_tbl SELECT * FROM [REMOTE_SRV].shop.dbo.source_tbl;","note":"Dans SQL Server, la copie vers une nouvelle table se fait avec SELECT ... INTO (l'équivalent de CTAS). Entre serveurs, utilisez un linked server (nom en quatre parties), bcp out/in ou SSIS.","danger":"caution","priv":"SELECT sur la source, INSERT / CREATE TABLE sur la cible"},"mongodb":{"command":"// Write an aggregation result to another collection\ndb.source_tbl.aggregate([\n  { $match: { status: \"ACTIVE\" } },\n  { $out: \"target_tbl\" }\n]);\n\n// Merge into an existing collection (upsert)\ndb.source_tbl.aggregate([ { $merge: { into: \"target_tbl\", whenMatched: \"merge\", whenNotMatched: \"insert\" } } ]);","note":"$out remplace entièrement la collection cible ; $merge fusionne avec les documents existants ou les insère (même DB ou autre). Pour les transferts entre serveurs, utilisez mongodump | mongorestore ou mongoexport/mongoimport.","danger":"caution","priv":"find sur la source, insert sur la cible"},"redis":{"command":"# Move a single key to another server (deleted from the source)\nMIGRATE target_host 6379 customer:1 0 5000\n\n# To copy (without deleting): DUMP + RESTORE\nDUMP customer:1          # returns the serialized value\nRESTORE customer:1new 0 \"<dump-data>\"","note":"MIGRATE déplace atomiquement la clé vers l'instance cible et la supprime de la source (avec l'option COPY, elle n'est pas supprimée). Pour copier dans la même instance, utilisez la commande COPY (6.0+) ou DUMP+RESTORE.","danger":"caution"},"elasticsearch":{"command":"# Within the same cluster\nPOST _reindex\n{ \"source\": { \"index\": \"source_tbl\" }, \"dest\": { \"index\": \"target_tbl\" } }\n\n# From a remote cluster (remote reindex; requires the whitelist setting)\nPOST _reindex\n{ \"source\": { \"remote\": { \"host\": \"http://remote:9200\" }, \"index\": \"source_tbl\" },\n  \"dest\": { \"index\": \"target_tbl\" } }","note":"_reindex copie entre index ; il peut être filtré par une requête et transformé par un script. Entre clusters, utilisez le remote reindex (reindex.remote.whitelist doit être défini sur la cible). Les gros traitements sont parallélisés avec slices.","danger":"caution","priv":"read sur la source, write sur la cible"}}},{"id":"generate-insert","title":"Générer les données sous forme de script INSERT","aliases":["generate insert","insert script","generate script","data dump","inserts","generate script"],"engines":{"oracle":{"command":"-- DBMS_METADATA for object DDL\nSELECT DBMS_METADATA.GET_DDL('TABLE','CUSTOMER') FROM dual;\n\n-- Generating row data as INSERTs is not built in;\n-- use SQL Developer 'Export > Insert' or SQLcl 'SET SQLFORMAT INSERT':\n-- SET SQLFORMAT INSERT\n-- SELECT * FROM customer;","note":"Oracle ne propose aucune commande SQL qui convertit directement les données en script INSERT prêt à l'emploi. Pour le DDL, utilisez DBMS_METADATA.GET_DDL ; pour les INSERT de lignes, utilisez SQLcl (SET SQLFORMAT INSERT) ou l'assistant Export de SQL Developer.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"postgres":{"command":"-- Data only, as INSERT statements\npg_dump --data-only --inserts --table=customer shop > insert.sql\n\n-- With column names (more robust to schema differences)\npg_dump --data-only --column-inserts --table=customer shop > insert.sql","note":"pg_dump --inserts écrit chaque ligne comme un INSERT séparé ; --column-inserts ajoute aussi les noms de colonnes (robuste aux différences de schéma). Le format par défaut (sans option) est COPY, qui se charge beaucoup plus vite.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mysql":{"command":"-- Data only (INSERTs), without the table definition\nmysqldump --no-create-info shop customer > insert.sql\n\n-- One INSERT per row (more readable)\nmysqldump --no-create-info --skip-extended-insert shop customer > insert.sql","note":"mysqldump produit des instructions INSERT par défaut ; --no-create-info omet CREATE TABLE et ne produit que les données. --skip-extended-insert fait de chaque ligne un INSERT séparé (par défaut, plusieurs lignes sont combinées en une instruction).","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mssql":{"command":"-- SSMS: right-click > Tasks > Generate Scripts >\n--   Advanced > 'Types of data to script' = Data only (or Schema and data)\n\n-- Command-line alternative (sqlpackage / mssql-scripter tool)\n-- mssql-scripter -S server -d shop -t customer --data-only > insert.sql","note":"SQL Server n'a pas de commande T-SQL intégrée pour cela. Le moyen le plus courant consiste à choisir Data only dans l'assistant « Generate Scripts » de SSMS. En ligne de commande, l'outil mssql-scripter de Microsoft génère des INSERT avec --data-only.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mongodb":{"command":"# Produces a binary/BSON backup instead of INSERT SQL\nmongodump --db=shop --collection=customer --out=./backup\n\n# As JSON text (can be adapted to insertMany)\nmongoexport --db=shop --collection=customer --jsonArray --out=customer.json","note":"Il n'existe pas de notion d'INSERT SQL. Pour une sauvegarde portable, utilisez mongodump (BSON, restauré avec mongorestore). Pour un insertMany intégré au code, la sortie de mongoexport --jsonArray peut être utilisée.","danger":"safe","readonly":true,"priv":"find (read)"},"redis":{"command":"# Portable serialization of a single key\nDUMP customer:1\n# The output is restored with RESTORE:\n# RESTORE customer:1 0 \"<dump-data>\"\n\n# RDB snapshot for all data\nredis-cli --rdb dump.rdb","note":"Aucun script INSERT SQL n'est produit. Utilisez la sérialisation DUMP + RESTORE par clé et un fichier RDB (--rdb) pour l'ensemble des données. Si une liste de commandes est nécessaire, itérez avec SCAN et construisez les commandes SET/HSET appropriées pour chaque type.","danger":"safe","readonly":true},"elasticsearch":{"command":"# The INSERT equivalent is _bulk NDJSON; produce it with elasticdump\nelasticdump --input=http://localhost:9200/customer --output=customer_bulk.json --type=data\n\n# Manual: converting a _search result to _bulk NDJSON requires an external script","note":"Aucun INSERT SQL n'est produit ; dans Elasticsearch, l'unité de chargement est le NDJSON _bulk. Pour extraire les données existantes sous une forme rechargeable, utilisez elasticdump (type=data) ; la sortie _search + PIT peut être convertie en NDJSON par un script externe.","danger":"safe","readonly":true,"priv":"read sur l'index"}}}]},"rosetta/monitoring.json":{"category":"Supervision et diagnostic","categoryId":"monitoring","order":12,"concepts":[{"id":"current-locks","title":"Lister les verrous actuels","aliases":["locks","lock list","lock list","current locks","active locks"],"engines":{"oracle":{"command":"SELECT s.sid, s.serial#, l.type, DECODE(l.lmode,0,'None',1,'Null',2,'Row-S',3,'Row-X',4,'Share',5,'S/Row-X',6,'Exclusive') AS lmode, o.object_name, s.username, s.blocking_session\nFROM v$lock l\nJOIN v$session s ON s.sid = l.sid\nLEFT JOIN v$locked_object lo ON lo.session_id = l.sid\nLEFT JOIN dba_objects o ON o.object_id = lo.object_id\nORDER BY s.sid;","note":"Affiche les verrous actifs et la session bloquante (blocking_session) via v$lock et v$locked_object.","danger":"safe","readonly":true,"priv":"SELECT sur v$lock, v$session"},"postgres":{"command":"SELECT l.locktype, l.mode, l.granted, a.pid, a.usename, a.state, a.wait_event_type, a.query\nFROM pg_locks l\nJOIN pg_stat_activity a ON a.pid = l.pid\nWHERE l.pid <> pg_backend_pid()\nORDER BY l.granted, a.pid;","note":"Joint pg_locks à pg_stat_activity ; les lignes avec granted=false indiquent des verrous en attente (bloqués).","danger":"safe","readonly":true,"priv":"rôle pg_monitor (pour voir toutes les requêtes)"},"mysql":{"command":"SELECT ENGINE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, THREAD_ID\nFROM performance_schema.data_locks;\n-- For waiters: SELECT * FROM performance_schema.data_lock_waits;","note":"performance_schema.data_locks liste les verrous InnoDB actuels et data_lock_waits les relations d'attente.","danger":"safe","readonly":true,"priv":"SELECT sur performance_schema","minver":"8.0"},"mssql":{"command":"SELECT tl.resource_type, tl.resource_database_id, tl.request_mode, tl.request_status, tl.request_session_id, es.login_name, er.blocking_session_id\nFROM sys.dm_tran_locks tl\nLEFT JOIN sys.dm_exec_sessions es ON es.session_id = tl.request_session_id\nLEFT JOIN sys.dm_exec_requests er ON er.session_id = tl.request_session_id\nORDER BY tl.request_session_id;","note":"sys.dm_tran_locks affiche les demandes de verrou actuelles ; celles avec request_status = WAIT sont des demandes bloquées.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"$or\": [ { \"waitingForLock\": true }, { \"locks\": { \"$exists\": true } } ] })","note":"db.currentOp affiche les opérations en attente de verrou (waitingForLock) et celles qui détiennent des verrous ; MongoDB verrouille au niveau document/collection, pas au niveau ligne.","danger":"safe","priv":"inprog / rôle clusterMonitor"},"redis":{"command":"# Not applicable: Redis has no row/table lock concept; commands run sequentially on a single thread. For blocking analysis, use the SLOWLOG and LATENCY commands.","note":"Redis étant mono-thread, il n'y a pas de liste de verrous traditionnelle.","danger":"safe"},"elasticsearch":{"command":"GET _cluster/pending_tasks","note":"Elasticsearch n'a pas de verrous de ligne ; ceci liste les tâches de modification du cluster en file d'attente sur le master (pending tasks).","danger":"safe","readonly":true}}},{"id":"long-operations","title":"Suivre la progression des opérations longues","aliases":["progress","operation progress","long operations","long-running operations","longops"],"engines":{"oracle":{"command":"SELECT sid, serial#, opname, target, sofar, totalwork,\n       ROUND(sofar / NULLIF(totalwork,0) * 100, 2) AS pct_done,\n       time_remaining, elapsed_seconds\nFROM v$session_longops\nWHERE totalwork > 0 AND sofar < totalwork\nORDER BY start_time;","note":"v$session_longops affiche le pourcentage de progression et le temps restant (time_remaining) des opérations de plus de 6 secondes.","danger":"safe","readonly":true,"priv":"SELECT sur v$session_longops"},"postgres":{"command":"SELECT pid, phase, heap_blks_total, heap_blks_scanned,\n       ROUND(heap_blks_scanned::numeric / NULLIF(heap_blks_total,0) * 100, 2) AS pct\nFROM pg_stat_progress_vacuum;\n-- Others: pg_stat_progress_create_index, _cluster, _analyze, _copy, _basebackup","note":"Les vues pg_stat_progress_* indiquent la progression des opérations VACUUM, CREATE INDEX, CLUSTER, ANALYZE et COPY.","danger":"safe","readonly":true,"minver":"9.6"},"mysql":{"command":"SELECT t.processlist_id, st.event_name, st.work_completed, st.work_estimated,\n       ROUND(st.work_completed / NULLIF(st.work_estimated,0) * 100, 2) AS pct\nFROM performance_schema.events_stages_current st\nJOIN performance_schema.threads t ON t.thread_id = st.thread_id\nWHERE st.work_estimated > 0;","note":"Lorsque l'instrumentation des stages est activée, events_stages_current indique la progression via work_completed/work_estimated (par ex. ALTER TABLE).","danger":"safe","readonly":true,"priv":"SELECT sur performance_schema"},"mssql":{"command":"SELECT session_id, command, percent_complete,\n       DATEADD(ms, estimated_completion_time, GETDATE()) AS est_finish,\n       total_elapsed_time / 1000 AS elapsed_sec\nFROM sys.dm_exec_requests\nWHERE percent_complete > 0\nORDER BY percent_complete;","note":"sys.dm_exec_requests.percent_complete donne le pourcentage pour des opérations comme BACKUP, RESTORE, DBCC et les reconstructions d'index.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"active\": true, \"secs_running\": { \"$gte\": 3 } })","note":"Dans la sortie de db.currentOp, les opérations longues et, pour les constructions d'index, le champ 'progress' (done/total) indiquent la progression.","danger":"safe","priv":"inprog / rôle clusterMonitor"},"redis":{"command":"INFO persistence\n# Keys: rdb_bgsave_in_progress, aof_rewrite_in_progress, rdb_last_bgsave_status, loading, loading_loaded_perc","note":"La section INFO persistence indique si des opérations de sauvegarde ou de réécriture RDB/AOF sont en cours, ainsi que le pourcentage de chargement.","danger":"safe","readonly":true},"elasticsearch":{"command":"GET _tasks?detailed=true&actions=*reindex,*update/byquery,*delete/byquery\n# For recovery/shard relocation: GET _cat/recovery?v&active_only=true","note":"_tasks?detailed affiche l'état des tâches longues (reindex, update_by_query) ; _cat/recovery affiche la progression de la récupération des shards actifs.","danger":"safe","readonly":true}}},{"id":"session-trace","title":"Tracer une session / requête (activer trace / profiling)","aliases":["trace","enable tracing","profiling","session trace","query tracing"],"engines":{"oracle":{"command":"-- For a specific session (10046 event tracing):\nBEGIN\n  DBMS_MONITOR.SESSION_TRACE_ENABLE(session_id => 123, serial_num => 4567, waits => TRUE, binds => TRUE);\nEND;\n/\n-- To disable: DBMS_MONITOR.SESSION_TRACE_DISABLE(123, 4567);","note":"DBMS_MONITOR.SESSION_TRACE_ENABLE produit un fichier de trace 10046 pour la session ; désactivez-le toujours une fois terminé (la trace ajoute de la surcharge et consomme du disque).","danger":"caution","priv":"EXECUTE sur DBMS_MONITOR"},"postgres":{"command":"LOAD 'auto_explain';\nSET auto_explain.log_min_duration = 0;\nSET auto_explain.log_analyze = true;\n-- Simple alternative: SET log_min_duration_statement = 0;","note":"auto_explain écrit les plans de requêtes, et log_min_duration_statement les requêtes lentes/toutes les requêtes, dans le fichier journal ; une valeur de 0 journalise toutes les requêtes, surveillez donc le volume du journal.","danger":"caution","priv":"SUPERUSER (pour LOAD) ou SET au niveau session"},"mysql":{"command":"-- performance_schema approach (recommended):\nUPDATE performance_schema.setup_consumers SET ENABLED='YES' WHERE NAME LIKE 'events_statements%';\n-- Legacy/simple method (being deprecated):\nSET profiling = 1;\n-- run the query, then:\nSHOW PROFILES;","note":"La trace des requêtes se fait avec les events_statements de performance_schema ; la méthode SET profiling est ancienne (dépréciée) mais donne une mesure rapide par session.","danger":"caution","priv":"SELECT/UPDATE sur performance_schema"},"mssql":{"command":"CREATE EVENT SESSION [trace_slow] ON SERVER\nADD EVENT sqlserver.sql_statement_completed(\n  WHERE duration > 1000000)\nADD TARGET package0.event_file(SET filename=N'trace_slow.xel');\nGO\nALTER EVENT SESSION [trace_slow] ON SERVER STATE = START;","note":"Extended Events (XEvents) est la méthode de trace moderne ; créez la session, démarrez-la avec START et arrêtez-la avec STOP/DROP une fois terminé.","danger":"caution","priv":"ALTER ANY EVENT SESSION"},"mongodb":{"command":"db.setProfilingLevel(2)\n// 1 = slow operations only (slowms threshold), 0 = off\n// Results: db.system.profile.find().sort({ ts: -1 }).limit(10)","note":"setProfilingLevel(2) enregistre toutes les opérations dans la collection system.profile ; en production, 1 (lentes uniquement) est préférable, car le niveau 2 ajoute de la surcharge.","danger":"caution","priv":"dbAdmin / enableProfiler"},"redis":{"command":"MONITOR\n# Streams all commands arriving at the server in real time. Exit: Ctrl-C","note":"MONITOR affiche toutes les commandes en direct mais provoque une forte baisse de performances ; utilisez-le brièvement et avec prudence en production. Alternative : SLOWLOG GET.","danger":"caution"},"elasticsearch":{"command":"GET my-index/_search\n{\n  \"profile\": true,\n  \"query\": { \"match\": { \"title\": \"elastic\" } }\n}\n// Persistent slow query tracing: the index.search.slowlog.threshold.query.warn setting","note":"\"profile\": true donne la décomposition du temps de requête/agrégation d'une recherche unique ; pour une surveillance continue, configurez des seuils slowlog.","danger":"safe","readonly":true}}},{"id":"top-wait-events","title":"Afficher les principaux événements d'attente","aliases":["wait events","wait event list","top waits","waits"],"engines":{"oracle":{"command":"SELECT event, total_waits, time_waited, ROUND(time_waited/100,2) AS sec_waited\nFROM v$system_event\nWHERE wait_class <> 'Idle'\nORDER BY time_waited DESC\nFETCH FIRST 20 ROWS ONLY;\n-- For real-time analysis: v$active_session_history (ASH)","note":"v$system_event donne les événements d'attente cumulés ; v$active_session_history donne l'échantillonnage des sessions actives des dernières minutes (nécessite la licence Diagnostics Pack).","danger":"safe","readonly":true,"priv":"SELECT sur v$system_event"},"postgres":{"command":"SELECT wait_event_type, wait_event, count(*)\nFROM pg_stat_activity\nWHERE wait_event IS NOT NULL AND state = 'active'\nGROUP BY 1, 2\nORDER BY 3 DESC;","note":"pg_stat_activity affiche l'état d'attente actuel ; l'échantillonner de façon répétée (polling) révèle les événements d'attente les plus fréquents.","danger":"safe","readonly":true,"priv":"rôle pg_monitor"},"mysql":{"command":"SELECT event_name, count_star, sum_timer_wait,\n       ROUND(sum_timer_wait/1000000000000, 2) AS sec_waited\nFROM performance_schema.events_waits_summary_global_by_event_name\nWHERE count_star > 0\nORDER BY sum_timer_wait DESC\nLIMIT 20;","note":"events_waits_summary_global_by_event_name donne les temps d'attente totaux par événement (en picosecondes) ; les instruments wait doivent être activés.","danger":"safe","readonly":true,"priv":"SELECT sur performance_schema"},"mssql":{"command":"SELECT wait_type, waiting_tasks_count, wait_time_ms,\n       signal_wait_time_ms, wait_time_ms - signal_wait_time_ms AS resource_wait_ms\nFROM sys.dm_os_wait_stats\nWHERE wait_time_ms > 0\nORDER BY wait_time_ms DESC;","note":"sys.dm_os_wait_stats affiche les types d'attente cumulés depuis le démarrage de l'instance ; il peut être réinitialisé avec DBCC SQLPERF.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.serverStatus().wiredTiger.concurrentTransactions\n// For general waits/congestion:\n// db.serverStatus().globalLock  and  db.serverStatus().wiredTiger.cache","note":"WiredTiger n'a pas d'événements d'attente classiques ; l'épuisement de concurrentTransactions (tickets), globalLock.currentQueue et la pression sur le cache indiquent une congestion.","danger":"safe","priv":"rôle clusterMonitor"},"redis":{"command":"CONFIG SET latency-monitor-threshold 100\nLATENCY LATEST\nLATENCY HISTORY command\n# For the slowest commands: SLOWLOG GET 10","note":"Une fois le seuil du latency monitor défini, LATENCY LATEST/HISTORY indique les événements qui causent le plus de latence.","danger":"safe"},"elasticsearch":{"command":"GET _nodes/hot_threads","note":"_nodes/hot_threads révèle les goulots d'étranglement en donnant les stack traces des threads les plus consommateurs de CPU.","danger":"safe","readonly":true}}},{"id":"os-resource","title":"Consulter l'utilisation des ressources du serveur depuis la DB (CPU/IO)","aliases":["os resource","cpu io","resource usage","server resources","system metrics"],"engines":{"oracle":{"command":"SELECT stat_name, value FROM v$osstat\nWHERE stat_name IN ('NUM_CPUS','BUSY_TIME','IDLE_TIME','IOWAIT_TIME','PHYSICAL_MEMORY_BYTES');\n-- Real-time metrics: SELECT metric_name, value FROM v$sysmetric WHERE group_id = 2;","note":"v$osstat donne les compteurs CPU/mémoire de l'OS ; v$sysmetric donne les métriques DB en temps réel (CPU, IOPS) du dernier intervalle.","danger":"safe","readonly":true,"priv":"SELECT sur v$osstat, v$sysmetric"},"postgres":{"command":"SELECT backend_type, object, context, reads, writes, hits, evictions\nFROM pg_stat_io\nORDER BY reads DESC;\n-- Note: CPU usage is not visible from inside the DB; an OS tool (top, sar) is required.","note":"pg_stat_io donne les compteurs de lecture/écriture/succès cache ; PostgreSQL n'expose pas l'utilisation CPU, les outils du système d'exploitation sont donc nécessaires.","danger":"safe","readonly":true,"minver":"16"},"mysql":{"command":"SELECT * FROM sys.host_summary;\n-- For InnoDB I/O and thread details:\nSHOW ENGINE INNODB STATUS\\G","note":"Les vues de synthèse du schéma sys (host_summary) donnent une ventilation IO/latence ; SHOW ENGINE INNODB STATUS affiche l'activité du buffer pool et des IO.","danger":"safe","readonly":true,"priv":"SELECT sur sys, PROCESS"},"mssql":{"command":"SELECT object_name, counter_name, cntr_value\nFROM sys.dm_os_performance_counters\nWHERE counter_name LIKE '%CPU%' OR counter_name LIKE '%Page life expectancy%';\n-- CPU history: sys.dm_os_ring_buffers (RING_BUFFER_SCHEDULER_MONITOR)","note":"sys.dm_os_performance_counters donne les compteurs de performance de la DB ; dm_os_ring_buffers donne la part CPU SQL/autres processus sur les ~256 dernières minutes.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.serverStatus().metrics\n// Relevant fields: .opcounters, .network, .wiredTiger.cache, .mem, .extra_info.page_faults","note":"serverStatus() donne les métriques mémoire, réseau, opcounters et cache WiredTiger ; pour le détail CPU/IO de l'OS, utilisez mongostat ou les outils du système d'exploitation.","danger":"safe","priv":"rôle clusterMonitor"},"redis":{"command":"INFO cpu\nINFO stats\n# used_cpu_sys, used_cpu_user, instantaneous_ops_per_sec, total_net_input_bytes","note":"INFO cpu indique la consommation CPU du processus Redis, et INFO stats les opérations par seconde et le trafic réseau.","danger":"safe","readonly":true},"elasticsearch":{"command":"GET _nodes/stats/os,process\n// Look at the os.cpu.percent, os.mem, process.cpu.percent, fs (disk) fields","note":"_nodes/stats/os,process donne le pourcentage CPU, la mémoire et les métriques de processus par nœud ; utilisez la section fs pour le disque.","danger":"safe","readonly":true}}}]},"rosetta/automation.json":{"category":"Automatisation et audit","categoryId":"automation","order":13,"concepts":[{"id":"schedule-job","title":"Créer un job planifié","aliases":["schedule job","scheduled job","cron","create job","scheduled task"],"engines":{"oracle":{"command":"BEGIN\n  DBMS_SCHEDULER.CREATE_JOB(\n    job_name        => 'NIGHTLY_MAINT',\n    job_type        => 'PLSQL_BLOCK',\n    job_action      => 'BEGIN my_maintenance_proc; END;',\n    start_date      => SYSTIMESTAMP,\n    repeat_interval => 'FREQ=DAILY; BYHOUR=3',\n    enabled         => TRUE);\nEND;\n/","note":"DBMS_SCHEDULER.CREATE_JOB définit un job planifié ; repeat_interval fixe la récurrence avec une expression de calendrier.","danger":"caution","priv":"CREATE JOB"},"postgres":{"command":"SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM ANALYZE');\n-- Requires the pg_cron extension. Alternative: pgAgent.","note":"L'extension pg_cron définit des jobs en syntaxe cron standard avec cron.schedule ; si l'extension n'est pas installée, utilisez pgAgent ou le cron du système d'exploitation.","danger":"caution","priv":"pg_cron installé ; généralement superuser/propriétaire de l'extension"},"mysql":{"command":"SET GLOBAL event_scheduler = ON;\nCREATE EVENT nightly_cleanup\nON SCHEDULE EVERY 1 DAY STARTS '2026-01-01 03:00:00'\nDO\n  DELETE FROM logs WHERE created_at < NOW() - INTERVAL 30 DAY;","note":"CREATE EVENT s'exécute via l'event scheduler ; la variable globale event_scheduler doit valoir ON, sinon les événements ne se déclenchent pas.","danger":"caution","priv":"EVENT"},"mssql":{"command":"USE msdb;\nEXEC dbo.sp_add_job @job_name = N'NightlyMaint';\nEXEC dbo.sp_add_jobstep @job_name = N'NightlyMaint', @step_name = N'Step1',\n     @subsystem = N'TSQL', @command = N'EXEC dbo.usp_Maintenance;';\nEXEC dbo.sp_add_schedule @schedule_name = N'Daily3AM',\n     @freq_type = 4, @freq_interval = 1, @active_start_time = 030000;\nEXEC dbo.sp_attach_schedule @job_name = N'NightlyMaint', @schedule_name = N'Daily3AM';\nEXEC dbo.sp_add_jobserver @job_name = N'NightlyMaint';","note":"Les jobs SQL Server Agent sont créés avec les procédures msdb : le job, l'étape et la planification sont définis puis rattachés au serveur. Le service SQL Server Agent doit être en cours d'exécution.","danger":"caution","priv":"SQLAgentUserRole ou sysadmin"},"mongodb":{"command":"# Not applicable: MongoDB has no built-in scheduler. Use external cron/systemd timers, an application scheduler or a MongoDB Atlas Scheduled Trigger.","note":"L'édition Community n'a pas de jobs planifiés ; un planificateur externe ou un Atlas Trigger est nécessaire.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no built-in job scheduler. Use EXPIRE/keyspace notifications for key expiry and external cron for periodic tasks.","note":"Redis n'exécute pas de jobs planifiés ; cela se gère avec les TTL/notifications keyspace et un cron externe.","danger":"safe"},"elasticsearch":{"command":"PUT _slm/policy/daily-snapshot\n{\n  \"schedule\": \"0 30 1 * * ?\",\n  \"name\": \"<snap-{now/d}>\",\n  \"repository\": \"backup_repo\",\n  \"config\": { \"indices\": [\"*\"] },\n  \"retention\": { \"expire_after\": \"30d\" }\n}","note":"SLM (Snapshot Lifecycle Management) prend des snapshots planifiés ; ILM sert au cycle de vie des données et Watcher à l'automatisation avancée.","danger":"caution","priv":"privilège cluster manage_slm"}}},{"id":"list-jobs","title":"Lister les jobs planifiés / consulter leur état","aliases":["list jobs","show jobs","job status","scheduled jobs","job history"],"engines":{"oracle":{"command":"SELECT job_name, enabled, state, last_start_date, next_run_date\nFROM dba_scheduler_jobs\nORDER BY next_run_date;\n-- Run history: SELECT job_name, status, actual_start_date, run_duration\n--                 FROM dba_scheduler_job_run_details ORDER BY log_date DESC;","note":"dba_scheduler_jobs affiche les jobs définis et leurs états ; dba_scheduler_job_run_details affiche les résultats (succès/échec) des exécutions passées.","danger":"safe","readonly":true,"priv":"SELECT sur dba_scheduler_jobs"},"postgres":{"command":"SELECT jobid, schedule, command, nodename, active FROM cron.job;\n-- Run history:\nSELECT jobid, status, return_message, start_time, end_time\nFROM cron.job_run_details ORDER BY start_time DESC LIMIT 20;","note":"Avec pg_cron, cron.job liste les jobs définis et cron.job_run_details donne l'état et le message de retour de chaque exécution.","danger":"safe","readonly":true,"priv":"pg_cron installé ; accès au schéma cron"},"mysql":{"command":"SELECT event_schema, event_name, status, interval_value, interval_field, last_executed\nFROM information_schema.events\nORDER BY last_executed DESC;","note":"information_schema.events liste les événements définis, leur état (ENABLED/DISABLED) et l'heure de dernière exécution.","danger":"safe","readonly":true,"priv":"EVENT / SELECT"},"mssql":{"command":"SELECT j.name, j.enabled, h.run_status, h.run_date, h.run_time\nFROM msdb.dbo.sysjobs j\nLEFT JOIN msdb.dbo.sysjobhistory h ON h.job_id = j.job_id AND h.step_id = 0\nORDER BY h.run_date DESC, h.run_time DESC;","note":"msdb.dbo.sysjobs donne les jobs Agent définis, et sysjobhistory l'historique d'exécution et le résultat (run_status).","danger":"safe","readonly":true,"priv":"SQLAgentReaderRole / accès à msdb"},"mongodb":{"command":"# Not applicable: there is no built-in scheduler, so there are no jobs to list. If Atlas Triggers are used, view them in the Atlas UI/API.","note":"MongoDB Community n'a pas de table de jobs ; les Atlas Triggers se gèrent dans l'interface Atlas.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of scheduled jobs, so there are no jobs to list.","note":"Redis n'inclut pas de planificateur de jobs ; il n'y a aucun job à lister.","danger":"safe"},"elasticsearch":{"command":"GET _slm/policy\nGET _ilm/policy\n// For Watcher states: GET _watcher/stats  and  GET _watcher/_query/watches","note":"_slm/policy liste les politiques de snapshot, _ilm/policy les politiques de cycle de vie, et _watcher/stats affiche l'état d'exécution de Watcher.","danger":"safe","readonly":true,"priv":"privilège read_slm / read_ilm"}}},{"id":"enable-audit","title":"Activer l'audit / la journalisation","aliases":["audit","auditing","logging","enable audit","audit log"],"engines":{"oracle":{"command":"-- Unified Auditing (12c+):\nCREATE AUDIT POLICY table_audit\n  ACTIONS SELECT, INSERT, UPDATE, DELETE ON hr.employees;\nAUDIT POLICY table_audit;\n-- Monitoring: SELECT * FROM unified_audit_trail ORDER BY event_timestamp DESC;","note":"Avec Unified Auditing, une politique est créée puis activée avec AUDIT ; les enregistrements sont conservés dans la vue unified_audit_trail.","danger":"caution","priv":"AUDIT SYSTEM / rôle AUDIT_ADMIN","minver":"12.1"},"postgres":{"command":"-- pgaudit extension (recommended; first shared_preload_libraries = 'pgaudit' + restart + CREATE EXTENSION pgaudit):\nALTER SYSTEM SET pgaudit.log = 'write, ddl';\nSELECT pg_reload_conf();\n-- Simple built-in method:\nALTER SYSTEM SET log_statement = 'ddl';\nSELECT pg_reload_conf();","note":"pgaudit fournit une journalisation d'audit détaillée ; réglez-le avec ALTER SYSTEM pour l'appliquer à tout le serveur (un SET au niveau session n'audite que cette session). Sans l'extension, une journalisation de base s'active avec log_statement ('ddl','mod','all'). Surveillez le volume des journaux.","danger":"caution","priv":"superuser (ALTER SYSTEM)"},"mysql":{"command":"-- Enterprise/MariaDB audit plugin:\nINSTALL PLUGIN audit_log SONAME 'audit_log.so';\nSET GLOBAL audit_log_policy = 'ALL';\n-- Simple alternative (all queries):\nSET GLOBAL general_log = 'ON';","note":"Le plugin audit_log (Enterprise, ou server_audit dans MariaDB) conserve une piste d'audit détaillée ; general_log journalise toutes les requêtes mais dégrade les performances.","danger":"caution","priv":"SUPER / SYSTEM_VARIABLES_ADMIN"},"mssql":{"command":"USE master;\nCREATE SERVER AUDIT Audit_A TO FILE (FILEPATH = 'C:\\\\audit\\\\');\nALTER SERVER AUDIT Audit_A WITH (STATE = ON);\nUSE mydb;\nCREATE DATABASE AUDIT SPECIFICATION db_spec\n  FOR SERVER AUDIT Audit_A\n  ADD (SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo BY public)\n  WITH (STATE = ON);","note":"Créez et activez d'abord une cible SERVER AUDIT (fichier/journal d'événements), puis définissez les événements à suivre avec une AUDIT SPECIFICATION de serveur ou de base.","danger":"caution","priv":"ALTER ANY SERVER AUDIT / CONTROL SERVER"},"mongodb":{"command":"# MongoDB Enterprise / Atlas only. Configuration (mongod.conf):\n#   auditLog:\n#     destination: file\n#     format: JSON\n#     path: /var/log/mongodb/audit.json\n#     filter: '{ atype: { $in: [\"authCheck\",\"createCollection\",\"dropCollection\"] } }'\n# mongod --auditDestination file --auditPath /var/log/mongodb/audit.json","note":"L'audit n'est disponible que dans Enterprise et Atlas ; il s'active avec la section auditLog de la configuration mongod. Il n'existe pas dans l'édition Community.","danger":"caution"},"redis":{"command":"ACL LOG\n# Shows rejected authentication/command/key access attempts.\n# To reset: ACL LOG RESET\n# For general command auditing: an external proxy or MONITOR (with care).","note":"ACL LOG enregistre automatiquement les événements de sécurité (échecs d'authentification, commandes/clés non autorisées) ; l'audit complet des commandes n'est pas intégré.","danger":"safe","priv":"permission ACL (admin par défaut)"},"elasticsearch":{"command":"# elasticsearch.yml (on all nodes, restart required):\nxpack.security.audit.enabled: true\n# Optional event filter:\nxpack.security.audit.logfile.events.include: [ \"authentication_failed\", \"access_denied\" ]","note":"La journalisation d'audit s'active avec xpack.security.audit.enabled ; elle se règle dans elasticsearch.yml et nécessite un redémarrage du nœud (non dynamique). Nécessite une licence commerciale.","danger":"caution"}}},{"id":"current-config-diff","title":"Lister les paramètres non par défaut","aliases":["config diff","changed settings","non-default settings","modified settings","modified config"],"engines":{"oracle":{"command":"SELECT name, value, isdefault\nFROM v$parameter\nWHERE isdefault = 'FALSE'\nORDER BY name;","note":"Les lignes de v$parameter avec isdefault = 'FALSE' indiquent les paramètres différents de la valeur par défaut (définis explicitement).","danger":"safe","readonly":true,"priv":"SELECT sur v$parameter"},"postgres":{"command":"SELECT name, setting, unit, source\nFROM pg_settings\nWHERE source NOT IN ('default', 'override')\nORDER BY name;","note":"Si pg_settings.source vaut autre chose que 'default' et 'override', le paramètre a été modifié par un fichier de configuration, la ligne de commande ou la session.","danger":"safe","readonly":true},"mysql":{"command":"SHOW GLOBAL VARIABLES;\n-- MySQL does not automatically compare variables with defaults; with performance_schema\n-- you can see the source of each setting:\nSELECT VARIABLE_NAME, VARIABLE_SOURCE, VARIABLE_VALUE\nFROM performance_schema.variables_info\nWHERE VARIABLE_SOURCE <> 'COMPILED';","note":"SHOW GLOBAL VARIABLES renvoie tous les paramètres ; il n'affiche pas directement l'écart par rapport aux valeurs par défaut. La colonne VARIABLE_SOURCE de performance_schema.variables_info indique les modifications autres que la valeur compilée par défaut (fichier, ligne de commande, dynamique).","danger":"safe","readonly":true,"minver":"8.0"},"mssql":{"command":"SELECT name, value, value_in_use, is_dynamic, is_advanced\nFROM sys.configurations\nWHERE value <> value_in_use\nORDER BY name;\n-- value <> value_in_use: changes not yet RECONFIGUREd or awaiting a restart (is_dynamic = 0).","note":"sys.configurations renvoie les paramètres du serveur ; si value et value_in_use diffèrent, une modification n'a pas été appliquée avec RECONFIGURE (ou attend un redémarrage). sys.configurations ne stocke pas la valeur par défaut (minimum/maximum n'est que la plage autorisée) ; pour voir les écarts par rapport aux valeurs par défaut, comparez avec les valeurs par défaut documentées.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.adminCommand({ getCmdLineOpts: 1 })\n// The 'parsed' field shows settings explicitly provided (configuration file/command line) beyond the defaults.","note":"La section 'parsed' de la commande getCmdLineOpts ne liste que les paramètres explicitement définis (non par défaut).","danger":"safe","priv":"privilège clusterMonitor / hostInfo"},"redis":{"command":"CONFIG GET *\n# Redis lists all settings but does not compare them with defaults.\n# To see differences, compare the output with the default redis.conf values for your version.","note":"CONFIG GET * renvoie tous les paramètres en vigueur ; il n'offre pas de comparaison intégrée avec les valeurs par défaut, comparez donc la sortie avec les valeurs par défaut du redis.conf de la version concernée.","danger":"safe","readonly":true},"elasticsearch":{"command":"GET _cluster/settings\n// Returns only explicitly set persistent and transient settings.\n// For everything including defaults: GET _cluster/settings?include_defaults=true&flat_settings=true","note":"_cluster/settings renvoie les paramètres de cluster non par défaut (persistent/transient) ; avec include_defaults=true, les valeurs par défaut sont aussi affichées pour comparaison.","danger":"safe","readonly":true}}}]},"rosetta/lifecycle.json":{"category":"Installation et mise à niveau","categoryId":"lifecycle","order":14,"concepts":[{"id":"check-version","title":"Afficher la version installée et le niveau de patch","aliases":["version","patch level","server version","patch level check","release","build"],"engines":{"oracle":{"command":"SELECT * FROM v$version;\n-- Registered RU/patch history (SQL layer):\nSELECT patch_id, action, status, action_time\nFROM dba_registry_sqlpatch ORDER BY action_time;\n-- From the OS shell (patches installed in ORACLE_HOME):\n-- $ORACLE_HOME/OPatch/opatch lspatches","note":"La bannière de v$version donne la version. dba_registry_sqlpatch indique si les Release Updates (RU) et les patchs one-off appliqués ont été enregistrés dans la couche SQL, tandis que 'opatch lspatches' affiche les patchs binaires du home.","danger":"safe","readonly":true,"priv":"SELECT sur v$version, dba_registry_sqlpatch"},"postgres":{"command":"SELECT version();\n-- Numeric version only:\nSHOW server_version;\nSELECT current_setting('server_version_num');","note":"version() renvoie le texte complet, informations de build comprises. PostgreSQL n'a pas de numéro de patch distinct ; la version mineure (par ex. 16.3) représente le niveau de correctifs.","danger":"safe","readonly":true},"mysql":{"command":"SELECT VERSION();\n-- Detailed build information:\nSHOW VARIABLES LIKE 'version%';","note":"VERSION() renvoie par ex. '8.0.36' ; le dernier composant est le niveau de patch (release). La variable version_comment indique si le build est Community/Enterprise ou MariaDB.","danger":"safe","readonly":true},"mssql":{"command":"SELECT @@VERSION;\n-- Structured version fields:\nSELECT SERVERPROPERTY('ProductVersion')       AS version,\n       SERVERPROPERTY('ProductLevel')         AS service_pack,\n       SERVERPROPERTY('ProductUpdateLevel')   AS cu_level,\n       SERVERPROPERTY('ProductUpdateReference') AS kb_number,\n       SERVERPROPERTY('Edition')              AS edition;","note":"ProductUpdateLevel donne la Cumulative Update appliquée (par ex. CU12), et ProductUpdateReference le numéro KB associé. ProductLevel affiche 'RTM' ou, sur les anciennes versions, 'SP'.","danger":"safe","readonly":true},"mongodb":{"command":"db.version()\n// Detailed build information:\ndb.serverBuildInfo()\n// or: db.adminCommand({ buildInfo: 1 })","note":"db.version() renvoie la chaîne de version (par ex. '7.0.11'). Dans MongoDB, les correctifs sont publiés sous forme de nouvelle version de patch ; il n'existe pas de notion distincte de « niveau de patch ».","danger":"safe"},"redis":{"command":"INFO server\n# redis-cli one-liner: redis-cli INFO server | grep redis_version","note":"Le champ redis_version de la sortie INFO server donne la version installée. Dans Redis, les correctifs sont livrés sous forme de nouvelle version de patch (par ex. 7.2.5) ; le champ redis_git_sha1 indique aussi l'identifiant de build.","danger":"safe"},"elasticsearch":{"command":"GET /\n# curl: curl -s http://localhost:9200/","note":"Le point de terminaison racine renvoie version.number (par ex. '8.14.1'), lucene_version et build_flavor. Pour voir les versions de tous les nœuds : GET _cat/nodes?v&h=name,version","danger":"safe","readonly":true}}},{"id":"check-upgrade-readiness","title":"Vérification de compatibilité/préparation avant mise à niveau","aliases":["upgrade readiness","preupgrade","compatibility check","readiness","compatibility check","deprecation"],"engines":{"oracle":{"command":"-- Recommended method for 19c and later (AutoUpgrade analyze mode):\n-- $ java -jar autoupgrade.jar -config db.cfg -mode analyze\n-- Legacy/alternative: preupgrade script (produces a report only):\n-- $ $NEW_ORACLE_HOME/jdk/bin/java -jar $NEW_ORACLE_HOME/rdbms/admin/preupgrade.jar FILE TEXT DIR /tmp","note":"Le mode 'analyze' d'AutoUpgrade produit un rapport préalable sans modifier les données ; il liste les avertissements à corriger et les éléments qui seront résolus automatiquement. Exécutez-le toujours avant la mise à niveau proprement dite.","danger":"safe"},"postgres":{"command":"# With the new version's binaries, compatibility check only (moves no data):\n$ pg_upgrade \\\n    --old-datadir=/var/lib/pgsql/16/data \\\n    --new-datadir=/var/lib/pgsql/17/data \\\n    --old-bindir=/usr/pgsql-16/bin \\\n    --new-bindir=/usr/pgsql-17/bin \\\n    --check","note":"--check vérifie que la mise à niveau est faisable sans modifier les données (signale les extensions manquantes, les types de données incompatibles, etc.). --check peut aussi s'exécuter pendant que l'ancien cluster tourne ; la mise à niveau effective se fait avec les deux clusters arrêtés.","danger":"safe"},"mysql":{"command":"-- 8.0 and later: Upgrade Checker utility (MySQL Shell):\n-- $ mysqlsh -- util check-for-server-upgrade { --user=root --host=localhost }\n-- Table consistency for legacy 5.7->8.0:\n$ mysqlcheck --all-databases --check-upgrade -u root -p","note":"util.checkForServerUpgrade() de MySQL Shell signale les fonctionnalités supprimées, les conflits de mots réservés et les définitions incompatibles. À partir de 8.0, le serveur effectue lui-même la mise à niveau du dictionnaire au démarrage ; un mysql_upgrade séparé n'est plus nécessaire.","danger":"safe"},"mssql":{"command":"-- Run from the desktop with Data Migration Assistant (DMA):\n-- DmaCmd.exe /AssessmentName=... /AssessmentDatabases=...\n-- (Produces compatibility/deprecated feature and blocker reports.)","note":"Data Migration Assistant (DMA) a remplacé l'ancien Upgrade Advisor ; il signale par base les fonctionnalités dépréciées, les changements incompatibles et les blocages. Exécutez-le avant la mise à niveau et traitez les constats.","danger":"safe"},"mongodb":{"command":"// Verify the current Feature Compatibility Version (FCV):\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// Compatibility items (deprecations) in the release notes are reviewed manually.","note":"Avant de mettre à niveau la version binaire, la FCV doit être égale à la version actuelle ; sinon la mise à niveau est bloquée. MongoDB n'a pas de « scanner de dépréciations » automatique ; les notes de compatibilité des versions sont examinées manuellement.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no separate pre-upgrade readiness tool.\n# Checklist: read the target version's release notes for RDB/AOF persistence format,\n# changed configuration directives, and removed commands/arguments.","note":"Les mises à niveau Redis sont généralement compatibles ascendantes ; faute de scanner de préparation officiel, vérifiez manuellement dans les notes de version le format des fichiers RDB/AOF, les directives de configuration supprimées et les changements de commandes.","danger":"safe"},"elasticsearch":{"command":"GET _migration/deprecations\n# For a specific component: GET <index>/_migration/deprecations","note":"L'API Deprecation Info liste, en 'critical' / 'warning', les paramètres de cluster, de nœud, d'index et ML qui poseront problème lors du passage à la version majeure suivante. Kibana Upgrade Assistant présente les mêmes données visuellement.","danger":"safe","readonly":true}}},{"id":"apply-patch","title":"Appliquer un patch","aliases":["apply patch","patching","patch","minor upgrade","hotfix","ru apply"],"engines":{"oracle":{"command":"# 1) Shut down the database, apply the binary patch with OPatch:\n$ $ORACLE_HOME/OPatch/opatch apply\n# 2) Open the database and load the SQL layer changes:\n$ $ORACLE_HOME/OPatch/datapatch -verbose","note":"opatch apply met à jour les binaires de l'Oracle Home ; datapatch applique ensuite les modifications du dictionnaire/SQL sur la base ouverte et les enregistre dans dba_registry_sqlpatch. Sur RAC, les patchs installables en rolling peuvent être appliqués nœud par nœud.","danger":"caution","priv":"OS : propriétaire de l'ORACLE_HOME ; SQL : SYSDBA"},"postgres":{"command":"# Minor version patch = replace binaries + restart (no dump needed):\n$ sudo systemctl stop postgresql-16\n$ sudo dnf update postgresql16-server   # or apt upgrade\n$ sudo systemctl start postgresql-16","note":"Les patchs mineurs d'une même série majeure (16.3 -> 16.4) ne modifient pas le format des données : mettre à jour le paquet et redémarrer le service suffit ; ni pg_upgrade ni dump/restore n'est nécessaire.","danger":"caution"},"mysql":{"command":"# Minor/patch update: update the package and restart the service\n$ sudo systemctl stop mysqld\n$ sudo dnf update mysql-community-server   # or apt upgrade\n$ sudo systemctl start mysqld","note":"MySQL 8.0+ met à niveau le dictionnaire de données automatiquement au démarrage ; aucune étape mysql_upgrade séparée n'est nécessaire. En réplication, patchez d'abord les réplicas, puis la source.","danger":"caution"},"mssql":{"command":"-- Run the Cumulative Update (CU) or GDR security update installer:\n-- SQLServer2022-KB<no>-x64.exe /quiet /IAcceptSQLServerLicenseTerms\n--   /Action=Patch /InstanceName=MSSQLSERVER\n-- The instance then restarts; verify the version with SERVERPROPERTY.","note":"Les mises à jour CU (Cumulative Update) et GDR (General Distribution Release) s'appliquent avec le programme d'installation setup et nécessitent un redémarrage de l'instance. Dans un groupe Always On, patchez d'abord les réplicas secondaires et basculez pour minimiser l'interruption.","danger":"caution"},"mongodb":{"command":"// 'Rolling' patch on a replica set: one node at a time\n// 1) Stop mongod, replace the binary with the new patch version, restart\n// 2) Secondaries first, the primary last\n// Step down the primary manually before patching it:\nrs.stepDown()","note":"Les mises à jour de patch d'une même version majeure (7.0.x) se font en rolling, les secondaires d'abord. Le primaire est patché après passation avec rs.stepDown(), ce qui minimise l'interruption des écritures.","danger":"caution"},"redis":{"command":"# Patch via rolling restart in a replicated deployment:\n# 1) Stop one replica node, install the new version, start it, wait for sync\n# 2) Repeat for the remaining replicas\n# 3) Finally, fail over the master and patch it:\nredis-cli -h <replica> REPLICAOF NO ONE   # promote the upgraded replica","note":"Les versions de patch (7.2.x) s'appliquent par redémarrage rolling : les réplicas sont mis à niveau et resynchronisés d'abord, puis un réplica est promu master et l'ancien master est patché. Sentinel/Cluster automatise le failover.","danger":"caution"},"elasticsearch":{"command":"# Rolling upgrade (patch within the same major version), per node:\nPUT _cluster/settings\n{ \"persistent\": { \"cluster.routing.allocation.enable\": \"primaries\" } }\n# -> stop the node, update the package, start the node, then:\nPUT _cluster/settings\n{ \"persistent\": { \"cluster.routing.allocation.enable\": null } }","note":"Les mises à niveau de patch/mineures se font en rolling upgrade : l'allocation des shards est restreinte, les nœuds sont mis à jour et redémarrés un par un, puis l'allocation est réactivée et l'on attend que le cluster repasse en green.","danger":"caution"}}},{"id":"upgrade-major","title":"Mise à niveau de version majeure","aliases":["major upgrade","major version","version upgrade","release upgrade","version upgrade"],"engines":{"oracle":{"command":"# Recommended: AutoUpgrade (analyze + fixups + deploy in a single flow):\n$ java -jar $NEW_ORACLE_HOME/rdbms/admin/autoupgrade.jar -config db.cfg -mode deploy\n# Graphical alternative: DBUA (Database Upgrade Assistant)\n$ $NEW_ORACLE_HOME/bin/dbua","note":"AutoUpgrade exécute l'analyse, les pre-fixups, la mise à niveau et les post-étapes en une seule commande ; il peut traiter plusieurs bases à la suite pour réduire l'interruption. DBUA fait de même avec une interface guidée. Une sauvegarde complète préalable est obligatoire.","danger":"danger","priv":"SYSDBA + OS : propriétaire du nouvel ORACLE_HOME"},"postgres":{"command":"# pg_upgrade to the new major version (with both clusters stopped):\n$ pg_upgrade \\\n    --old-datadir=/var/lib/pgsql/16/data \\\n    --new-datadir=/var/lib/pgsql/17/data \\\n    --old-bindir=/usr/pgsql-16/bin \\\n    --new-bindir=/usr/pgsql-17/bin \\\n    --link   # fast migration with hard links\n# Then gather statistics (analyze_new_cluster.sh was removed in PG14):\n$ vacuumdb --all --analyze-in-stages","note":"pg_upgrade effectue la migration de version majeure (16 -> 17) sur place ; le mode --link réduit la durée en créant des liens physiques au lieu de copier les fichiers, mais rend l'ancien cluster inutilisable. Les statistiques doivent être recollectées après la migration.","danger":"danger"},"mysql":{"command":"# In-place upgrade (same datadir, new binaries):\n$ sudo systemctl stop mysqld\n$ sudo dnf install mysql-community-server-8.4   # replacing the old package\n$ sudo systemctl start mysqld   # the server upgrades the dictionary automatically","note":"À partir de MySQL 8.x, les mises à niveau majeures sur place sont prises en charge : un serveur démarré avec les nouveaux binaires convertit automatiquement le dictionnaire de données et les tables système (l'ancienne étape séparée mysql_upgrade a été supprimée). Montez d'une version majeure à la fois.","danger":"danger"},"mssql":{"command":"-- In-place major version upgrade (with setup):\n-- setup.exe /q /ACTION=upgrade /INSTANCENAME=MSSQLSERVER\n--   /IAcceptSQLServerLicenseTerms\n-- Alternative: install a new instance side by side and move the databases (restore/attach).","note":"setup /ACTION=upgrade met à niveau l'instance sur place vers la nouvelle version majeure. La méthode la plus sûre est une installation côte à côte, puis le déplacement des bases par backup/restore et l'élévation du niveau de compatibilité dans une étape distincte.","danger":"danger"},"mongodb":{"command":"// Stepwise major upgrade (e.g. 6.0 -> 7.0), one major version at a time:\n// 1) Roll the binary to 7.0 on all nodes\n// 2) Once the cluster is stable, raise the FCV:\ndb.adminCommand({ setFeatureCompatibilityVersion: \"7.0\", confirm: true })","note":"Une mise à niveau majeure MongoDB se fait en deux phases : d'abord les binaires de tous les nœuds passent en rolling à la nouvelle version, puis, une fois le cluster stable, la FCV est élevée à la nouvelle version. On ne peut monter que d'une version majeure à la fois ; la FCV est gérée séparément pour permettre un retour arrière.","danger":"danger"},"redis":{"command":"# Major upgrade via a replica (no downtime):\n# 1) Create a replica on the new version, wait for full sync from the master\n# 2) Prepare the application; fail over the master:\nredis-cli -h <new-replica> REPLICAOF NO ONE   # make the new version the master\n# 3) Upgrade the old master, attach it as a replica of the new master","note":"Une migration de version majeure Redis (6.x -> 7.x) se fait en installant un réplica sur la nouvelle version, en le laissant se synchroniser, puis en le promouvant master ; l'interruption des écritures est ainsi réduite quasiment à zéro. Les fichiers RDB sont compatibles avec les versions ultérieures.","danger":"danger"},"elasticsearch":{"command":"# Major versions cannot be skipped; one major at a time, via the last minor first (e.g. 6.8 -> 7.17 -> 8.x):\n# 1) Upgrade to the last minor (7.17), resolve the GET _migration/deprecations warnings\n# 2) Rolling upgrade, per node: set allocation to \"primaries\" -> update the node -> start -> wait for green\n# Alternative: full-cluster restart (stop all nodes -> upgrade -> start together); this also moves only one major","note":"Elasticsearch ne permet par aucune méthode de sauter des versions majeures : le rolling upgrade comme le redémarrage complet du cluster ne passent qu'à la majeure suivante (n -> n+1) et exigent que la source soit sur la dernière mineure (par ex. 7.17). Les indices créés deux majeures plus tôt (par ex. en 6.x) ne peuvent pas être ouverts dans la nouvelle version ; ils doivent être reconstruits avec _reindex avant la migration (8.x ne peut les lire que de façon limitée, comme archive indices en lecture seule). Prenez un snapshot et traitez les avertissements de dépréciation avant la migration.","danger":"danger"}}},{"id":"set-compatibility","title":"Définir le niveau de compatibilité / la version des fonctionnalités","aliases":["compatibility level","database compatibility level","feature version","fcv","compatible","compat level"],"engines":{"oracle":{"command":"-- Raise the COMPATIBLE initialization parameter (irreversible!):\nALTER SYSTEM SET COMPATIBLE = '19.0.0' SCOPE=SPFILE;\n-- Then restart the instance. To see the current value:\nSHOW PARAMETER compatible;","note":"Le paramètre COMPATIBLE détermine quelles fonctionnalités de version et quels formats de fichiers sont activés. Une fois élevé et l'instance redémarrée, il NE PEUT PLUS ÊTRE ABAISSÉ (le downgrade est bloqué) ; élevez-le donc progressivement, jusqu'à confirmation du succès de la mise à niveau.","danger":"danger","priv":"ALTER SYSTEM (SYSDBA)"},"postgres":{"command":"# Not applicable: PostgreSQL has no database compatibility level.\n# If legacy behavior is needed, set the relevant GUCs (e.g. SET standard_conforming_strings=off)\n# or manage the major migration with dump/restore.","note":"PostgreSQL n'a pas de notion de « niveau de compatibilité » comme SQL Server/Oracle ; le comportement est directement lié à la version binaire. La migration entre versions se fait avec pg_dump/pg_restore ou pg_upgrade.","danger":"safe"},"mysql":{"command":"# Not applicable: MySQL has no configurable database compatibility level.\n# Behavioral differences are managed with sql_mode:\nSET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';","note":"MySQL n'a pas de niveau de compatibilité par version ; le comportement SQL dépend surtout de la version binaire et du réglage sql_mode. Ajustez finement sql_mode pour préserver le comportement des applications existantes.","danger":"caution"},"mssql":{"command":"-- Set the database compatibility level (e.g. SQL Server 2022 = 160):\nALTER DATABASE Sales SET COMPATIBILITY_LEVEL = 160;\n-- To see the current value:\nSELECT name, compatibility_level FROM sys.databases;","note":"COMPATIBILITY_LEVEL détermine le comportement de version de l'optimiseur de requêtes et des fonctionnalités du langage (150=2019, 160=2022). Après la mise à niveau de l'instance, les bases peuvent rester à l'ancien niveau ; il est recommandé de l'élever progressivement en surveillant les régressions de plans. Il peut être abaissé de nouveau.","danger":"caution"},"mongodb":{"command":"// Set the Feature Compatibility Version (FCV):\ndb.adminCommand({ setFeatureCompatibilityVersion: \"7.0\", confirm: true })\n// Read the current value:\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })","note":"La FCV contrôle le moment où les changements de format des données persistantes et les nouvelles fonctionnalités d'une version sont activés. Élevez-la après la mise à niveau binaire, cluster stable ; l'abaisser n'est possible que si la même version binaire prend encore en charge la FCV inférieure.","danger":"caution","priv":"rôle clusterManager / root"},"redis":{"command":"# Not applicable: Redis has no compatibility level / feature version concept.\n# Behavior depends directly on the running binary version.","note":"Redis n'a pas de niveau de compatibilité configurable ; l'ensemble des fonctionnalités dépend entièrement de la version en cours d'exécution. La compatibilité n'est assurée qu'au niveau du format de fichier RDB/AOF, dans le sens ascendant.","danger":"safe"},"elasticsearch":{"command":"# Not applicable: Elasticsearch has no configurable compatibility level.\n# An index is tied to the format of the version it was created in; very old indices\n# cannot be read in the new version and require a reindex.","note":"Elasticsearch n'a pas de réglage de niveau de compatibilité ; chaque index porte le format de la version majeure dans laquelle il a été créé. Les indices créés dans la version majeure précédente sont lisibles, les plus anciens doivent être migrés vers la nouvelle version avec _reindex.","danger":"safe"}}},{"id":"post-upgrade-tasks","title":"Tâches post-mise à niveau (recompilation/analyse)","aliases":["post upgrade","post-upgrade","recompile","analyze","refresh statistics","invalid objects"],"engines":{"oracle":{"command":"-- 1) Compile invalid objects in parallel:\n@?/rdbms/admin/utlrp.sql\n-- 2) Refresh dictionary/fixed object statistics:\nEXEC DBMS_STATS.GATHER_DICTIONARY_STATS;\nEXEC DBMS_STATS.GATHER_FIXED_OBJECTS_STATS;\n-- Check remaining invalid objects:\nSELECT owner, object_name, object_type FROM dba_objects WHERE status='INVALID';","note":"utlrp.sql recompile les packages/vues/triggers devenus INVALID après la mise à niveau. Collecter ensuite les statistiques du dictionnaire et des fixed objects permet à l'optimiseur de produire des plans sains dans la nouvelle version.","danger":"caution","priv":"SYSDBA"},"postgres":{"command":"# 1) Regather statistics after pg_upgrade (mandatory):\n$ vacuumdb --all --analyze-in-stages\n-- 2) Bring extensions to the new version:\nALTER EXTENSION postgis UPDATE;\n-- 3) If the collation changed, rebuild text indexes (can take long):\nREINDEX DATABASE database_name;","note":"pg_upgrade (PG17 et antérieur) ne transfère pas les statistiques du planificateur ; analyze-in-stages rend le système vite utilisable en collectant d'abord des statistiques grossières, puis complètes. Les extensions doivent être actualisées avec ALTER EXTENSION ... UPDATE, et les index avec REINDEX si la collation a changé.","danger":"caution"},"mysql":{"command":"-- 8.0+ dictionary upgrade happens automatically at startup; additionally:\nANALYZE TABLE customer;   -- refresh statistics of critical tables\n-- Legacy 5.7 -> if needed on first startup:\n-- $ mysql_upgrade -u root -p   (before 8.0.16)","note":"Depuis MySQL 8.0.16, la fonction de mysql_upgrade a été intégrée au serveur et ne s'exécute donc plus séparément. Après la mise à niveau, actualiser les statistiques avec ANALYZE TABLE sur les tables volumineuses/critiques préserve la qualité des plans.","danger":"safe"},"mssql":{"command":"-- 1) Correct page/row usage counters:\nDBCC UPDATEUSAGE(Sales);\n-- 2) Refresh statistics (sp_updatestats uses sampling; FULLSCAN for critical tables):\nEXEC sp_updatestats;\n-- UPDATE STATISTICS dbo.CriticalTable WITH FULLSCAN;\n-- 3) Raise the compatibility level to the new version (while monitoring):\nALTER DATABASE Sales SET COMPATIBILITY_LEVEL = 160;","note":"Après la mise à niveau, DBCC UPDATEUSAGE corrige les compteurs d'espace/de lignes inexacts hérités des anciennes versions, et sp_updatestats actualise les statistiques. Lors de l'élévation du niveau de compatibilité, surveillez les régressions de plans que peut causer le nouveau Cardinality Estimator (CE).","danger":"caution"},"mongodb":{"command":"// 1) Verify the FCV upgrade:\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// 2) Check indexes and compatibility warnings in the server logs:\ndb.customer.getIndexes()\n// If an index is incompatible: dropIndex + createIndex (rolling); do NOT use reIndex","note":"Après une mise à niveau majeure, vérifiez que la FCV a atteint la version cible. La reconstruction des index n'est pas une étape de routine ; si un index est mentionné dans les notes de version ou produit un avertissement dans le journal, recréez-le avec dropIndex + createIndex. En 6.0+, reIndex ne fonctionne que sur un standalone et est interdit sur les membres d'un replica set.","danger":"caution"},"redis":{"command":"# Usually no extra task is needed. Recommended: verify persistence and replication:\nredis-cli INFO replication   # is the master/replica link healthy\nredis-cli INFO persistence   # rdb_last_bgsave_status / aof_last_bgrewrite_status\nredis-cli DBSIZE             # is the key count as expected","note":"Après une mise à niveau Redis, il n'y a pas de tâche de compilation/analyse de schéma ; il s'agit de vérifier que la réplication est rétablie, que la persistance RDB/AOF est saine et que le nombre de clés est préservé.","danger":"safe"},"elasticsearch":{"command":"# Verify cluster health:\nGET _cluster/health\n# Move indices created in an old major version that can no longer be read:\nPOST _reindex\n{ \"source\": { \"index\": \"old_index\" }, \"dest\": { \"index\": \"new_index\" } }","note":"Après la mise à niveau, vérifiez que l'état du cluster est 'green' et que tous les shards sont alloués. Les indices dans un format antérieur à la version majeure précédente ne sont pas lisibles dans la nouvelle version ; ils doivent être migrés au format actuel avec _reindex.","danger":"caution"}}}]},"rosetta/migration.json":{"category":"Migration et transfert","categoryId":"migration","order":15,"concepts":[{"id":"compare-schema","title":"Comparer deux schémas/environnements (schema diff)","aliases":["schema diff","diff","compare schemas","compare","schema difference","ddl diff"],"engines":{"oracle":{"command":"-- Extract the schema DDL from both environments and take a text diff\n-- (generate deterministic DDL with DBMS_METADATA)\nEXEC DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,'SQLTERMINATOR',TRUE);\nEXEC DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,'STORAGE',FALSE);\nSELECT DBMS_METADATA.GET_DDL('TABLE', table_name) FROM user_tables ORDER BY table_name;\n-- Spool the output to a file in both environments and compare with diff/fc.\n\n-- Export the schema as a changelog with SQLcl's Liquibase integration:\n-- SQL> lb generate-schema\n-- Commercial: the Oracle SQL Developer 'Database Diff' tool is also used.","note":"Oracle n'a pas de comparaison de schémas intégrée en une commande. La méthode courante consiste à générer un DDL déterministe des deux environnements avec DBMS_METADATA.GET_DDL (attributs STORAGE/segment désactivés) et à faire un diff texte. Pour une comparaison visuelle/automatisée, utilisez l'assistant Database Diff de SQL Developer ou SQLcl.","danger":"safe","readonly":true,"priv":"SELECT_CATALOG_ROLE ou accès en lecture au schéma"},"postgres":{"command":"# Dump only the schema from both databases and diff them\npg_dump --schema-only --no-owner --no-privileges sourcedb > source_schema.sql\npg_dump --schema-only --no-owner --no-privileges targetdb  > target_schema.sql\ndiff -u source_schema.sql target_schema.sql\n\n# Tool that generates applicable ALTERs: apgdiff (Another PostgreSQL Diff Tool)\n# apgdiff source_schema.sql target_schema.sql > migration.sql\n# Alternatives: migra, Liquibase diffChangeLog","note":"Le plus simple est de prendre la sortie de pg_dump --schema-only dans les deux environnements et de la comparer avec diff. apgdiff et migra sont des outils qui transforment directement l'écart en instructions ALTER exécutables. Ajoutez --no-owner --no-privileges pour une sortie déterministe.","danger":"safe","readonly":true,"priv":"SELECT / lecture du schéma (accès aux objets pour pg_dump)"},"mysql":{"command":"# Schema/object comparison with MySQL Utilities (5.x):\nmysqldbcompare --server1=root:password@host1 --server2=root:password@host2 \\\n  shop:shop --difftype=sql --run-all-tests\n\n# Built-in method: take CREATE statements from both environments and diff them\nmysqldump --no-data --skip-comments shop > source_schema.sql\n# SHOW CREATE TABLE customer;  (to see a single table definition)","note":"L'outil mysqldbcompare de MySQL Utilities signale les différences d'objets/de schéma entre deux serveurs (sous forme d'instructions applicables avec difftype=sql). La méthode sans outil consiste à prendre la sortie de mysqldump --no-data (schéma seul) ou de SHOW CREATE TABLE des deux environnements et à comparer avec diff. mysqldbcompare est désormais déprécié ; pour MariaDB/8.x, Liquibase ou Skeema est préférable.","danger":"safe","readonly":true,"priv":"SELECT, SHOW VIEW sur les deux schémas"},"mssql":{"command":"-- Schema comparison/deploy report with SqlPackage (what will change before applying):\nsqlpackage /Action:DeployReport /SourceFile:source.dacpac \\\n  /TargetConnectionString:\"Server=target;Database=shop;Integrated Security=true\" \\\n  /OutputPath:diff_report.xml\n\n-- For data differences (row level), the tablediff utility:\ntablediff -sourceserver S1 -sourcedatabase shop -sourcetable customer \\\n  -destinationserver S2 -destinationdatabase shop -destinationtable customer","note":"Pour les différences de schéma, SqlPackage /Action:DeployReport renvoie sous forme de rapport XML les modifications à appliquer à la cible ; /Action:Script génère un script de diff exécutable. « Schema Compare » de Visual Studio / Azure Data Studio utilise le même moteur. Pour les différences de données au niveau ligne, il existe l'utilitaire en ligne de commande tablediff.","danger":"safe","readonly":true,"priv":"VIEW DEFINITION (accès en lecture à la cible pour DeployReport)"},"mongodb":{"command":"// The schema is implicit; extract and compare the field structure of both environments.\n// Read the $jsonSchema validator definition (collection rules):\ndb.getCollectionInfos({ name: \"customer\" })[0].options.validator\n\n// External tools for inferred schema comparison by sampling:\n// mongodb-schema (npm) or the Compass 'Schema' tab\n// Take samples with mongoexport and diff the field/type distribution of both environments.","note":"MongoDB est sans schéma ; il n'y a pas de DDL fixe. La comparaison se fait de deux façons : comparer les règles du validateur $jsonSchema défini sur la collection (via getCollectionInfos), ou générer un schéma déduit des données avec mongodb-schema / Compass et comparer les distributions de champs et de types. Pour les index, faites un diff de la sortie de db.collection.getIndexes().","danger":"safe","readonly":true,"priv":"read (getCollectionInfos, listIndexes)"},"redis":{"command":"# Not applicable: Redis is a schemaless key-value store; there are no\n# table/column definitions (DDL) to compare.\n# Closest equivalent: compare the key space (namespace) structure\n# redis-cli --scan --pattern 'customer:*' | sort > source_keys.txt","note":"Non applicable : Redis n'a pas de notion de schéma (table/colonne/DDL), un schema diff classique ne s'applique donc pas. Si une comparaison structurelle est nécessaire, seul le motif de nommage des clés (namespace) peut être listé avec SCAN et comparé.","danger":"safe","readonly":true},"elasticsearch":{"command":"# Fetch the index mapping from both clusters and compare\ncurl -s 'http://source:9200/customer/_mapping?pretty' > source_mapping.json\ncurl -s 'http://target:9200/customer/_mapping?pretty'  > target_mapping.json\ndiff -u source_mapping.json target_mapping.json\n\n# To compare settings as well:\n# GET customer/_settings   and   GET customer/_mapping","note":"Dans Elasticsearch, l'équivalent d'un schéma est le mapping de l'index. La méthode standard consiste à prendre la sortie de GET <index>/_mapping (et _settings si nécessaire) des deux clusters et à comparer avec diff. Les champs de mapping étant en grande partie immuables, un écart détecté peut nécessiter un nouvel index + _reindex.","danger":"safe","readonly":true,"priv":"view_index_metadata / monitor"}}},{"id":"export-for-migration","title":"Export cohérent pour migration","aliases":["export","consistent export","consistent export","take a dump","migration export","snapshot"],"engines":{"oracle":{"command":"# Consistent logical export with Data Pump (at a single SCN)\nexpdp system/password schemas=shop directory=DIR_DMP \\\n  dumpfile=shop_%U.dmp logfile=shop_exp.log \\\n  flashback_scn=<scn> parallel=4\n\n# Current SCN for point-in-time consistency:\n# SELECT current_scn FROM v$database;\n# Alternative: FLASHBACK_TIME=SYSTIMESTAMP","note":"Data Pump (expdp) est l'outil d'export logique standard pour la migration. Pour la cohérence en lecture, passez FLASHBACK_SCN=<scn> (ou FLASHBACK_TIME) afin que toutes les tables soient exportées de façon cohérente à un même SCN ; c'est essentiel sur un système en production. PARALLEL accélère l'export et %U produit plusieurs fichiers.","danger":"caution","readonly":true,"priv":"DATAPUMP_EXP_FULL_DATABASE, objet DIRECTORY","minver":"10g (Data Pump)"},"postgres":{"command":"# Single compressed file in custom format (restorable in parallel)\npg_dump -Fc -Z6 -f shop.dump shop\n\n# Directory format (parallel dump + parallel restore):\npg_dump -Fd -j4 -f shop_dir shop\n\n# For the whole cluster + roles/tablespaces:\n# pg_dumpall --globals-only > roles.sql","note":"Le format pg_dump -Fc (custom) ou -Fd (directory) est préférable pour la migration ; tous deux permettent un chargement sélectif/parallèle avec pg_restore. pg_dump s'exécutant dans une seule transaction, sa sortie est automatiquement un snapshot cohérent. Les objets de niveau cluster comme les rôles et tablespaces sont exportés séparément avec pg_dumpall --globals-only.","danger":"safe","readonly":true,"priv":"SELECT (sur tous les objets à exporter)"},"mysql":{"command":"# Consistent, lock-free export for InnoDB\nmysqldump --single-transaction --quick --routines --triggers --events \\\n  --source-data=2 shop > shop.sql\n\n# Note: older versions use --master-data=2 instead of --source-data.\n# Parallel, fast alternative: mydumper -B shop -o ./backup --threads 4","note":"Sur InnoDB, --single-transaction fournit un snapshot cohérent unique et ne verrouille pas les tables. --source-data=2 (anciennement --master-data=2) écrit en commentaire en tête de sortie la position binlog/le GTID nécessaire pour démarrer la réplication/le CDC ; c'est indispensable pour une bascule sans interruption. Pour de gros volumes, mydumper fonctionne en parallèle.","danger":"safe","readonly":true,"priv":"SELECT, LOCK TABLES, RELOAD, REPLICATION CLIENT (pour --source-data)"},"mssql":{"command":"-- Full backup for physical migration (to the same/compatible version):\nBACKUP DATABASE shop TO DISK = 'C:\\backup\\shop.bak' WITH COMPRESSION, CHECKSUM;\n\n-- Portable/version-independent logical export (BACPAC = schema+data):\n-- sqlpackage /Action:Export /SourceConnectionString:\"...\" /TargetFile:shop.bacpac\n\n-- For a single table: bcp shop.dbo.customer out customer.dat -n -S server -T","note":"Pour migrer vers une version de SQL Server identique ou plus récente, BACKUP ... RESTORE est la voie la plus fiable (intégrité vérifiée avec CHECKSUM). Pour une migration indépendante de la version/plateforme ou vers Azure SQL, l'export du schéma et des données dans un seul fichier .bacpac avec SqlPackage /Action:Export est préférable. bcp sert pour les tables individuelles.","danger":"safe","readonly":true,"priv":"BACKUP DATABASE (db_backupoperator) ou accès en lecture pour Export"},"mongodb":{"command":"# Consistent binary (BSON) export; point-in-time consistency with the oplog\nmongodump --uri=\"mongodb://source:27017\" \\\n  --oplog --out=./backup --numParallelCollections=4\n\n# --oplog captures changes made during the dump (requires a replica set);\n# it works only with a FULL dump and cannot be combined with --db / --collection.\n# On restore, mongorestore --oplogReplay brings it to a single point in time.\n# If you need a single database: take it with --db=shop (without the oplog) or use --nsInclude='shop.*' on restore.","note":"mongodump est l'export binaire (BSON) standard pour la migration. Sur un replica set, l'option --oplog capture depuis l'oplog les modifications écrites pendant le dump, ce qui donne un instant cohérent (à combiner avec mongorestore --oplogReplay) ; cette option n'est valable que pour un dump complet. Pour la migration d'un grand cluster au niveau cluster, mongosync/les outils en direct sont plus adaptés.","danger":"safe","readonly":true,"priv":"read (read sur la db local pour l'oplog / clusterMonitor)"},"redis":{"command":"# Create an RDB snapshot in the background (non-blocking)\nredis-cli BGSAVE\n# Check for completion:\nredis-cli INFO persistence | grep rdb_bgsave_in_progress\n\n# Move the resulting file (path: CONFIG GET dir + dbfilename)\n# e.g. /var/lib/redis/dump.rdb  ->  copy to the target server","note":"Dans Redis, l'export cohérent est le snapshot RDB. BGSAVE produit en arrière-plan, via fork, un dump.rdb cohérent sans bloquer le processus principal (SAVE bloque et ne doit pas être utilisé). Le chemin du fichier s'obtient avec CONFIG GET dir et dbfilename ; migrez en copiant ce dump.rdb dans le répertoire de données du serveur cible.","danger":"safe","readonly":true},"elasticsearch":{"command":"# 1) Register a snapshot repository (shared file system / S3)\nPUT _snapshot/migration_repo\n{ \"type\": \"fs\", \"settings\": { \"location\": \"/mnt/es_backup\" } }\n\n# 2) Take a consistent snapshot\nPUT _snapshot/migration_repo/migration_1?wait_for_completion=true\n{ \"indices\": \"customer,orders\", \"include_global_state\": false }","note":"Dans Elasticsearch, la méthode d'export cohérent pour la migration est le snapshot. On enregistre d'abord un repository (fs, S3, GCS, etc.), puis on prend le snapshot ; les snapshots sont incrémentaux et capturent un état cohérent au moment de leur création. elasticdump peut aussi servir pour de petits volumes/un seul index, mais le snapshot est la norme.","danger":"safe","readonly":true,"priv":"manage / create_snapshot"}}},{"id":"import-for-migration","title":"Importer dans la cible","aliases":["import","import data","restore","load","load into target","load"],"engines":{"oracle":{"command":"# Import with Data Pump; remap schema and tablespace\nimpdp system/password directory=DIR_DMP dumpfile=shop_%U.dmp \\\n  logfile=shop_imp.log \\\n  remap_schema=shop:shop_new \\\n  remap_tablespace=USERS:DATA parallel=4\n\n# For existing objects: TABLE_EXISTS_ACTION=TRUNCATE|APPEND|REPLACE","note":"impdp charge dans la cible le fichier .dmp produit par expdp. REMAP_SCHEMA déplace la source vers un autre schéma et REMAP_TABLESPACE vers un autre tablespace ; ils sont souvent utilisés lors des migrations entre environnements. Le comportement pour les tables existantes se règle avec TABLE_EXISTS_ACTION, et la vitesse avec PARALLEL.","danger":"caution","priv":"DATAPUMP_IMP_FULL_DATABASE, quota sur le tablespace cible"},"postgres":{"command":"# Parallel restore (from custom/directory format)\npg_restore -j4 -d targetdb --no-owner --clean --if-exists shop.dump\n\n# A specific table only: pg_restore -d targetdb -t customer shop.dump\n# For a plain .sql dump:  psql -d targetdb -f shop.sql","note":"Avec pg_restore -j, les dumps au format custom/directory sont chargés en parallèle (plusieurs processus) ; cela accélère nettement le chargement des grandes bases. --clean --if-exists supprime d'abord les objets dans la cible, et --no-owner s'adapte à une autre structure de rôles. Les dumps SQL simples se chargent avec psql -f (sans parallélisme).","danger":"caution","priv":"privilèges CREATE / propriétaire sur la base cible"},"mysql":{"command":"# Load a plain SQL dump\nmysql --binary-mode shop < shop.sql\n\n# Parallel/fast load (if taken with mydumper):\nmyloader --directory=./backup --database=shop --threads=4 --overwrite-tables\n\n# Tip: SET FOREIGN_KEY_CHECKS=0; unique_checks=0 before loading speeds it up.","note":"Un dump SQL mysqldump se charge en le redirigeant vers le client mysql (mysql shop < dump.sql). Pour un chargement parallèle et bien plus rapide, un répertoire produit par mydumper se restaure avec myloader --threads. Désactiver FOREIGN_KEY_CHECKS et UNIQUE_CHECKS pendant le chargement améliore les performances.","danger":"caution","priv":"INSERT, CREATE (et DROP, pour --overwrite/--clean)"},"mssql":{"command":"-- Restore from a physical backup (move file paths to the new environment)\nRESTORE DATABASE shop FROM DISK = 'C:\\backup\\shop.bak'\nWITH MOVE 'shop_data' TO 'D:\\data\\shop.mdf',\n     MOVE 'shop_log'  TO 'D:\\log\\shop.ldf', REPLACE;\n\n-- Import from a BACPAC (schema+data):\n-- sqlpackage /Action:Import /TargetConnectionString:\"...\" /SourceFile:shop.bacpac","note":"Une sauvegarde .bak se charge avec RESTORE DATABASE ; lors d'un passage à une autre disposition des disques, WITH MOVE redirige les fichiers de données/journal vers de nouveaux chemins. Pour une cible indépendante de la version/Azure SQL, SqlPackage /Action:Import crée le fichier .bacpac (schéma + données) en tant que nouvelle base.","danger":"caution","priv":"dbcreator/sysadmin pour RESTORE ; CREATE DATABASE pour Import"},"mongodb":{"command":"# Restore the BSON backup; replay the oplog to land on a single point in time\nmongorestore --uri=\"mongodb://target:27017\" --oplogReplay \\\n  --numParallelCollections=4 ./backup\n\n# Renaming: --nsFrom='shop.*' --nsTo='shop_new.*'\n# Drop existing data before loading: --drop","note":"mongorestore charge dans la cible la sortie BSON de mongodump. --oplogReplay rejoue les modifications de l'oplog d'une sauvegarde prise avec --oplog, amenant le dump à un instant cohérent unique. Les noms de base/collection se changent avec --nsFrom/--nsTo, et les données existantes peuvent être effacées avec --drop.","danger":"caution","priv":"readWrite (restore/clusterAdmin pour oplogReplay)"},"redis":{"command":"# Stop the server, place dump.rdb in the data directory, start it again\n# 1) systemctl stop redis  (or SHUTDOWN NOSAVE)\n# 2) cp dump.rdb  <path found with CONFIG GET dir>/dump.rdb\n# 3) systemctl start redis   -> Redis loads the RDB into memory at startup\n\n# Alternative (live): feed a RESP command stream with redis-cli --pipe","note":"Redis ne charge le RDB qu'au démarrage. Le serveur cible est donc arrêté, le dump.rdb migré est placé dans le répertoire de données (CONFIG GET dir), puis le serveur est redémarré ; au démarrage, la mémoire est alimentée à partir du RDB. Pour charger sans arrêter le serveur, envoyez les clés au format RESP avec redis-cli --pipe.","danger":"caution"},"elasticsearch":{"command":"# Register the same repository on the target cluster, then restore the snapshot\nPUT _snapshot/migration_repo\n{ \"type\": \"fs\", \"settings\": { \"location\": \"/mnt/es_backup\" } }\n\nPOST _snapshot/migration_repo/migration_1/_restore\n{ \"indices\": \"customer,orders\",\n  \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_$1\" }","note":"Sur le cluster cible, enregistrez le même dépôt de snapshots que la source (les fichiers doivent être accessibles), puis restaurez les indices avec _snapshot/.../_restore. Les noms d'index peuvent être changés avec rename_pattern/rename_replacement. Les indices existants ne peuvent pas être restaurés tant qu'ils sont ouverts ; fermez-les ou renommez-les d'abord.","danger":"caution","priv":"manage (create_index, restore)"}}},{"id":"heterogeneous-migration","title":"Migrer vers une autre base de données (hétérogène)","aliases":["heterogeneous","heterogeneous migration","cross-database","different database","convert database","convert"],"engines":{"oracle":{"command":"# Oracle -> PostgreSQL: Ora2Pg (schema + data + PL/SQL conversion)\nora2pg -c ora2pg.conf -t TABLE   -o schema.sql\nora2pg -c ora2pg.conf -t COPY    -o data.sql   # data in COPY format\nora2pg -t SHOW_REPORT --estimate_cost           # migration cost estimate\n\n# Oracle -> (multiple targets) managed/online: AWS DMS or Oracle GoldenGate","note":"Pour une migration hétérogène depuis Oracle (notamment vers PostgreSQL), l'outil open source le plus courant est Ora2Pg : il convertit tables, contraintes, PL/SQL et données dans le dialecte cible ; --estimate_cost estime l'effort de migration. Pour une migration en ligne/à faible interruption, on utilise AWS DMS ou Oracle GoldenGate (avec CDC).","danger":"caution","readonly":true,"priv":"SELECT sur le schéma source (accès en lecture aux vues DBA)"},"postgres":{"command":"# MySQL -> PostgreSQL: pgloader (schema+data+type conversion in one command)\npgloader mysql://app_user:password@source/shop \\\n         postgresql://app_user:password@target/shop\n\n# Also from SQLite/MSSQL sources: with a pgloader ./migration.load script\n# For a managed/online PostgreSQL target: AWS DMS","note":"pgloader est l'outil standard pour migrer vers PostgreSQL depuis d'autres moteurs (MySQL, SQLite, MS SQL) : à partir de chaînes de connexion, il crée le schéma en une commande, mappe les types et copie les données rapidement ; les transformations/mappages de types personnalisés se définissent dans un script .load. Si la source est Oracle, Ora2Pg est préférable ; pour une migration en ligne, AWS DMS.","danger":"caution","priv":"CREATE sur la cible ; SELECT sur la source"},"mysql":{"command":"# PostgreSQL/Oracle/MSSQL -> MySQL: AWS DMS (managed) is the most common path\n# or adapt the source-specific export for MySQL:\n#   pg_dump --inserts ... | (type/syntax fixes) | mysql shop\n\n# MySQL <-> MariaDB (compatible): mysqldump output loads directly\nmysqldump --single-transaction shop | mysql -h target_mariadb shop","note":"Il n'existe pas d'outil ouvert unique et dominant pour migrer vers MySQL depuis d'autres moteurs ; la solution la plus courante est AWS DMS (géré, compatible CDC) ou l'adaptation du dump source (syntaxe/types) puis son chargement avec le client mysql. MySQL et MariaDB sont largement compatibles ; la sortie de mysqldump migre généralement telle quelle.","danger":"caution","priv":"CREATE, INSERT sur la cible"},"mssql":{"command":"# SQL Server -> PostgreSQL: with pgloader\npgloader mssql://app_user:password@source/shop \\\n         postgresql://app_user:password@target/shop\n\n# SQL Server -> PostgreSQL compatibility layer: Babelfish for Aurora PostgreSQL\n# (runs T-SQL and the TDS protocol on top of PostgreSQL)\n# To other targets: SSMA (SQL Server Migration Assistant) or AWS DMS","note":"pgloader (source mssql://) sert à migrer de SQL Server vers PostgreSQL ; si vous voulez conserver le T-SQL sans modifier l'application, Babelfish for Aurora PostgreSQL offre une couche de compatibilité. L'outil SSMA de Microsoft automatise la migration d'autres moteurs (Oracle, MySQL, DB2) vers SQL Server. Pour une migration en ligne, AWS DMS.","danger":"caution","priv":"SELECT sur la source ; CREATE sur la cible"},"mongodb":{"command":"# Relational <-> MongoDB conversion requires schema mapping (ETL):\n#   - Relational Migrator (official MongoDB tool): maps tables to the document model\n#   - or generate JSON from the source -> load with mongoimport\nmongoexport --db=shop --collection=customer --type=csv --fields=id,name > c.csv\n# (To an SQL target: mongoexport CSV -> relational COPY/LOAD DATA)","note":"La migration entre MongoDB et les moteurs SQL nécessite une transformation de modèle ligne<->document (ETL) ; il n'y a pas de commande unique automatique. De SQL vers MongoDB, MongoDB Relational Migrator mappe les tables vers un schéma de documents ; dans les cas simples, générez du CSV/JSON depuis la source et chargez-le avec mongoimport. Dans le sens inverse, la sortie de mongoexport se charge dans la table relationnelle cible avec COPY/LOAD DATA.","danger":"caution","priv":"read sur la source, write sur la cible"},"redis":{"command":"# With RIOT (Redis Input/Output Tools), to/from Redis from other sources:\n#   Relational -> Redis:\nriot db-import --jdbc-url \"jdbc:postgresql://host/shop\" \\\n  --sql \"SELECT * FROM customer\" hset --keyspace customer --keys id\n#   Redis -> another Redis / file:  riot replicate / riot file-export","note":"Dans une migration hétérogène, Redis est généralement la couche de cache, en cible ou en source ; pour charger dans Redis des données issues de sources relationnelles ou de fichiers, on utilise l'outil officiel RIOT de Redis (db-import mappe un résultat SQL vers des structures HSET/SET). Pour Redis vers Redis ou vers un fichier, il existe les commandes riot replicate / file-export.","danger":"caution"},"elasticsearch":{"command":"# Relational/other source -> Elasticsearch: Logstash (JDBC input)\n# logstash.conf: input { jdbc { statement => \"SELECT * FROM customer\" } }\n#                output { elasticsearch { index => \"customer\" } }\n\n# ES -> ES (version/cluster upgrade): reindex from a remote source\nPOST _reindex\n{ \"source\": { \"remote\": { \"host\": \"http://old:9200\" }, \"index\": \"customer\" },\n  \"dest\": { \"index\": \"customer\" } }","note":"L'outil le plus courant pour migrer d'autres bases vers Elasticsearch est Logstash (entrée JDBC/fichier -> sortie elasticsearch) ; les connecteurs/Beats d'Elastic sont aussi utilisés. Pour passer d'un ancien cluster Elasticsearch à un nouveau, le remote reindex (_reindex avec une source 'remote') est courant ; reindex.remote.whitelist doit être défini sur la cible.","danger":"caution","priv":"write/create_index sur la cible"}}},{"id":"setup-cdc-replication","title":"Mettre en place une réplication continue / CDC (bascule sans interruption)","aliases":["cdc","replication","replication","zero-downtime migration","live migration","change data capture"],"engines":{"oracle":{"command":"# CDC-based replication with Oracle GoldenGate (outline)\n# 1) Enable supplemental logging on the source:\nALTER DATABASE ADD SUPPLEMENTAL LOG DATA;\n# 2) Extract (capture changes from redo) and Replicat (apply to the target) processes:\n#   GGSCI> ADD EXTRACT ext1, INTEGRATED TRANLOG, BEGIN NOW\n#   GGSCI> ADD REPLICAT rep1, INTEGRATED\n# Alternative: initial load with Data Pump + incremental capture with GoldenGate","note":"Dans Oracle, l'outil standard pour le CDC sans interruption/hétérogène est Oracle GoldenGate : le processus Extract capture les modifications dans les redo logs et Replicat les applique à la cible ; le supplemental logging doit être activé. Migration typique : le chargement initial se fait avec Data Pump, puis GoldenGate transmet en continu les modifications à la cible, ce qui permet une bascule quasi sans interruption.","danger":"caution","priv":"privilèges d'administrateur GoldenGate, supplemental logging"},"postgres":{"command":"-- Source (publisher): create a publication (wal_level must be logical)\nCREATE PUBLICATION migration_pub FOR ALL TABLES;\n\n-- Target (subscriber): create a subscription (initial copy + continuous stream)\nCREATE SUBSCRIPTION migration_sub\n  CONNECTION 'host=source dbname=shop user=repl password=***'\n  PUBLICATION migration_pub;","note":"PostgreSQL fournit le CDC via la réplication logique intégrée : CREATE PUBLICATION sur la source, CREATE SUBSCRIPTION sur la cible. Au démarrage, la souscription copie les données existantes (copie initiale), puis transmet en continu les modifications du WAL ; wal_level=logical est requis. Pour les scénarios volumineux/complexes, pglogical ou Debezium est préférable.","danger":"caution","priv":"rôle REPLICATION sur la source ; superuser sur la cible pour CREATE SUBSCRIPTION (en PG16+, le rôle pg_create_subscription suffit aussi)","minver":"10 (réplication logique)"},"mysql":{"command":"-- Source: binlog must be enabled and in ROW format (log_bin, binlog_format=ROW)\n-- Grant a replication user: GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';\n\n-- Define the source on the target (start from the binlog position of the initial load):\nCHANGE REPLICATION SOURCE TO\n  SOURCE_HOST='source', SOURCE_USER='repl', SOURCE_PASSWORD='***',\n  SOURCE_LOG_FILE='binlog.000001', SOURCE_LOG_POS=1547;\nSTART REPLICA;","note":"Dans MySQL, une bascule sans interruption se fait par réplication basée sur le binlog. Le chargement initial se fait avec mysqldump --source-data et la position binlog/le GTID est enregistré ; sur la cible, la réplication démarre à cette position avec CHANGE REPLICATION SOURCE TO (syntaxe 8.0.23+ ; anciennement CHANGE MASTER TO). START REPLICA applique les modifications en continu. Une configuration basée sur GTID est plus sûre.","danger":"caution","priv":"REPLICATION SLAVE (utilisateur repl), REPLICATION CLIENT","minver":"8.0.23 (CHANGE REPLICATION SOURCE)"},"mssql":{"command":"-- Option A: Transactional Replication (publisher->distributor->subscriber) — SSMS wizard\n\n-- Option B: Change Data Capture (track changes as tables)\nEXEC sys.sp_cdc_enable_db;              -- enable CDC at database level\nEXEC sys.sp_cdc_enable_table\n  @source_schema = N'dbo', @source_name = N'customer',\n  @role_name = NULL;                    -- enable CDC at table level\n-- Changes are read from the cdc.dbo_customer_CT table and moved to the target.","note":"SQL Server offre deux voies principales pour la réplication continue : pour un flux direct vers la cible, mettez en place la Transactional Replication (publisher/subscriber) ; si vous transférez les modifications avec votre propre ETL, activez Change Data Capture (sp_cdc_enable_db + sp_cdc_enable_table) et lisez les tables _CT. Pour des cibles hétérogènes, le CDC se combine avec AWS DMS/Debezium.","danger":"caution","priv":"sysadmin (pour activer CDC) ; SQL Server Agent doit être en cours d'exécution"},"mongodb":{"command":"// Watch changes live with Change Streams (requires a replica set/sharded cluster)\nconst cs = db.customer.watch([], { fullDocument: \"updateLookup\" });\nwhile (cs.hasNext()) { printjson(cs.next()); }  // apply to the target\n\n// Official tool for managed cluster-to-cluster replication/migration: mongosync\n// mongosync --cluster0 \"mongodb://source\" --cluster1 \"mongodb://target\"","note":"Dans MongoDB, les Change Streams servent au CDC : db.collection.watch() transmet en direct depuis l'oplog les événements insert/update/delete à appliquer à la cible (un replica set est requis). Pour une migration/réplication continue complète de cluster à cluster, l'outil officiel Cluster-to-Cluster Sync de MongoDB, mongosync (non spécifique à Atlas ; il fonctionne aussi avec des clusters autogérés), gère le chargement initial + la synchronisation continue.","danger":"caution","priv":"changeStream (read) ; clusterAdmin pour mongosync"},"redis":{"command":"# Make the target instance a replica of the source (live synchronization)\n# On the target Redis:\nREPLICAOF source_host 6379\n# Monitor sync status:\nINFO replication   # master_link_status must be up\n\n# Once the cutover is complete, make the target standalone:\nREPLICAOF NO ONE","note":"Dans Redis, une bascule sans interruption se fait par réplication native : exécutez REPLICAOF <source host port> (anciennement SLAVEOF) sur le serveur cible ; la cible reçoit d'abord le RDB complet, puis reste synchronisée en continu via le flux de commandes. Vérifiez master_link_status:up avec INFO replication et, une fois la bascule terminée, rendez la cible autonome avec REPLICAOF NO ONE.","danger":"caution"},"elasticsearch":{"command":"# Incremental reindex from the old cluster to the new one within the same version family\nPOST _reindex?wait_for_completion=false\n{ \"source\": { \"remote\": { \"host\": \"http://old:9200\" }, \"index\": \"customer\",\n    \"query\": { \"range\": { \"updated_at\": { \"gt\": \"<last-cursor>\" } } } },\n  \"dest\": { \"index\": \"customer\" } }\n\n# Licensed feature: Cross-Cluster Replication (CCR) — continuous following\n# On the target (follower) cluster: PUT /customer/_ccr/follow { \"remote_cluster\": \"old\", \"leader_index\": \"customer\" }","note":"La véritable fonctionnalité de réplication continue d'Elasticsearch est Cross-Cluster Replication (CCR) : le cluster cible suit le leader index de la source et récupère les modifications en continu (nécessite une licence commerciale/Platinum). Sans licence, une synchronisation quasi continue s'obtient par remote reindex périodique (incrémental, basé sur un champ de temps/séquence).","danger":"caution","priv":"manage/read_ccr pour CCR ; whitelist pour le remote reindex"}}},{"id":"verify-migration","title":"Vérification post-migration (nombre de lignes/checksum)","aliases":["verify","verification","row count","checksum","row count","consistency check"],"engines":{"oracle":{"command":"-- Compare row counts on source and target\nSELECT 'customer' table_name, COUNT(*) row_count FROM customer;\n\n-- To find content differences at object level, DBMS_COMPARISON (via db link)\nBEGIN DBMS_COMPARISON.CREATE_COMPARISON(\n  comparison_name => 'COMPARE_CUSTOMER',\n  schema_name => 'SHOP', object_name => 'CUSTOMER',\n  dblink_name => 'TARGET_DB'); END;\n/\n-- SELECT DBMS_COMPARISON.COMPARE('COMPARE_CUSTOMER', scan_info) FROM dual;","note":"Le contrôle le plus rapide consiste à comparer COUNT(*) par table des deux côtés. Pour trouver les vraies différences de contenu au niveau ligne, utilisez le package DBMS_COMPARISON : il compare les tables source et cible ligne par ligne via un database link et peut signaler/résorber les écarts. Pour les checksums, une empreinte de colonne peut être calculée avec ORA_HASH ou STANDARD_HASH.","danger":"safe","readonly":true,"priv":"SELECT des deux côtés ; EXECUTE sur DBMS_COMPARISON"},"postgres":{"command":"-- Row count\nSELECT count(*) FROM customer;\n\n-- Ordered digest (checksum) of the whole table content — compare on both sides\nSELECT md5(string_agg(t.row_md5, '' ORDER BY t.row_md5)) AS digest\nFROM (SELECT md5(customer::text) AS row_md5 FROM customer) t;","note":"La première étape consiste à comparer count(*) sur chaque table. Pour vérifier le contenu, calculez le md5 de la représentation texte de chaque ligne, concaténez-les dans l'ordre trié et hachez de nouveau ; cela donne un checksum indépendant de l'ordre entre les deux côtés. Pour de gros volumes, calculez l'empreinte uniquement sur les colonnes modifiables/clés ; trier par PK est important pour la reproductibilité.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mysql":{"command":"-- Built-in table checksum (run on both servers and compare)\nCHECKSUM TABLE customer;\n\n-- Row count\nSELECT COUNT(*) FROM customer;\n\n-- Preferred tool for live/large environments (compares row chunks online):\n-- pt-table-checksum h=source,D=shop  (Percona Toolkit)","note":"Dans MySQL, CHECKSUM TABLE donne un checksum unique du contenu de la table ; exécutez-le sur la source et la cible et comparez (moteur/version identiques requis). Utilisez COUNT(*) pour le nombre de lignes. En production et pour la cohérence de la réplication, le standard du secteur est pt-table-checksum de Percona Toolkit ; il compare par chunks, en ligne et avec un faible impact.","danger":"safe","readonly":true,"priv":"SELECT sur la table"},"mssql":{"command":"-- Full row-level comparison (official tool that produces differences)\ntablediff -sourceserver S1 -sourcedatabase shop -sourcetable customer \\\n  -destinationserver S2 -destinationdatabase shop -destinationtable customer\n\n-- Quick aggregate checksum (single query, compare on both sides):\nSELECT COUNT_BIG(*) row_count, CHECKSUM_AGG(BINARY_CHECKSUM(*)) digest FROM dbo.customer;","note":"Dans SQL Server, l'outil officiel en ligne de commande qui trouve les différences au niveau ligne est tablediff (il signale les lignes ajoutées/modifiées et, avec l'option -f, écrit un script de correction). Pour un contrôle global rapide, CHECKSUM_AGG(BINARY_CHECKSUM(*)) donne une empreinte unique de la table ; comparez les deux côtés conjointement avec COUNT_BIG(*) (indépendant de l'ordre des lignes).","danger":"safe","readonly":true,"priv":"SELECT sur les deux tables"},"mongodb":{"command":"// Compare document counts (on source and target)\ndb.customer.countDocuments({});\n\n// For a content checksum, dbHash (md5 digest at collection/DB level):\ndb.runCommand({ dbHash: 1, collections: [\"customer\"] });\n// -> compare the md5 field across both environments.","note":"Le premier contrôle consiste à comparer le nombre de documents des deux côtés avec countDocuments() (estimatedDocumentCount est rapide mais repose sur les métadonnées ; utilisez countDocuments pour un nombre exact). Pour vérifier le contenu, la commande dbHash produit une empreinte md5 au niveau collection/base ; comparez les hachages des deux environnements. Vérifiez les index séparément avec getIndexes().","danger":"safe","readonly":true,"priv":"read (accès pour dbHash)"},"redis":{"command":"# Compare key counts (on source and target)\nredis-cli DBSIZE\n\n# For a memory/data summary and key distribution:\nredis-cli INFO keyspace     # db0:keys=... information\n# For full content verification: DUMP each key and compare with the target\n# (e.g. redis-cli --scan | xargs -I{} redis-cli DUMP {})","note":"Dans Redis, la vérification de base consiste à comparer le nombre total de clés avec DBSIZE ; la répartition par base est affichée par INFO keyspace. Redis n'a pas de checksum de table intégré ; si une vérification exacte du contenu est nécessaire, prenez la sérialisation DUMP par clé et comparez les deux côtés (tenez compte des différences de TTL). L'outil riot compare peut aussi être utilisé.","danger":"safe","readonly":true},"elasticsearch":{"command":"# Compare document counts (on source and target clusters)\nGET customer/_count\n\n# Details: for health/segment/storage comparison\nGET _cat/indices/customer?v&h=index,docs.count,store.size\n# Content by sampling: compare GET customer/_doc/<id> for the same _ids","note":"Dans Elasticsearch, la vérification de base consiste à comparer le nombre de documents des deux clusters avec GET <index>/_count (_cat/indices docs.count donne aussi un résumé rapide). Basez la comparaison sur _count : _cat/indices docs.count compte au niveau Lucene (sous-documents nested inclus, par ex. un document avec deux éléments nested compte pour 3), il peut donc différer de _count. Pour vérifier le contenu, comparez en échantillonnant des documents sur le même ensemble d'_id ou via un champ de hachage.","danger":"safe","readonly":true,"priv":"read / monitor sur l'index"}}}]},"rosetta/tuning.json":{"category":"Optimisation des performances","categoryId":"tuning","order":16,"concepts":[{"id":"plan-baseline","title":"Figer / forcer un plan de requête (plan baseline / hint)","aliases":["plan baseline","force plan","pin plan","hint","plan stability","query store force"],"engines":{"oracle":{"command":"-- Load the good plan from the cursor cache as a baseline:\nDECLARE n NUMBER; BEGIN\n  n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id => '&sql_id');\nEND;\n/","note":"SQL Plan Management fige le plan ; c'est la protection la plus forte contre les régressions. Les hints /*+ ... */ peuvent aussi être utilisés.","danger":"caution","priv":"ADMINISTER SQL MANAGEMENT OBJECT"},"postgres":{"command":"-- There is no built-in plan pinning; the pg_hint_plan extension:\n/*+ IndexScan(customer customer_city_idx) */ SELECT ... ;","note":"Alternative : orienter au niveau session avec des réglages comme enable_seqscan ; pour un figeage persistant, utilisez pg_hint_plan.","danger":"caution"},"mysql":{"command":"SELECT /*+ INDEX(customer customer_city_idx) */ * FROM customer WHERE city='Ankara';","note":"Les optimizer hints sont intégrés à la requête. Pour une réécriture persistante, utilisez le plugin query rewrite.","danger":"safe"},"mssql":{"command":"-- Force the good plan in Query Store:\nEXEC sp_query_store_force_plan @query_id = 42, @plan_id = 17;","note":"Query Store doit être activé. Sinon OPTION (USE PLAN N'...') ou un plan guide.","danger":"caution","priv":"ALTER sur la base"},"mongodb":{"command":"// Force an index for the query:\ndb.customer.find({ city: \"Ankara\" }).hint({ city: 1 })\n// Persistent: db.customer.getPlanCache() / index filter","note":"hint force un index précis ; effacez un plan obsolète avec planCacheClear.","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner.","note":"Les performances dépendent de la structure de données et de la complexité des commandes.","danger":"safe"},"elasticsearch":{"command":"# There is no plan pinning; optimize the query/index and routing.\nGET <index>/_search { \"profile\": true, \"query\": {...} }","note":"Utilisez profile pour voir quelle requête est coûteuse ; orientez avec les shards/le routing.","danger":"safe"}}},{"id":"gather-stats-advanced","title":"Collecter des statistiques avec options avancées (histogrammes, etc.)","aliases":["gather stats","histogram","statistics","analyze advanced","method_opt","estimate percent"],"engines":{"oracle":{"command":"EXEC DBMS_STATS.GATHER_TABLE_STATS(\n  ownname=>'HR', tabname=>'EMPLOYEE',\n  estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,\n  method_opt=>'FOR ALL COLUMNS SIZE AUTO', degree=>4);","note":"method_opt contrôle les histogrammes et degree le parallélisme. Utilisez LOCK_TABLE_STATS pour verrouiller les statistiques.","danger":"safe"},"postgres":{"command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500;\nANALYZE customer;","note":"Cible d'échantillonnage par colonne ; default_statistics_target fixe la valeur par défaut globale.","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer UPDATE HISTOGRAM ON city WITH 32 BUCKETS;","note":"Prise en charge des histogrammes dans MySQL 8.0 ; utile sur les colonnes filtrées mais peu sélectives.","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Filtered statistics: CREATE STATISTICS ... WHERE ...","note":"FULLSCAN est le plus précis ; utilisez SAMPLE sur les grandes tables. AUTO_UPDATE_STATISTICS pour les mises à jour automatiques.","danger":"safe"},"mongodb":{"command":"// There are no manual statistics; the planner measures at runtime.\ndb.customer.getPlanCache().clear()","note":"Hormis l'effacement d'un plan cache obsolète, aucune statistique manuelle n'est nécessaire.","danger":"safe"},"redis":{"command":"# Not applicable.","note":"Il n'y a ni statistiques ni optimiseur.","danger":"safe"},"elasticsearch":{"command":"# There is no statistics gathering; refresh searchability:\nPOST <index>/_refresh","note":"Les field data et doc values sont utilisés au moment de la requête ; force merge réduit le nombre de segments.","danger":"safe"}}},{"id":"perf-report","title":"Générer un rapport de performances (AWR / ASH et équivalents)","aliases":["awr","ash","performance report","performance report","profile","workload report"],"engines":{"oracle":{"command":"-- AWR report (SQL*Plus):\n@?/rdbms/admin/awrrpt.sql\n-- ASH report: @?/rdbms/admin/ashrpt.sql\n-- Live: SELECT * FROM v$active_session_history WHERE sample_time > SYSDATE-1/24;","note":"AWR/ASH nécessitent la licence Diagnostics Pack. Statspack est l'équivalent sans licence.","danger":"safe","priv":"SELECT_CATALOG_ROLE"},"postgres":{"command":"-- pg_stat_statements summary + pg_profile/pgBadger:\nSELECT queryid, calls, total_exec_time, mean_exec_time, query\nFROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;","note":"Pour un équivalent d'AWR, utilisez l'extension pg_profile (snapshots périodiques) ou l'analyse de journaux pgBadger.","danger":"safe"},"mysql":{"command":"-- sys schema summaries:\nSELECT * FROM sys.statement_analysis LIMIT 20;\nSELECT * FROM sys.io_global_by_file_by_bytes LIMIT 10;","note":"Le schéma sys transforme performance_schema en rapports lisibles ; Enterprise Monitor est plus complet.","danger":"safe"},"mssql":{"command":"-- Query Store reports (SSMS) or:\nSELECT TOP 20 * FROM sys.query_store_runtime_stats ORDER BY avg_duration DESC;","note":"Query Store conserve les plans/performances dans le temps ; sp_BlitzFirst (communauté) fournit un diagnostic rapide.","danger":"safe"},"mongodb":{"command":"# Live from the shell: mongostat --rowcount 10 ; mongotop\n// or Atlas Performance Advisor / profiler:\ndb.system.profile.find().sort({millis:-1}).limit(20)","note":"mongostat/mongotop montrent la charge instantanée ; le profiler enregistre les opérations lentes.","danger":"safe"},"redis":{"command":"INFO stats\nredis-cli --latency-history\nSLOWLOG GET 20","note":"--latency et --latency-history donnent le profil de latence ; INFO donne tous les compteurs.","danger":"safe"},"elasticsearch":{"command":"GET _nodes/stats\nGET _cat/thread_pool?v&h=node_name,name,active,queue,rejected\nGET _nodes/hot_threads","note":"hot_threads affiche les threads les plus coûteux ; _nodes/stats donne des métriques globales. La supervision (Kibana) est historique.","danger":"safe"}}},{"id":"optimizer-params","title":"Consulter/modifier les réglages de l'optimiseur / du parallélisme","aliases":["optimizer","parallelism","parallel","maxdop","optimizer_switch","cost","planner settings"],"engines":{"oracle":{"command":"SHOW PARAMETER optimizer;\n-- Session: ALTER SESSION SET optimizer_mode = FIRST_ROWS_10;\n-- Parallelism: ALTER SESSION FORCE PARALLEL QUERY PARALLEL 4;","note":"Des réglages comme optimizer_index_cost_adj et parallel_degree_policy influencent le choix des plans ; modifiez-les avec prudence.","danger":"caution"},"postgres":{"command":"SHOW random_page_cost;\nSHOW max_parallel_workers_per_gather;\n-- Session: SET enable_seqscan = off;","note":"Sur SSD, random_page_cost ~1.1 est recommandé. Les flags enable_* servent au diagnostic, pas de correctif permanent.","danger":"caution"},"mysql":{"command":"SELECT @@optimizer_switch;\nSET SESSION optimizer_switch='index_merge=off';","note":"optimizer_switch active/désactive des optimisations individuelles ; testez au niveau session.","danger":"caution"},"mssql":{"command":"-- Instance-level MAXDOP and cost threshold:\nEXEC sp_configure 'max degree of parallelism', 8; RECONFIGURE;\n-- Database level: ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 8;","note":"MAXDOP=0 signifie illimité ; généralement 4-8 pour l'OLTP. La valeur par défaut de 5 pour « cost threshold for parallelism » est trop basse dans la plupart des cas.","danger":"caution"},"mongodb":{"command":"# There are no optimizer parameters; indexes and query shape are decisive.\ndb.customer.find({...}).explain('allPlansExecution')","note":"allPlansExecution affiche tous les plans évalués par le planificateur.","danger":"safe"},"redis":{"command":"# Not applicable.","note":"Il n'y a pas de planificateur ; io-threads et le choix de structure de données déterminent les performances.","danger":"safe"},"elasticsearch":{"command":"GET <index>/_settings?include_defaults=true\n# search limits such as search.max_buckets, index.max_result_window","note":"Il n'y a pas d'optimiseur ; le nombre de shards, le routing et la structure des requêtes déterminent les performances.","danger":"safe"}}},{"id":"sql-monitor","title":"Surveiller en temps réel le SQL en cours/récent","aliases":["sql monitor","live monitoring","real time sql","live query","current sql","monitor active queries"],"engines":{"oracle":{"command":"SET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 250 PAGESIZE 0\nSELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(sql_id=>'&sql_id', type=>'TEXT') AS report FROM dual;\n-- or: SELECT sql_id, status, elapsed_time FROM v$sql_monitor WHERE status='EXECUTING';","note":"Real-Time SQL Monitoring affiche en direct les requêtes longues/parallèles (Tuning Pack).","danger":"safe"},"postgres":{"command":"SELECT pid, now()-query_start AS dur, state, wait_event, query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start;","note":"Avec auto_explain, les plans des requêtes lentes peuvent être journalisés automatiquement.","danger":"safe"},"mysql":{"command":"SELECT thread_id, event_name, timer_wait/1e9 AS ms, sql_text\nFROM performance_schema.events_statements_current;","note":"events_statements_current affiche les instructions en cours d'exécution.","danger":"safe"},"mssql":{"command":"SELECT r.session_id, r.status, r.wait_type, r.percent_complete, t.text\nFROM sys.dm_exec_requests r JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE s.is_user_process = 1;","note":"« Live Query Statistics » dans SSMS affiche la progression du plan en direct.","danger":"safe"},"mongodb":{"command":"db.currentOp({ active: true, secs_running: { $gt: 1 } })","note":"Affiche en direct les opérations longues ; elles peuvent être terminées avec killOp.","danger":"safe"},"redis":{"command":"MONITOR","note":"Diffuse TOUTES les commandes en direct — impacte les performances en production, à n'utiliser que brièvement. Préférez SLOWLOG.","danger":"caution"},"elasticsearch":{"command":"GET _tasks?detailed=true&group_by=parents","note":"Affiche en direct les tâches en cours ; une tâche longue s'annule avec POST _tasks/<id>/_cancel.","danger":"safe"}}}]},"rosetta/tuning-adv.json":{"category":"Optimisation avancée","categoryId":"tuning-adv","order":17,"concepts":[{"id":"parallel-query","title":"Exécuter une requête parallèle / définir le degré de parallélisme","aliases":["parallel","parallelism","dop","degree of parallelism","parallel query"],"engines":{"oracle":{"command":"-- At query level with a hint:\nSELECT /*+ PARALLEL(t, 4) */ * FROM big_table t;\n-- Force at session level:\nALTER SESSION FORCE PARALLEL QUERY PARALLEL 4;","note":"Le hint PARALLEL applique un parcours parallèle à la table ; degree=DOP. Un DOP excessif épuise les ressources du serveur, choisissez-le avec soin. parallel_degree_policy=AUTO le gère automatiquement.","danger":"caution"},"postgres":{"command":"-- Set the number of parallel workers for the session:\nSET max_parallel_workers_per_gather = 4;\n-- The planner adds a Gather/Gather Merge node:\nEXPLAIN (ANALYZE) SELECT count(*) FROM big_table;","note":"Si Gather/Parallel Seq Scan apparaît dans le plan, le parallélisme est actif. max_parallel_workers et min_parallel_table_scan_size jouent aussi un rôle.","danger":"safe"},"mysql":{"command":"-- Limited InnoDB parallelism (certain operations only):\nSET GLOBAL innodb_parallel_read_threads = 4;\n-- Effective for COUNT(*) and primary key scans.","note":"MySQL n'offre pas d'exécution parallèle générale des requêtes ; innodb_parallel_read_threads ne parallélise que les parcours d'index cluster (par ex. COUNT(*)). Les JOIN complexes sont mono-thread.","danger":"safe"},"mssql":{"command":"-- MAXDOP hint at query level:\nSELECT * FROM dbo.big_table OPTION (MAXDOP 4);\n-- Instance level: sp_configure 'max degree of parallelism', 4;","note":"MAXDOP limite le nombre de threads parallèles ; MAXDOP 1 désactive le parallélisme. Une fois le « cost threshold for parallelism » dépassé, le plan devient parallèle.","danger":"safe"},"mongodb":{"command":"// Allow disk use for a large aggregation:\ndb.sale.aggregate([ { $group: { _id: \"$city\", t: { $sum: \"$amount\" } } } ], { allowDiskUse: true })\n// A sharded cluster processes each shard in parallel.","note":"Dans un cluster shardé, la requête s'exécute en parallèle sur chaque shard ; sur un seul nœud, les étapes d'agrégation sont surtout séquentielles. allowDiskUse permet les opérations qui dépassent la limite mémoire.","danger":"safe"},"redis":{"command":"# Not applicable: there is no concept of query parallelism.","note":"L'exécution des commandes est mono-thread ; passez à l'échelle avec Cluster (sharding) ou utilisez io-threads pour paralléliser les E/S réseau.","danger":"safe"},"elasticsearch":{"command":"# Shard count = unit of parallelism; a query runs in parallel across shards.\n# Parallelism by slicing within a single shard (with scroll/PIT):\nGET <index>/_search\n{ \"slice\": { \"id\": 0, \"max\": 4 }, \"query\": { \"match_all\": {} } }","note":"Une recherche s'exécute simultanément sur tous les shards concernés ; le nombre de primary shards fixe le niveau de parallélisme de base. slices découpe les opérations scroll/reindex volumineuses.","danger":"safe"}}},{"id":"bind-variables","title":"Gérer les variables de liaison / le parameter sniffing","aliases":["bind","bind variable","parameter","parameter sniffing","cursor sharing","prepared statement"],"engines":{"oracle":{"command":"-- Use binds instead of literals (for plan sharing):\nSELECT * FROM customer WHERE city = :city;\n-- Share legacy code that uses literals:\nALTER SESSION SET cursor_sharing = FORCE;","note":"Les variables de liaison réduisent les hard parses. Adaptive Cursor Sharing atténue les problèmes de sniffing en conservant plusieurs plans pour les valeurs de liaison à distribution asymétrique. Utilisez cursor_sharing=FORCE avec prudence.","danger":"caution"},"postgres":{"command":"-- Parameterize with a prepared statement:\nPREPARE q (text) AS SELECT * FROM customer WHERE city = $1;\nEXECUTE q('Ankara');\n-- Set plan cache behavior:\nSET plan_cache_mode = force_custom_plan;","note":"plan_cache_mode=auto choisit entre plans génériques et personnalisés ; force_custom_plan produit un plan spécifique aux paramètres à chaque exécution (une solution aux problèmes de type sniffing).","danger":"safe"},"mysql":{"command":"-- Server-side prepared statement:\nPREPARE stmt FROM 'SELECT * FROM customer WHERE city = ?';\nSET @city = 'Ankara';\nEXECUTE stmt USING @city;\nDEALLOCATE PREPARE stmt;","note":"Les prepared statements empêchent l'injection SQL et réduisent le coût d'analyse. MySQL prend aussi en charge la paramétrisation au niveau du pilote.","danger":"safe"},"mssql":{"command":"-- Parameterized query (sp_executesql):\nEXEC sp_executesql N'SELECT * FROM dbo.customer WHERE city = @city', N'@city nvarchar(50)', @city = N'Ankara';\n-- Handle sniffing with a hint:\nSELECT ... OPTION (RECOMPILE);\n-- or OPTION (OPTIMIZE FOR (@city = 'Ankara'));","note":"OPTION(RECOMPILE) produit un plan spécifique aux paramètres à chaque exécution (coûteux en CPU). OPTIMIZE FOR fige le plan sur une valeur précise/typique ; OPTIMIZE FOR UNKNOWN utilise la sélectivité moyenne.","danger":"caution"},"mongodb":{"command":"// The query shape is the key of the plan cache:\ndb.customer.find({ city: \"Ankara\" }).explain(\"queryPlanner\")\n// Clear a stale plan:\ndb.customer.getPlanCache().clear()","note":"Les requêtes de même forme (mêmes champs/opérateurs) partagent le plan en cache ; des valeurs littérales différentes ne produisent pas de nouveau plan. Si la distribution des valeurs est très asymétrique, réinitialisez-le avec planCacheClear.","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner or parameter sniffing.","note":"Les commandes s'exécutent directement ; c'est la conception des clés, et non la paramétrisation, qui détermine les performances.","danger":"safe"},"elasticsearch":{"command":"# Reuse the query with a parameterized search template:\nPOST _scripts/customer_city\n{ \"script\": { \"lang\": \"mustache\", \"source\": { \"query\": { \"term\": { \"city\": \"{{city}}\" } } } } }\nGET <index>/_search/template\n{ \"id\": \"customer_city\", \"params\": { \"city\": \"Ankara\" } }","note":"Un search template (Mustache) paramètre le corps de la requête ; l'application appelle un template fixe avec des valeurs différentes, ce qui améliore la cohérence des requêtes et l'utilisation du cache.","danger":"safe"}}},{"id":"partition-pruning","title":"Vérifier le partition pruning","aliases":["partition pruning","partition elimination","partition","pruning","pstart pstop"],"engines":{"oracle":{"command":"EXPLAIN PLAN FOR\nSELECT * FROM sale WHERE sale_date = DATE '2026-01-15';\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);\n-- Look for 'PARTITION RANGE ITERATOR/SINGLE' and the Pstart/Pstop columns in the plan.","note":"Pstart/Pstop montrent que seules les partitions concernées sont lues (un nombre plutôt que KEY est idéal). Appliquer une fonction/conversion implicite à la clé de partitionnement empêche le pruning.","danger":"safe"},"postgres":{"command":"SET enable_partition_pruning = on;\nEXPLAIN (ANALYZE)\nSELECT * FROM sale WHERE sale_date = DATE '2026-01-15';\n-- Verify in the plan that only the relevant partition (child table) is scanned.","note":"Avec enable_partition_pruning activé, seules les partitions correspondantes apparaissent dans le plan ; les autres sont indiquées 'never executed'. Les valeurs constantes sont élaguées à la planification, les paramètres à l'exécution.","danger":"safe"},"mysql":{"command":"-- Show which partitions are scanned:\nEXPLAIN SELECT * FROM sale WHERE sale_date = '2026-01-15';\n-- 5.7 and earlier: EXPLAIN PARTITIONS SELECT ... (removed in 8.0)","note":"Dans MySQL 5.7+, la colonne 'partitions' de la sortie EXPLAIN simple liste les partitions à parcourir (la syntaxe EXPLAIN PARTITIONS a été supprimée en 8.0) ; seule(s) la ou les partitions concernées doivent apparaître. Si toutes les partitions sont listées, le pruning n'a pas lieu.","danger":"safe"},"mssql":{"command":"-- Find which partition a value falls into:\nSELECT $PARTITION.pf_sale_date('2026-01-15') AS partition_no;\n-- Examine 'Actual Partition Count' and 'Actual Partitions Accessed' in the actual plan.","note":"La fonction $PARTITION renvoie le numéro de partition d'une valeur. Si le nombre de partitions accédées dans le plan d'exécution réel est proche de 1, le pruning est actif ; si toutes les partitions sont parcourues, le prédicat ne correspond pas.","danger":"safe"},"mongodb":{"command":"// Targeted query using the shard key in a sharded cluster:\ndb.sale.find({ shop_id: 42 }).explain(\"executionStats\")\n// In explain, only the relevant shard should appear under 'shards'.","note":"Si la requête contient la shard key, mongos ne la route que vers le shard concerné (targeted) ; sinon elle est diffusée à tous les shards (scatter-gather). Vérifiez les shards accédés dans la sortie explain.","danger":"safe"},"redis":{"command":"# Not applicable: there is no table/partition concept.","note":"Les données sont réparties via les hash slots du Cluster ; au lieu d'un élagage des requêtes, l'accès par clé est direct.","danger":"safe"},"elasticsearch":{"command":"# Route to the relevant shard with routing:\nGET <index>/_search?routing=customer42\n{ \"query\": { \"term\": { \"customer_id\": 42 } } }\n# Query only the relevant indices with a time-based index pattern:\nGET sale-2026-01/_search","note":"Le routing personnalisé restreint la requête à un seul shard (similaire au pruning). Pour les données temporelles, un nom d'index daté (sale-2026-01) garantit que seuls les indices concernés sont interrogés ; utilisez un motif étroit plutôt qu'un caractère générique.","danger":"safe"}}},{"id":"result-cache","title":"Utiliser un cache de résultats / de requêtes","aliases":["result cache","result caching","query cache","query result cache","request cache"],"engines":{"oracle":{"command":"-- Result cache at query level:\nSELECT /*+ RESULT_CACHE */ city, COUNT(*) FROM customer GROUP BY city;\n-- Global mode: ALTER SYSTEM SET result_cache_mode = FORCE;","note":"Le Server Result Cache conserve le résultat dans la SGA ; il est invalidé automatiquement lorsque les tables sous-jacentes changent. RESULT_CACHE_MODE=FORCE l'applique à toutes les requêtes éligibles, MANUAL uniquement via le hint. Peu d'intérêt sur les tables qui changent souvent.","danger":"caution"},"postgres":{"command":"-- There is no built-in result cache.\n-- Precompute with a materialized view:\nCREATE MATERIALIZED VIEW mv_city_summary AS\n  SELECT city, COUNT(*) FROM customer GROUP BY city;\nREFRESH MATERIALIZED VIEW mv_city_summary;","note":"PostgreSQL n'offre pas de cache de résultats de requêtes ; utilisez une vue matérialisée (REFRESH manuel/planifié) ou un cache applicatif (Redis). Remarque : le nœud Memoize assure un cache au niveau ligne dans une nested loop, pas un cache des résultats de requêtes.","danger":"safe"},"mysql":{"command":"-- Query Cache was REMOVED in MySQL 8.0.\n-- Alternative: a materialized summary table or an application cache:\nCREATE TABLE city_summary AS SELECT city, COUNT(*) c FROM customer GROUP BY city;","note":"L'ancien query_cache_type a été entièrement supprimé dans MySQL 8.0 (pour des problèmes de scalabilité). Utilisez à la place le buffer pool InnoDB, des tables de synthèse ou un cache externe (Redis/Memcached).","danger":"safe"},"mssql":{"command":"-- There is no built-in query result cache.\n-- Precompute with an indexed (materialized) view:\nCREATE VIEW dbo.vw_city_summary WITH SCHEMABINDING AS\n  SELECT city, COUNT_BIG(*) c FROM dbo.customer GROUP BY city;\nCREATE UNIQUE CLUSTERED INDEX ix ON dbo.vw_city_summary(city);","note":"SQL Server ne conserve pas de cache de résultats (il conserve un plan cache). Pour les agrégations répétées, une vue indexée est maintenue automatiquement ; la correspondance automatique dans les plans n'existe qu'en édition Enterprise (et Developer), tandis qu'en Standard la vue doit être interrogée avec le hint WITH (NOEXPAND) ; en outre, le buffer pool garde en mémoire les pages fréquemment lues.","danger":"safe"},"mongodb":{"command":"// There is no built-in query result cache.\n// Pre-aggregate and write to a collection ($merge):\ndb.sale.aggregate([\n  { $group: { _id: \"$city\", t: { $sum: \"$amount\" } } },\n  { $merge: \"city_summary\" }\n])","note":"MongoDB n'offre pas de cache de résultats ; WiredTiger garde en mémoire les données fréquemment consultées. Pour les agrégations coûteuses, utilisez une collection de synthèse précalculée via $merge/$out ou un cache applicatif (Redis).","danger":"safe"},"redis":{"command":"# Redis is already a cache layer; cache the result manually:\nSET query:city_summary \"{...}\" EX 300\nGET query:city_summary\n# If already computed, return it with GET; otherwise compute it and write it with SET.","note":"Le schéma cache-aside courant : si la clé existe, renvoyez-la rapidement avec GET ; sinon calculez depuis la source et faites un SET avec EX (TTL). Redis sert généralement de cache de résultats pour d'autres bases.","danger":"safe"},"elasticsearch":{"command":"# Shard request cache (enabled by default for size:0 aggregations):\nGET <index>/_search?request_cache=true\n{ \"size\": 0, \"aggs\": { \"city\": { \"terms\": { \"field\": \"city\" } } } }\n# Setting: PUT <index>/_settings { \"index.requests.cache.enable\": true }","note":"Le shard request cache met en cache les résultats par shard des requêtes size:0 (agrégation seule) ; il est invalidé lors du refresh de l'index. Les requêtes avec size>0 (qui renvoient des hits) ne sont pas mises en cache par défaut.","danger":"safe"}}}]},"rosetta/advanced-features.json":{"category":"Fonctionnalités avancées","categoryId":"advanced-features","order":18,"concepts":[{"id":"full-text-search","title":"Recherche plein texte","aliases":["full text","full text","fts","search","text search"],"engines":{"oracle":{"command":"-- Oracle Text: create a CONTEXT index\nCREATE INDEX article_idx ON article(content) INDEXTYPE IS CTXSYS.CONTEXT;\n-- Search with CONTAINS (label 1 ties CONTAINS to SCORE):\nSELECT id, SCORE(1) FROM article\nWHERE CONTAINS(content, 'database AND performance', 1) > 0\nORDER BY SCORE(1) DESC;","note":"Oracle Text (anciennement interMedia) fournit une recherche plein texte tenant compte de la langue via un index CONTEXT. L'opérateur CONTAINS prend en charge des opérateurs de recherche tels que AND/OR/NEAR. L'index est maintenu avec CTX_DDL.SYNC_INDEX ou le SYNC automatique.","danger":"safe"},"postgres":{"command":"-- Search with tsvector/tsquery, accelerated by a GIN index:\nCREATE INDEX article_fts_idx ON article\n  USING GIN (to_tsvector('english', content));\nSELECT id, ts_rank(to_tsvector('english', content), q) AS score\nFROM article, to_tsquery('english', 'database & performance') q\nWHERE to_tsvector('english', content) @@ q\nORDER BY score DESC;","note":"La recherche plein texte intégrée de PostgreSQL fait correspondre tsvector (document) et tsquery (requête) avec l'opérateur @@. Un index GIN accélère les recherches sur de grands textes. La configuration de langue ('english') gère la racinisation et les mots vides.","danger":"safe"},"mysql":{"command":"-- Create a FULLTEXT index (InnoDB/MyISAM):\nALTER TABLE article ADD FULLTEXT INDEX ft_content (content);\n-- Search in natural language / boolean mode:\nSELECT id, MATCH(content) AGAINST('database performance') AS score\nFROM article\nWHERE MATCH(content) AGAINST('+database +performance' IN BOOLEAN MODE);","note":"Un index FULLTEXT MySQL s'interroge avec la syntaxe MATCH ... AGAINST. IN NATURAL LANGUAGE MODE renvoie un score de pertinence ; IN BOOLEAN MODE donne un contrôle fin avec les opérateurs +/-/*. InnoDB prend en charge la recherche plein texte à partir de 5.6.","since":"MySQL 5.6","xnote":"La recherche plein texte InnoDB est arrivée avec MySQL 5.6 ; auparavant, elle n'existait que dans MyISAM.","danger":"safe"},"mssql":{"command":"-- Create a Full-Text Catalog and index:\nCREATE FULLTEXT CATALOG ft_catalog;\nCREATE FULLTEXT INDEX ON dbo.article(content)\n  KEY INDEX PK_article ON ft_catalog;\n-- Search with CONTAINS / FREETEXT:\nSELECT id FROM dbo.article\nWHERE CONTAINS(content, '\"database\" AND \"performance\"');","note":"Full-Text Search de SQL Server nécessite d'abord un Full-Text Catalog, puis un Full-Text Index sur la table (un KEY INDEX unique est obligatoire). CONTAINS prend en charge la recherche exacte/de proximité et les opérateurs logiques ; FREETEXT la recherche par le sens (linguistique). Le composant Full-Text Search doit être installé.","danger":"safe"},"mongodb":{"command":"// Create a text index:\ndb.article.createIndex({ content: \"text\" })\n// Search with $text and sort by relevance score:\ndb.article.find(\n  { $text: { $search: \"database performance\" } },\n  { score: { $meta: \"textScore\" } }\n).sort({ score: { $meta: \"textScore\" } })","note":"Un index text MongoDB fournit la recherche plein texte via l'opérateur $text ; une collection ne peut avoir qu'un seul index text. Le méta-champ textScore donne la pertinence. Pour une recherche plus avancée, on utilise Atlas Search ($search, basé sur Lucene).","danger":"safe"},"redis":{"command":"# Requires the RediSearch module (Redis Stack).\n# Define the index:\nFT.CREATE article_idx ON HASH PREFIX 1 article: SCHEMA content TEXT\n# Full-text search:\nFT.SEARCH article_idx \"database performance\"","note":"Redis de base n'offre pas de recherche plein texte ; le module RediSearch (fourni avec Redis Stack) assure l'indexation avec FT.CREATE et la recherche avec FT.SEARCH. La racinisation, la recherche floue, le filtrage et le tri sont pris en charge. Sans le module installé, la fonctionnalité n'est pas disponible.","danger":"safe"},"elasticsearch":{"command":"# Elasticsearch is natively a full-text search engine.\n# match query (analyzed text search):\nGET article/_search\n{\n  \"query\": {\n    \"match\": { \"content\": \"database performance\" }\n  }\n}","note":"Elasticsearch est fondamentalement un moteur de recherche plein texte basé sur Lucene ; les champs text sont analysés à l'indexation (tokenisation, racinisation). La requête match renvoie des résultats triés par score de pertinence (_score). Des analyseurs propres à une langue (par ex. 'english') peuvent être affectés.","danger":"safe"}}},{"id":"json-query","title":"Interroger des données JSON","aliases":["json","json query","json query","jsonb","document query"],"engines":{"oracle":{"command":"-- Extract a scalar value with JSON_VALUE:\nSELECT JSON_VALUE(data, '$.customer.city') AS city FROM orders;\n-- Expand into rows with JSON_TABLE:\nSELECT t.product, t.quantity\nFROM orders s,\n     JSON_TABLE(s.data, '$.items[*]'\n       COLUMNS (product VARCHAR2(50) PATH '$.product',\n                quantity NUMBER PATH '$.quantity')) t;","note":"Oracle (12c+) interroge les données JSON avec des expressions de chemin SQL/JSON. JSON_VALUE renvoie une valeur scalaire unique, JSON_QUERY un fragment objet/tableau, et JSON_TABLE convertit le JSON en structure relationnelle lignes-colonnes. 21c+ prend en charge un type de données JSON natif.","since":"Oracle 12c","xnote":"Les requêtes SQL/JSON sont arrivées avec Oracle 12c, le type de données JSON natif avec 21c.","danger":"safe"},"postgres":{"command":"-- jsonb operators:\nSELECT data->'customer'->>'city' AS city FROM orders;\n-- Containment (@>) and a GIN index:\nCREATE INDEX ix ON orders USING GIN (data);\nSELECT * FROM orders\nWHERE data @> '{\"status\": \"approved\"}';","note":"Le type jsonb de PostgreSQL offre de riches opérateurs : -> (objet/élément json), ->> (valeur texte), #> (chemin), @> (inclusion), ? (existence de clé). Un index GIN accélère les requêtes @> et ?. Les requêtes de chemin SQL/JSON sont aussi possibles avec jsonb_path_query.","since":"PostgreSQL 9.4","xnote":"Le type json est arrivé avec PostgreSQL 9.2, le type jsonb avec 9.4.","danger":"safe"},"mysql":{"command":"-- JSON_EXTRACT or shorthand operators:\nSELECT JSON_EXTRACT(data, '$.customer.city') AS city FROM orders;\n-- ->> returns unquoted text:\nSELECT data->>'$.customer.city' AS city FROM orders\nWHERE data->>'$.status' = 'approved';","note":"MySQL (5.7+) interroge le type JSON natif avec JSON_EXTRACT() ; l'opérateur -> est un raccourci de JSON_EXTRACT, et ->> de JSON_UNQUOTE(JSON_EXTRACT()). Les recherches sur les champs JSON peuvent être accélérées avec une generated column + un index.","since":"MySQL 5.7","xnote":"Le type de données JSON natif est arrivé avec MySQL 5.7.","danger":"safe"},"mssql":{"command":"-- Extract a scalar value with JSON_VALUE:\nSELECT JSON_VALUE(data, '$.customer.city') AS city FROM orders;\n-- Expand into rows with OPENJSON:\nSELECT k.product, k.quantity\nFROM orders s\nCROSS APPLY OPENJSON(s.data, '$.items')\n  WITH (product NVARCHAR(50) '$.product', quantity INT '$.quantity') k;","note":"SQL Server (2016+) stocke le JSON dans une colonne NVARCHAR. JSON_VALUE renvoie une valeur scalaire, JSON_QUERY un objet/tableau, et OPENJSON convertit le JSON en forme tabulaire. ISJSON() le valide. Les recherches sur des chemins JSON peuvent être accélérées avec une computed column + un index.","since":"SQL Server 2016","xnote":"Les fonctions JSON (JSON_VALUE/OPENJSON) sont arrivées avec SQL Server 2016.","danger":"safe"},"mongodb":{"command":"// MongoDB is natively a document (BSON/JSON) database.\n// Access nested fields with dot notation:\ndb.orders.find({ \"customer.city\": \"Ankara\" })\n// Array element and projection:\ndb.orders.find(\n  { \"items.product\": \"pen\" },\n  { \"items.$\": 1 }\n)","note":"MongoDB stocke toutes les données sous forme de documents (BSON, proche du JSON) ; leur interrogation est native. La notation pointée atteint les champs imbriqués et les éléments de tableaux. Les transformations complexes se font avec l'Aggregation Pipeline, et les index donnent des recherches rapides sur les champs imbriqués.","danger":"safe"},"redis":{"command":"# Requires the RedisJSON module (Redis Stack).\n# Write a JSON document:\nJSON.SET order:1 $ '{\"customer\":{\"city\":\"Ankara\"},\"status\":\"approved\"}'\n# Read a value with JSONPath:\nJSON.GET order:1 $.customer.city","note":"Redis de base n'a pas de type JSON ; le module RedisJSON (Redis Stack) fournit des lectures et écritures basées sur JSONPath avec JSON.SET/JSON.GET. Avec RediSearch, les documents JSON peuvent être indexés et interrogés. Sans le module, le JSON est stocké comme une chaîne et analysé dans l'application.","danger":"safe"},"elasticsearch":{"command":"# Documents are already JSON; query fields directly:\nGET orders/_search\n{ \"query\": { \"term\": { \"customer.city\": \"Ankara\" } } }\n# For nested arrays, use the nested type and a nested query:\nGET orders/_search\n{ \"query\": { \"nested\": { \"path\": \"items\",\n  \"query\": { \"match\": { \"items.product\": \"pen\" } } } } }","note":"Elasticsearch stocke et indexe nativement les documents en JSON. Pour préserver les limites des éléments dans les tableaux d'objets imbriqués, utilisez le type de champ 'nested' et une requête nested ; sinon les tableaux de type object sont aplatis (risque de correspondances croisées). La distinction object vs nested est cruciale pour l'exactitude.","danger":"safe"}}},{"id":"spatial-geo","title":"Requête géographique / spatiale","aliases":["spatial","geographic","geo","gis","location","map"],"engines":{"oracle":{"command":"-- Oracle Spatial: SDO_GEOMETRY column and spatial index\nCREATE INDEX location_sidx ON shop(location)\n  INDEXTYPE IS MDSYS.SPATIAL_INDEX;\n-- Shops within 5 km:\nSELECT name FROM shop\nWHERE SDO_WITHIN_DISTANCE(location,\n  SDO_GEOMETRY(2001, 4326,\n    SDO_POINT_TYPE(32.85, 39.92, NULL), NULL, NULL),\n  'distance=5 unit=KM') = 'TRUE';","note":"Oracle Spatial stocke les données géographiques dans le type SDO_GEOMETRY ; une entrée USER_SDO_GEOM_METADATA et un index spatial sont requis. Les opérateurs comme SDO_WITHIN_DISTANCE, SDO_RELATE et SDO_NN répondent aux requêtes de distance/intersection/plus proche voisin. Le SRID 4326 est le système WGS84 (latitude/longitude).","danger":"safe"},"postgres":{"command":"-- PostGIS extension:\nCREATE EXTENSION IF NOT EXISTS postgis;\nCREATE INDEX location_gix ON shop USING GIST (location);\n-- Shops within 5 km (geography, meters):\nSELECT name FROM shop\nWHERE ST_DWithin(location,\n  ST_SetSRID(ST_MakePoint(32.85, 39.92), 4326)::geography,\n  5000);","note":"PostGIS fait de PostgreSQL une base spatiale complète. Il fournit les types geometry (planaire) et geography (sphérique) et des centaines de fonctions ST_ telles que ST_Distance/ST_DWithin/ST_Intersects/ST_Contains. Un index GiST/SP-GiST accélère les requêtes spatiales.","danger":"safe"},"mysql":{"command":"-- Spatial column (with SRID) and SPATIAL index:\nALTER TABLE shop MODIFY location POINT NOT NULL SRID 4326;\nALTER TABLE shop ADD SPATIAL INDEX (location);\n-- Compute distance (meters, geography):\nSELECT name,\n  ST_Distance(location,\n    ST_SRID(POINT(32.85, 39.92), 4326)) AS distance\nFROM shop\nORDER BY distance LIMIT 10;","note":"MySQL (5.7+) prend en charge les types de données spatiales conformes OGC (POINT, POLYGON, etc.) et les fonctions ST_. Un index SPATIAL (R-tree) ne peut être créé que sur InnoDB/MyISAM et sur des colonnes NOT NULL ; pour que l'optimiseur l'utilise, la colonne doit être définie avec un attribut SRID (SRID 4326). Avec SRID 4326, ST_Distance renvoie la distance géodésique en mètres. Avec POINT(x, y) + ST_SRID, x est la longitude et y la latitude ; pour une entrée WKT (ST_GeomFromText) avec SRID 4326, l'ordre est latitude-longitude.","danger":"safe"},"mssql":{"command":"-- geography type and spatial index:\nCREATE SPATIAL INDEX location_sidx ON dbo.shop(location);\n-- Shops within 5 km (STDistance returns meters):\nSELECT name FROM dbo.shop\nWHERE location.STDistance(\n  geography::Point(39.92, 32.85, 4326)) <= 5000;","note":"SQL Server propose deux types spatiaux : geography (sphérique, latitude/longitude) et geometry (planaire). Des méthodes comme STDistance, STIntersects et STContains répondent aux requêtes. Un index spatial (basé sur une grille) améliore les performances. Attention à l'ordre des arguments de geography::Point(latitude, longitude, SRID).","danger":"safe"},"mongodb":{"command":"// 2dsphere index (for GeoJSON):\ndb.shop.createIndex({ location: \"2dsphere\" })\n// Shops nearest to a given point ($near):\ndb.shop.find({\n  location: {\n    $near: {\n      $geometry: { type: \"Point\", coordinates: [32.85, 39.92] },\n      $maxDistance: 5000\n    }\n  }\n})","note":"MongoDB stocke les données géographiques au format GeoJSON et exécute des requêtes sphériques avec un index 2dsphere. Les opérateurs $near (tri par distance), $geoWithin (dans une zone) et $geoIntersects sont pris en charge. Les coordonnées sont dans l'ordre [longitude, latitude] ; $maxDistance est en mètres.","danger":"safe"},"redis":{"command":"# Built-in geospatial commands (built on sorted sets):\nGEOADD shops 32.85 39.92 \"kizilay\"\nGEOADD shops 32.86 39.93 \"cankaya\"\n# Search within 5 km of a point:\nGEOSEARCH shops FROMLONLAT 32.85 39.92\n  BYRADIUS 5 km ASC","note":"Redis stocke les positions géographiques dans un sorted set avec GEOADD (longitude latitude membre). GEOSEARCH (Redis 6.2+, qui remplace l'ancien GEORADIUS) renvoie les membres situés dans un rayon ou un rectangle, triés par distance. GEODIST donne la distance entre deux points. C'est une fonctionnalité de base ; aucun module n'est nécessaire.","danger":"safe"},"elasticsearch":{"command":"# Map the field as geo_point:\nPUT shop\n{ \"mappings\": { \"properties\": { \"location\": { \"type\": \"geo_point\" } } } }\n# Query within 5 km:\nGET shop/_search\n{ \"query\": { \"geo_distance\": {\n  \"distance\": \"5km\",\n  \"location\": { \"lat\": 39.92, \"lon\": 32.85 } } } }","note":"Elasticsearch prend en charge les types de champs geo_point (point) et geo_shape (polygone/ligne). Le filtrage se fait avec les requêtes geo_distance (rayon), geo_bounding_box (rectangle) et geo_polygon ; le tri avec geo_distance, et l'agrégation avec geohash_grid/geotile_grid. Les coordonnées sont données en lat/lon ou sous forme de tableau [lon, lat].","danger":"safe"}}},{"id":"window-functions-adv","title":"Fonctions de fenêtrage (ROW_NUMBER/RANK/LAG)","aliases":["window function","window function","row_number","rank","lag","over","analytic"],"engines":{"oracle":{"command":"-- Rank by amount within each city and fetch the previous value:\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"Oracle a été pionnier des fonctions analytiques (de fenêtrage) ; ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM/AVG OVER, NTILE et FIRST_VALUE sont entièrement pris en charge. Dans OVER, PARTITION BY définit les groupes, ORDER BY l'ordre, et le cadre (ROWS/RANGE BETWEEN) la fenêtre glissante.","since":"Oracle 8i","danger":"safe"},"postgres":{"command":"-- Window functions with OVER():\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"PostgreSQL prend entièrement en charge les fonctions de fenêtrage avec la syntaxe SQL standard. ROW_NUMBER/RANK/DENSE_RANK, LAG/LEAD, les agrégats avec OVER, NTILE et FIRST_VALUE/LAST_VALUE sont disponibles. Les définitions de fenêtre répétées peuvent être nommées et partagées avec la clause WINDOW.","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"-- MySQL 8.0+ window functions:\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"MySQL a ajouté les fonctions de fenêtrage en version 8.0 (MariaDB 10.2+ les prend aussi en charge). ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, NTILE et les agrégats avec OVER sont disponibles. Les versions antérieures à 8.0 n'ont pas ces fonctions ; on les émulait avec des variables utilisateur.","since":"MySQL 8.0","xnote":"Avant MySQL 8.0, il n'y avait pas de fonctions de fenêtrage ; on les émulait avec des variables utilisateur.","danger":"safe"},"mssql":{"command":"-- Window functions with OVER():\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM dbo.sale;","note":"SQL Server prend en charge les fonctions de fenêtrage depuis 2005 (ROW_NUMBER/RANK) et 2012 (LAG/LEAD, cadres, agrégats avec OVER). PARTITION BY, ORDER BY et le cadre ROWS/RANGE sont entièrement disponibles. OFFSET/FETCH ou ROW_NUMBER est courant pour la pagination.","since":"SQL Server 2005","xnote":"Les fonctions de classement sont arrivées en 2005, LAG/LEAD et les cadres en 2012.","danger":"safe"},"mongodb":{"command":"// Window calculations with $setWindowFields:\ndb.sale.aggregate([\n  { $setWindowFields: {\n      partitionBy: \"$city\",\n      sortBy: { amount: -1 },\n      output: {\n        row_num: { $documentNumber: {} },\n        rnk: { $rank: {} },\n        prev_amount: { $shift: { output: \"$amount\", by: -1 } }\n      }\n  } }\n])","note":"L'étape d'agrégation $setWindowFields, introduite dans MongoDB 5.0, fournit les fonctions de fenêtrage : $documentNumber (ROW_NUMBER), $rank/$denseRank, $shift (LAG/LEAD), les fonctions d'agrégat et les fenêtres glissantes (window: { documents/range }). partitionBy et sortBy correspondent à PARTITION BY/ORDER BY en SQL.","since":"MongoDB 5.0","xnote":"L'étape $setWindowFields est arrivée avec MongoDB 5.0.","danger":"safe"},"redis":{"command":"# Not applicable: there are no SQL-like window functions.","note":"Redis est un magasin clé-valeur ; il n'a pas de fonctions analytiques de fenêtrage. Pour des résultats ordonnés, un Sorted Set peut être utilisé (accès de type rang avec ZADD/ZRANGE) ; les opérations de fenêtrage relationnelles comme PARTITION BY/LAG se font toutefois dans la couche applicative.","danger":"safe"},"elasticsearch":{"command":"# Not applicable: there are no SQL-like window functions.\n# Closest equivalent: bucket aggregations.\nGET sale/_search\n{ \"size\": 0, \"aggs\": { \"by_city\": {\n  \"terms\": { \"field\": \"city\" },\n  \"aggs\": { \"total\": { \"sum\": { \"field\": \"amount\" } } } } } }","note":"Elasticsearch n'offre pas de fonctions de fenêtrage classiques (ROW_NUMBER/RANK/LAG). Le regroupement et le tri se font avec le framework d'agrégation : une agrégation terms se rapproche de PARTITION BY, top_hits des premiers enregistrements, et derivative/serial_diff (pipeline aggregations) des différences successives de type LAG.","danger":"safe"}}},{"id":"columnstore-analytics","title":"Stockage en colonnes / analytique","aliases":["columnstore","columnar","column store","analytics","olap","columnar"],"engines":{"oracle":{"command":"-- Database In-Memory: keep the table in memory in columnar format\nALTER TABLE sale INMEMORY;\n-- With priority and compression:\nALTER TABLE sale INMEMORY PRIORITY HIGH\n  MEMCOMPRESS FOR QUERY HIGH;","note":"Oracle Database In-Memory (12.1.0.2+) accélère les requêtes analytiques en gardant les tables en mémoire au format colonne (In-Memory Column Store, IM) tout en conservant le format ligne sur disque (dual format). INMEMORY est une option sous licence séparée. Le paramètre inmemory_size dimensionne la zone IM.","since":"Oracle 12.1.0.2","xnote":"Le column store Database In-Memory est arrivé avec Oracle 12.1.0.2 (licence séparée).","danger":"caution"},"postgres":{"command":"-- Core PostgreSQL is row-based; columnar storage needs an extension:\n-- Citus columnar access method:\nCREATE EXTENSION IF NOT EXISTS citus;\nCREATE TABLE sale_columnar (...) USING columnar;\n-- Alternatives: the Hydra/cstore_fdw extensions.","note":"PostgreSQL de base n'offre qu'un stockage en lignes (heap). Pour le stockage en colonnes, on utilise la méthode d'accès 'columnar' de Citus (USING columnar), Hydra ou l'ancienne extension cstore_fdw ; elles apportent compression et vitesse de parcours pour les charges analytiques/entrepôt de données.","danger":"safe"},"mysql":{"command":"-- Not applicable: core InnoDB does not offer columnar storage.\n-- For analytics: MySQL HeatWave (cloud, in-memory columnar)\n-- or integration with external ClickHouse/Apache Doris.","note":"MySQL standard (InnoDB) stocke en lignes et n'a pas de columnstore intégré. Pour l'accélération analytique, on utilise MySQL HeatWave (un accélérateur en colonnes en mémoire dans Oracle Cloud) ; les solutions on-premise utilisent généralement des moteurs en colonnes externes comme ClickHouse ou Apache Doris.","danger":"safe"},"mssql":{"command":"-- Clustered columnstore index (for analytic tables):\nCREATE CLUSTERED COLUMNSTORE INDEX cci_sale ON dbo.sale;\n-- Nonclustered columnstore on a rowstore table (HTAP):\nCREATE NONCLUSTERED COLUMNSTORE INDEX ncci_sale\n  ON dbo.sale (city, product, amount, created_at);","note":"Les index columnstore de SQL Server (nonclustered en lecture seule en 2012 ; clustered modifiable en 2014 ; nonclustered modifiable en 2016) stockent les données par colonne avec une forte compression ; avec le batch mode, ils accélèrent fortement les requêtes analytiques riches en agrégations/parcours. Le clustered convient aux entrepôts de données, le nonclustered à l'HTAP.","since":"SQL Server 2012","xnote":"L'index columnstore est arrivé en 2012 (lecture seule), le clustered columnstore modifiable en 2014, le nonclustered columnstore modifiable en 2016.","danger":"safe"},"mongodb":{"command":"-- Not applicable: MongoDB is document (row-like) based.\n-- For analytics: dedicated Analytics Nodes in Atlas\n-- or export to a columnar format ($out)/an external engine.","note":"MongoDB stocke les documents en BSON, de type ligne ; il n'y a pas de stockage en colonnes (columnstore) intégré. Pour les charges analytiques, on utilise des Analytics Nodes dédiés dans un replica set Atlas, Atlas Data Federation, ou l'export des données vers un système en colonnes (par ex. Parquet).","danger":"safe"},"redis":{"command":"-- Not applicable: there is no concept of columnar storage.","note":"Redis est un magasin clé-valeur en mémoire ; il n'y a pas de distinction stockage ligne/colonne. Les agrégations analytiques se font avec des structures spécifiques comme Sorted Set, HyperLogLog (comptage approximatif) ou RedisTimeSeries ; il ne convient pas à l'OLAP à grande échelle.","danger":"safe"},"elasticsearch":{"command":"# doc_values: fields are stored column-wise on disk (enabled by default).\n# Required for sorting/aggregation; active on non-text fields.\nPUT sale\n{ \"mappings\": { \"properties\":\n  { \"amount\": { \"type\": \"double\", \"doc_values\": true } } } }","note":"Pour le tri et les agrégations, Elasticsearch conserve les champs dans une structure en colonnes sur disque appelée doc_values (activée par défaut sur les champs numeric/keyword/date). Cette disposition en colonnes accélère les requêtes analytiques/d'agrégation ; elle est désactivée par défaut sur les champs text.","danger":"safe"}}},{"id":"in-memory","title":"Tables en mémoire / accélération","aliases":["in memory","in-memory","memory table","acceleration","in-memory oltp"],"engines":{"oracle":{"command":"-- Keep the table in memory with Database In-Memory:\nALTER TABLE orders INMEMORY;\n-- Memory size (init parameter):\nALTER SYSTEM SET inmemory_size = 4G SCOPE=SPFILE;","note":"Oracle Database In-Memory accélère l'analytique en gardant en mémoire, au format colonne, les tables fréquemment interrogées (licence séparée). Pour l'OLTP, le Buffer Cache et le pool KEEP gardent aussi en mémoire les blocs fréquemment consultés. La solution en mémoire préserve la durabilité via le format ligne sur disque.","danger":"caution"},"postgres":{"command":"-- Fast table without durability (writes no WAL):\nCREATE UNLOGGED TABLE temp_calc (id int, data text);\n-- Session-private temporary table (tends to stay in memory):\nCREATE TEMP TABLE session_data (id int) ON COMMIT DELETE ROWS;","note":"PostgreSQL n'offre pas de moteur de tables en mémoire dédié ; une table UNLOGGED évite les écritures WAL pour accélérer l'écriture (son contenu est perdu en cas de crash ; non durable). Les tables TEMP vivent le temps de la session et sont gardées en mémoire si possible via temp_buffers. Les données chaudes sont mises en cache dans shared_buffers.","danger":"caution"},"mysql":{"command":"-- Table using the MEMORY (HEAP) storage engine:\nCREATE TABLE cache_table (\n  id INT PRIMARY KEY,\n  data VARCHAR(100)\n) ENGINE=MEMORY;","note":"Le moteur de stockage MEMORY (anciennement HEAP) de MySQL garde les données de la table entièrement en RAM ; il est très rapide, mais les données sont perdues au redémarrage du serveur (la structure reste). Il ne prend pas en charge BLOB/TEXT et utilise une taille de ligne fixe. Pour des données chaudes durables, le buffer pool InnoDB est préférable.","danger":"caution"},"mssql":{"command":"-- In-Memory OLTP: memory-optimized table\nCREATE TABLE dbo.session_state (\n  id INT NOT NULL PRIMARY KEY NONCLUSTERED,\n  data NVARCHAR(100)\n) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);","note":"In-Memory OLTP (Hekaton) de SQL Server offre un OLTP hautement concurrent avec des tables optimisées en mémoire (MEMORY_OPTIMIZED=ON) et des structures sans latch. DURABILITY=SCHEMA_AND_DATA garde les données durables ; SCHEMA_ONLY ne préserve que le schéma. Nécessite un filegroup memory-optimized.","since":"SQL Server 2014","xnote":"In-Memory OLTP (Hekaton) est arrivé avec SQL Server 2014.","danger":"caution"},"mongodb":{"command":"-- Hot data is kept in the WiredTiger cache automatically.\n-- Set the cache size (default ~50% of RAM):\n// mongod --wiredTigerCacheSizeGB 8\n// or in the configuration: storage.wiredTiger.engineConfig.cacheSizeGB","note":"MongoDB n'a pas de type de table en mémoire distinct (l'ancien In-Memory Storage Engine était réservé à Enterprise et a été déprécié). Le moteur WiredTiger par défaut améliore la vitesse de lecture en gardant les données et index fréquemment consultés dans son cache mémoire ; le cache de fichiers de l'OS contribue aussi.","danger":"safe"},"redis":{"command":"# Redis already runs entirely in memory; nothing extra is needed.\n# Persistence and memory policy settings:\nCONFIG SET maxmemory 4gb\nCONFIG SET maxmemory-policy allkeys-lru","note":"Redis garde toutes les données en RAM ; c'est par conception la source de sa rapidité. La persistance est assurée par RDB (snapshots) et/ou AOF (journal des commandes). La limite maxmemory et la politique d'éviction (LRU/LFU/random) déterminent le comportement lorsque la mémoire est pleine.","danger":"safe"},"elasticsearch":{"command":"-- Not applicable: there is no fully in-memory table mode.\n# Place hot data on fast nodes with a hot/cold architecture:\nPUT sale/_settings\n{ \"index.routing.allocation.require.data\": \"hot\" }","note":"Elasticsearch stocke les données sur disque (segments Lucene) ; il n'offre pas de moteur de tables en mémoire dédié. Pour la vitesse, il s'appuie sur le cache de fichiers de l'OS (page cache), les doc_values et une architecture hot-warm-cold. Une RAM suffisante et des réglages de heap adaptés sont essentiels pour les performances.","danger":"safe"}}},{"id":"time-series","title":"Données de séries temporelles","aliases":["time series","time series","timeseries","iot","metrics","sensor"],"engines":{"oracle":{"command":"-- Range partitioning by date:\nCREATE TABLE measurements (\n  ts TIMESTAMP, sensor_id NUMBER, value NUMBER)\nPARTITION BY RANGE (ts)\nINTERVAL (NUMTODSINTERVAL(1,'DAY'))\n( PARTITION p0 VALUES LESS THAN (TIMESTAMP '2026-01-01 00:00:00') );","note":"Dans Oracle, les séries temporelles sont généralement gérées avec un partitionnement RANGE (INTERVAL) ; les nouvelles partitions sont créées automatiquement et les anciennes facilement supprimées (le partition pruning accélère les requêtes). La compression (ILM/ADO) et les vues matérialisées pour l'agrégation sont aussi utilisées.","danger":"safe"},"postgres":{"command":"-- Hypertable with the TimescaleDB extension:\nCREATE EXTENSION IF NOT EXISTS timescaledb;\nCREATE TABLE measurements (ts TIMESTAMPTZ, sensor_id INT, value DOUBLE PRECISION);\nSELECT create_hypertable('measurements', 'ts');\n-- Summarize with a continuous aggregate.","note":"PostgreSQL peut héberger des séries temporelles avec le partitionnement natif (PARTITION BY RANGE) ; l'extension TimescaleDB ajoute les hypertables avec découpage automatique par temps (chunks), compression, politiques de rétention et continuous aggregates. C'est une solution courante pour les grosses charges IoT/métriques.","danger":"safe"},"mysql":{"command":"-- RANGE partitioning by date:\nCREATE TABLE measurements (\n  ts DATETIME, sensor_id INT, value DOUBLE,\n  PRIMARY KEY (ts, sensor_id))\nPARTITION BY RANGE (TO_DAYS(ts)) (\n  PARTITION p2026_01 VALUES LESS THAN (TO_DAYS('2026-02-01')),\n  PARTITION pmax VALUES LESS THAN MAXVALUE);","note":"MySQL n'offre pas de moteur dédié aux séries temporelles ; le partitionnement RANGE (avec TO_DAYS/UNIX_TIMESTAMP) permet de supprimer rapidement les anciennes données et le partition pruning. Pour les séries temporelles à fort volume, on préfère généralement des solutions externes (TimescaleDB, InfluxDB) ou des tables de synthèse.","danger":"safe"},"mssql":{"command":"-- Partitioned table (partition function + scheme) or\n-- system-versioned temporal table (history kept automatically):\nCREATE TABLE dbo.measurements (\n  id INT PRIMARY KEY, value DECIMAL(10,2),\n  valid_from DATETIME2 GENERATED ALWAYS AS ROW START,\n  valid_to DATETIME2 GENERATED ALWAYS AS ROW END,\n  PERIOD FOR SYSTEM_TIME (valid_from, valid_to))\nWITH (SYSTEM_VERSIONING = ON);","note":"SQL Server gère les séries temporelles par partitionnement avec une partition function/scheme ; cela apporte le pruning et un archivage rapide pour les données couvrant de larges plages de dates. En outre, les tables temporelles system-versioned (SYSTEM_VERSIONING) conservent automatiquement l'historique des modifications de lignes et permettent l'interrogation à un instant donné (FOR SYSTEM_TIME).","since":"SQL Server 2016","xnote":"Les tables temporelles system-versioned sont arrivées avec SQL Server 2016.","danger":"safe"},"mongodb":{"command":"// Time-series collection (MongoDB 5.0+):\ndb.createCollection(\"measurements\", {\n  timeseries: {\n    timeField: \"ts\",\n    metaField: \"sensor_id\",\n    granularity: \"seconds\"\n  }\n})","note":"Les collections time-series, introduites dans MongoDB 5.0, stockent automatiquement les données dans un format compressé de type colonne (par buckets) selon le temps et le champ meta ; elles réduisent l'utilisation du disque et des index et accélèrent les requêtes par plage. timeField est obligatoire ; metaField (source/tag) et granularity déterminent les performances.","since":"MongoDB 5.0","xnote":"Les collections time-series sont arrivées avec MongoDB 5.0.","danger":"safe"},"redis":{"command":"# Requires the RedisTimeSeries module (Redis Stack).\n# Create a series with a retention period:\nTS.CREATE sensor:1 RETENTION 86400000 LABELS type temperature\n# Add a sample (with an automatic timestamp):\nTS.ADD sensor:1 * 23.5","note":"Redis de base n'a pas de type série temporelle ; le module RedisTimeSeries (Redis Stack) fournit l'ingestion rapide d'échantillons avec TS.CREATE/TS.ADD, la rétention automatique (RETENTION), le sous-échantillonnage (TS.CREATERULE) et les requêtes par plage basées sur des labels avec TS.RANGE/TS.MRANGE. Sans le module, on l'émule avec un Sorted Set.","danger":"safe"},"elasticsearch":{"command":"# Data stream: automatically rolling indexes for time series\nPUT _index_template/measurements-tpl\n{ \"index_patterns\": [\"measurements*\"], \"data_stream\": {},\n  \"template\": {\n    \"settings\": { \"index.mode\": \"time_series\", \"index.routing_path\": [\"sensor_id\"] },\n    \"mappings\": { \"properties\": {\n      \"@timestamp\": { \"type\": \"date\" },\n      \"sensor_id\": { \"type\": \"keyword\", \"time_series_dimension\": true },\n      \"temperature\": { \"type\": \"double\", \"time_series_metric\": \"gauge\" } } } } }\nPUT _data_stream/measurements","note":"Elasticsearch gère les séries temporelles avec les data streams ; de nouveaux backing indexes sont créés automatiquement au fil du temps (rollover), et ILM fait passer les anciennes données par les tiers hot/warm/cold puis les supprime. TSDS (index.mode: time_series) fournit des optimisations de stockage propres aux métriques ; au moins un champ time_series_dimension, un index.routing_path pointant vers lui et un champ @timestamp sont obligatoires (sinon le template est rejeté).","danger":"safe"}}},{"id":"vector-search","title":"Recherche vectorielle (IA/embeddings)","aliases":["vector search","vector search","embedding","ai","semantic search","knn"],"engines":{"oracle":{"command":"-- Oracle 23ai native VECTOR type:\nCREATE TABLE docs (id NUMBER, content CLOB, embedding VECTOR);\n-- Similarity search (cosine distance):\nSELECT id FROM docs\nORDER BY VECTOR_DISTANCE(embedding, :query_vector, COSINE)\nFETCH FIRST 5 ROWS ONLY;","note":"Oracle Database 23ai propose un type de données VECTOR natif et la fonctionnalité AI Vector Search. La fonction VECTOR_DISTANCE calcule la similarité avec les métriques COSINE/EUCLIDEAN/DOT ; les index vectoriels HNSW et IVF sont pris en charge pour la recherche approximative. Les embeddings sont générés dans la base ou par des modèles externes.","since":"Oracle 23ai","xnote":"Le type VECTOR natif et AI Vector Search sont arrivés avec Oracle 23ai.","danger":"safe"},"postgres":{"command":"-- pgvector extension:\nCREATE EXTENSION IF NOT EXISTS vector;\nCREATE TABLE docs (id int, embedding vector(1536));\nCREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);\n-- 5 nearest neighbors (<-> Euclidean, <=> cosine):\nSELECT id FROM docs ORDER BY embedding <=> :query_vec LIMIT 5;","note":"L'extension pgvector ajoute à PostgreSQL un type vector et des opérateurs de similarité : <-> (L2/euclidienne), <=> (cosinus), <#> (produit scalaire). Les index HNSW ou IVFFlat accélèrent la recherche approximative des plus proches voisins (ANN). Largement utilisé dans les applications RAG et de recherche sémantique.","xnote":"PostgreSQL de base n'a pas de type vector ; l'extension pgvector est nécessaire.","danger":"safe"},"mysql":{"command":"-- MySQL 9.0+ native VECTOR type:\nCREATE TABLE docs (id INT, embedding VECTOR(1536));\n-- Cosine distance (DISTANCE is HeatWave-only):\nSELECT id, DISTANCE(embedding, :query_vec, 'COSINE') AS d\nFROM docs ORDER BY d LIMIT 5;","note":"MySQL 9.0 a ajouté le type de données VECTOR natif et les fonctions STRING_TO_VECTOR/VECTOR_TO_STRING et VECTOR_DIM (aussi en Community). La fonction DISTANCE() et la recherche vectorielle approximative à grande échelle ne sont disponibles que dans MySQL HeatWave sur Oracle Cloud ; en Community/Enterprise, calculez les distances dans l'application ou dans un vector store externe. Les versions on-premise plus anciennes nécessitent un vector store externe.","since":"MySQL 9.0","xnote":"Le type de données VECTOR natif est arrivé avec MySQL 9.0.","danger":"safe"},"mssql":{"command":"-- SQL Server 2025 native vector type:\nCREATE TABLE docs (id INT, embedding VECTOR(1536));\n-- Nearest neighbors by cosine distance:\nSELECT TOP 5 id,\n  VECTOR_DISTANCE('cosine', embedding, @query_vec) AS distance\nFROM dbo.docs ORDER BY distance;","note":"SQL Server 2025 (et Azure SQL) proposent un type de données vector natif et la fonction VECTOR_DISTANCE (cosine/euclidean/dot) ; les index vectoriels basés sur DiskANN pour la recherche approximative des plus proches voisins sont en cours de développement. Les embeddings sont générés par des modèles externes comme Azure OpenAI puis stockés.","since":"SQL Server 2025","xnote":"Le type de données vector natif est arrivé avec SQL Server 2025.","danger":"safe"},"mongodb":{"command":"// Atlas Vector Search ($vectorSearch aggregation stage):\ndb.docs.aggregate([\n  { $vectorSearch: {\n      index: \"vector_idx\",\n      path: \"embedding\",\n      queryVector: [0.12, 0.87, /* ... */],\n      numCandidates: 100,\n      limit: 5\n  } }\n])","note":"MongoDB Atlas Vector Search interroge les embeddings avec l'étape d'agrégation $vectorSearch ; en interne, il effectue une recherche approximative des plus proches voisins basée sur HNSW. L'index vectoriel est défini comme un index Atlas Search. Cette fonctionnalité est propre au service géré Atlas (non disponible dans l'édition Community on-premise).","danger":"safe"},"redis":{"command":"# Requires the RediSearch (Redis Stack) VECTOR field type.\n# Define an HNSW vector index:\nFT.CREATE docs_idx ON HASH PREFIX 1 docs: SCHEMA\n  embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE\n# KNN search (with a query vector):\nFT.SEARCH docs_idx \"*=>[KNN 5 @embedding $vec]\" PARAMS 2 vec \"...\"","note":"Redis fournit la recherche vectorielle via le module RediSearch (Redis Stack) ; le type de champ VECTOR prend en charge les index FLAT (exacts) ou HNSW (approximatifs) et les métriques COSINE/L2/IP. Les plus proches voisins sont récupérés avec la syntaxe KNN dans FT.SEARCH. Utilisé pour la recherche sémantique à faible latence et le RAG.","danger":"safe"},"elasticsearch":{"command":"# dense_vector field and kNN search:\nPUT docs\n{ \"mappings\": { \"properties\": { \"embedding\":\n  { \"type\": \"dense_vector\", \"dims\": 1536, \"similarity\": \"cosine\" } } } }\nGET docs/_search\n{ \"knn\": { \"field\": \"embedding\", \"query_vector\": [0.12, 0.87],\n  \"k\": 5, \"num_candidates\": 100 } }","note":"Elasticsearch prend en charge les requêtes approximatives des plus proches voisins (HNSW) avec le type de champ dense_vector et la recherche knn intégrée ; la similarité peut être cosine/dot_product/l2_norm. La pertinence peut être améliorée en combinant la recherche vectorielle avec une requête texte classique (recherche hybride / RRF).","since":"Elasticsearch 8.0","xnote":"La recherche kNN dense_vector intégrée est arrivée avec Elasticsearch 8.x.","danger":"safe"}}},{"id":"cdc-change-tracking","title":"Suivi des modifications (CDC / change tracking)","aliases":["cdc","change data capture","change tracking","change tracking","replication feed","binlog"],"engines":{"oracle":{"command":"-- Read changes from redo logs with LogMiner:\n-- 1) Add the archived/redo log file to analyze:\nEXECUTE DBMS_LOGMNR.ADD_LOGFILE(\n  LOGFILENAME => '/u01/arch/1_1234_1122334455.arc',\n  OPTIONS => DBMS_LOGMNR.NEW);\n-- 2) Start the session:\nEXECUTE DBMS_LOGMNR.START_LOGMNR(\n  OPTIONS => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG);\nSELECT sql_redo, sql_undo FROM V$LOGMNR_CONTENTS\nWHERE seg_name = 'ORDERS';\n-- 3) When finished: EXECUTE DBMS_LOGMNR.END_LOGMNR;\n-- For enterprise CDC/replication, Oracle GoldenGate is used.","note":"Oracle suit les modifications à partir du redo log. LogMiner traduit les redo/archived logs en SQL pour examiner le DML passé ; les fichiers journaux à analyser sont ajoutés avec DBMS_LOGMNR.ADD_LOGFILE. Pour des résultats exploitables, au moins le supplemental logging minimal doit être activé (ALTER DATABASE ADD SUPPLEMENTAL LOG DATA). Pour un CDC continu à faible latence et la réplication hétérogène, Oracle GoldenGate (produit distinct) est la norme ; les anciennes fonctionnalités Streams/Change Data Capture sont dépréciées.","danger":"caution"},"postgres":{"command":"-- Create a logical replication (logical decoding) slot:\nSELECT * FROM pg_create_logical_replication_slot(\n  'cdc_slot', 'wal2json');\n-- Consume changes as JSON:\nSELECT data FROM pg_logical_slot_get_changes('cdc_slot', NULL, NULL);","note":"Le logical decoding de PostgreSQL transforme les enregistrements WAL en flux de modifications lisible via un output plugin (wal2json, pgoutput, test_decoding) ; le replication slot garantit qu'aucune modification n'est perdue. wal_level doit valoir logical. Debezium utilise ce mécanisme pour fournir un CDC d'entreprise.","danger":"caution"},"mysql":{"command":"-- The row-based binlog is the CDC source (binlog_format=ROW):\nSHOW BINARY LOGS;\nSHOW BINLOG EVENTS IN 'binlog.000042';\n-- Usually Debezium/Maxwell reads the binlog and turns it into a stream.","note":"Dans MySQL, la source du CDC est le binary log. Avec binlog_format=ROW, chaque modification de ligne est enregistrée. Des outils comme Debezium ou Maxwell lisent le binlog comme un réplica et produisent un flux d'événements vers Kafka. Activer GTID facilite un suivi de position fiable.","danger":"caution"},"mssql":{"command":"-- Enable Change Data Capture on the database and the table:\nEXEC sys.sp_cdc_enable_db;\nEXEC sys.sp_cdc_enable_table\n  @source_schema = N'dbo', @source_name = N'orders',\n  @role_name = NULL;\n-- For lightweight tracking: ALTER DATABASE ... SET CHANGE_TRACKING = ON;","note":"SQL Server offre deux mécanismes : Change Data Capture (CDC) écrit tout le DML (avec anciennes/nouvelles valeurs) depuis le journal des transactions dans des tables de modifications et nécessite SQL Server Agent. Change Tracking suit seulement, de façon légère, quelles lignes ont changé (net changes) et ne conserve pas l'historique complet des données.","since":"SQL Server 2008","xnote":"CDC et Change Tracking sont arrivés avec SQL Server 2008.","danger":"caution"},"mongodb":{"command":"// Watch changes in real time with Change Streams:\nconst stream = db.orders.watch([\n  { $match: { operationType: { $in: [\"insert\", \"update\"] } } }\n]);\nwhile (stream.hasNext()) { printjson(stream.next()); }","note":"Les Change Streams de MongoDB, basés sur l'oplog, fournissent les événements insert, update et delete au niveau collection/base/cluster sous forme de flux temps réel. Nécessite un replica set ou un cluster shardé. Avec un resumeToken, un flux peut reprendre là où il s'était arrêté après une interruption ; c'est la base des intégrations CDC.","since":"MongoDB 3.6","xnote":"Les Change Streams sont arrivés avec MongoDB 3.6.","danger":"safe"},"redis":{"command":"# Listen for key changes with keyspace notifications:\nCONFIG SET notify-keyspace-events KEA\n# Subscribe to changed keys via Pub/Sub:\nPSUBSCRIBE __keyevent@0__:set\nPSUBSCRIBE __keyevent@0__:expired","note":"Les keyspace notifications de Redis publient des événements sur les clés (SET, DEL, EXPIRE, etc.) via des canaux Pub/Sub ; elles s'activent avec le réglage notify-keyspace-events (K=keyspace, E=keyevent, A=tous les événements). Pour un flux de modifications fiable et durable, les Redis Streams (XADD/XREAD) sont préférables.","danger":"safe"},"elasticsearch":{"command":"-- Not applicable: there is no built-in CDC/change stream.\n# Closest equivalent: incremental query on a timestamp field:\nGET orders/_search\n{ \"query\": { \"range\": { \"updated_ts\": { \"gt\": \"2026-07-05T00:00:00\" } } },\n  \"sort\": [ { \"updated_ts\": \"asc\" } ] }","note":"Elasticsearch n'offre pas de flux de change data capture (CDC) ; il ne publie pas nativement le moment où un document a changé. L'approche courante consiste à ajouter un champ updated_ts à chaque document et à exécuter des requêtes incrémentales par horodatage (range + sort). En général, le CDC de la base source alimente Elasticsearch.","danger":"safe"}}}]},"rosetta/sql-syntax.json":{"category":"Syntaxe SQL et mots-clés","categoryId":"sql-syntax","order":19,"concepts":[{"id":"with-cte","title":"WITH (Common Table Expression / CTE)","aliases":["with","cte","common table expression","temporary query","temporary result set"],"engines":{"oracle":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"WITH définit un jeu de résultats temporaire nommé (CTE) dans une requête ; au lieu d'écrire plusieurs fois une sous-requête, vous la définissez une fois et y faites référence par son nom. Il découpe les requêtes complexes en éléments lisibles. Pris en charge depuis Oracle 9i.","since":"Oracle 9i","danger":"safe"},"postgres":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"Une CTE découpe les requêtes complexes en blocs nommés. Avant PostgreSQL 12, une CTE était une barrière d'optimisation (toujours matérialisée) ; à partir de 12, elle peut être intégrée (inlined), et l'ancien comportement peut être forcé avec WITH ... AS MATERIALIZED.","danger":"safe"},"mysql":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"Une CTE définit un jeu de résultats nommé temporaire dans une requête et améliore la lisibilité. Prise en charge à partir de MySQL 8.0 (et MariaDB 10.2) ; les versions plus anciennes nécessitent une table dérivée (une sous-requête dans FROM).","since":"MySQL 8.0","xnote":"Avant MySQL 8.0, il n'y avait pas de CTE ; on faisait la même chose avec une table dérivée (une sous-requête dans FROM).","danger":"safe"},"mssql":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"Une CTE est un jeu de résultats temporaire nommé. Dans SQL Server, l'instruction qui précède immédiatement une CTE doit se terminer par un point-virgule, c'est pourquoi WITH s'écrit souvent ';WITH'. Elle est valable le temps d'une seule instruction (SELECT/INSERT/UPDATE/DELETE).","danger":"safe"},"mongodb":{"command":"// Closest equivalent: aggregation pipeline stages\ndb.customer.aggregate([\n  { $group: { _id: \"$city\", n: { $sum: 1 } } },\n  { $match: { n: { $gt: 10 } } }\n])","note":"MongoDB n'a ni SQL ni CTE ; les étapes du pipeline d'agrégation font le même travail. Chaque étape prend en entrée la sortie de la précédente, ce qui donne un traitement pas à pas semblable aux chaînes de CTE. $facet ou $lookup peuvent aussi servir pour les résultats intermédiaires.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of SQL or CTEs.","note":"Redis est un magasin de données clé-valeur sans langage de requête. Vous combinez les résultats intermédiaires côté client, ou utilisez un script Lua (EVAL) pour la logique côté serveur.","danger":"safe"},"elasticsearch":{"command":"# CTEs are not supported in Elasticsearch SQL.\nPOST _sql\n{ \"query\": \"SELECT city, COUNT(*) n FROM customer GROUP BY city HAVING COUNT(*) > 10\" }","note":"Elasticsearch SQL (l'API _sql) ne prend pas en charge la syntaxe WITH/CTE ; le regroupement et le filtrage s'écrivent dans un seul SELECT. Avec l'approche native, on fait la même chose avec une agrégation 'terms' et une pipeline aggregation 'bucket_selector'.","danger":"safe"}}},{"id":"recursive-cte","title":"CTE récursive (WITH RECURSIVE)","aliases":["recursive","recursion","with recursive","connect by","hierarchy","tree query"],"engines":{"oracle":{"command":"-- Hierarchy with CONNECT BY:\nSELECT id, name, manager_id, LEVEL\nFROM employee\nSTART WITH manager_id IS NULL\nCONNECT BY PRIOR id = manager_id;\n\n-- Alternative (11gR2+): standard recursive WITH (the RECURSIVE keyword is not written)\nWITH tree (id, name, manager_id) AS (\n  SELECT id, name, manager_id FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree;","note":"Sert à parcourir des données hiérarchiques (arborescentes) : organigrammes, nomenclatures, etc. La méthode classique d'Oracle est CONNECT BY / PRIOR et la pseudo-colonne LEVEL ; START WITH définit la ligne racine. Depuis 11gR2, la CTE récursive standard est aussi prise en charge.","since":"Oracle 11gR2","xnote":"CONNECT BY est la méthode classique ; le WITH récursif standard (sans le mot-clé RECURSIVE) est arrivé avec Oracle 11gR2.","danger":"safe"},"postgres":{"command":"WITH RECURSIVE tree (id, name, manager_id, level) AS (\n  SELECT id, name, manager_id, 1\n  FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id, a.level + 1\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree ORDER BY level;","note":"WITH RECURSIVE combine une requête d'ancrage et une partie récursive avec UNION ALL ; la partie récursive s'exécute de façon répétée jusqu'à ce que le résultat soit vide. Idéal pour les hiérarchies et le parcours de graphes. Prémunissez-vous contre les boucles infinies avec UNION (au lieu de ALL) ou une protection contre les cycles.","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"WITH RECURSIVE tree (id, name, manager_id, level) AS (\n  SELECT id, name, manager_id, 1\n  FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id, a.level + 1\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree ORDER BY level;","note":"MySQL 8.0 (et MariaDB 10.2+) prennent en charge la syntaxe WITH RECURSIVE conforme au standard. La profondeur de récursion est limitée par la variable système cte_max_recursion_depth (1000 par défaut). Les versions plus anciennes obligeaient à écrire une boucle dans une procédure stockée.","since":"MySQL 8.0","xnote":"Avant MySQL 8.0 (MariaDB 10.2), il n'y avait pas de requêtes récursives ; on écrivait une boucle dans une procédure stockée.","danger":"safe"},"mssql":{"command":"WITH tree (id, name, manager_id, level) AS (\n  SELECT id, name, manager_id, 1\n  FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id, a.level + 1\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree ORDER BY level\nOPTION (MAXRECURSION 100);","note":"Dans SQL Server, le mot-clé RECURSIVE ne s'écrit pas pour une CTE récursive ; une CTE contenant UNION ALL qui se référence elle-même est automatiquement traitée comme récursive. La limite de récursion par défaut est 100 ; modifiez-la avec OPTION (MAXRECURSION n) (0 = illimité, à utiliser avec prudence).","since":"SQL Server 2005","danger":"safe"},"mongodb":{"command":"// Closest equivalent: the $graphLookup stage\ndb.employee.aggregate([\n  { $match: { manager_id: null } },\n  { $graphLookup: {\n      from: \"employee\",\n      startWith: \"$id\",\n      connectFromField: \"id\",\n      connectToField: \"manager_id\",\n      as: \"subordinates\"\n  } }\n])","note":"MongoDB n'a pas de CTE récursive ; l'étape d'agrégation $graphLookup sert au parcours hiérarchique. connectFromField/connectToField définissent les champs de liaison et maxDepth la limite de profondeur. Elle résout les relations d'arbre et de graphe en une seule requête.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no recursive queries.","note":"Redis n'offre pas de langage de requête. Le parcours de hiérarchie/graphe se fait dans le code applicatif ou côté serveur avec un script Lua ; dans Redis de base, le parcours d'arbre s'écrit côté client. (La prise en charge du module RedisGraph, autrefois utilisé, a pris fin en 2023.)","danger":"safe"},"elasticsearch":{"command":"# Not applicable: recursive queries are not supported.\n# Approach: model with parent-child (join field) or nested,\n# deep hierarchies are traversed on the application side.","note":"Elasticsearch n'offre pas de requête récursive (aucun équivalent de WITH RECURSIVE / CONNECT BY). Les relations hiérarchiques sont modélisées avec le type de champ 'join' (parent/child) ou en stockant le chemin de l'arbre (materialized path) dans un champ ; le parcours multiniveau se fait généralement dans l'application.","danger":"safe"}}},{"id":"joins","title":"Types de JOIN (INNER/LEFT/RIGHT/FULL/CROSS)","aliases":["join","inner join","left join","right join","full join","cross join","combine tables","table join"],"engines":{"oracle":{"command":"-- INNER: matching rows\nSELECT m.name, s.amount\nFROM customer m JOIN orders s ON m.id = s.customer_id;\n-- LEFT: customers without orders are included too\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n-- CROSS: Cartesian product\nSELECT r.name, u.name FROM color r CROSS JOIN product u;","note":"Un JOIN combine deux tables selon la condition donnée. INNER ne renvoie que les correspondances ; LEFT/RIGHT renvoient toutes les lignes d'un côté (NULL en l'absence de correspondance) ; FULL renvoie les lignes des deux côtés ; CROSS renvoie toutes les combinaisons. Outre les JOIN ANSI standard, Oracle prend aussi en charge l'ancienne syntaxe de jointure externe (+).","danger":"safe"},"postgres":{"command":"SELECT m.name, s.amount\nFROM customer m INNER JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m FULL OUTER JOIN orders s ON m.id = s.customer_id;","note":"PostgreSQL prend entièrement en charge tous les types de JOIN standard (INNER, LEFT/RIGHT/FULL OUTER, CROSS). USING(my_column) est un raccourci pour les colonnes de même nom, tandis que NATURAL JOIN joint automatiquement sur toutes les colonnes de même nom (à utiliser avec prudence).","danger":"safe"},"mysql":{"command":"SELECT m.name, s.amount\nFROM customer m INNER JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n\n-- MySQL has no FULL OUTER JOIN:\nSELECT m.name, s.amount FROM customer m LEFT JOIN orders s ON m.id = s.customer_id\nUNION\nSELECT m.name, s.amount FROM customer m RIGHT JOIN orders s ON m.id = s.customer_id;","note":"MySQL/MariaDB prennent en charge INNER, LEFT, RIGHT et CROSS JOIN mais n'offrent pas directement FULL OUTER JOIN ; on l'émule en combinant les résultats LEFT et RIGHT avec UNION. L'ancienne jointure par virgule produit un produit cartésien et doit être restreinte avec ON/WHERE.","danger":"safe"},"mssql":{"command":"SELECT m.name, s.amount\nFROM customer m INNER JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m FULL OUTER JOIN orders s ON m.id = s.customer_id;","note":"SQL Server prend en charge tous les types de JOIN standard. Les anciens opérateurs de jointure externe *= / =* ont été supprimés ; la syntaxe JOIN ANSI doit être utilisée. APPLY (CROSS/OUTER APPLY) est une forme de jointure supplémentaire pour les sous-requêtes corrélées ligne par ligne.","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $lookup (left outer join)\ndb.customer.aggregate([\n  { $lookup: {\n      from: \"orders\",\n      localField: \"id\",\n      foreignField: \"customer_id\",\n      as: \"orders\"\n  } }\n])","note":"MongoDB n'a pas de JOIN classique ; l'étape d'agrégation $lookup récupère sous forme de tableau les documents correspondants d'une autre collection (semblable à une left outer join). Pour un résultat de type INNER, filtrez ensuite les tableaux vides avec $match et développez le tableau en lignes avec $unwind.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no tables or JOINs.","note":"Redis n'offre pas de jointures relationnelles ; les données sont généralement dénormalisées, ou les relations sont reconstituées côté client en lisant plusieurs clés. RediSearch (FT.AGGREGATE) peut servir pour des requêtes limitées de type jointure ; la prise en charge du module RedisGraph a pris fin en 2023.","danger":"safe"},"elasticsearch":{"command":"# Not applicable: there is no general JOIN in a distributed design.\n# Limited options: nested, parent/child (join field),\n# terms lookup. Data is usually denormalized at indexing time.","note":"En raison de son architecture distribuée, Elasticsearch n'offre pas de JOIN arbitraires entre tables. Pour des relations limitées, il existe le type de champ 'nested' au sein d'un même index et le type de champ 'join' (parent/child) sur le même shard. La solution la plus courante consiste à dénormaliser les données à l'indexation.","danger":"safe"}}},{"id":"window-functions","title":"Fonctions de fenêtrage (OVER / PARTITION BY)","aliases":["window","window function","over","partition by","row_number","rank","lag","lead","analytic function"],"engines":{"oracle":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_no,\n  RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank_no,\n  LAG(salary)    OVER (PARTITION BY department ORDER BY salary)      AS prev_salary\nFROM employee;","note":"Les fonctions de fenêtrage calculent sur une « fenêtre » pour chaque ligne sans regrouper les lignes (sans réduire leur nombre). PARTITION BY découpe la fenêtre, ORDER BY la trie. Oracle a été pionnier de ces fonctions analytiques et en offre un riche ensemble, dont ROW_NUMBER, RANK, DENSE_RANK, LAG/LEAD et SUM() OVER.","since":"Oracle 8i","danger":"safe"},"postgres":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER w AS row_no,\n  RANK()       OVER w AS rank_no,\n  LEAD(salary)   OVER w AS next_salary\nFROM employee\nWINDOW w AS (PARTITION BY department ORDER BY salary DESC);","note":"PostgreSQL prend en charge toutes les fonctions de fenêtrage standard et permet de nommer une définition de fenêtre répétée avec WINDOW ... AS. Avec les définitions de cadre ROWS/RANGE/GROUPS, vous pouvez calculer des cumuls, des moyennes mobiles, etc.","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_no,\n  DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_no,\n  LAG(salary)    OVER (PARTITION BY department ORDER BY salary)      AS prev_salary\nFROM employee;","note":"MySQL 8.0 (et MariaDB 10.2+) prennent en charge les fonctions de fenêtrage ; auparavant, on les émulait avec des variables utilisateur ou des sous-requêtes. ROW_NUMBER, RANK, DENSE_RANK, NTILE, LAG/LEAD, FIRST_VALUE/LAST_VALUE et la forme OVER des fonctions d'agrégat sont disponibles.","since":"MySQL 8.0","xnote":"Avant MySQL 8.0, il n'y avait pas de fonctions de fenêtrage ; on les émulait avec des variables/auto-jointures.","danger":"safe"},"mssql":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_no,\n  RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank_no,\n  SUM(salary)    OVER (PARTITION BY department) AS dept_total\nFROM employee;","note":"SQL Server prend en charge les fonctions de fenêtrage de classement depuis 2005, et les agrégats avec cadre et des fonctions comme LAG/LEAD depuis 2012. ROW_NUMBER est couramment utilisé pour la pagination (avec OFFSET/FETCH) et pour trouver les N premières lignes par groupe.","since":"SQL Server 2005","xnote":"Les fonctions de fenêtrage de classement sont arrivées en 2005, les cadres et LAG/LEAD en 2012.","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $setWindowFields (MongoDB 5.0+)\ndb.employee.aggregate([\n  { $setWindowFields: {\n      partitionBy: \"$department\",\n      sortBy: { salary: -1 },\n      output: { row_no: { $rank: {} },\n                prev_salary: { $shift: { output: \"$salary\", by: -1 } } }\n  } }\n])","note":"L'étape $setWindowFields de MongoDB 5.0+ est l'équivalent des fonctions de fenêtrage SQL : la fenêtre est définie avec partitionBy et sortBy, et produit des sorties comme $rank/$denseRank/$documentNumber, $shift (LAG/LEAD) et des cumuls. Dans les anciennes versions, on résolvait cela avec $group + du code.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no window functions.","note":"Redis n'offre pas de fonctions analytiques de fenêtrage. Pour des résultats ordonnés et le classement, utilisez la structure Sorted Set (ZSET) : attribuez des scores avec ZADD, lisez le rang avec ZRANK/ZREVRANK et les plages avec ZRANGE. L'analytique complexe se fait dans l'application.","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: top_hits and pipeline aggregations\nGET employee/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"dept\": { \"terms\": { \"field\": \"department\" },\n    \"aggs\": { \"top_paid\": { \"top_hits\": {\n      \"sort\": [ { \"salary\": \"desc\" } ], \"size\": 3 } } } } }\n}","note":"Elasticsearch n'a pas de fonctions de fenêtrage directes. Pour les N premiers enregistrements par groupe, utilisez une sous-agrégation 'top_hits' ; pour les calculs mobiles/cumulés, utilisez des pipeline aggregations comme 'derivative', 'cumulative_sum' et 'moving_fn'. Le tri et le partitionnement se font avec une agrégation terms.","danger":"safe"}}},{"id":"group-rollup","title":"GROUP BY, HAVING, ROLLUP/CUBE/GROUPING SETS","aliases":["group by","having","rollup","cube","grouping sets","grouping","subtotal","subtotals"],"engines":{"oracle":{"command":"-- Basic grouping + HAVING\nSELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING COUNT(*) > 5;\n\n-- Subtotals and grand total with ROLLUP\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY ROLLUP(city, district);","note":"GROUP BY regroupe les lignes selon les colonnes données et applique des fonctions d'agrégat ; HAVING filtre le résultat regroupé (WHERE filtre les lignes avant le regroupement). ROLLUP produit des sous-totaux hiérarchiques + un total général, CUBE toutes les combinaisons, et GROUPING SETS les regroupements choisis. Oracle prend en charge les trois.","danger":"safe"},"postgres":{"command":"SELECT city, district, SUM(balance)\nFROM customer\nGROUP BY GROUPING SETS ((city, district), (city), ());\n\n-- CUBE produces all subset combinations\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY CUBE(city, district);","note":"PostgreSQL 9.5+ prend en charge ROLLUP, CUBE et GROUPING SETS de façon conforme au standard. La fonction GROUPING() indique si une colonne figure dans une ligne de total (NULL). HAVING filtre après l'agrégation, WHERE avant.","since":"PostgreSQL 9.5","xnote":"ROLLUP, CUBE et GROUPING SETS sont arrivés avec PostgreSQL 9.5.","danger":"safe"},"mysql":{"command":"SELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING n > 5;\n\n-- ROLLUP in MySQL:\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY city, district WITH ROLLUP;","note":"MySQL/MariaDB produisent des sous-totaux/totaux généraux avec la syntaxe GROUP BY ... WITH ROLLUP ; CUBE et GROUPING SETS ne sont pas pris en charge. Les alias du SELECT peuvent être utilisés dans HAVING (ce qui ne fonctionne généralement pas dans Oracle/SQL Server). Dans les lignes ROLLUP, la colonne regroupée apparaît à NULL.","danger":"safe"},"mssql":{"command":"SELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING COUNT(*) > 5;\n\n-- ROLLUP / CUBE / GROUPING SETS\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY ROLLUP(city, district);","note":"Outre GROUP BY, SQL Server prend en charge ROLLUP(), CUBE() et GROUPING SETS (2008+) avec une syntaxe moderne ; l'ancienne forme WITH ROLLUP/WITH CUBE est dépréciée. GROUPING() et GROUPING_ID() aident à distinguer les lignes de total.","since":"SQL Server 2008","xnote":"La syntaxe moderne GROUPING SETS est arrivée avec SQL Server 2008.","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $group + $sum\ndb.customer.aggregate([\n  { $group: { _id: \"$city\", n: { $sum: 1 } } },\n  { $match: { n: { $gt: 5 } } }\n])\n// $facet is used for ROLLUP-like multi-level totals","note":"Dans MongoDB, $group est l'équivalent du GROUP BY SQL ; _id définit la clé de regroupement et les accumulateurs ($sum, $avg, $max) définissent les agrégats. Le filtrage après regroupement se fait avec $match (l'équivalent de HAVING). Les totaux multiniveaux de type ROLLUP/CUBE s'obtiennent en exécutant plusieurs pipelines avec $facet.","danger":"safe"},"redis":{"command":"# Not applicable: core Redis has no GROUP BY.\n# Aggregation with RediSearch:\nFT.AGGREGATE customer_idx \"*\" GROUPBY 1 @city REDUCE COUNT 0 AS n","note":"Redis de base n'offre pas de regroupement ; la commande FT.AGGREGATE du module RediSearch effectue une agrégation de type SQL avec GROUPBY et REDUCE (COUNT, SUM, AVG). L'étape FILTER filtre le résultat regroupé comme HAVING. Sans le module, l'agrégation se fait dans l'application.","danger":"safe"},"elasticsearch":{"command":"GET customer/_search\n{\n  \"size\": 0,\n  \"aggs\": {\n    \"by_city\": { \"terms\": { \"field\": \"city\" },\n      \"aggs\": { \"total\": { \"sum\": { \"field\": \"balance\" } } } }\n  }\n}","note":"Dans Elasticsearch, le regroupement se fait avec l'agrégation de buckets 'terms' et les agrégats avec des metric aggregations (sum, avg, cardinality). Pour un filtrage de type HAVING, utilisez la pipeline aggregation 'bucket_selector'. Les sous-totaux multiniveaux se construisent avec des agrégations imbriquées.","danger":"safe"}}},{"id":"case-expr","title":"Expression CASE (valeur conditionnelle)","aliases":["case","conditional","when","then","decode","if then","conditional value"],"engines":{"oracle":{"command":"-- Searched CASE\nSELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;\n\n-- Oracle-specific shortcut: DECODE (equality comparison)\nSELECT name, DECODE(status, 'A', 'Active', 'P', 'Passive', 'Unknown') FROM employee;","note":"CASE associe une expression à différentes valeurs selon des conditions ; il existe une forme simple (CASE my_column WHEN valeur) et une forme recherchée (CASE WHEN condition). DECODE d'Oracle fait un travail similaire mais ne compare que l'égalité et traite les NULL comme égaux ; préférez CASE pour la portabilité.","danger":"safe"},"postgres":{"command":"SELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;","note":"PostgreSQL prend entièrement en charge l'expression CASE standard, utilisable partout où une valeur est produite (SELECT, WHERE, ORDER BY, GROUP BY). Il n'a pas de DECODE à la Oracle ; utilisez CASE à la place, ou COALESCE/NULLIF pour les conditions courtes.","danger":"safe"},"mysql":{"command":"SELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;\n\n-- IF() for a short condition:\nSELECT name, IF(active = 1, 'Yes', 'No') FROM employee;","note":"MySQL/MariaDB prennent en charge l'expression CASE standard ; il existe aussi la fonction IF(condition, si_vrai, si_faux) pour une condition rapide à deux branches (semblable à IIF de SQL Server). CASE peut être utilisé dans tout contexte d'expression.","danger":"safe"},"mssql":{"command":"SELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;\n\n-- Shortcut: IIF() (2012+)\nSELECT name, IIF(active = 1, 'Yes', 'No') FROM employee;","note":"SQL Server prend en charge le CASE standard ; depuis 2012, il existe le raccourci IIF() pour les conditions à deux branches et CHOOSE() pour les correspondances multiples. IIF est en réalité traduit en CASE. CASE peut aussi être utilisé dans ORDER BY et dans les computed columns.","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $switch or $cond\ndb.employee.aggregate([\n  { $project: { name: 1, tier: {\n      $switch: { branches: [\n        { case: { $gte: [\"$salary\", 20000] }, then: \"high\" },\n        { case: { $gte: [\"$salary\", 10000] }, then: \"medium\" } ],\n        default: \"low\" } } } }\n])","note":"Dans l'agrégation MongoDB, les équivalents de CASE sont les opérateurs $switch (multibranche) et $cond (if-then-else à trois arguments). Ils s'utilisent dans $project ou $addFields pour créer des champs dérivés conditionnels.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no in-query conditional expression.","note":"Redis n'a pas d'expression de requête comme CASE. La logique conditionnelle s'écrit dans le code applicatif ou dans un script Lua côté serveur (EVAL). Une évaluation d'expressions limitée est possible avec APPLY dans FT.AGGREGATE de RediSearch.","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: a condition inside a runtime field or script\nGET employee/_search\n{\n  \"runtime_mappings\": { \"tier\": { \"type\": \"keyword\",\n    \"script\": \"if (doc['salary'].value >= 20000) emit('high'); else if (doc['salary'].value >= 10000) emit('medium'); else emit('low');\" } },\n  \"fields\": [\"tier\"]\n}","note":"Dans Elasticsearch, la logique CASE dans une requête s'écrit sous forme de script Painless (runtime field, script_fields ou scripted metric). Le flux if/else se définit en langage Painless. L'agrégation 'range' peut aussi produire des buckets de type CASE pour la catégorisation.","danger":"safe"}}},{"id":"union-set","title":"UNION / UNION ALL / INTERSECT / EXCEPT (MINUS)","aliases":["union","union all","intersect","except","minus","set operations","combine","intersection","difference"],"engines":{"oracle":{"command":"-- Union without duplicates\nSELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\n-- Intersection and difference (MINUS instead of EXCEPT in Oracle)\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;\nSELECT city FROM customer MINUS SELECT city FROM supplier;","note":"Les opérateurs ensemblistes combinent les jeux de résultats de deux requêtes. UNION élimine les doublons (plus lent), UNION ALL les conserve tous (plus rapide), INTERSECT renvoie les lignes communes, et l'opérateur de différence renvoie les lignes présentes uniquement dans le premier ensemble. Pour la différence, Oracle utilise MINUS au lieu du EXCEPT standard (EXCEPT existe aussi depuis 21c).","danger":"safe"},"postgres":{"command":"SELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;\nSELECT city FROM customer EXCEPT SELECT city FROM supplier;","note":"PostgreSQL prend entièrement en charge UNION, UNION ALL, INTERSECT et EXCEPT standard (EXCEPT, et non MINUS, pour la différence). La forme ALL de chaque opérateur (INTERSECT ALL, EXCEPT ALL) conserve le nombre de doublons. Le nombre et les types de colonnes doivent être compatibles.","danger":"safe"},"mysql":{"command":"SELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\n-- INTERSECT/EXCEPT arrive with MySQL 8.0.31+ and MariaDB 10.3+\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;","note":"Pendant longtemps, MySQL n'a pris en charge que UNION / UNION ALL ; INTERSECT et EXCEPT n'ont été ajoutés qu'en MySQL 8.0.31 (MariaDB 10.3). Sur les versions plus anciennes, utilisez INNER JOIN/EXISTS pour l'intersection et LEFT JOIN ... IS NULL ou NOT EXISTS pour la différence.","since":"MySQL 8.0.31","xnote":"INTERSECT/EXCEPT sont arrivés avec MySQL 8.0.31 (MariaDB 10.3) ; auparavant, on les résolvait avec JOIN/EXISTS.","danger":"safe"},"mssql":{"command":"SELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;\nSELECT city FROM customer EXCEPT SELECT city FROM supplier;","note":"SQL Server prend en charge UNION, UNION ALL, INTERSECT et EXCEPT standard (2005+) ; l'opérateur de différence est EXCEPT (pas MINUS). INTERSECT/EXCEPT comparent les lignes en traitant les valeurs NULL comme égales.","since":"SQL Server 2005","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $unionWith (MongoDB 4.4+)\ndb.customer.aggregate([\n  { $project: { city: 1, _id: 0 } },\n  { $unionWith: { coll: \"supplier\",\n      pipeline: [ { $project: { city: 1, _id: 0 } } ] } },\n  { $group: { _id: \"$city\" } }  // UNION (distinct) effect\n])","note":"L'étape $unionWith de MongoDB 4.4+ combine les résultats de deux collections (comme UNION ALL). Pour éliminer les doublons, dédupliquez ensuite avec $group. INTERSECT/EXCEPT n'ont pas d'équivalent direct ; on les modélise avec $lookup ou des opérateurs ensemblistes ($setIntersection, $setDifference).","danger":"safe"},"redis":{"command":"# Set operations with the Set data type:\nSUNION city_customer city_supplier    # union\nSINTER city_customer city_supplier    # intersection\nSDIFF  city_customer city_supplier    # difference","note":"Redis n'a pas de SQL, mais le type de données Set offre des opérations ensemblistes natives : SUNION (union), SINTER (intersection), SDIFF (différence) et leurs variantes STORE qui enregistrent le résultat. Elles portent sur des membres d'ensembles déjà construits, pas sur des résultats de requêtes.","danger":"safe"},"elasticsearch":{"command":"# There is no direct set operator.\n# For a union, search multiple indices in one query:\nGET customer,supplier/_search\n{ \"query\": { \"match_all\": {} } }","note":"Elasticsearch n'offre pas UNION/INTERSECT/EXCEPT. Un effet d'union s'obtient en interrogeant plusieurs indices séparés par des virgules ; pour l'intersection et la différence, utilisez les clauses must/should/must_not d'une requête bool ou une logique ensembliste côté application.","danger":"safe"}}},{"id":"exists-in","title":"EXISTS / IN / NOT EXISTS (sous-requête)","aliases":["exists","in","not exists","not in","subquery","sub-select","correlated subquery"],"engines":{"oracle":{"command":"-- Customers with orders (EXISTS)\nSELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\n-- List membership with IN\nSELECT name FROM customer\nWHERE city IN ('Ankara', 'Izmir');\n\n-- Customers with no orders at all (NOT EXISTS)\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"EXISTS vérifie si une sous-requête corrélée renvoie au moins une ligne (il s'arrête à la première correspondance, donc rapide). IN vérifie si une valeur figure dans une liste/le résultat d'une sous-requête. Attention : si la sous-requête peut produire NULL, NOT IN donne des résultats inattendus ; NOT EXISTS est plus sûr. L'optimiseur Oracle transforme souvent les deux en un même plan.","danger":"safe"},"postgres":{"command":"SELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\nSELECT name FROM customer\nWHERE city IN (SELECT city FROM target_cities);\n\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"PostgreSQL prend entièrement en charge EXISTS/IN/NOT EXISTS. Avec NOT IN, si la sous-requête contient NULL, tout le résultat peut devenir vide ; NOT EXISTS ou LEFT JOIN ... IS NULL est donc recommandé. Le planificateur traite IN et EXISTS de façon équivalente comme une semi-jointure dans la plupart des cas.","danger":"safe"},"mysql":{"command":"SELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\nSELECT name FROM customer\nWHERE city IN ('Ankara', 'Izmir');\n\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"MySQL/MariaDB prennent en charge EXISTS/IN/NOT EXISTS. Avant MySQL 5.6, les sous-requêtes corrélées pouvaient être lentes ; 5.6+ a accéléré les sous-requêtes IN grâce aux optimisations semi-join et materialization. Le piège NOT IN + NULL s'applique ici aussi.","danger":"safe"},"mssql":{"command":"SELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\nSELECT name FROM customer\nWHERE city IN (SELECT city FROM target_cities);\n\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"SQL Server prend en charge EXISTS/IN/NOT EXISTS, et l'optimiseur les traite le plus souvent comme des semi/anti-jointures. Si une sous-requête NOT IN contient NULL, le résultat peut devenir vide de façon inattendue ; NOT EXISTS est le choix sûr. INTERSECT/EXCEPT peuvent aussi servir pour une logique d'appartenance similaire.","danger":"safe"},"mongodb":{"command":"// IN equivalent: the $in operator\ndb.customer.find({ city: { $in: [\"Ankara\", \"Izmir\"] } })\n\n// EXISTS equivalent: match check with $lookup + $match\ndb.customer.aggregate([\n  { $lookup: { from: \"orders\", localField: \"id\",\n      foreignField: \"customer_id\", as: \"s\" } },\n  { $match: { \"s.0\": { $exists: true } } }  // at least one order\n])","note":"Dans MongoDB, l'appartenance à une liste de valeurs se fait avec les opérateurs $in / $nin. L'équivalent d'une sous-requête EXISTS consiste à récupérer le tableau des documents correspondants avec $lookup et à filtrer selon que le tableau est vide ou non. La vérification de l'existence d'un champ est un opérateur distinct { field: { $exists: true } } (présence d'un champ, pas relation entre lignes).","danger":"safe"},"redis":{"command":"# Membership check with Set/Hash:\nSISMEMBER target_cities \"Ankara\"   # is it in the set? (IN-like)\nEXISTS customer:1                 # does the key exist?","note":"Redis n'a pas de sous-requêtes. Pour l'appartenance d'une valeur, utilisez SISMEMBER sur un Set (de type IN). La commande EXISTS n'est pas une sous-requête ; elle indique si la clé donnée existe. Les vérifications d'existence relationnelles se font côté application en lisant plusieurs clés.","danger":"safe"},"elasticsearch":{"command":"# IN equivalent: terms query\nGET customer/_search\n{ \"query\": { \"terms\": { \"city\": [\"Ankara\", \"Izmir\"] } } }\n\n# Field existence: exists query\nGET customer/_search\n{ \"query\": { \"exists\": { \"field\": \"phone\" } } }","note":"Dans Elasticsearch, l'équivalent de IN est la requête 'terms' (correspondance avec l'une de plusieurs valeurs), et NOT IN correspond à terms dans bool must_not. La requête 'exists' ne correspond pas au EXISTS SQL ; elle vérifie si un champ a une valeur dans le document. Un EXISTS corrélé entre tables nécessite généralement une dénormalisation.","danger":"safe"}}},{"id":"limit-paging","title":"Limitation de lignes / pagination (LIMIT/OFFSET, FETCH, ROWNUM, TOP)","aliases":["limit","offset","fetch","rownum","top","paging","page through results","pagination","row limit"],"engines":{"oracle":{"command":"-- 12c+ standard: OFFSET/FETCH\nSELECT name, salary FROM employee\nORDER BY salary DESC\nOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;\n\n-- Older versions: with ROWNUM\nSELECT * FROM (\n  SELECT name, salary FROM employee ORDER BY salary DESC\n) WHERE ROWNUM <= 10;","note":"Sert à limiter/paginer un jeu de résultats. Depuis Oracle 12c, la syntaxe standard OFFSET n ROWS FETCH NEXT m ROWS ONLY est prise en charge. Auparavant, on utilisait la pseudo-colonne ROWNUM ; ROWNUM étant attribué avant le tri, il est obligatoire d'encapsuler une sous-requête avec ORDER BY.","since":"Oracle 12c","xnote":"Avant Oracle 12c, la limitation de lignes se faisait avec ROWNUM.","danger":"safe"},"postgres":{"command":"-- LIMIT/OFFSET\nSELECT name, salary FROM employee\nORDER BY salary DESC\nLIMIT 10 OFFSET 20;\n\n-- Standard equivalent: FETCH\nSELECT name, salary FROM employee\nORDER BY salary DESC\nOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;","note":"PostgreSQL prend en charge à la fois LIMIT n OFFSET m et la forme standard OFFSET ... FETCH. Les grandes valeurs d'OFFSET sont lentes ; pour la pagination profonde, la pagination par clé (keyset / 'WHERE id > last_id') est plus efficace. LIMIT sans ORDER BY ne garantit pas l'ordre des résultats.","danger":"safe"},"mysql":{"command":"-- LIMIT [offset,] row_count\nSELECT name, salary FROM employee\nORDER BY salary DESC\nLIMIT 10 OFFSET 20;\n\n-- Short form: LIMIT offset, row_count\nSELECT name, salary FROM employee ORDER BY salary DESC LIMIT 20, 10;","note":"MySQL/MariaDB utilisent le mot-clé LIMIT sous deux formes : LIMIT row_count OFFSET offset ou LIMIT offset, row_count. MySQL 8.0.19+ accepte aussi la syntaxe standard FETCH. La pagination keyset est recommandée pour les performances avec un OFFSET profond.","danger":"safe"},"mssql":{"command":"-- First N with TOP\nSELECT TOP 10 name, salary FROM employee ORDER BY salary DESC;\n\n-- OFFSET/FETCH for paging (2012+), ORDER BY mandatory\nSELECT name, salary FROM employee\nORDER BY salary DESC\nOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;","note":"SQL Server utilise TOP n (avec les variantes TOP (n) PERCENT / WITH TIES) pour les N premières lignes et, depuis 2012, OFFSET ... FETCH pour la pagination. ORDER BY est obligatoire pour qu'OFFSET/FETCH fonctionne. SQL Server n'a pas de mot-clé LIMIT.","since":"SQL Server 2012","xnote":"La pagination OFFSET/FETCH est arrivée avec 2012 ; auparavant, elle se faisait avec TOP ou ROW_NUMBER.","danger":"safe"},"mongodb":{"command":"// Paging with skip/limit\ndb.employee.find()\n  .sort({ salary: -1 })\n  .skip(20)\n  .limit(10)","note":"Dans MongoDB, limit() fixe le nombre de documents renvoyés et skip() le nombre de documents ignorés ; combinés à sort(), ils assurent la pagination. Les grandes valeurs de skip sont coûteuses ; pour la pagination profonde, une requête par plage (keyset) basée sur la dernière valeur vue est préférable.","danger":"safe"},"redis":{"command":"# Range read on a sorted set (ZSET):\nZREVRANGE salary_sorted 20 29    # members ranked 21-30\n# Range on a list:\nLRANGE queue 0 9              # first 10 elements","note":"Redis n'a pas de LIMIT SQL, mais il dispose de commandes de lecture par plage : ZRANGE/ZREVRANGE start stop (par index) ou ZRANGEBYSCORE ... LIMIT offset count pour les Sorted Sets ; LRANGE pour les Lists. Ces commandes offrent un accès de type pagination.","danger":"safe"},"elasticsearch":{"command":"GET employee/_search\n{\n  \"from\": 20,\n  \"size\": 10,\n  \"sort\": [ { \"salary\": \"desc\" } ]\n}","note":"Dans Elasticsearch, 'size' fixe le nombre de hits renvoyés et 'from' le nombre ignoré (l'équivalent de LIMIT/OFFSET). from+size ne peut pas dépasser index.max_result_window (10 000 par défaut) ; pour la pagination profonde, utilisez 'search_after' (keyset) ou un PIT (point-in-time).","danger":"safe"}}},{"id":"merge-upsert","title":"MERGE / UPSERT (mettre à jour si existe, sinon insérer)","aliases":["merge","upsert","on conflict","on duplicate key","insert or update","update if exists"],"engines":{"oracle":{"command":"MERGE INTO customer m\nUSING (SELECT 1 AS id, 'Ali' AS name, 500 AS balance FROM dual) k\nON (m.id = k.id)\nWHEN MATCHED THEN\n  UPDATE SET m.name = k.name, m.balance = k.balance\nWHEN NOT MATCHED THEN\n  INSERT (id, name, balance) VALUES (k.id, k.name, k.balance);","note":"MERGE (upsert) rapproche une source d'une cible, met à jour les lignes correspondantes et insère les autres ; idéal pour la synchronisation de données en masse. Oracle offre une prise en charge solide de MERGE : WHEN MATCHED ... DELETE et des mises à jour conditionnelles peuvent aussi être écrits. Pour une source d'une seule ligne, on utilise la table dual.","since":"Oracle 9i","danger":"caution"},"postgres":{"command":"-- The PostgreSQL way: INSERT ... ON CONFLICT\nINSERT INTO customer (id, name, balance)\nVALUES (1, 'Ali', 500)\nON CONFLICT (id) DO UPDATE\n  SET name = EXCLUDED.name, balance = EXCLUDED.balance;","note":"L'upsert classique de PostgreSQL est INSERT ... ON CONFLICT (colonne unique) DO UPDATE / DO NOTHING ; les nouvelles valeurs de la ligne en conflit sont accessibles via l'alias EXCLUDED. Depuis PostgreSQL 15, l'instruction MERGE standard est aussi prise en charge. ON CONFLICT est atomique sur une seule table et largement utilisé.","since":"PostgreSQL 15","xnote":"L'instruction MERGE standard est arrivée avec PostgreSQL 15 ; auparavant, les upserts se faisaient avec INSERT ... ON CONFLICT (9.5).","danger":"caution"},"mysql":{"command":"INSERT INTO customer (id, name, balance)\nVALUES (1, 'Ali', 500)\nON DUPLICATE KEY UPDATE\n  name = VALUES(name), balance = VALUES(balance);","note":"MySQL/MariaDB utilisent INSERT ... ON DUPLICATE KEY UPDATE pour les upserts ; en cas de conflit sur une clé unique ou primaire, la ligne est mise à jour. Les nouvelles valeurs sont accessibles avec VALUES(my_column) (ou l'alias de ligne 'new.my_column' en 8.0.19+). INSERT IGNORE ignore silencieusement la ligne en cas de conflit. MySQL n'a pas de MERGE standard.","danger":"caution"},"mssql":{"command":"MERGE INTO customer AS m\nUSING (VALUES (1, 'Ali', 500)) AS k(id, name, balance)\nON m.id = k.id\nWHEN MATCHED THEN\n  UPDATE SET m.name = k.name, m.balance = k.balance\nWHEN NOT MATCHED THEN\n  INSERT (id, name, balance) VALUES (k.id, k.name, k.balance);","note":"SQL Server prend en charge l'instruction MERGE depuis 2008 ; elle offre des branches supplémentaires comme WHEN NOT MATCHED BY SOURCE pour supprimer les lignes excédentaires de la cible. Une instruction MERGE doit se terminer par un point-virgule. Remarque : en raison de certains bugs dans des cas limites, des instructions INSERT/UPDATE séparées peuvent être préférables pour les upserts simples.","since":"SQL Server 2008","danger":"caution"},"mongodb":{"command":"// Find-and-update or insert with upsert:true\ndb.customer.updateOne(\n  { id: 1 },\n  { $set: { name: \"Ali\", balance: 500 } },\n  { upsert: true }\n)","note":"Dans MongoDB, l'upsert est une option native : si { upsert: true } est ajouté à updateOne/updateMany/findOneAndUpdate, un document correspondant est mis à jour, sinon un nouveau document est créé. Pour la synchronisation en masse, l'étape d'agrégation $merge (écriture/mise à jour dans une collection cible) est plus proche du MERGE SQL.","danger":"caution"},"redis":{"command":"# SET is a natural upsert: it overwrites the key if present, otherwise creates it.\nSET customer:1 '{\"name\":\"Ali\",\"balance\":500}'\n# Update/add a hash field:\nHSET customer:1 name Ali balance 500","note":"Dans Redis, les commandes d'écriture sont par nature des upserts : SET/HSET mettent à jour la clé ou le champ s'il existe, sinon le créent. Utilisez l'option SET ... NX pour écrire uniquement en cas d'absence, et XX uniquement en cas de présence. La distinction « mettre à jour si existe, sinon insérer » est déjà le comportement par défaut de la plupart des commandes.","danger":"safe"},"elasticsearch":{"command":"# An index call with a document id acts like an upsert (replaces if present):\nPUT customer/_doc/1\n{ \"name\": \"Ali\", \"balance\": 500 }\n# Partial update + insert if absent:\nPOST customer/_update/1\n{ \"doc\": { \"balance\": 500 }, \"doc_as_upsert\": true }","note":"Dans Elasticsearch, un appel index (PUT) avec le même _id remplace entièrement le document s'il existe, sinon le crée (de type upsert). Pour des mises à jour partielles avec insertion si absent, utilisez 'doc_as_upsert': true ou un bloc 'upsert' dans l'API _update. Les upserts scriptés sont aussi possibles.","danger":"caution"}}},{"id":"returning-output","title":"Renvoyer la ligne modifiée (RETURNING / OUTPUT)","aliases":["returning","output","inserted","deleted","returned value","modified row"],"engines":{"oracle":{"command":"-- Single row: RETURNING ... INTO (PL/SQL bind variable)\nUPDATE employee SET salary = salary * 1.1\nWHERE id = 5\nRETURNING salary INTO :new_salary;\n\n-- Get back the key generated by an INSERT\nINSERT INTO employee (name) VALUES ('Ayse')\nRETURNING id INTO :new_id;","note":"RETURNING renvoie les valeurs des lignes modifiées par INSERT/UPDATE/DELETE (par ex. un ID généré automatiquement) sans SELECT séparé. Dans Oracle, une seule ligne est renvoyée avec RETURNING ... INTO une variable de liaison, et plusieurs lignes avec RETURNING ... BULK COLLECT INTO une collection.","danger":"safe"},"postgres":{"command":"INSERT INTO employee (name, salary)\nVALUES ('Ayse', 15000)\nRETURNING id, name;\n\nUPDATE employee SET salary = salary * 1.1\nWHERE department = 'IT'\nRETURNING id, salary;","note":"La clause RETURNING de PostgreSQL est très souple : elle renvoie directement sous forme de jeu de résultats n'importe quelle colonne (expressions calculées comprises) des lignes affectées par les instructions INSERT, UPDATE, DELETE et MERGE. C'est le moyen le plus propre d'obtenir les valeurs serial/identity générées.","since":"PostgreSQL 8.2","danger":"safe"},"mysql":{"command":"-- MySQL has no RETURNING; the generated key is retrieved separately:\nINSERT INTO employee (name, salary) VALUES ('Ayse', 15000);\nSELECT LAST_INSERT_ID();\n\n-- MariaDB 10.5+ supports INSERT ... RETURNING:\nINSERT INTO employee (name) VALUES ('Ayse') RETURNING id;","note":"MySQL ne prend pas en charge le RETURNING standard ; la clé auto-incrémentée générée par un INSERT s'obtient avec LAST_INSERT_ID(), et les valeurs mises à jour se lisent avec un SELECT séparé. MariaDB propose INSERT ... RETURNING depuis 10.5 et DELETE ... RETURNING depuis 10.0.","danger":"safe"},"mssql":{"command":"INSERT INTO employee (name, salary)\nOUTPUT INSERTED.id, INSERTED.name\nVALUES ('Ayse', 15000);\n\nUPDATE employee SET salary = salary * 1.1\nOUTPUT DELETED.salary AS old_salary, INSERTED.salary AS new_salary\nWHERE id = 5;","note":"L'équivalent dans SQL Server est la clause OUTPUT : elle accède aux pseudo-tables INSERTED (nouvelles valeurs) et DELETED (anciennes valeurs) ; les deux peuvent être utilisées dans un UPDATE. Avec OUTPUT ... INTO, les résultats peuvent être écrits dans une table/variable de table. Idéal pour copier les modifications dans une table d'audit.","since":"SQL Server 2005","xnote":"La clause OUTPUT est arrivée avec SQL Server 2005.","danger":"safe"},"mongodb":{"command":"// findOneAndUpdate returns the modified document\ndb.employee.findOneAndUpdate(\n  { id: 5 },\n  { $mul: { salary: 1.1 } },\n  { returnDocument: \"after\" }   // return the updated version\n)","note":"Dans MongoDB, les commandes findOneAndUpdate / findOneAndDelete / findOneAndReplace renvoient le document affecté en une seule opération atomique. returnDocument: 'before' donne la version avant la modification, 'after' la version après. insertOne renvoie le _id du document inséré.","danger":"safe"},"redis":{"command":"# Some commands return the old/new value directly:\nSET counter 0 GET       # (6.2+) returns the old value and writes the new one (replaces GETSET)\nINCR visits          # increments and returns the new value","note":"Redis n'a pas de RETURNING distinct, mais de nombreuses commandes d'écriture renvoient directement le résultat : INCR/INCRBY renvoient la nouvelle valeur, SET ... GET l'ancienne valeur (GETSET est déprécié depuis 6.2), et LPOP/RPOP l'élément retiré. Pour une lecture-écriture atomique, ce comportement remplace RETURNING.","danger":"safe"},"elasticsearch":{"command":"# The _update response can return the result:\nPOST employee/_update/5\n{\n  \"script\": { \"source\": \"ctx._source.salary *= 1.1\" },\n  \"_source\": true\n}\n# The response returns the updated document in get._source","note":"Dans Elasticsearch, les opérations index/update renvoient des métadonnées de résultat (_id, _version, result). Pour obtenir le corps du document mis à jour, ajoutez '_source': true à la requête _update ; le champ get de la réponse contient le _source actuel. Aucun appel GET séparé n'est nécessaire.","danger":"safe"}}},{"id":"lateral-apply","title":"LATERAL / CROSS APPLY (sous-requête ligne par ligne)","aliases":["lateral","cross apply","outer apply","row-by-row subquery","correlated table function"],"engines":{"oracle":{"command":"-- Each customer's 3 most recent orders (LATERAL, 12c+)\nSELECT m.name, s.created_at, s.amount\nFROM customer m,\n     LATERAL (SELECT created_at, amount FROM orders\n              WHERE customer_id = m.id\n              ORDER BY created_at DESC FETCH FIRST 3 ROWS ONLY) s;\n\n-- Alternative: CROSS APPLY (12c+)\nSELECT m.name, s.amount\nFROM customer m\nCROSS APPLY (SELECT amount FROM orders WHERE customer_id = m.id) s;","note":"LATERAL/APPLY permet à une sous-requête dans FROM de référencer les colonnes de la table située à sa gauche, ce qui permet d'écrire des jointures corrélées telles que « les N premières lignes pour chaque ligne ». Oracle 12c prend en charge à la fois la syntaxe ANSI LATERAL et CROSS/OUTER APPLY.","since":"Oracle 12c","xnote":"LATERAL et CROSS/OUTER APPLY sont arrivés avec Oracle 12c.","danger":"safe"},"postgres":{"command":"SELECT m.name, s.created_at, s.amount\nFROM customer m\nCROSS JOIN LATERAL (\n  SELECT created_at, amount FROM orders\n  WHERE customer_id = m.id\n  ORDER BY created_at DESC LIMIT 3\n) s;","note":"Dans PostgreSQL, les sous-requêtes qui dépendent de la ligne de gauche s'écrivent avec le mot-clé LATERAL (JOIN LATERAL / CROSS JOIN LATERAL / LEFT JOIN LATERAL). Très pratique pour le « top N par groupe » et pour développer des lignes avec des fonctions renvoyant des ensembles. Il n'y a pas de syntaxe CROSS APPLY ; on utilise LATERAL.","since":"PostgreSQL 9.3","xnote":"LATERAL est arrivé avec PostgreSQL 9.3.","danger":"safe"},"mysql":{"command":"-- MySQL 8.0.14+ supports LATERAL\nSELECT m.name, s.amount\nFROM customer m\nJOIN LATERAL (\n  SELECT amount FROM orders\n  WHERE customer_id = m.id\n  ORDER BY created_at DESC LIMIT 3\n) s ON true;","note":"Depuis MySQL 8.0.14, les tables dérivées LATERAL sont prises en charge ; la sous-requête peut référencer les colonnes de la table externe. Auparavant, et pour CROSS APPLY, il n'y avait pas d'équivalent ; le « top N par groupe » se résolvait avec des fonctions de fenêtrage ou des sous-requêtes corrélées. MariaDB n'a pas encore LATERAL.","since":"MySQL 8.0.14","xnote":"Les tables dérivées LATERAL sont arrivées avec MySQL 8.0.14 ; MariaDB ne les a pas.","danger":"safe"},"mssql":{"command":"SELECT m.name, s.created_at, s.amount\nFROM customer m\nCROSS APPLY (\n  SELECT TOP 3 created_at, amount FROM orders\n  WHERE customer_id = m.id\n  ORDER BY created_at DESC\n) s;\n-- Use OUTER APPLY to return non-matching rows too","note":"L'équivalent de LATERAL dans SQL Server est l'opérateur APPLY : CROSS APPLY (comme INNER JOIN LATERAL, écarte les non-correspondances) et OUTER APPLY (comme LEFT JOIN LATERAL, NULL pour les non-correspondances). Courant pour appeler des fonctions table pour chaque ligne et pour les requêtes top-N-par-groupe.","since":"SQL Server 2005","xnote":"L'opérateur CROSS/OUTER APPLY est arrivé avec SQL Server 2005.","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $lookup with a sub-pipeline\ndb.customer.aggregate([\n  { $lookup: {\n      from: \"orders\",\n      let: { mid: \"$id\" },\n      pipeline: [\n        { $match: { $expr: { $eq: [\"$customer_id\", \"$$mid\"] } } },\n        { $sort: { created_at: -1 } }, { $limit: 3 }\n      ],\n      as: \"last3\"\n  } }\n])","note":"Dans MongoDB, l'équivalent de LATERAL/APPLY est la forme de $lookup avec sous-pipeline et variables let : les champs du document externe sont transmis au sous-pipeline via let sous forme de $$variable, où s'effectuent correspondance/tri/limitation. Cela résout le schéma « top N associé pour chaque document ».","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no subqueries / LATERAL.","note":"Redis n'offre pas de sous-requêtes corrélées ligne par ligne. Le schéma « top N associé pour chaque élément » se résout avec une boucle côté application (par ex. un ZRANGE distinct par clé) ou avec un script Lua côté serveur.","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: terms + top_hits sub-aggregation\nGET orders/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"by_customer\": { \"terms\": { \"field\": \"customer_id\" },\n    \"aggs\": { \"last3\": { \"top_hits\": {\n      \"sort\": [ { \"created_at\": \"desc\" } ], \"size\": 3 } } } } }\n}","note":"Elasticsearch n'a pas de LATERAL/APPLY ; le schéma « N premiers enregistrements par groupe » s'obtient avec une sous-agrégation top_hits imbriquée dans une agrégation terms. Au lieu d'une sous-requête corrélée, il s'agit d'un regroupement avec échantillonnage intra-groupe sur un seul index.","danger":"safe"}}},{"id":"pivot","title":"PIVOT / UNPIVOT (lignes↔colonnes)","aliases":["pivot","unpivot","crosstab","cross tab","rows to columns","transpose"],"engines":{"oracle":{"command":"-- Turn sales by month into columns\nSELECT * FROM (\n  SELECT month, amount FROM sale\n)\nPIVOT (SUM(amount) FOR month IN ('Jan' AS jan, 'Feb' AS feb, 'Mar' AS mar));\n\n-- UNPIVOT: turn columns back into rows\nSELECT product, month, amount\nFROM sale_wide\nUNPIVOT (amount FOR month IN (jan AS 'Jan', feb AS 'Feb'));","note":"PIVOT transforme les valeurs distinctes des lignes en colonnes (tableau croisé), et UNPIVOT retransforme les colonnes en lignes ; utilisé dans les tables de synthèse/rapports. Oracle offre des opérateurs PIVOT/UNPIVOT intégrés depuis 11g ; la liste des colonnes doit être statique (les colonnes dynamiques nécessitent PIVOT XML ou du code).","since":"Oracle 11g","xnote":"Les opérateurs PIVOT/UNPIVOT intégrés sont arrivés avec Oracle 11g.","danger":"safe"},"postgres":{"command":"-- The tablefunc extension for crosstab\nCREATE EXTENSION IF NOT EXISTS tablefunc;\nSELECT * FROM crosstab(\n  'SELECT product, month, amount FROM sale ORDER BY 1,2'\n) AS ct(product text, jan numeric, feb numeric, mar numeric);\n\n-- Without an extension: conditional aggregation\nSELECT product,\n  SUM(amount) FILTER (WHERE month = 'Jan') AS jan,\n  SUM(amount) FILTER (WHERE month = 'Feb') AS feb\nFROM sale GROUP BY product;","note":"PostgreSQL n'a pas d'opérateur PIVOT intégré. Il existe deux méthodes : la fonction crosstab() de l'extension tablefunc, ou l'agrégation conditionnelle portable (SUM(...) FILTER (WHERE ...) ou avec CASE). La clause FILTER est une technique de pivot élégante propre à PostgreSQL.","danger":"safe"},"mysql":{"command":"-- There is no PIVOT; conditional aggregation with manual CASE\nSELECT product,\n  SUM(CASE WHEN month = 'Jan' THEN amount ELSE 0 END) AS jan,\n  SUM(CASE WHEN month = 'Feb' THEN amount ELSE 0 END) AS feb,\n  SUM(CASE WHEN month = 'Mar' THEN amount ELSE 0 END) AS mar\nFROM sale\nGROUP BY product;","note":"MySQL/MariaDB n'ont pas d'opérateur PIVOT/UNPIVOT ; un tableau croisé se construit à la main avec GROUP BY plus un SUM(CASE WHEN ... THEN ... END) pour chaque colonne cible. Si les colonnes sont dynamiques, la requête est générée sous forme de chaîne avec un prepared statement.","danger":"safe"},"mssql":{"command":"SELECT * FROM (\n  SELECT product, month, amount FROM sale\n) src\nPIVOT (\n  SUM(amount) FOR month IN ([Jan], [Feb], [Mar])\n) pvt;\n\n-- Converting back with UNPIVOT is also supported","note":"SQL Server prend en charge nativement les opérateurs PIVOT et UNPIVOT depuis 2005. La liste des colonnes du PIVOT est donnée sous forme de valeurs statiques entre crochets ; pour des colonnes dynamiques, la requête est construite en SQL dynamique. Pour de petites synthèses, l'agrégation conditionnelle (CASE) est une alternative.","since":"SQL Server 2005","xnote":"Les opérateurs PIVOT/UNPIVOT sont arrivés avec SQL Server 2005.","danger":"safe"},"mongodb":{"command":"// PIVOT equivalent: conditional aggregation inside $group\ndb.sale.aggregate([\n  { $group: {\n      _id: \"$product\",\n      jan: { $sum: { $cond: [ { $eq: [\"$month\", \"Jan\"] }, \"$amount\", 0 ] } },\n      feb: { $sum: { $cond: [ { $eq: [\"$month\", \"Feb\"] }, \"$amount\", 0 ] } }\n  } }\n])","note":"MongoDB n'a pas d'opérateur PIVOT ; un tableau croisé se construit dans l'étape $group avec $sum + $cond (agrégation conditionnelle) pour chaque colonne cible. Pour le sens inverse (UNPIVOT), les champs sont développés en lignes avec $objectToArray et $unwind.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no pivot/cross tab.","note":"Redis n'a pas de notion de rotation lignes/colonnes. Les synthèses/tableaux croisés se calculent côté application ; les synthèses fréquemment demandées peuvent être précalculées et stockées dans des champs de Hash (un champ par colonne). FT.AGGREGATE de RediSearch peut faire de l'agrégation groupée, mais ce n'est pas un vrai pivot.","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: nested terms aggregations\nGET sale/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"product\": { \"terms\": { \"field\": \"product\" },\n    \"aggs\": { \"month\": { \"terms\": { \"field\": \"month\" },\n      \"aggs\": { \"total\": { \"sum\": { \"field\": \"amount\" } } } } } }\n}","note":"Elasticsearch n'a pas d'opérateur PIVOT ; une synthèse croisée se construit avec des agrégations terms imbriquées (clé externe / clé interne), le résultat revient sous forme de buckets imbriqués plutôt que de colonnes et est mis en tableau côté client. Un job transform peut produire un index cible groupé, de type pivot.","danger":"safe"}}},{"id":"coalesce-null","title":"Gestion des NULL (COALESCE/NVL/NULLIF/IS NULL)","aliases":["coalesce","nvl","nullif","is null","ifnull","isnull","null handling","empty value"],"engines":{"oracle":{"command":"-- COALESCE: returns the first non-NULL value (standard)\nSELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- Oracle-specific NVL / NVL2\nSELECT NVL(balance, 0) FROM customer;\nSELECT NVL2(discount, 'yes', 'no') FROM orders;\n\n-- NULLIF and IS NULL\nSELECT NULLIF(a, b) FROM t;   -- NULL if a=b\nSELECT name FROM customer WHERE phone IS NULL;","note":"Pour gérer NULL (valeur inconnue) : COALESCE renvoie la première valeur non NULL de la liste (standard, plusieurs arguments). NVL(a,b) d'Oracle est l'équivalent à deux arguments ; NVL2 renvoie des valeurs différentes selon que la valeur est NULL ou non. NULLIF(a,b) renvoie NULL si elles sont égales. Testez toujours NULL avec IS NULL / IS NOT NULL (= NULL ne fonctionne pas).","danger":"safe"},"postgres":{"command":"SELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\nSELECT NULLIF(divisor, 0) FROM t;   -- prevent division by zero\nSELECT name FROM customer WHERE phone IS NULL;\n-- The equivalent of Oracle NVL is COALESCE","note":"PostgreSQL offre COALESCE, NULLIF et IS NULL/IS NOT NULL standard ; utilisez COALESCE au lieu du NVL d'Oracle. Utilisez IS, et non =, pour les comparaisons avec NULL ; si deux NULL doivent être considérés comme égaux, l'opérateur 'IS NOT DISTINCT FROM' fait l'affaire.","danger":"safe"},"mysql":{"command":"SELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- MySQL shortcut: IFNULL (two arguments)\nSELECT IFNULL(balance, 0) FROM customer;\nSELECT NULLIF(a, b) FROM t;\nSELECT name FROM customer WHERE phone IS NULL;","note":"MySQL/MariaDB prennent en charge COALESCE et NULLIF standard ; IFNULL(a,b) est un raccourci à deux arguments (semblable à NVL d'Oracle). L'opérateur <=> fournit une égalité sûre vis-à-vis de NULL (traite deux NULL comme égaux). NULL se teste avec IS NULL / IS NOT NULL.","danger":"safe"},"mssql":{"command":"SELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- SQL Server shortcut: ISNULL (two arguments)\nSELECT ISNULL(balance, 0) FROM customer;\nSELECT NULLIF(a, b) FROM t;\nSELECT name FROM customer WHERE phone IS NULL;","note":"SQL Server offre COALESCE standard (plusieurs arguments, avec promotion de type de données) et ISNULL(a,b) à deux arguments ; les deux diffèrent subtilement (COALESCE est standard et plus souple quant à la priorité des types). NULLIF et IS NULL sont aussi pris en charge. ISNULL est l'équivalent de NVL d'Oracle (les noms se ressemblent).","danger":"safe"},"mongodb":{"command":"// COALESCE equivalent: $ifNull\ndb.customer.aggregate([\n  { $project: { name: 1,\n      contact: { $ifNull: [\"$phone\", \"$mobile\", \"none\"] } } }\n])\n// NULL / missing field check:\ndb.customer.find({ phone: { $in: [null] } })","note":"Dans l'agrégation MongoDB, l'équivalent de COALESCE est l'opérateur $ifNull (plusieurs arguments, renvoie le premier qui n'est pas null/absent). Dans une requête, { field: null } correspond à la fois aux documents dont la valeur est null et à ceux où le champ est totalement absent ; rendez la distinction explicite avec { field: { $type: 'null' } } ou $exists.","danger":"safe"},"redis":{"command":"# Instead of NULL, Redis has the concept of 'no such key/field':\nEXISTS customer:1              # does the key exist?\nHGET customer:1 phone        # returns nil if the field is absent\nGET missing_key                  # (nil)","note":"Redis n'a pas de NULL au sens SQL ; l'équivalent est une clé absente ou un champ de Hash absent, et les commandes renvoient alors nil. La logique de valeur par défaut (comme COALESCE) s'applique côté application avec une vérification de nil. L'existence d'un champ se vérifie avec HEXISTS.","danger":"safe"},"elasticsearch":{"command":"# Default assignment when a field is missing (missing bucket / null_value)\n# Find documents without the field in a query:\nGET customer/_search\n{ \"query\": { \"bool\": { \"must_not\": { \"exists\": { \"field\": \"phone\" } } } } }\n# Map empty values to a specific value with null_value in the mapping","note":"Dans Elasticsearch, les champs null ou absents se vérifient avec la requête 'exists' (must_not exists = documents sans le champ). Pour une valeur par défaut de type COALESCE, définissez 'null_value' dans le mapping, ou attribuez une valeur aux entrées absentes avec le paramètre 'missing' dans les agrégations. Les valeurs par défaut peuvent aussi être produites avec des runtime fields/scripts.","danger":"safe"}}}]},"rosetta/overlay-elasticsearch.json":{"engineId":"elasticsearch","byConcept":{"active-sessions":{"command":"GET _tasks?detailed=true&actions=*search*\n# To see node pressure: GET _nodes/hot_threads","note":"L'API Task renvoie les tâches en cours (search, reindex, bulk). Pour la charge CPU instantanée, hot_threads affiche les threads les plus actifs.","danger":"safe"},"blocking-sessions":{"command":"# Not applicable: Elasticsearch has no concept of row/document locks or blocking.\n# Closest diagnosis: find long-running tasks\nGET _tasks?detailed=true","note":"Plutôt qu'un blocage, la lenteur provient généralement de tâches longues (grosses recherches, reindex). Évaluez la durée avec le champ running_time_in_nanos.","danger":"safe"},"kill-session":{"command":"POST _tasks/<task_id>/_cancel\n# task_id format: nodeId:taskNumber (e.g. oTUltX4IQMOUUVeiohTt8A:12345)","note":"Prenez la valeur task_id dans la sortie de GET _tasks. Seules les tâches annulables (cancellable:true) peuvent être arrêtées.","danger":"danger"},"connection-count":{"command":"GET _nodes/stats/http","note":"current_open donne les connexions HTTP actuellement ouvertes, total_opened le total des connexions depuis le démarrage du nœud. La limite supérieure des connexions HTTP est déterminée par les limites du système d'exploitation/des sockets, et non par http.max_content_length.","danger":"safe"},"database-size":{"command":"GET _cluster/stats\n# For a simple summary: GET _cat/indices?v&h=index,store.size","note":"_cluster/stats donne l'utilisation disque totale de tout le cluster via indices.store.size_in_bytes. _cat/indices liste la taille par index sous forme lisible.","danger":"safe"},"largest-tables":{"command":"GET _cat/indices?v&s=store.size:desc&h=index,docs.count,store.size","note":"Dans Elasticsearch, l'équivalent d'une \"table\" est un index. s=store.size:desc place les plus grands indices en tête.","danger":"safe"},"free-space":{"command":"GET _cat/allocation?v","note":"Les colonnes disk.used, disk.avail et disk.percent donnent l'utilisation disque par nœud. Lorsque les seuils de watermark (85 %/90 %/95 %) sont dépassés, l'allocation des shards s'arrête.","danger":"safe"},"row-count":{"command":"GET <index>/_count\n# Shortcut: GET _cat/count/<index>?v","note":"_count renvoie rapidement le nombre de documents. Pour un comptage filtré, passez une requête dans le corps : GET <index>/_count {\"query\":{\"term\":{\"status\":\"active\"}}}","danger":"safe"},"top-slow-queries":{"command":"PUT <index>/_settings\n{\n  \"index.search.slowlog.threshold.query.warn\": \"1s\",\n  \"index.search.slowlog.threshold.query.info\": \"500ms\",\n  \"index.search.slowlog.threshold.fetch.warn\": \"1s\"\n}\n# Queries exceeding the threshold are written to the <cluster>_index_search_slowlog.json log file","note":"Elasticsearch n'a pas de table centrale des requêtes lentes ; le search slowlog écrit les requêtes dépassant le seuil dans les fichiers journaux du nœud. Consultez les journaux à cet endroit.","danger":"caution"},"explain-plan":{"command":"GET <index>/_search\n{\n  \"profile\": true,\n  \"query\": { \"match\": { \"title\": \"test\" } }\n}\n# To check query validity/parsing:\n# GET <index>/_validate/query?explain=true","note":"profile:true décompose le temps de chaque composant de requête et de chaque étape d'agrégation. _validate/query?explain affiche l'équivalent Lucene de la requête et la raison de son rejet.","danger":"safe"},"unused-indexes":{"command":"# Not applicable: secondary indexes in ES differ from relational databases.\n# Closest: assess rarely used/unaccessed indices by size and search count\nGET _cat/indices?v&h=index,docs.count,store.size\nGET <index>/_stats/search","note":"Au lieu d'index de champs inutilisés, identifiez via _stats/search les indices peu interrogés (search.query_total) ou qui ne le sont plus, puis fermez-les/supprimez-les.","danger":"safe"},"cache-hit-ratio":{"command":"GET _nodes/stats/indices/query_cache\n# Ratio = hit_count / (hit_count + miss_count)","note":"query_cache met en cache les résultats en contexte filter. Examinez request_cache séparément avec GET _nodes/stats/indices/request_cache.","danger":"safe"},"logical-dump":{"command":"# First define a repository (once): PUT _snapshot/<repo> {\"type\":\"fs\",\"settings\":{\"location\":\"/mnt/backups\"}}\nPUT _snapshot/<repo>/<snapshot_1>?wait_for_completion=true\n{\n  \"indices\": \"*\",\n  \"include_global_state\": true\n}","note":"Dans Elasticsearch, l'équivalent d'un dump logique est l'API snapshot. wait_for_completion=true attend la fin de l'opération ; pour les gros snapshots, laissez false et interrogez l'état séparément.","danger":"safe"},"restore-dump":{"command":"POST _snapshot/<repo>/<snapshot_1>/_restore\n{\n  \"indices\": \"*\",\n  \"rename_pattern\": \"(.+)\",\n  \"rename_replacement\": \"restored_$1\"\n}","note":"Pour restaurer un index existant, fermez-le d'abord (POST <index>/_close) ou restaurez-le sous un nouveau nom avec rename_pattern. Vous ne pouvez pas restaurer par-dessus un index ouvert.","danger":"danger"},"last-backup-status":{"command":"GET _snapshot/<repo>/_all\n# For in-progress/latest snapshot details:\n# GET _snapshot/<repo>/<snapshot_1>/_status","note":"Le champ state prend les valeurs SUCCESS / IN_PROGRESS / PARTIAL / FAILED. _status donne la progression détaillée par shard.","danger":"safe"},"list-users":{"command":"GET _security/user","note":"X-Pack Security doit être activé. Pour un seul utilisateur : GET _security/user/<user>. Les utilisateurs intégrés apparaissent aussi dans la liste.","danger":"safe"},"create-user-grant":{"command":"# 1) Create a role\nPOST _security/role/<role>\n{\n  \"cluster\": [\"monitor\"],\n  \"indices\": [ { \"names\": [\"log-*\"], \"privileges\": [\"read\"] } ]\n}\n# 2) Create the user and assign the role\nPOST _security/user/<user>\n{\n  \"password\": \"StrongPassword123\",\n  \"roles\": [\"<role>\"]\n}","note":"L'autorisation est basée sur les rôles : définissez d'abord le rôle contenant les privilèges index/cluster, puis affectez-le à l'utilisateur. Le mot de passe doit comporter au moins 6 caractères.","danger":"caution"},"show-privileges":{"command":"GET _security/user/<user>\nGET _security/role/<role>\n# Check whether a specific privilege is held:\n# POST _security/user/<user>/_has_privileges { \"index\": [ { \"names\": [\"log-*\"], \"privileges\": [\"read\"] } ] }","note":"Consultez les rôles d'un utilisateur avec _security/user et les privilèges d'un rôle avec _security/role. _has_privileges vérifie si une action précise est autorisée.","danger":"safe"},"change-password":{"command":"POST _security/user/<user>/_password\n{\n  \"password\": \"NewStrongPassword123\"\n}","note":"Pour changer votre propre mot de passe, POST _security/user/_password (sans nom d'utilisateur) suffit. Pour l'utilisateur intégré elastic, l'outil bin/elasticsearch-reset-password peut aussi être utilisé.","danger":"caution"},"update-statistics":{"command":"# Not applicable: Elasticsearch has no cost-based optimizer or table statistics gathering.\n# Closest: make newly written documents searchable\nPOST <index>/_refresh","note":"Lucene fonctionne par segments ; aucune étape distincte de mise à jour des statistiques n'est nécessaire. _refresh rend immédiatement interrogeables les documents qui ne l'étaient pas encore (par défaut, cela se fait automatiquement toutes les 1s).","danger":"safe"},"rebuild-index":{"command":"POST _reindex\n{\n  \"source\": { \"index\": \"old_index\" },\n  \"dest\":   { \"index\": \"new_index\" }\n}\n# For large data: run in the background with POST _reindex?wait_for_completion=false","note":"Reindex sert aux modifications de mapping ou à reconstruire un index endommagé. Pour une bascule transparente, faites pointer un alias vers le nouvel index.","danger":"caution"},"reclaim-space":{"command":"POST <index>/_forcemerge?only_expunge_deletes=true\n# Full merge on read-only/old indices: POST <index>/_forcemerge?max_num_segments=1","note":"Les documents supprimés/mis à jour restent dans les segments sous forme de tombstones ; forcemerge récupère cet espace. Coûteux sur les indices en écriture active ; exécutez-le en heures creuses.","danger":"caution"},"version-uptime":{"command":"GET /\n# Uptime (JVM uptime):\n# GET _nodes/stats/jvm\n# Short summary: GET _cat/nodes?v&h=name,uptime,version","note":"Le point de terminaison racine GET / renvoie la version, la version de Lucene et le nom du cluster. jvm.uptime_in_millis est l'uptime du nœud.","danger":"safe"},"show-config-param":{"command":"GET _cluster/settings?include_defaults=true&flat_settings=true","note":"include_defaults=true affiche aussi les valeurs par défaut. Les paramètres persistent et transient sont renvoyés dans des sections distinctes ; pour les paramètres de niveau nœud, utilisez GET _nodes/settings.","danger":"safe"},"list-tables":{"command":"GET _cat/indices?v","note":"Dans Elasticsearch, l'équivalent d'une table relationnelle est un index. Donne une synthèse avec les colonnes health, status, docs.count et store.size. La sortie par défaut liste les indices ouverts et fermés mais pas les indices masqués (par ex. backing indices .ds-, indices système) ; ajoutez &expand_wildcards=all pour les inclure, ou &expand_wildcards=open pour les seuls indices ouverts.","danger":"safe"},"describe-table":{"command":"GET <index>/_mapping","note":"Le mapping donne les types de champs et les réglages d'analyse ; c'est l'équivalent de DESCRIBE TABLE. Pour un seul champ : GET <index>/_mapping/field/<field>.","danger":"safe"},"list-indexes":{"command":"GET <index>/_mapping","note":"Dans ES, les champs sont indexés par défaut ; il n'y a pas de table d'index secondaire distincte. Les champs avec \"index\": false dans le mapping ne sont pas interrogeables. Pour les indices (tables) de tout le cluster : GET _cat/indices?v.","danger":"safe"},"create-index":{"command":"PUT <index>\n{\n  \"settings\": { \"number_of_shards\": 1, \"number_of_replicas\": 1 },\n  \"mappings\": {\n    \"properties\": {\n      \"title\": { \"type\": \"text\" },\n      \"created_at\": { \"type\": \"date\" }\n    }\n  }\n}\n# To add fields later: PUT <index>/_mapping {\"properties\":{...}}","note":"Après la création d'un index, le nombre de primary shards ne peut pas être modifié sur place (le nombre de réplicas est dynamique) ; si nécessaire, produisez un nouvel index avec les API _split / _shrink ou avec _reindex. Le type d'un champ existant ne peut pas être modifié ; de nouveaux champs peuvent être ajoutés.","danger":"caution"},"show-ddl":{"command":"GET <index>","note":"Un seul appel renvoie settings, mappings et aliases ; ensemble, ces trois éléments constituent l'équivalent du \"DDL\" de l'index. Vous pouvez réutiliser la même sortie comme corps d'un nouveau PUT <index>.","danger":"safe"},"replication-status":{"command":"GET _cluster/health\n# For ongoing shard copy/recovery: GET _cat/recovery?v&active_only=true\n# For cross-cluster replication (CCR): GET <index>/_ccr/stats","note":"status résume la santé des shards en green/yellow/red ; yellow = réplica non affecté. active_shards_percent donne le taux d'achèvement.","danger":"safe"},"list-replicas":{"command":"GET _cat/shards?v&h=index,shard,prirep,state,node\n# For node roles: GET _cat/nodes?v","note":"La colonne prirep prend la valeur p (primary) ou r (replica) ; elle montre la répartition des shards entre les nœuds et leur état (STARTED/RELOCATING).","danger":"safe"},"promote-failover":{"command":"# Master election is automatic; no manual promote is needed.\n# To convert a CCR follower into an independent (writable) index:\nPOST <index>/_ccr/pause_follow\nPOST <index>/_close\nPOST <index>/_ccr/unfollow\nPOST <index>/_open","note":"En cas de défaillance d'un nœud, Elasticsearch réélit automatiquement le master et réaffecte les primary shards. Une reprise manuelle n'est nécessaire que pour détacher un follower index CCR de son leader et le rendre accessible en écriture.","danger":"danger"},"open-transactions":{"command":"# Not applicable: Elasticsearch has no multi-document transactions (BEGIN/COMMIT).\n# Operations are atomic at the document level; the closest diagnosis is running tasks:\nGET _tasks?detailed=true","note":"Une écriture sur un document unique est atomique et versionnée (concurrence optimiste : if_seq_no / if_primary_term). Il n'existe pas de notion de suivi des transactions ouvertes.","danger":"safe"},"connect-cli":{"command":"curl -u user:password -k https://host:9200\n# Kibana Dev Tools console (recommended): Kibana > Management > Dev Tools\n# With an API key: curl -H \"Authorization: ApiKey <base64key>\" https://host:9200","note":"Il est basé sur REST ; au lieu d'un client CLI distinct, on utilise curl ou Kibana Dev Tools. -k ignore la vérification des certificats TLS auto-signés (utilisez une CA en production).","danger":"safe"},"list-databases":{"command":"GET _cat/indices?v\n# For aliases (logical views): GET _aliases","note":"Elasticsearch n'a pas de couche \"database\" distincte ; les notions les plus proches sont les indices et les alias qui les regroupent. Pour les data streams : GET _data_stream.","danger":"safe"},"switch-database":{"command":"# Not applicable: the target index is specified directly in the request path; there is no session context to change.\nGET <index>/_search\n# In Kibana, a data view (index pattern) is selected as the default view","note":"Il n'y a pas de commande du type USE <db> ; l'index/alias cible est indiqué dans le chemin de chaque requête. Pour interroger plusieurs indices, séparez-les par des virgules : GET log-2024,log-2025/_search.","danger":"safe"}}},"errors/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00001","title":"Unique constraint violated (contrainte unique violée)","severity":"caution","meaning":"Une ligne insérée ou mise à jour viole une contrainte UNIQUE ou PRIMARY KEY. Autrement dit, la même valeur existe déjà.","cause":"Réinsertion d'une ligne avec la même valeur de clé, saisie de données en double dans une colonne qui doit être unique, ou traitement du même enregistrement deux fois lors d'une réplication/d'une réexécution.","fix":"Identifiez la colonne violée à partir du nom de la contrainte dans le message d'erreur : SELECT column_name FROM all_cons_columns WHERE constraint_name = 'CONSTRAINT_NAME'. Utilisez GROUP BY ... HAVING COUNT(*) > 1 pour trouver les doublons. Dans l'application, utilisez UPDATE ou MERGE au lieu d'INSERT pour les enregistrements existants. Si la valeur de la séquence a pris du retard, avancez-la avec ALTER SEQUENCE.","tags":["unique","uniqueness","constraint","unique constraint","primary key","duplicate","duplicate key","insert"]},{"code":"ORA-00054","title":"Resource busy and acquire with NOWAIT specified or timeout expired (ressource occupée ; NOWAIT spécifié ou délai expiré)","severity":"caution","meaning":"La ressource (ligne ou table) à verrouiller est verrouillée par une autre session et n'a pas pu être acquise immédiatement, car NOWAIT était spécifié ou DDL_LOCK_TIMEOUT a expiré.","cause":"Généralement un DDL (ALTER TABLE, DROP, CREATE INDEX) tenté alors que la table est verrouillée par une autre transaction ; ou une ligne verrouillée rencontrée avec SELECT ... FOR UPDATE NOWAIT.","fix":"Trouvez la session qui détient le verrou : SELECT * FROM v$locked_object. Joignez v$session et v$lock pour identifier la session bloquante. Attendez la fin de la transaction ou, si nécessaire, terminez-la avec ALTER SYSTEM KILL SESSION. Pour le DDL, vous pouvez définir un temps d'attente : ALTER SESSION SET DDL_LOCK_TIMEOUT = 60; Exécutez le DDL pendant des fenêtres de maintenance calmes.","tags":["lock","locked","ddl","nowait","busy","resource busy","timeout"]},{"code":"ORA-00060","title":"Deadlock detected while waiting for resource (interblocage détecté en attente de ressource)","severity":"danger","meaning":"Deux sessions ou plus sont en interblocage, chacune attendant une ressource détenue par l'autre. Oracle détecte le cycle, choisit une victime et annule (rollback) l'instruction de cette session.","cause":"Verrous de ligne acquis dans des ordres différents, mêmes lignes mises à jour de manière croisée par différentes transactions, verrous de table lors des mises à jour de la table parente dus à des clés étrangères non indexées, ou DML concurrent sur un index bitmap.","fix":"Examinez le fichier de trace (graphe) de l'interblocage référencé dans l'alert log pour trouver les instructions SQL et les lignes en conflit. Dans l'application, accédez toujours aux tables/lignes dans le même ordre. Indexez les colonnes de clé étrangère (les FK non indexées sont une cause classique d'interblocage). Gardez les transactions courtes et revoyez la fréquence des commits. Interceptez la transaction victime dans l'application et relancez-la.","tags":["deadlock","deadlock detected","lock","locking","tx","transaction"]},{"code":"ORA-01017","title":"Invalid username/password; logon denied (nom d'utilisateur/mot de passe invalide ; connexion refusée)","severity":"caution","meaning":"La connexion a été refusée, car le nom d'utilisateur ou le mot de passe fourni lors de la connexion à la base de données est incorrect.","cause":"Mot de passe erroné, nom d'utilisateur erroné, paramètre de sensibilité à la casse des mots de passe (SEC_CASE_SENSITIVE_LOGON) différent de celui attendu, mot de passe modifié, ou identifiants obsolètes dans la chaîne de connexion de l'application.","fix":"Vérifiez le nom d'utilisateur et le mot de passe. Si nécessaire, réinitialisez le mot de passe en tant que DBA : ALTER USER app_user IDENTIFIED BY new_password. Vérifiez que l'utilisateur existe et n'est pas verrouillé : SELECT username, account_status FROM dba_users. Avec les versions de hachage de mot de passe 12c+, les clients 11g peuvent avoir du mal à se connecter ; revoyez le paramètre SQLNET.ALLOWED_LOGON_VERSION_SERVER.","tags":["auth","credentials","password","password","logon","login","privilege"]},{"code":"ORA-01555","title":"Snapshot too old: rollback segment number too small (instantané trop ancien)","severity":"danger","meaning":"Une requête de longue durée n'a pas pu accéder aux anciennes données undo nécessaires pour construire l'image cohérente en lecture à son instant de démarrage, car ces informations undo ont été écrasées.","cause":"Tablespace undo trop petit, UNDO_RETENTION trop court, requêtes de longue durée pendant un DML concurrent très intense, ou boucle qui effectue des commits sur la même table à l'intérieur d'une requête (fetch across commit).","fix":"Augmentez UNDO_RETENTION et agrandissez le tablespace undo ; dans la mesure du possible, laissez-le en gestion automatique (AUTOEXTEND activé). Découpez les longues requêtes en plus petits morceaux ou exécutez-les hors des heures de pointe. Envisagez RETENTION GUARANTEE : ALTER TABLESPACE undotbs1 RETENTION GUARANTEE. Corrigez la logique applicative qui effectue des commits dans une boucle.","tags":["undo","snapshot","rollback","read consistency","consistency","long query"]},{"code":"ORA-01652","title":"Unable to extend temp segment by N in tablespace (impossible d'étendre le segment temporaire de N dans le tablespace)","severity":"danger","meaning":"Oracle n'a pas trouvé assez d'espace libre dans le tablespace pour étendre un segment temporaire (temp). Se produit généralement lors de tris, de jointures par hachage ou d'opérations sur des tables temporaires globales.","cause":"Le tablespace TEMP est plein ; des opérations volumineuses ORDER BY, GROUP BY, DISTINCT, jointure par hachage ou création d'index ne tiennent pas dans la mémoire PGA et débordent sur disque ; les fichiers de données ont AUTOEXTEND désactivé, ou l'espace disque est épuisé.","fix":"Surveillez l'utilisation de TEMP : SELECT * FROM v$sort_usage / v$tempseg_usage. Ajoutez un fichier au tablespace TEMP ou agrandissez-le : ALTER TABLESPACE temp ADD TEMPFILE '...' SIZE 2G AUTOEXTEND ON. Si un tablespace permanent est plein, ajoutez un fichier de données. Réduisez les tris sur disque en optimisant la requête (index approprié, PGA_AGGREGATE_TARGET plus grand).","tags":["temp","tablespace","free space","space","sort","sorting","extend"]},{"code":"ORA-04031","title":"Unable to allocate N bytes of shared memory (impossible d'allouer N octets de mémoire partagée)","severity":"danger","meaning":"Oracle n'a pas pu allouer une zone de mémoire contiguë de la taille demandée dans un pool de mémoire partagée (généralement le shared pool ou le large pool). Un composant de la SGA est sous pression mémoire.","cause":"Fragmentation du shared pool due à de nombreuses instructions SQL uniques sans variables de liaison (remplies de littéraux), SHARED_POOL_SIZE insuffisant, trop de connexions simultanées, gros packages PL/SQL, ou dimensionnement ASMM/AMM incorrect.","fix":"Vérifiez à quel pool le message d'erreur fait référence (shared pool, large pool, etc.). À court terme : ALTER SYSTEM FLUSH SHARED_POOL (attention : en production, cela force la réanalyse de tout le SQL, c.-à-d. une tempête temporaire de hard parses, et ne corrige pas durablement la fragmentation). Correction permanente : imposez l'utilisation des variables de liaison (y compris CURSOR_SHARING), augmentez SGA_TARGET / SHARED_POOL_SIZE / LARGE_POOL_SIZE. Épinglez en mémoire les packages fréquemment utilisés avec DBMS_SHARED_POOL.KEEP. Examinez la répartition de la mémoire avec V$SGASTAT.","tags":["memory","shared memory","shared pool","sga","ora-4031","bind"]},{"code":"ORA-12154","title":"TNS: could not resolve the connect identifier specified (identifiant de connexion spécifié non résolu)","severity":"caution","meaning":"Le nom de service (identifiant de connexion / alias TNS) utilisé pour la connexion n'a pas pu être résolu. Oracle Net ne parvient pas à associer le nom donné aux informations de serveur/service.","cause":"L'alias est absent de tnsnames.ora, la variable d'environnement TNS_ADMIN pointe vers le mauvais répertoire, le nom de l'alias est mal orthographié, la configuration de nommage EZConnect/LDAP est absente, ou l'ordre NAMES.DIRECTORY_PATH dans sqlnet.ora est incorrect.","fix":"Vérifiez que l'alias utilisé est défini dans tnsnames.ora. Testez la connexion : tnsping service_name. Assurez-vous que TNS_ADMIN pointe vers le bon dossier. Vous pouvez aussi essayer EZConnect : app_user/password@host:1521/service_name. Vérifiez la ligne NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT) dans sqlnet.ora.","tags":["tns","connect","connection","network","networking","tnsnames","service"]},{"code":"ORA-12514","title":"TNS: listener does not currently know of service requested in connect descriptor (le listener ne connaît pas le service demandé)","severity":"caution","meaning":"Le listener est en cours d'exécution mais ne reconnaît pas le nom de service demandé ; autrement dit, le service n'est pas enregistré auprès du listener.","cause":"L'instance de base de données n'est pas entièrement ouverte, le service ne s'est pas encore enregistré auprès du listener (LREG en 12c+, délai d'enregistrement PMON en 11g et antérieures), le mauvais SERVICE_NAME est utilisé, ou le SID et le nom de service sont confondus.","fix":"Affichez les services connus du listener : lsnrctl status et lsnrctl services. Vérifiez que la base de données est OPEN. Déclenchez l'enregistrement du service : ALTER SYSTEM REGISTER en SQL. Vérifiez le paramètre LOCAL_LISTENER et l'utilisation correcte de SERVICE_NAME. Dans une base de données enfichable, assurez-vous que la PDB est OPEN.","tags":["tns","listener","service","service_name","connection","network","networking"]},{"code":"ORA-00942","title":"Table or view does not exist (la table ou la vue n'existe pas)","severity":"caution","meaning":"La table ou la vue référencée dans la requête n'existe pas ou n'est pas visible pour l'utilisateur qui y accède.","cause":"L'objet n'existe réellement pas, il se trouve dans un autre schéma et aucun préfixe de schéma n'a été indiqué, l'utilisateur ne dispose pas de privilèges sur l'objet (tels que SELECT), un synonyme est manquant, ou le nom de l'objet est mal orthographié.","fix":"Vérifiez que l'objet existe et qui en est le propriétaire : SELECT owner, object_type FROM all_objects WHERE object_name = 'TABLE_NAME'. S'il se trouve dans un autre schéma, qualifiez-le entièrement sous la forme schema.table ou accordez GRANT SELECT ON app_schema.my_table TO app_user. Si nécessaire, créez un synonyme public/privé : CREATE SYNONYM my_table FOR app_schema.my_table. Faites attention à la casse et aux identifiants entre guillemets.","tags":["table","missing table","view","missing view","grant","privilege","synonym","alias"]},{"code":"ORA-01031","title":"Insufficient privileges (privilèges insuffisants)","severity":"caution","meaning":"L'utilisateur ne dispose pas du privilège (système ou objet) requis pour l'opération tentée.","cause":"Le privilège système ou objet requis n'a pas été accordé, le privilège n'est obtenu que par un rôle (les rôles ne sont pas effectifs dans les blocs PL/SQL à droits du définisseur), une opération d'administration a été tentée sans SYSDBA/SYSOPER, ou un octroi direct manque pour des procédures à droits du définisseur.","fix":"Accordez le privilège manquant, par exemple : GRANT CREATE TABLE TO app_user ou GRANT SELECT ON app_schema.my_table TO app_user. N'oubliez pas que pour les objets PL/SQL nommés, les privilèges doivent être accordés directement, et non par un rôle. Pour les opérations nécessitant SYSDBA, connectez-vous avec les bons identifiants. Vérifiez les privilèges actuels : SELECT * FROM session_privs / user_sys_privs.","tags":["privilege","privileges","grant","permission","auth","role grant","role"]},{"code":"ORA-00257","title":"Archiver error. Connect internal only, until freed (erreur de l'archiveur ; connexion interne uniquement)","severity":"danger","meaning":"Le processus archiveur (ARCH) ne peut pas archiver les redo logs ; généralement, la destination d'archivage (FRA ou destination des journaux archivés) est pleine. La base de données suspend les nouvelles transactions.","cause":"La Flash Recovery Area (DB_RECOVERY_FILE_DEST) ou la destination des journaux archivés est pleine, l'espace disque est épuisé, les sauvegardes RMAN ne suppriment pas les journaux archivés, ou la limite DB_RECOVERY_FILE_DEST_SIZE est atteinte.","fix":"Libérez de l'espace : sauvegardez les anciens journaux archivés avec RMAN vers une destination hors de la FRA et supprimez-les : RMAN> BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT (écrire la sauvegarde dans la FRA alors qu'elle est pleine échoue). Avec Data Guard, ne supprimez pas les archives pas encore transmises au standby ou appliquées sur celui-ci ; sinon un écart (gap) se forme sur le standby (CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY). Affichez l'utilisation de la FRA : SELECT * FROM v$recovery_file_dest / v$flash_recovery_area_usage. Augmentez temporairement DB_RECOVERY_FILE_DEST_SIZE ou déplacez la destination vers un disque plus grand. Vérifiez l'espace libre au niveau du disque. Mettez en place une politique régulière de nettoyage des archives.","tags":["archive","archivelog","archiver","fra","disk","space","redo"]},{"code":"ORA-01000","title":"Maximum open cursors exceeded (nombre maximal de curseurs ouverts dépassé)","severity":"caution","meaning":"Une session a ouvert plus de curseurs que n'en autorise le paramètre OPEN_CURSORS. Aucun nouveau curseur ne peut être ouvert.","cause":"L'application ne ferme pas les objets Statement/ResultSet qu'elle ouvre (fuite de curseurs), OPEN_CURSORS est trop bas pour la charge, ou de nouvelles instructions préparées sont sans cesse préparées dans une boucle et jamais fermées.","fix":"Surveillez les curseurs ouverts : SELECT * FROM v$open_cursor WHERE sid = ...  et consultez le nombre par session via v$sesstat. La vraie correction consiste à fermer chaque Statement/ResultSet dans un bloc finally de l'application (élimination de la fuite de curseurs). En contournement, augmentez OPEN_CURSORS : ALTER SYSTEM SET OPEN_CURSORS = 1000. Revoyez la taille du cache d'instructions JDBC.","tags":["cursor","open cursor","open_cursors","leak","cursor leak","jdbc"]},{"code":"ORA-00018","title":"Maximum number of sessions exceeded (nombre maximal de sessions dépassé)","severity":"danger","meaning":"La base de données a atteint le nombre maximal de sessions autorisé par le paramètre SESSIONS ; aucune nouvelle session ne peut être ouverte.","cause":"SESSIONS/PROCESSES définis en dessous des besoins de la charge, configuration de pool de connexions surdimensionnée, fuite de connexions, ou accumulation de sessions jamais fermées.","fix":"Affichez le nombre actuel de sessions : SELECT COUNT(*) FROM v$session. Vérifiez les limites : SELECT * FROM v$resource_limit WHERE resource_name IN ('sessions','processes'). Augmentez PROCESSES et, indirectement, SESSIONS (via le SPFILE, redémarrage requis) : ALTER SYSTEM SET PROCESSES = 500 SCOPE=SPFILE. Revoyez la taille des pools de connexions et les fuites dans l'application.","tags":["session","session limit","sessions","processes","connection","connections","limit"]},{"code":"ORA-28000","title":"The account is locked (le compte est verrouillé)","severity":"caution","meaning":"La connexion échoue, car le compte utilisateur de base de données auquel on se connecte est verrouillé.","cause":"La limite FAILED_LOGIN_ATTEMPTS du profil a été dépassée (mots de passe erronés répétés), ou le compte a été verrouillé manuellement par un DBA (ALTER USER ... ACCOUNT LOCK). Un verrouillage pour échecs de connexion est levé automatiquement après PASSWORD_LOCK_TIME. Remarque : l'expiration de PASSWORD_LIFE_TIME ne verrouille pas le compte ; le compte passe à EXPIRED / EXPIRED(GRACE) et renvoie ORA-28001/ORA-28002.","fix":"Vérifiez l'état du compte : SELECT username, account_status, lock_date FROM dba_users WHERE username = 'APP_USER'. Déverrouillez-le : ALTER USER app_user ACCOUNT UNLOCK. Pour éviter que cela se reproduise, trouvez la source des échecs de connexion et, si nécessaire, revoyez FAILED_LOGIN_ATTEMPTS dans le profil. Si le mot de passe a expiré, attribuez-en un nouveau.","tags":["account","user account","locked","account locked","auth","password","profile"]},{"code":"ORA-01578","title":"Oracle data block corrupted (file #, block #) (bloc de données Oracle corrompu)","severity":"danger","meaning":"Un bloc de données du fichier de données indiqué est corrompu ; Oracle signale que le bloc a échoué à sa vérification de cohérence (checksum). Il s'agit d'une corruption physique.","cause":"Défaillance disque/matérielle, erreurs de la couche de stockage, problèmes du système d'exploitation ou du contrôleur, blocs partiellement écrits après une coupure de courant, ou sauvegarde/restauration corrompue.","fix":"Confirmez la corruption et son étendue : RMAN> VALIDATE DATABASE / VALIDATE CHECK LOGICAL DATAFILE n; exécutez aussi DBVERIFY (dbv) et consultez v$database_block_corruption. Si une sauvegarde existe, récupérez au niveau du bloc : RMAN> RECOVER DATAFILE n BLOCK m; ou, pour tous les blocs listés, RMAN> RECOVER CORRUPTION LIST; (l'ancienne commande BLOCKRECOVER est dépréciée depuis 11g). S'il s'agit d'un bloc d'index, reconstruisez l'index. Vérifiez toujours l'état du matériel/des disques ; la cause racine est généralement le stockage.","tags":["corruption","block corruption","block","corrupt block","datafile","rman","disk"]},{"code":"ORA-01722","title":"Invalid number (nombre invalide)","severity":"safe","meaning":"Une chaîne de caractères non convertible en nombre a été utilisée là où un contexte numérique est requis. Une conversion implicite ou explicite a échoué.","cause":"Comparaison d'une colonne numérique avec du texte (par ex. WHERE num_col = 'ABC'), insertion d'une valeur contenant des lettres dans un champ numérique, format inadapté pour TO_NUMBER, ou incohérence des séparateurs décimal/de milliers (virgule ou point).","fix":"Trouvez l'expression dont la conversion échoue ; généralement une condition WHERE ou une valeur d'INSERT. Assurez la compatibilité des types dans les comparaisons texte/nombre et utilisez une conversion explicite si nécessaire : TO_NUMBER(my_column) ou TO_CHAR côté texte. Empêchez les saisies non numériques dans les colonnes numériques au niveau de la couche applicative. Faites attention au paramètre NLS_NUMERIC_CHARACTERS.","tags":["number","numeric","conversion","type conversion","to_number","datatype","data type"]},{"code":"ORA-02049","title":"Timeout: distributed transaction waiting for lock (délai expiré : transaction distribuée en attente de verrou)","severity":"danger","meaning":"Une transaction distribuée a dépassé DISTRIBUTED_LOCK_TIMEOUT en attendant un verrou sur la base de données distante. La transaction a expiré.","cause":"Une transaction s'exécutant via un database link attend une ligne verrouillée côté distant ; longues transactions dans la session distante, latence réseau, ou verrous en conflit détenus aux deux extrémités.","fix":"Trouvez et résolvez le verrou et la session bloquants sur la base de données distante (v$lock, v$session). Gardez les transactions distribuées courtes et acquérez les verrous nécessaires le plus tard possible. Si nécessaire, augmentez DISTRIBUTED_LOCK_TIMEOUT selon la charge (SPFILE, redémarrage requis). Pour les transactions distribuées in-doubt, examinez la vue DBA_2PC_PENDING et résolvez-les manuellement si nécessaire.","tags":["distributed","distributed transaction","dblink","lock","lock wait","timeout","2pc"]},{"code":"enq: TX - row lock contention","title":"Attente : session bloquée sur un verrou de ligne détenu par une autre transaction","severity":"caution","meaning":"Une session attend la libération d'un verrou de ligne détenu par une autre transaction. C'est l'événement d'attente de verrou classique au niveau applicatif.","cause":"Transactions concurrentes tentant de mettre à jour la même ligne, transactions laissées longtemps sans commit, verrouillage étendu dû à des clés étrangères non indexées, ou concentration sur une clé séquentielle (hot block).","fix":"Trouvez les sessions bloquantes et bloquées : SELECT * FROM v$session WHERE blocking_session IS NOT NULL; et faites la corrélation avec v$lock. Si les longues transactions sont la cause racine, revoyez la stratégie de commit. Indexez les colonnes de clé étrangère. Repensez la conception (séquence, hachage) pour les lignes chaudes. Terminez la session bloquante si nécessaire.","tags":["wait","wait event","enq","row lock","row lock wait","tx","contention","lock"]},{"code":"log file sync","title":"Attente : session attendant que LGWR écrive le redo sur disque au commit","severity":"caution","meaning":"Lorsqu'une session effectue un COMMIT, elle attend que LGWR écrive (flush) les enregistrements redo dans les fichiers redo log. Si cette attente est élevée, il y a une latence de commit.","cause":"Disques de redo log lents (latence d'E/S élevée), application qui effectue des commits trop fréquents (commit ligne par ligne), pression CPU sur le processus LGWR, ou volume de redo très élevé.","fix":"Mesurez le temps d'attente moyen avec AWR/Statspack. Déplacez les fichiers redo log sur des disques rapides à faible latence (SSD). Réduisez la fréquence des commits inutiles dans l'application et regroupez les traitements par lots. Si nécessaire, évaluez les options COMMIT_LOGGING/COMMIT_WAIT (commit_write) au regard des exigences métier ; ATTENTION : NOWAIT/BATCH réduisent la durabilité, et des transactions apparemment validées peuvent être perdues lors d'un crash. Utilisez-les uniquement au niveau de la session ou de l'instruction pour des traitements où une perte est tolérable ; ne les activez pas à l'échelle du système. Examinez ensemble la latence de LGWR et celle du stockage.","tags":["wait","wait event","log file sync","commit","lgwr","redo","latency"]},{"code":"db file sequential read","title":"Attente : lecture physique d'un seul bloc, généralement un accès par index ou à une table","severity":"caution","meaning":"Une session attend la lecture d'un seul bloc depuis le disque. S'observe généralement lors d'accès par index ou de lectures d'un seul bloc par ROWID ; c'est normalement un événement attendu.","cause":"Blocs absents du buffer cache lus depuis le disque, utilisation d'index peu sélectifs ou inadaptés, buffer cache insuffisant, ou couche de stockage lente.","fix":"Si elle est élevée, trouvez les instructions SQL qui effectuent le plus de lectures (AWR Top SQL, v$sql). Améliorez les requêtes avec des index appropriés et des chemins d'accès corrects, et réduisez les nested loops inutiles. Évaluez la taille du buffer cache (DB_CACHE_SIZE). Vérifiez la latence d'E/S du stockage. Cet événement est souvent normal ; s'il est anormalement élevé, concentrez-vous sur l'optimisation SQL.","tags":["wait","wait event","db file sequential read","io","disk io","index","index access","single block"]},{"code":"db file scattered read","title":"Attente : lecture physique multibloc, généralement un full table scan ou un index fast full scan","severity":"caution","meaning":"Une session attend la lecture de plusieurs blocs (multibloc) depuis le disque. Se produit généralement lors d'un full table scan ou d'un index fast full scan.","cause":"Full table scans dus à l'absence d'index approprié, plan erroné dû à des statistiques obsolètes, parcours de très grandes tables, ou chemin d'accès choisi qui se rabat sur un parcours de table.","fix":"Identifiez les instructions SQL effectuant des parcours complets et ajoutez les index requis ou assurez-vous que l'index existant est utilisé. Mettez à jour les statistiques : DBMS_STATS.GATHER_TABLE_STATS. Si un grand parcours est réellement nécessaire, envisagez la requête parallèle ou le partitionnement. Revoyez DB_FILE_MULTIBLOCK_READ_COUNT et le débit du stockage.","tags":["wait","wait event","db file scattered read","full scan","full table scan","io","disk io","multiblock"]},{"code":"buffer busy waits","title":"Attente : contention sur un buffer déjà en cours de lecture ou de modification par une autre session","severity":"caution","meaning":"Une session attend d'accéder à un bloc de buffer qu'une autre session lit ou modifie au même moment. Il s'agit d'une contention de bloc en mémoire.","cause":"Concentration sur des blocs chauds, contention sur le bord droit d'index à clés monotones croissantes, freelists insuffisantes/segments non ASSM, ou DML concurrent intense sur le même bloc.","fix":"Déterminez le segment et la classe de bloc concernés (v$session, v$segment_statistics, ASH). Pour les blocs d'index chauds, envisagez un index à clé inversée (reverse key) ou partitionné par hachage. Utilisez ASSM (Automatic Segment Space Management). Sous forte concurrence, augmentez INITRANS/PCTFREE. Répartissez le schéma d'accès de l'application.","tags":["wait","wait event","buffer busy","hot block","hot blocks","contention","block contention"]},{"code":"gc buffer busy","title":"Attente (RAC) : contention sur un bloc cache fusion en cours de transfert entre instances","severity":"caution","meaning":"Dans un environnement RAC, une session attend d'accéder à un bloc global en cours de transfert via cache fusion vers/depuis une autre instance, ou maintenu occupé par cette instance.","cause":"Mêmes blocs chauds partagés entre les nœuds RAC (hot block inter-instances), latence/goulot d'étranglement de l'interconnexion, contention inter-nœuds sur des index à clés séquentielles, ou répartition déséquilibrée de la charge.","fix":"Trouvez les segments concernés avec v$segment_statistics et les vues GV$. Vérifiez l'état et la latence de l'interconnexion (réseau privé et rapide). Utilisez des index à clé inversée ou un partitionnement par hachage pour les index chauds. Réduisez la contention inter-nœuds sur les mêmes blocs en séparant la charge entre les nœuds par service.","tags":["wait","wait event","rac","gc buffer busy","cache fusion","interconnect","global"]},{"code":"library cache lock","title":"Attente : contention sur un handle d'objet du library cache (sérialisation parse/DDL)","severity":"caution","meaning":"Une session attend un verrou sur un objet du library cache (SQL, PL/SQL, définition de table). Généralement causé par la sérialisation des opérations de parse et de DDL.","cause":"DDL (ALTER/CREATE) sur des objets très utilisés, objets PL/SQL fréquemment recompilés (invalidation), hard parsing excessif, invalidation d'objets dépendants, ou tempêtes de connexions simultanées.","fix":"Trouvez la session et l'objet bloquants (v$session, ASH, diagnostics de library cache lock si nécessaire). Évitez d'exécuter du DDL sur des objets très utilisés aux heures de pointe. Réduisez les chaînes de dépendances et évitez les recompilations inutiles. Diminuez le hard parsing en utilisant des variables de liaison. Lissez les tempêtes de connexions avec un pool de connexions.","tags":["wait","wait event","library cache lock","parse","parsing","ddl","hard parse"]}]},"errors/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"23505","title":"unique_violation","severity":"caution","meaning":"La ligne insérée ou mise à jour viole une contrainte UNIQUE ou la PRIMARY KEY ; la même valeur existe déjà dans la table.","cause":"Réinsertion avec la même clé unique, saisie de données en double dans une colonne unique, valeur de séquence en retard à cause d'insertions manuelles, ou deux requêtes concurrentes insérant la même clé.","fix":"Le nom de la contrainte et la valeur de clé dans le détail de l'erreur indiquent quelle ligne est en conflit. Pour mettre à jour la ligne existante, utilisez INSERT ... ON CONFLICT (my_column) DO UPDATE ou DO NOTHING. Pour trouver les doublons : SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1. Si la séquence a pris du retard : SELECT setval('seq_name', (SELECT MAX(id) FROM my_table)).","tags":["unique","unique","constraint","constraint","duplicate","duplicate","23505","primary key"]},{"code":"23503","title":"foreign_key_violation","severity":"caution","meaning":"Une contrainte FOREIGN KEY a été violée. La ligne parente référencée par la ligne insérée/mise à jour n'existe pas, ou la ligne parente supprimée possède encore des lignes enfants dépendantes.","cause":"INSERT/UPDATE référençant une clé parente inexistante, tentative de suppression/mise à jour d'une ligne parente encore référencée, ou données chargées dans le mauvais ordre (enfant d'abord, puis parent).","fix":"Insérez d'abord la ligne parente, puis la ligne enfant. Si un comportement en cascade est nécessaire à la suppression, définissez la contrainte avec ON DELETE CASCADE ou ON DELETE SET NULL. Lors des chargements en masse, chargez les données dans l'ordre parent-enfant. Trouvez les lignes qui référencent : SELECT * FROM child WHERE fk_column = value.","tags":["foreign key","foreign key","fk","constraint","constraint","reference","23503"]},{"code":"23502","title":"not_null_violation","severity":"caution","meaning":"Tentative d'insertion de NULL dans une colonne soumise à une contrainte NOT NULL, ou de mise à NULL de cette colonne.","cause":"Omission d'une colonne obligatoire dans l'INSERT, affectation explicite de NULL, colonne sans DEFAULT, ou valeur vide/manquante provenant de l'application.","fix":"Fournissez une valeur valide pour la colonne ou définissez-lui un DEFAULT pertinent : ALTER TABLE my_table ALTER COLUMN my_column SET DEFAULT value. Si la colonne n'a pas réellement besoin d'être obligatoire, supprimez la contrainte : ALTER TABLE my_table ALTER COLUMN my_column DROP NOT NULL. Ajoutez une validation des données manquantes dans la couche applicative.","tags":["not null","null","constraint","constraint","required","23502","insert"]},{"code":"40P01","title":"deadlock_detected","severity":"danger","meaning":"Deux transactions ou plus sont entrées en interblocage, chacune attendant des verrous détenus par l'autre. PostgreSQL détecte le cycle et annule l'une des transactions.","cause":"Verrous acquis dans des ordres différents, mises à jour croisées des mêmes lignes, longues transactions, ou verrous en conflit pris lors des vérifications de clés étrangères.","fix":"Le détail de l'interblocage dans le journal du serveur indique les requêtes en conflit. Accédez aux tables/lignes dans le même ordre dans toutes les transactions. Gardez les transactions courtes. Interceptez la transaction annulée dans l'application et relancez-la. Joignez pg_locks à pg_stat_activity pour examiner les verrous. Si nécessaire, rendez l'ordre de verrouillage explicite avec SELECT ... FOR UPDATE.","tags":["deadlock","deadlock","lock","lock","40P01","tx","transaction"]},{"code":"40001","title":"serialization_failure","severity":"caution","meaning":"Sous le niveau d'isolation SERIALIZABLE ou REPEATABLE READ, la transaction n'a pas pu être sérialisée, car elle est entrée en conflit avec des modifications concurrentes, et elle a été annulée. C'est un comportement attendu qui protège la cohérence des données.","cause":"Transactions accédant simultanément aux mêmes données ne pouvant pas être ordonnées de façon sérialisable, conflits d'écriture sous forte concurrence, ou dépendances dangereuses détectées par SSI (Serializable Snapshot Isolation).","fix":"Ce n'est pas un bogue mais un signal attendu : relancez la transaction telle quelle. Ajoutez une logique de relance automatique dans l'application. Pour réduire les conflits, raccourcissez les transactions et utilisez un niveau d'isolation inférieur (READ COMMITTED) s'il n'est pas réellement nécessaire. Concevez de façon à réduire la contention sur les lignes chaudes.","tags":["serialization","serialization","isolation","isolation","retry","retry","40001","mvcc"]},{"code":"53300","title":"too_many_connections","severity":"danger","meaning":"Le serveur a atteint le nombre maximal de connexions simultanées autorisé par max_connections ; les nouvelles connexions ne peuvent pas être acceptées.","cause":"Absence de pool de connexions ou pool mal dimensionné, fuites de connexions, applications ouvrant de nombreuses connexions de courte durée, ou max_connections défini en dessous de la charge.","fix":"Identifiez les sources de connexion : SELECT usename, state, COUNT(*) FROM pg_stat_activity GROUP BY 1,2. Terminez de manière ciblée les connexions clientes inactives depuis longtemps (une terminaison massive non filtrée affecte toutes les applications et tous les pools) : SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND backend_type = 'client backend' AND usename = 'app_user' AND state_change < now() - interval '10 minutes' AND pid <> pg_backend_pid(). La vraie correction est un pool de connexions tel que PgBouncer. Augmenter max_connections coûte de la mémoire ; privilégiez le pooling. Les connexions réservées aux superutilisateurs maintiennent l'accès administratif disponible.","tags":["connection","connection","max_connections","too many","53300","pool","pool","pgbouncer"]},{"code":"53100","title":"disk_full","severity":"danger","meaning":"Le serveur n'a pas pu écrire dans un fichier, car le disque est plein. Il ne reste plus d'espace sur le disque qui héberge le répertoire de données, le WAL ou les fichiers temporaires.","cause":"Croissance des données, accumulation incontrôlée de WAL (archivage ou replication slot en retard), gros fichiers temporaires (tris volumineux), fichiers journaux gonflés, ou bloat de tables/index.","fix":"Inspectez l'utilisation du disque et supprimez les fichiers inutiles. Vérifiez l'accumulation de WAL : pg_stat_replication et pg_replication_slots (supprimez les slots inutilisés ; ceux en retard retiennent du WAL). N'exécutez pas VACUUM FULL ni REINDEX tant que le disque est plein : comme ils réécrivent la table, ils nécessitent un espace supplémentaire égal à la taille de la table et peuvent aggraver la situation ; un VACUUM simple ne rend (presque) jamais d'espace au système d'exploitation. Ne supprimez JAMAIS à la main des fichiers du répertoire pg_wal. Libérez d'abord de l'espace en traitant les sources WAL/slot, journaux et fichiers temporaires ; revoyez l'emplacement des journaux et des fichiers temporaires. En correction permanente, augmentez la capacité disque et mettez en place une surveillance/des alertes.","tags":["disk","space","space","full","full","wal","53100","vacuum"]},{"code":"57014","title":"query_canceled","severity":"safe","meaning":"La requête a été annulée. Généralement causé par l'expiration de statement_timeout ou par une annulation de l'utilisateur ou du client (pg_cancel_backend, annulation par le pilote).","cause":"Dépassement de statement_timeout, annulation avec pg_cancel_backend, ou demande d'annulation envoyée par l'application/le client. Des situations similaires ont des codes différents : l'expiration de lock_timeout renvoie 55P03, idle_in_transaction_session_timeout 25P03 (FATAL), et une annulation due à un conflit de réplication sur un hot standby 40001 (40P01 dans certains cas).","fix":"Si la requête est réellement longue, optimisez-la (EXPLAIN ANALYZE, index, plan). Si le délai est trop court, ajustez statement_timeout pour la session/le rôle concerné. Les annulations volontaires ne nécessitent aucune action. Pour les annulations dues aux verrous et aux conflits sur le standby, consultez les entrées 55P03 et 40001.","tags":["cancel","cancel","timeout","timeout","statement_timeout","57014","standby"]},{"code":"42P01","title":"undefined_table","severity":"caution","meaning":"La table (ou la vue) référencée dans la requête n'existe pas ou est introuvable dans le search_path.","cause":"La table n'existe réellement pas, elle se trouve dans un autre schéma absent du search_path, faute de frappe/erreur de casse dans le nom de la table, incohérence de casse avec des identifiants entre guillemets, ou l'objet n'a pas encore été créé ou a été supprimé.","fix":"Vérifiez que la table existe et son schéma : SELECT schemaname, tablename FROM pg_tables WHERE tablename = 'my_table'. Si elle se trouve dans un autre schéma, qualifiez-la sous la forme schema.table ou définissez le search_path : SET search_path TO app_schema, public. N'oubliez pas que les identifiants sans guillemets sont convertis en minuscules ; si des guillemets ont été utilisés à la création, référencez-la de la même façon.","tags":["table","table","undefined","42P01","search_path","schema","schema"]},{"code":"42703","title":"undefined_column","severity":"caution","meaning":"La colonne référencée dans la requête n'existe pas dans la table ou dans la portée courante.","cause":"Faute de frappe dans le nom de la colonne, colonne supprimée/renommée, référence à la mauvaise table, incohérence de casse avec un identifiant entre guillemets, ou qualification ambiguë/incorrecte dans une requête multi-tables.","fix":"Listez les colonnes : \\d my_table ou SELECT column_name FROM information_schema.columns WHERE table_name = 'my_table'. Corrigez le nom et la table, et qualifiez les colonnes dans les JOIN sous la forme table.column. Si des guillemets ont été utilisés à la création, référencez-la avec la même casse.","tags":["column","column","undefined","42703","typo","typo","schema"]},{"code":"42501","title":"insufficient_privilege","severity":"caution","meaning":"Le rôle ne dispose pas du privilège requis pour l'opération qu'il tente.","cause":"Le GRANT requis sur la table/le schéma/la base de données n'a pas été accordé, USAGE sur le schéma manque, l'objet appartient à un autre rôle, ou des politiques de sécurité au niveau des lignes (RLS) bloquent l'accès.","fix":"Accordez le privilège manquant : GRANT SELECT, INSERT ON my_table TO app_role; pour le schéma, GRANT USAGE ON SCHEMA app_schema TO app_role. Pour les octrois en masse, utilisez GRANT ... ON ALL TABLES IN SCHEMA, et ALTER DEFAULT PRIVILEGES pour les objets futurs. Affichez les privilèges actuels : \\dp my_table. Si RLS est actif, revoyez les politiques concernées.","tags":["privilege","privilege","permission","grant","42501","permission","rls"]},{"code":"28P01","title":"invalid_password","severity":"caution","meaning":"Le mot de passe fourni lors de l'authentification est erroné ; la connexion a été refusée pour le rôle.","cause":"Mot de passe erroné, mot de passe modifié, identifiants obsolètes dans la chaîne de connexion de l'application, ou incompatibilité avec la méthode d'authentification attendue dans pg_hba.conf (scram-sha-256, md5).","fix":"Vérifiez le mot de passe et réinitialisez-le si nécessaire : ALTER ROLE app_role WITH PASSWORD 'new_password'. Assurez-vous que la méthode définie dans pg_hba.conf est compatible avec le client (les anciens clients peuvent avoir des problèmes avec scram-sha-256). Après modification, rechargez la configuration avec SELECT pg_reload_conf().","tags":["auth","identity","password","password","28P01","scram","md5","hba"]},{"code":"08006","title":"connection_failure","severity":"danger","meaning":"La connexion entre le client et le serveur n'a pas pu être établie ou a été interrompue de manière inattendue.","cause":"Serveur arrêté ou en cours de redémarrage, problèmes réseau/pare-feu, hôte/port erroné, paramètre listen_addresses qui bloque l'accès, connexion inactive coupée par le réseau, ou serveur surchargé.","fix":"Vérifiez que le serveur est démarré et écoute sur le bon hôte/port (pg_isready host port). Vérifiez listen_addresses dans postgresql.conf et les règles d'accès dans pg_hba.conf. Vérifiez les paramètres réseau/pare-feu et DNS. Revoyez les paramètres de TCP keepalive et de délai d'expiration du pool de connexions. Recherchez des traces de crash/redémarrage dans le journal du serveur.","tags":["connection","connection","network","network","08006","failure","disconnect"]},{"code":"55P03","title":"lock_not_available","severity":"caution","meaning":"Le verrou demandé n'a pas pu être acquis immédiatement. Cela se produit généralement lorsqu'un verrou est demandé avec NOWAIT alors qu'un autre le détient, ou lorsque lock_timeout expire.","cause":"Ligne/table verrouillée lors de l'utilisation de SELECT ... FOR UPDATE NOWAIT ou LOCK TABLE ... NOWAIT, ou expiration de lock_timeout. La plupart du temps, une longue transaction détient le verrou.","fix":"Trouvez la session bloquante : joignez pg_locks et pg_stat_activity (relation en attente/bloquant). Terminez la longue transaction ou, si nécessaire, mettez-y fin avec pg_terminate_backend(pid). Au lieu de NOWAIT, attendez avec un lock_timeout raisonnable et ajoutez une logique de relance. Raccourcissez les transactions pour réduire la durée de détention des verrous.","tags":["lock","lock","nowait","lock_timeout","55P03","not available","busy"]},{"code":"54000","title":"program_limit_exceeded","severity":"caution","meaning":"Une limite d'implémentation/de format de PostgreSQL a été dépassée ; par exemple la taille maximale d'une ligne ou d'un champ, la taille d'une ligne d'index, ou la limite du nombre de paramètres/colonnes d'une requête.","cause":"Tentative de stocker une valeur très grande (par ex. un champ de plus de 1 GB), valeur indexée dépassant la limite de page B-tree (environ 2704 octets), ou utilisation de trop de colonnes ou de paramètres de requête.","fix":"Identifiez la limite dépassée (le message l'indique généralement). Revoyez ou découpez la structure pour les textes/données très volumineux. Indexez les longues colonnes de texte avec un index hash ou un index sur expression/partiel au lieu d'un B-tree. Réduisez le nombre de paramètres/colonnes de la requête ou découpez le lot.","tags":["limit","limit","program_limit","54000","index size","size","row size"]},{"code":"22001","title":"string_data_right_truncation","severity":"caution","meaning":"La valeur aurait été tronquée, car la longueur du texte dépasse la longueur définie de la colonne cible (par ex. VARCHAR(n)) ; PostgreSQL la rejette et lève une erreur.","cause":"Insertion ou mise à jour d'un texte de plus de n caractères dans une colonne VARCHAR(n)/CHAR(n), données plus longues que prévu provenant de l'application, ou longueur de colonne définie trop étroitement pour le besoin.","fix":"Raccourcissez la valeur pour qu'elle tienne dans la colonne, ou augmentez la longueur de la colonne : ALTER TABLE my_table ALTER COLUMN my_column TYPE VARCHAR(500). Si un texte illimité est nécessaire, utilisez le type TEXT. Validez la longueur dans l'application. Si vous souhaitez une troncature volontaire, appliquez explicitement LEFT(value, n).","tags":["truncation","truncation","varchar","string","text","22001","length","length"]},{"code":"25P02","title":"in_failed_sql_transaction","severity":"caution","meaning":"Une erreur s'est déjà produite plus tôt dans la transaction ; aucune autre commande ne peut y être exécutée. La transaction est à l'état annulé (aborted) et seul ROLLBACK est accepté.","cause":"Une instruction précédente d'un bloc de transaction a échoué (par ex. une violation de contrainte) et de nouvelles commandes continuent malgré tout d'être envoyées dans la même transaction. L'application n'intercepte pas l'erreur et poursuit la transaction.","fix":"Annulez la transaction : ROLLBACK, puis relancez-la. Si vous souhaitez isoler l'erreur d'un bloc et continuer, utilisez SAVEPOINT et revenez à ce point avec ROLLBACK TO SAVEPOINT. Dans l'application, interceptez l'erreur de chaque instruction et gérez correctement la transaction (rendez explicite la décision commit/rollback).","tags":["transaction","transaction","aborted","aborted","25P02","rollback","savepoint"]},{"code":"53200","title":"out_of_memory","severity":"danger","meaning":"Le serveur n'a pas pu allouer la mémoire nécessaire à une opération. Un processus backend ou l'ensemble du serveur est sous pression mémoire.","cause":"work_mem/maintenance_work_mem trop élevés et multipliés par une forte concurrence, trop d'opérations de hachage/tri dans une même requête, nombre excessif de connexions, fuite mémoire, ou pression de l'OOM killer du système d'exploitation.","fix":"Vérifiez le contexte mémoire dans le journal du serveur et les traces de l'OOM killer. Définissez work_mem de façon raisonnable, en tenant compte du nombre de requêtes et de la concurrence (il est alloué séparément pour chaque tri/hachage). Limitez le nombre de connexions grâce au pooling. Revoyez les paramètres mémoire/overcommit du système d'exploitation. Optimisez les requêtes lourdes pour réduire leur besoin en mémoire.","tags":["memory","memory","out of memory","53200","work_mem","oom"]}]},"errors/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1045","title":"Access denied for user (using password: YES/NO) (accès refusé pour l'utilisateur)","severity":"caution","meaning":"L'authentification a échoué pour le nom d'utilisateur, le mot de passe ou l'hôte depuis lequel vous vous connectez ; la connexion a été refusée.","cause":"Mot de passe erroné, l'utilisateur n'a pas de privilèges depuis l'hôte d'où il se connecte (user@host), le compte n'existe pas, incompatibilité de plugin d'authentification (caching_sha2_password ou mysql_native_password) ou identifiants obsolètes dans la chaîne de connexion.","fix":"Vérifiez la correspondance utilisateur/hôte : SELECT user, host, plugin FROM mysql.user. Accordez les privilèges pour le bon hôte (par ex. 'app_user'@'%'). Réinitialisez le mot de passe : ALTER USER 'app_user'@'host' IDENTIFIED BY 'new_password'; (ALTER USER/GRANT prennent effet immédiatement ; FLUSH PRIVILEGES n'est nécessaire que si les tables de privilèges ont été modifiées directement par DML). Si vous voyez 'using password: NO', le client n'envoie pas du tout de mot de passe. En cas d'incompatibilité de plugin, revoyez le plugin d'authentification : dans MySQL 8.4, le plugin mysql_native_password est désactivé par défaut (chargé uniquement avec --mysql-native-password=ON) ; les anciens comptes utilisant ce plugin et les anciens clients ne peuvent pas se connecter. Migrez le compte avec ALTER USER ... IDENTIFIED WITH caching_sha2_password BY '...'; et mettez à niveau le pilote client.","tags":["auth","authentication","access denied","access","password","password","1045","privilege"]},{"code":"1062","title":"Duplicate entry for key (entrée en double pour la clé)","severity":"caution","meaning":"La ligne insérée ou mise à jour viole un index UNIQUE ou la PRIMARY KEY ; la même valeur de clé existe déjà.","cause":"INSERT répété avec la même clé unique, saisie de données en double, compteur AUTO_INCREMENT en retard, ou lignes en double existantes lors de l'ajout d'un index unique à une table existante.","fix":"Le message d'erreur indique la valeur de clé en conflit et le nom de l'index. Pour mettre à jour la ligne existante, envisagez INSERT ... ON DUPLICATE KEY UPDATE ou INSERT IGNORE / REPLACE. Trouvez les doublons : SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1. Si vous ne pouvez pas ajouter un index unique, nettoyez d'abord les doublons.","tags":["duplicate","duplicate entry","unique","unique key","1062","primary key","insert"]},{"code":"1213","title":"Deadlock found when trying to get lock; try restarting transaction (interblocage lors de l'acquisition du verrou ; relancez la transaction)","severity":"danger","meaning":"InnoDB a détecté un interblocage dans lequel deux transactions ou plus attendent mutuellement leurs verrous, et a rompu le cycle en annulant l'une des transactions.","cause":"Verrous de ligne acquis dans des ordres différents, mises à jour croisées des mêmes lignes, larges plages de lignes verrouillées (gap locks) faute d'index, ou conflits lors des vérifications de clés étrangères.","fix":"Affichez les détails du dernier interblocage : SHOW ENGINE INNODB STATUS (section LATEST DETECTED DEADLOCK). Accédez aux lignes/tables dans le même ordre dans toutes les transactions et gardez les transactions courtes. Réduisez la plage verrouillée en ajoutant des index appropriés. Interceptez la transaction annulée dans l'application et relancez-la (logique de relance).","tags":["deadlock","deadlock detected","lock","row lock","innodb","1213","tx","transaction"]},{"code":"1205","title":"Lock wait timeout exceeded; try restarting transaction (délai d'attente de verrou dépassé ; relancez la transaction)","severity":"danger","meaning":"Une transaction a attendu un verrou de ligne requis pendant innodb_lock_wait_timeout secondes, n'a pas pu l'acquérir et a expiré.","cause":"Une autre transaction détient le verrou longtemps sans commit ni rollback ; transactions ouvertes inactives, mises à jour en masse de longue durée ou lignes chaudes disputées.","fix":"Trouvez la transaction bloquante : joignez performance_schema.data_lock_waits à data_locks (information_schema.innodb_lock_waits/innodb_trx dans les versions plus anciennes). Validez/annulez la transaction bloquante ou terminez-la avec KILL <trx_mysql_thread_id> si nécessaire. Gardez les transactions courtes et ne laissez pas de transactions inactives (autocommit/logique applicative). Si nécessaire, ajustez innodb_lock_wait_timeout à la charge.","tags":["lock","lock wait","timeout","timed out","innodb","1205","wait","waiting"]},{"code":"1040","title":"Too many connections (trop de connexions)","severity":"danger","meaning":"Le serveur a atteint le nombre maximal de connexions simultanées autorisé par max_connections ; les nouvelles connexions sont rejetées.","cause":"Pool de connexions mal dimensionné, fuites de connexions, nombreuses connexions de courte durée, connexions en sommeil (Sleep) depuis longtemps ou valeur de max_connections trop basse pour la charge.","fix":"Inspectez les connexions : SHOW PROCESSLIST ou SELECT * FROM performance_schema.processlist. Fermez les connexions en sommeil inutiles avec KILL. Augmentez max_connections : SET GLOBAL max_connections = 500 (my.cnf pour le rendre persistant). La correction de fond consiste à utiliser un pool de connexions et à fermer les connexions inactives avec wait_timeout. Une connexion d'administration d'urgence supplémentaire est réservée à SUPER/CONNECTION_ADMIN.","tags":["connection","connections","too many","max_connections","1040","pool","connection pool"]},{"code":"1064","title":"You have an error in your SQL syntax (erreur de syntaxe SQL)","severity":"safe","meaning":"L'instruction SQL comporte une erreur de syntaxe ; le serveur n'a pas pu l'analyser. Le message indique le texte proche de l'erreur.","cause":"Faute de frappe, virgule ou parenthèse manquante/en trop, mot réservé utilisé sans guillemets comme nom de colonne/table, syntaxe non prise en charge par la version de MySQL, ou erreurs de guillemets/d'échappement.","fix":"La partie 'near ...' du message indique où commence l'erreur ; regardez juste avant. Encadrez les mots réservés de backticks : `order`, `group`. Équilibrez les parenthèses et les virgules. Vérifiez que vous utilisez une syntaxe prise en charge par votre version. Si vous générez du SQL dynamique, journalisez et inspectez la requête complète obtenue.","tags":["syntax","syntax error","1064","parse","parsing","reserved word","reserved keyword"]},{"code":"1146","title":"Table doesn't exist (la table n'existe pas)","severity":"caution","meaning":"La table référencée dans la requête n'existe pas ou est introuvable dans la base de données courante.","cause":"La table n'existe réellement pas, la mauvaise base de données est sélectionnée, faute de frappe dans le nom de la table, sensibilité à la casse des noms de tables sous Linux (lower_case_table_names) ou fichier de table InnoDB (.ibd) manquant/corrompu (en 5.7 et antérieures, également le fichier de définition .frm ; en 8.0, les définitions résident dans le dictionnaire de données).","fix":"Vérifiez la table : SHOW TABLES; et sélectionnez la bonne base de données : USE mydb. Faites attention à la sensibilité à la casse (surtout sous Linux). Vérifiez le nom de la table et le schéma avec SELECT ... FROM information_schema.tables. En cas de problème de fichier InnoDB, une restauration/réparation peut être nécessaire.","tags":["table","table missing","doesn't exist","not found","1146","schema","database"]},{"code":"1054","title":"Unknown column in field list (colonne inconnue dans la liste des champs)","severity":"caution","meaning":"La colonne référencée dans la requête n'existe pas dans la table ou dans la portée courante.","cause":"Faute de frappe dans le nom de la colonne, colonne supprimée/renommée, référence à la mauvaise table, qualification ambiguë/erronée dans un JOIN, ou valeur de chaîne écrite sans guillemets et interprétée comme une colonne (WHERE name = value au lieu de WHERE name = 'value').","fix":"Listez les colonnes : SHOW COLUMNS FROM my_table ou DESCRIBE my_table. Corrigez le nom de la colonne et qualifiez-la sous la forme my_table.my_column dans les JOIN. Encadrez les littéraux de chaîne de guillemets simples ; les backticks sont réservés aux identifiants. Vous pouvez vérifier avec information_schema.columns.","tags":["column","column name","unknown","unknown column","1054","typo","misspelling"]},{"code":"2002","title":"Can't connect to local MySQL server through socket (connexion impossible au serveur MySQL local via le socket)","severity":"danger","meaning":"Le client n'a pas pu se connecter au serveur MySQL local via le socket Unix. Généralement, le serveur n'est pas démarré ou le chemin du socket est erroné.","cause":"Le service MySQL/MariaDB n'est pas démarré ou a planté, le chemin du fichier socket (par ex. /var/run/mysqld/mysqld.sock) diffère entre le client et le serveur, problèmes de permissions, ou le serveur n'a pas démarré.","fix":"Vérifiez l'état du service et démarrez-le (systemctl status mysql / start mysql). Vérifiez le bon chemin du socket : SHOW VARIABLES LIKE 'socket' et le paramètre socket dans my.cnf. Si le chemin diffère, indiquez-le explicitement avec --socket ou utilisez TCP : mysql -h 127.0.0.1 -P 3306. Consultez les journaux du serveur pour les erreurs de démarrage/de crash.","tags":["connection","connect","socket","unix socket","2002","server","local","localhost"]},{"code":"2003","title":"Can't connect to MySQL server (TCP/IP) (connexion impossible au serveur MySQL)","severity":"danger","meaning":"Le client n'a pas pu se connecter au serveur MySQL via TCP/IP sur l'hôte et le port indiqués.","cause":"Le serveur n'est pas démarré, hôte/port erroné, pare-feu bloquant le port 3306, serveur lié uniquement à localhost (bind-address = 127.0.0.1) ou skip-networking activé.","fix":"Vérifiez que le serveur est démarré et écoute sur le bon port. Pour l'accès distant, définissez bind-address = 0.0.0.0 (ou l'IP appropriée) dans my.cnf et assurez-vous que skip-networking est désactivé. Ouvrez le port 3306 dans le pare-feu. Vérifiez l'hôte/le port : mysql -h host -P 3306 -u app_user -p. Assurez-vous que l'utilisateur dispose de privilèges depuis l'hôte distant.","tags":["connection","connect","tcp","port","2003","network","firewall","bind-address"]},{"code":"2006","title":"MySQL server has gone away (le serveur MySQL a disparu)","severity":"caution","meaning":"La connexion entre le client et le serveur a été perdue pendant une commande. Le serveur a peut-être fermé la connexion ou rejeté le paquet.","cause":"Connexion inactive fermée à l'expiration de wait_timeout/interactive_timeout, requête/données plus grandes que max_allowed_packet, redémarrage ou crash du serveur, ou panne réseau.","fix":"Pour les problèmes de connexions inactives, utilisez la validation dans le pool de connexions et revoyez wait_timeout. Si vous envoyez de grandes quantités de données, augmentez max_allowed_packet (par ex. SET GLOBAL max_allowed_packet = 64*1024*1024, my.cnf pour le rendre persistant). Consultez les journaux du serveur pour les redémarrages/crashs et les traces d'OOM. Ajoutez une logique de reconnexion dans l'application pour les connexions coupées.","tags":["connection","connection lost","gone away","disconnect","2006","timeout","max_allowed_packet"]},{"code":"1114","title":"The table is full (la table est pleine)","severity":"danger","meaning":"Plus aucune donnée ne peut être écrite dans la table, car une limite de stockage ou la fin de l'espace disponible a été atteinte.","cause":"Disque plein, limite max_heap_table_size atteinte pour les tables MEMORY, tmp_table_size dépassé pour les tables temporaires, tablespace de taille fixe défini par innodb_data_file_path rempli, ou limites de taille propres au système de fichiers/à la table.","fix":"Vérifiez et libérez d'abord l'espace disque. Pour les tables MEMORY/temporaires, augmentez max_heap_table_size et tmp_table_size. Pour InnoDB, assurez-vous qu'innodb_data_file_path est en autoextend ou ajoutez de l'espace. Optimisez les requêtes qui déversent de gros résultats temporaires sur disque. En correction permanente, augmentez la capacité de stockage.","tags":["table full","table is full","disk","space","1114","space","memory","innodb"]},{"code":"1215","title":"Cannot add foreign key constraint (impossible d'ajouter la contrainte de clé étrangère)","severity":"caution","meaning":"La contrainte de clé étrangère en cours de définition ne peut pas être créée ; MySQL ne peut pas ajouter la contrainte.","cause":"Incohérence de type de données/longueur/signe (signed-unsigned) ou de jeu de caractères/collation entre la colonne enfant et la colonne référencée, absence d'index/PRIMARY KEY approprié sur la colonne référencée, tables utilisant des moteurs de stockage différents (l'une est MyISAM) ou table référencée pas encore existante.","fix":"Assurez-vous que les types de colonnes des deux côtés sont EXACTEMENT identiques (par ex. tous deux INT UNSIGNED). Vérifiez que la colonne référencée est couverte par un index (généralement PRIMARY KEY ou UNIQUE). Assurez-vous que les deux tables sont en InnoDB. Utilisez le même charset/la même collation pour les clés de type chaîne. Pour les détails : SHOW ENGINE INNODB STATUS (LATEST FOREIGN KEY ERROR).","tags":["foreign key","foreign key constraint","fk","1215","constraint","constraint error","innodb","type"]},{"code":"1451","title":"Cannot delete or update a parent row: a foreign key constraint fails (suppression ou mise à jour de la ligne parente impossible : échec de la contrainte de clé étrangère)","severity":"caution","meaning":"Vous avez tenté de supprimer une ligne parente ou de modifier sa clé, mais la contrainte de clé étrangère ne le permet pas, car des lignes enfants dépendantes existent.","cause":"La ligne parente référencée possède encore des lignes enfants et la contrainte utilise RESTRICT/NO ACTION pour ON DELETE/UPDATE.","fix":"Supprimez d'abord les lignes enfants dépendantes ou rattachez-les à un autre parent, puis supprimez la ligne parente. Si vous souhaitez un comportement automatique, redéfinissez la contrainte avec ON DELETE CASCADE ou ON DELETE SET NULL. Trouvez les lignes dépendantes : SELECT * FROM child_table WHERE fk_column = value.","tags":["foreign key","foreign key constraint","fk","1451","parent","parent row","delete","reference"]},{"code":"1452","title":"Cannot add or update a child row: a foreign key constraint fails (ajout ou mise à jour de la ligne enfant impossible : échec de la contrainte de clé étrangère)","severity":"caution","meaning":"Vous insérez ou mettez à jour une ligne enfant, mais la valeur de clé parente vers laquelle elle pointe n'existe pas dans la table référencée ; la contrainte de clé étrangère la rejette.","cause":"INSERT/UPDATE référençant une clé parente inexistante, données chargées dans le mauvais ordre (enfant d'abord) ou ligne parente supprimée.","fix":"Insérez d'abord la ligne parente, puis la ligne enfant. Vérifiez que la valeur référencée existe : SELECT * FROM parent_table WHERE key_column = value. Respectez l'ordre parent-enfant lors des chargements en masse. N'utilisez SET FOREIGN_KEY_CHECKS=0 que lors d'opérations de maintenance maîtrisées et remettez-le à 1 une fois terminé.","tags":["foreign key","foreign key constraint","fk","1452","child","child row","reference","insert"]},{"code":"1366","title":"Incorrect string/integer value for column (valeur de chaîne/entière incorrecte pour la colonne)","severity":"caution","meaning":"Une valeur incompatible avec le type ou le jeu de caractères de la colonne a été insérée dans une colonne. Généralement causé par une incohérence d'encodage de caractères ou de type.","cause":"Écriture d'un emoji/caractère sur 4 octets (utf8mb4) dans une colonne latin1, saisie d'un texte non numérique dans une colonne numérique, mauvais charset de connexion, ou envoi d'une valeur d'un type différent de celui attendu.","fix":"Pour les problèmes de caractères, convertissez la colonne/table en utf8mb4 : ALTER TABLE my_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci et définissez le charset de connexion sur utf8mb4. Validez les types dans l'application pour les champs numériques. N'oubliez pas que le serveur fonctionne en mode SQL strict (STRICT) ; la cause racine est le type/l'encodage des données.","tags":["charset","character","incorrect value","1366","utf8mb4","encoding","type","collation"]},{"code":"1044","title":"Access denied for user to database (accès refusé à la base de données pour l'utilisateur)","severity":"caution","meaning":"Bien que l'utilisateur se soit authentifié correctement, il ne dispose pas des privilèges pour opérer sur la base de données indiquée.","cause":"Aucun GRANT sur la base de données pour l'utilisateur, nom de base de données erroné, privilège défini pour un autre hôte (user@host) ou base de données inexistante.","fix":"Affichez les privilèges de l'utilisateur : SHOW GRANTS FOR 'app_user'@'host'. Accordez le privilège requis : GRANT ALL PRIVILEGES ON mydb.* TO 'app_user'@'host'; (GRANT prend effet immédiatement, FLUSH PRIVILEGES n'est pas nécessaire). Vérifiez la bonne correspondance d'hôte et le nom de la base de données. Selon le principe du moindre privilège, n'accordez que les droits nécessaires.","tags":["access denied","access","database","database access","1044","grant","privilege","permission"]},{"code":"1264","title":"Out of range value for column (valeur hors limites pour la colonne)","severity":"caution","meaning":"Une valeur hors de la plage autorisée par le type a été insérée dans une colonne numérique ou de date. Cette erreur est levée en mode SQL strict.","cause":"Dépassement de la limite de types tels que TINYINT/INT (par ex. 200 dans un TINYINT, ou une valeur négative dans une colonne UNSIGNED), dépassement de date/nombre, ou envoi d'une valeur plus grande que prévu.","fix":"Élargissez le type de colonne selon le besoin (par ex. INT au lieu de TINYINT, BIGINT au lieu d'INT) ou corrigez le signe (SIGNED/UNSIGNED). Validez les plages de valeurs dans l'application. Même si vous souhaitez un écrêtage silencieux (non recommandé), rendez les données compatibles avec le type plutôt que de retirer STRICT de sql_mode.","tags":["out of range","value out of range","1264","value","int","tinyint","unsigned","overflow"]}]},"errors/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"1205","title":"Transaction (Process ID) was deadlocked on lock resources with another process and has been chosen as the deadlock victim (transaction choisie comme victime de l'interblocage)","severity":"danger","meaning":"Deux transactions ou plus attendent chacune des verrous détenus par l'autre (un interblocage). Pour rompre le cycle, SQL Server choisit une transaction comme victime et l'annule.","cause":"Les causes typiques sont des transactions verrouillant les tables/lignes dans des ordres différents, des transactions de longue durée, de larges parcours dus à des index manquants, l'escalade de verrous et une forte concurrence.","fix":"Interceptez l'erreur 1205 dans l'application et relancez automatiquement la transaction. Accédez aux objets dans le même ordre dans chaque transaction et gardez les transactions courtes. Examinez le graphe d'interblocage : utilisez les Extended Events (la session system_health) ou le trace flag 1222 pour les détails. Ajoutez des index appropriés pour réduire les parcours et les verrouillages. Si nécessaire, évaluez le niveau d'isolation READ COMMITTED SNAPSHOT (RCSI).","tags":["deadlock","lock","locking","victim","rollback","1205","retry"]},{"code":"2627","title":"Violation of PRIMARY KEY / UNIQUE KEY constraint. Cannot insert duplicate key in object (violation de contrainte PRIMARY KEY / UNIQUE KEY ; clé en double non insérable)","severity":"caution","meaning":"Vous avez tenté d'insérer ou de mettre à jour une valeur qui existe déjà dans une ou plusieurs colonnes protégées par une contrainte PRIMARY KEY ou UNIQUE. Les clés en double ne sont pas acceptées.","cause":"Insertion de la même valeur unique une seconde fois, UPDATE entrant en collision avec une valeur existante, absence de contrôle d'unicité, ou deux requêtes concurrentes produisant la même ligne.","fix":"Vérifiez si la ligne existe avant l'insertion, ou utilisez MERGE / IF NOT EXISTS. Pour des insertions idempotentes, appliquez le modèle \"INSERT ... WHERE NOT EXISTS\". Interceptez l'erreur et renvoyez un message explicite. Si vous avez réellement besoin de données en double, revoyez la contrainte. Utilisez IDENTITY ou SEQUENCE pour éviter les collisions lors de la génération des clés.","tags":["unique","primary key","uniqueness","duplicate","constraint","constraint violation","2627"]},{"code":"2601","title":"Cannot insert duplicate key row in object with unique index. The duplicate key value is ... (ligne à clé en double non insérable dans un objet avec index unique)","severity":"caution","meaning":"Un UNIQUE INDEX a rejeté une ligne en raison d'une valeur de clé en double. 2627 est levée pour les contraintes, 2601 pour les index uniques.","cause":"Réécriture d'une valeur existante dans une ou plusieurs colonnes protégées par un UNIQUE INDEX, insertions concurrentes, ou collision entre lignes couvertes par un index unique filtré.","fix":"Supprimez ou mettez à jour la valeur en double. Si les doublons doivent être tolérés, envisagez l'option IGNORE_DUP_KEY = ON (les doublons sont ignorés silencieusement). Ajoutez un contrôle NOT EXISTS avant l'insertion. S'il existe un index filtré, vérifiez sa clause WHERE et la collision.","tags":["unique index","duplicate key","unique idx","duplicate","2601","index"]},{"code":"547","title":"The INSERT/UPDATE/DELETE statement conflicted with the FOREIGN KEY / CHECK constraint (instruction en conflit avec une contrainte FOREIGN KEY / CHECK)","severity":"caution","meaning":"L'instruction a violé une contrainte FOREIGN KEY ou CHECK. Soit elle a référencé une ligne parente inexistante, soit elle a écrit une valeur qui enfreint une règle CHECK, soit elle a tenté de supprimer une ligne parente encore référencée.","cause":"FK : la ligne parente référencée par la ligne enfant insérée n'existe pas. DELETE/UPDATE : des lignes enfants dépendent de la ligne parente supprimée/modifiée. CHECK : la valeur de la colonne ne satisfait pas la condition définie.","fix":"Insérez d'abord la ligne parente, puis la ligne enfant. Pour les suppressions, supprimez d'abord les lignes dépendantes ou définissez ON DELETE CASCADE le cas échéant. Pour une violation de CHECK, rendez la valeur conforme à la règle. Identifiez la contrainte violée à partir de son nom dans le texte de l'erreur.","tags":["foreign key","fk","check","constraint","constraint violation","referential","547"]},{"code":"18456","title":"Login failed for user (échec de connexion pour l'utilisateur)","severity":"danger","meaning":"Une tentative de connexion a été rejetée lors de l'authentification. Le nom d'utilisateur/mot de passe est peut-être erroné, le compte peut être désactivé, ou l'utilisateur peut ne pas avoir accès à la base de données.","cause":"Mot de passe erroné, login inexistant, compte désactivé/verrouillé, utilisation d'un login SQL alors que seule l'authentification Windows est activée, ou absence d'accès à la base de données par défaut. Le code State dans le texte de l'erreur identifie la cause réelle (State 5 : le login n'existe pas, State 7 : login désactivé et mot de passe également erroné, State 8 : mot de passe erroné (le plus fréquent), State 18 : le mot de passe doit être changé, State 38 : la base de données indiquée ne peut pas être ouverte, State 58 : login SQL tenté alors que le serveur est en mode d'authentification Windows uniquement).","fix":"Déterminez la cause racine à partir de la valeur State dans le journal d'erreurs de SQL Server (les détails ne sont pas envoyés au client pour des raisons de sécurité). Vérifiez le nom d'utilisateur/mot de passe. Vérifiez que le serveur fonctionne en 'SQL Server and Windows Authentication Mode' (mode mixte) ; si vous l'avez modifié, redémarrez le service. Vérifiez le mappage utilisateur du login et l'autorisation d'accès dans la base de données cible.","tags":["login","session","authentication","authentication failed","password","18456","state"]},{"code":"8152","title":"String or binary data would be truncated (les données chaîne ou binaires seraient tronquées)","severity":"caution","meaning":"Vous avez tenté d'écrire des données plus longues que la longueur définie d'une colonne. SQL Server arrête l'instruction pour éviter une perte de données (2628 est la version plus récente de la même erreur, qui indique la colonne/la valeur).","cause":"Par exemple, écriture de 15 caractères dans une colonne VARCHAR(10), colonne cible plus étroite que la source, incohérence de type/longueur de données, ou taille de champ insuffisante lors d'ETL/de transferts.","fix":"Élargissez la colonne pour qu'elle contienne les données (ALTER TABLE ... ALTER COLUMN) ou raccourcissez les données. Sur SQL Server 2019+, utilisez l'erreur 2628, qui indique la table/la colonne/la valeur en dépassement ; sur les versions plus anciennes, vérifiez les colonnes une par une avec LEN() pour trouver la ligne fautive. Lors des transferts, alignez les longueurs des schémas source et cible.","tags":["truncation","truncate","overflow","varchar","length","column length","8152","2628"]},{"code":"2628","title":"String or binary data would be truncated in table, column ...; truncated value (données tronquées dans la table, la colonne ... ; valeur tronquée)","severity":"caution","meaning":"La version améliorée de 8152 (SQL Server 2019 et versions ultérieures). Elle indique explicitement quelle table, quelle colonne et quelle valeur seraient tronquées.","cause":"Écriture d'une valeur texte/binaire plus longue que la longueur définie de la colonne cible. Généralement une incohérence de schéma ou des données d'entrée de longueur inattendue.","fix":"Consultez le nom de la table et de la colonne dans le texte de l'erreur ; élargissez la colonne ou raccourcissez la valeur source. Dans SQL Server 2019, 2628 n'est levée par défaut qu'au niveau de compatibilité 150+ ; aux niveaux inférieurs, activez-la avec ALTER DATABASE SCOPED CONFIGURATION SET VERBOSE_TRUNCATION_WARNINGS = ON ou le trace flag 460. À partir de SQL Server 2016 SP2 CU6 et 2017 CU12, le trace flag 460 donne le même message détaillé. Alignez les longueurs des champs sur la cible dans la couche de transfert.","tags":["truncation","truncate","overflow","column","column name","length","2628","460"]},{"code":"1222","title":"Lock request time out period exceeded (délai de la demande de verrou dépassé)","severity":"caution","meaning":"Le délai d'une requête a expiré pendant qu'elle attendait d'acquérir un verrou. La demande a été annulée, car une autre transaction maintenait trop longtemps verrouillée la ressource requise.","cause":"Dépassement du temps d'attente défini avec SET LOCK_TIMEOUT, transactions bloquantes de longue durée, transactions ouvertes non validées, longs parcours et conflits de verrous.","fix":"Trouvez le bloqueur : examinez sys.dm_exec_requests, sys.dm_tran_locks et la colonne blocking_session_id, ou utilisez sp_who2 / sp_whoisactive. Raccourcissez les longues transactions et validez-les. Ajustez LOCK_TIMEOUT si nécessaire. Sur les systèmes à forte charge de lecture, réduisez les conflits lecteurs-écrivains avec RCSI/l'isolation snapshot.","tags":["lock timeout","lock wait timeout","blocking","blocked session","1222","lock_timeout"]},{"code":"9002","title":"The transaction log for database is full due to ... (le journal des transactions de la base est plein)","severity":"danger","meaning":"Le fichier journal des transactions de la base de données est plein et les nouvelles transactions ne peuvent pas être journalisées. La base de données rejette les écritures et peut se comporter comme si elle était en lecture seule.","cause":"Absence de sauvegardes du journal dans le mode de récupération FULL, transaction ouverte depuis longtemps, retard de réplication/AlwaysOn, ou journal incapable de croître (autogrow désactivé ou disque plein). log_reuse_wait_desc indique la raison.","fix":"Vérifiez log_reuse_wait_desc dans sys.databases. S'il vaut LOG_BACKUP en mode FULL, effectuez une sauvegarde du journal (BACKUP LOG). S'il vaut ACTIVE_TRANSACTION, trouvez la transaction ouverte et validez-la/annulez-la (DBCC OPENTRAN). Libérez de l'espace disque ou corrigez le paramètre autogrow du fichier journal. Si le journal a réellement grossi, réduisez-le avec DBCC SHRINKFILE après la sauvegarde. Si vous n'avez pas besoin de la chaîne de journaux, revoyez le mode de récupération.","tags":["transaction log","log is full","log full","9002","backup log","recovery model"]},{"code":"1105","title":"Could not allocate space for object in database because the filegroup is full (allocation d'espace impossible : groupe de fichiers plein)","severity":"danger","meaning":"Le groupe de fichiers qui contient le fichier de données est plein et aucun espace ne peut être alloué aux nouvelles données. Les insertions/mises à jour échouent.","cause":"Le fichier de données ne peut pas croître (autogrowth désactivé ou MAXSIZE atteint), aucun espace disque libre, groupe de fichiers PRIMARY épuisé, ou limite de taille de fichier.","fix":"Libérez de l'espace disque ou ajoutez un nouveau disque/fichier. Activez l'autogrowth sur le fichier de données ou agrandissez-le (ALTER DATABASE ... MODIFY FILE). Ajoutez un nouveau fichier au groupe de fichiers. Archivez et purgez les données inutiles/anciennes. Pour tempdb, vérifiez les fichiers tempdb et leur disque.","tags":["filegroup full","file full","allocate space","disk","autogrow","1105"]},{"code":"208","title":"Invalid object name (nom d'objet invalide)","severity":"safe","meaning":"La table, la vue ou l'objet référencé dans la requête est introuvable. SQL Server n'a pas pu résoudre le nom indiqué.","cause":"Nom d'objet mal orthographié, mauvais contexte de base de données/schéma (un schéma autre que dbo), objet réellement inexistant ou pas encore créé, ou nom en deux parties (schema.table) manquant.","fix":"Orthographiez correctement le nom de l'objet et le schéma ; utilisez le nom qualifié (par ex. dbo.Customers). Assurez-vous d'être connecté à la bonne base de données (USE). Vérifiez que l'objet existe : SELECT * FROM sys.objects WHERE name = 'YourObjectName'. Assurez-vous que les tables temporaires sont créées dans la même session.","tags":["invalid object","invalid object name","table not found","table does not exist","schema","app schema","208"]},{"code":"229","title":"The SELECT/EXECUTE permission was denied on the object (autorisation SELECT/EXECUTE refusée sur l'objet)","severity":"caution","meaning":"L'utilisateur n'a pas l'autorisation d'effectuer l'opération demandée (SELECT, INSERT, EXECUTE, etc.) sur l'objet. L'accès a été refusé pour des raisons de sécurité.","cause":"L'autorisation requise n'a pas été accordée à l'utilisateur/au rôle, propriété de schéma incorrecte (chaînage de propriété rompu), DENY explicite sur l'objet, ou utilisateur non membre d'un rôle suffisant.","fix":"Accordez l'autorisation requise : GRANT SELECT ON dbo.MyTable TO UserName (ou GRANT EXECUTE ON dbo.MyProcedure TO ...). Ajoutez l'utilisateur à un rôle approprié (par ex. db_datareader). Supprimez tout DENY explicite. Organisez les autorisations selon le principe du moindre privilège. Auditez les autorisations existantes avec sys.database_permissions.","tags":["permission denied","access denied","grant","privilege","security","security permissions","229"]},{"code":"4060","title":"Cannot open database requested by the login. The login failed (impossible d'ouvrir la base demandée par le login ; échec de connexion)","severity":"danger","meaning":"L'authentification peut réussir, mais la connexion est rejetée, car la base de données demandée ne peut pas être ouverte. S'observe généralement avec 18456.","cause":"La base de données n'existe pas, est OFFLINE/SINGLE_USER/RESTORING/SUSPECT, le login n'a pas de mappage utilisateur dans cette base, ou la base de données par défaut de la chaîne de connexion est inaccessible.","fix":"Vérifiez que la base de données existe et est ONLINE : SELECT name, state_desc FROM sys.databases. Créez un utilisateur pour le login dans la base de données et accordez-lui des autorisations. Vérifiez la valeur Initial Catalog/Database dans la chaîne de connexion. Si la base est RESTORING/SUSPECT, suivez les étapes de réparation/restauration.","tags":["cannot open database","database cannot be opened","4060","login","state","offline"]},{"code":"233","title":"A connection was successfully established with the server, but then an error occurred (no process is on the other end of the pipe) (connexion établie puis interrompue par une erreur)","severity":"danger","meaning":"Le client a atteint le serveur, mais la connexion a été coupée pendant la négociation (handshake). Généralement causé par la configuration Named Pipes/TCP, les paramètres de protocole ou un rejet côté serveur. Lié à 10054 (connexion fermée de force).","cause":"Protocoles Shared Memory/Named Pipes/TCP désactivés, connexions distantes non autorisées, compte incapable de se connecter (voir 18456), panne de pare-feu/réseau, ou fermeture forcée de la connexion par le serveur (10054).","fix":"Activez TCP/IP et, si nécessaire, Named Pipes dans SQL Server Configuration Manager, puis redémarrez le service. Vérifiez que les connexions distantes sont autorisées. Ouvrez le port SQL (1433 par défaut) dans le pare-feu. Pour les problèmes d'authentification, examinez le code State de 18456 dans le journal d'erreurs. Vérifiez la stabilité du réseau et les paramètres TLS/de chiffrement.","tags":["connection","connection error","pipe","10054","tcp","named pipes","233","network"]},{"code":"823","title":"The operating system returned error to SQL Server during a read/write at offset in file (I/O error) (erreur d'E/S du système d'exploitation)","severity":"danger","meaning":"SQL Server a reçu une erreur d'E/S au niveau du système d'exploitation lors de la lecture ou de l'écriture d'un fichier de données/journal. C'est un avertissement d'intégrité grave qui signale un problème de matériel disque ou du sous-système de stockage.","cause":"Secteurs disque défectueux, contrôleur/pilote défaillant, problèmes de stockage/SAN, erreurs du système de fichiers, ou page totalement illisible (erreur d'E/S matérielle). Généralement lié au matériel.","fix":"Effectuez immédiatement une sauvegarde saine (si possible). Examinez le journal d'événements Windows et les journaux du stockage, et testez l'état du matériel/des disques. Exécutez DBCC CHECKDB pour vérifier l'intégrité. Consultez la table suspect_pages (msdb.dbo.suspect_pages). En cas de corruption, restaurez à partir d'une sauvegarde saine ou effectuez une restauration au niveau de la page. Remplacez/réparez le matériel.","tags":["io error","i/o error","disk","corruption","data corruption","823","checkdb"]},{"code":"824","title":"SQL Server detected a logical consistency-based I/O error (page corruption) (erreur d'E/S de cohérence logique)","severity":"danger","meaning":"SQL Server a lu une page, mais son contenu est logiquement incohérent (checksum erroné, page déchirée ou mauvais ID de page). La page de données est corrompue.","cause":"Erreurs du sous-système de stockage, coupure de courant soudaine/pages déchirées, problèmes de contrôleur ou de pilote, corruption silencieuse détectée par PAGE_VERIFY CHECKSUM. Comme 823, principalement lié au matériel/stockage.","fix":"Exécutez DBCC CHECKDB pour déterminer l'étendue de la corruption. Restaurez à partir de la dernière sauvegarde saine ou réparez les pages concernées par une restauration au niveau de la page. Examinez msdb.dbo.suspect_pages. Assurez-vous que PAGE_VERIFY = CHECKSUM est activé. Inspectez le matériel/le stockage. REPAIR_ALLOW_DATA_LOSS doit être le dernier recours et entraîne une perte de données.","tags":["page corruption","corrupt page","consistency","824","checkdb","checksum","torn page"]},{"code":"701","title":"There is insufficient system memory in resource pool to run this query (mémoire système insuffisante dans le pool de ressources)","severity":"danger","meaning":"SQL Server n'a pas pu allouer, depuis le pool de ressources concerné, la mémoire nécessaire à l'exécution de la requête. L'opération a échoué en raison d'une pression mémoire.","cause":"Mémoire serveur insuffisante, paramètre max server memory bas, requêtes gourmandes en mémoire (gros tris/hachages), fuite mémoire, nombreuses requêtes simultanées, ou limites de pool de Resource Governor.","fix":"Revoyez le paramètre max server memory et prévoyez suffisamment de RAM physique. Optimisez les requêtes gourmandes en mémoire (index appropriés, moins de tris/hachages). Surveillez la mémoire : sys.dm_os_memory_clerks, DBCC MEMORYSTATUS, compteurs mémoire Perfmon. Ajustez les limites de pool de Resource Governor. Réduisez la concurrence si nécessaire.","tags":["memory","memory pressure","insufficient memory","resource pool","701","oom"]},{"code":"845","title":"Time-out occurred while waiting for buffer latch (délai expiré en attente d'un latch de buffer)","severity":"danger","meaning":"Un délai a expiré lors de l'attente d'un latch de buffer sur une page de données. SQL Server n'a pas pu acquérir dans un délai raisonnable le verrou interne de courte durée (latch) nécessaire pour accéder à une page ; généralement un signe de forte charge d'E/S ou de pression mémoire.","cause":"Sous-système disque lent/surchargé, pages lues constamment depuis le disque en raison d'une pression mémoire, problèmes matériels, pagination excessive, ou goulot d'étranglement du stockage.","fix":"Mesurez la latence d'E/S disque (sys.dm_io_virtual_file_stats, latence disque Perfmon). Vérifiez les performances du stockage et l'état du matériel. Revoyez la suffisance de la mémoire et le paramètre max server memory. Examinez les types d'attente (sys.dm_os_wait_stats, attentes PAGEIOLATCH). Éliminez le goulot d'étranglement du stockage ; prévoyez des disques plus rapides/plus de RAM si nécessaire.","tags":["buffer latch","latch timeout","845","io","pageiolatch","memory","memory pressure"]},{"code":"3201","title":"Cannot open backup device. Operating system error (backup device error) (impossible d'ouvrir l'unité de sauvegarde)","severity":"caution","meaning":"L'unité/le fichier de sauvegarde cible n'a pas pu être ouvert lors de la sauvegarde ou de la restauration. SQL Server n'a pas pu accéder au chemin indiqué.","cause":"Chemin de dossier erroné/inexistant, compte de service SQL Server sans autorisation d'écriture sur le dossier cible, absence d'accès à un partage réseau (UNC), disque plein, ou fichier verrouillé par un autre processus.","fix":"Vérifiez que le chemin de sauvegarde existe et est correctement orthographié. Accordez au compte de service SQL Server l'autorisation d'écriture sur le dossier cible. Pour les partages UNC, vérifiez l'accès du compte de service. Assurez-vous qu'il y a suffisamment d'espace disque libre. Pour un chemin local, vérifiez que la lettre de lecteur est visible depuis le serveur (et non un lecteur du client).","tags":["backup device","backup file","backup","restore","3201","permission denied","permission","os error"]}]},"errors/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"E11000","title":"E11000 duplicate key error collection (erreur de clé en double dans la collection)","severity":"caution","meaning":"Une insertion ou une mise à jour a tenté d'écrire, dans un ou plusieurs champs protégés par un index unique, une valeur qui existe déjà dans la collection. MongoDB rejette la clé en double.","cause":"Collision de _id, écriture de la même valeur une seconde fois dans un champ doté d'un index unique, condition de concurrence (race condition) lors d'un upsert, ou valeurs null en collision pour plusieurs documents dans un index unique (index non sparse).","fix":"Vérifiez si le document existe avant l'écriture, ou utilisez un upsert : db.collection.updateOne(filter, {$set:{...}}, {upsert:true}). Consultez le nom de l'index et la valeur dup key dans le message d'erreur. Si l'unicité n'est pas réellement requise, supprimez l'index ; si les null posent problème, utilisez un index partiel/sparse. Dans l'application, interceptez l'erreur et relancez l'opération ou signalez-la à l'utilisateur.","tags":["duplicate key","duplicate key","unique index","unique","e11000","upsert","_id"]},{"code":"11000","title":"DuplicateKey (code d'erreur 11000)","severity":"caution","meaning":"Le code numérique de l'erreur de clé en double (renvoyé avec le texte E11000). Une opération d'écriture a violé une contrainte d'index unique.","cause":"Réécriture du même champ unique, upserts concurrents générant la même clé, ou documents en double dans une écriture en masse (bulk write).","fix":"Interceptez le code d'erreur 11000 dans le code applicatif et implémentez un comportement idempotent (mise à jour s'il existe, insertion sinon). Dans les opérations en masse, utilisez ordered:false pour permettre l'écriture des documents autres que les doublons. Revoyez la règle d'unicité et la définition de l'index.","tags":["duplicatekey","11000","duplicate","unique","unique","bulk write","write error"]},{"code":"13","title":"Unauthorized (code d'erreur 13)","severity":"caution","meaning":"L'utilisateur authentifié ne dispose d'aucun privilège pour l'opération ou la ressource tentée. L'utilisateur est connecté, mais ses rôles ne couvrent pas cette action.","cause":"Le rôle requis (par ex. readWrite, dbAdmin) n'est pas accordé à l'utilisateur, l'opération s'exécute sur la mauvaise base de données, ou les privilèges sont insuffisants pour une commande d'administration. Configuration RBAC incomplète.","fix":"Vérifiez les rôles de l'utilisateur : db.getUser(\"app_user\"). Accordez le rôle requis : db.grantRolesToUser(\"app_user\", [{role:\"readWrite\", db:\"mydb\"}]). Assurez-vous que l'opération s'exécute dans la bonne base de données. Attribuez un rôle intégré ou personnalisé approprié selon le principe du moindre privilège. Vérifiez la valeur authSource dans la chaîne de connexion.","tags":["unauthorized","unauthorized","authorization","role","role","rbac","13","permission"]},{"code":"18","title":"AuthenticationFailed (code d'erreur 18)","severity":"caution","meaning":"L'authentification a échoué. Le nom d'utilisateur, le mot de passe ou les identifiants fournis sont invalides ; l'utilisateur n'a pas pu se connecter.","cause":"Nom d'utilisateur/mot de passe erroné, mauvaise base authSource, mécanisme d'authentification incompatible (SCRAM-SHA-1/SCRAM-SHA-256), ou utilisateur non défini dans la base de données indiquée.","fix":"Vérifiez le nom d'utilisateur et le mot de passe. Indiquez le bon authSource dans la chaîne de connexion (la base de données où l'utilisateur a été créé, généralement admin) : ...?authSource=admin. Vérifiez que le mécanisme d'authentification est compatible avec le serveur. Assurez-vous que l'utilisateur a été créé dans la bonne base de données. Encodez le mot de passe en URL s'il contient des caractères spéciaux.","tags":["authentication failed","authentication","password","password","authsource","18","scram"]},{"code":"11600","title":"InterruptedAtShutdown (code d'erreur 11600)","severity":"danger","meaning":"L'opération a été interrompue, car le serveur a commencé à s'arrêter. L'opération en cours n'a pas pu se terminer, car le processus mongod était en cours d'arrêt.","cause":"Redémarrage/arrêt de mongod, arrêt lors d'un stepdown du primaire d'un replica set, maintenance planifiée, crash, ou processus tué par l'OOM killer.","fix":"Attendez que le serveur redémarre et relancez l'opération. Dans l'application, interceptez les erreurs transitoires et relancez automatiquement ; activez les retryable writes. En cas d'arrêts inattendus, examinez les journaux mongod et les journaux système (OOM, crash). Utilisez un replica set pour la haute disponibilité.","tags":["interrupted","shutdown","shutdown","downtime","11600","retry","replica set"]},{"code":"50","title":"MaxTimeMSExpired (code d'erreur 50)","severity":"caution","meaning":"Le serveur a annulé l'opération, car elle a dépassé la limite de temps définie avec maxTimeMS. La requête ne s'est pas terminée dans le temps imparti.","cause":"Requêtes de longue durée, parcours de collection (COLLSCAN) dus à des index manquants, gros volumes de données, opérations d'agrégation lourdes ou charge serveur élevée.","fix":"Optimisez la requête : inspectez le plan avec explain() et ajoutez un index approprié. Si davantage de temps est réellement nécessaire, augmentez maxTimeMS. Simplifiez les agrégations lourdes ou filtrez tôt avec $match. Découpez les résultats avec une pagination. Vérifiez la charge du serveur et la capacité des ressources.","tags":["maxtimems","timeout","timeout","50","slow query","slow query","index"]},{"code":"251","title":"NoSuchTransaction (code d'erreur 251)","severity":"caution","meaning":"La transaction multi-documents référencée est introuvable. La transaction est déjà terminée, a expiré, ou un numéro de transaction invalide a été utilisé.","cause":"La transaction a dépassé transactionLifetimeLimitSeconds et a été annulée automatiquement, la session s'est terminée, txnNumber erroné/obsolète, ou poursuite d'une transaction déjà annulée après une erreur.","fix":"Relancez la transaction depuis le début (avec une nouvelle session). Gardez les transactions courtes ; concevez-les pour se terminer dans la durée de vie par défaut (60 s). Pour les erreurs étiquetées TransientTransactionError, relancez l'ensemble de la transaction en toute sécurité. Si de longues transactions sont nécessaires, revoyez le paramètre transactionLifetimeLimitSeconds.","tags":["nosuchtransaction","transaction","transaction","251","session","abort","retry"]},{"code":"112","title":"WriteConflict (code d'erreur 112)","severity":"caution","meaning":"Conflit d'écriture qui survient lorsque deux opérations concurrentes tentent de modifier le ou les mêmes documents. En raison du contrôle de concurrence optimiste, WiredTiger annule l'une des opérations.","cause":"Écritures très concurrentes sur les mêmes documents, mises à jour en conflit lors de transactions de longue durée, forte contention sur un document chaud.","fix":"Relancez l'opération ; WriteConflict est généralement transitoire et résolu par une relance (il est étiqueté TransientTransactionError). Gardez les transactions courtes et réduisez le nombre de documents touchés. Revoyez le modèle de données pour réduire la contention sur un même document. Implémentez des relances avec backoff exponentiel dans l'application.","tags":["writeconflict","write conflict","112","concurrency","concurrency","retry","wiredtiger"]},{"code":"133","title":"FailedToSatisfyReadPreference (code d'erreur 133)","severity":"danger","meaning":"Aucun membre de replica set approprié n'a été trouvé pour satisfaire la read preference indiquée (par ex. primary, secondary). Le pilote n'a pas pu sélectionner de nœud vers lequel router la requête.","cause":"Membres du replica set injoignables/arrêtés, absence de primary pendant une élection, partition réseau, aucun membre ne correspond aux critères de tag set, ou majorité (quorum) impossible à atteindre.","fix":"Vérifiez l'état du replica set : rs.status(). Vérifiez l'état des nœuds et la connectivité réseau. Attendez la fin de l'élection du primaire et relancez. Revoyez votre paramètre de read preference ; si nécessaire, utilisez une préférence plus souple (par ex. primaryPreferred). Si vous utilisez des tag sets, assurez-vous qu'un membre correspondant existe.","tags":["read preference","readpreference","133","replica set","election","secondary","primary"]},{"code":"6","title":"HostUnreachable (code d'erreur 6)","severity":"danger","meaning":"Le pilote/client n'a pas pu joindre le serveur MongoDB cible (hôte). Aucune connexion n'a pu être établie au niveau réseau.","cause":"Serveur arrêté, hôte/port erroné, blocage par un pare-feu, problème de résolution DNS, panne réseau, ou configuration bindIp qui exclut le client.","fix":"Vérifiez que le nom d'hôte et le port sont corrects (27017 par défaut). Vérifiez que le service mongod est en cours d'exécution. Testez le pare-feu et l'accès réseau (ping/telnet). Assurez-vous que le paramètre bindIp de mongod.conf autorise l'accès du client. Vérifiez les enregistrements DNS/SRV (pour les connexions mongodb+srv).","tags":["hostunreachable","host","connection","network","network","6","firewall","bindip"]},{"code":"89","title":"NetworkTimeout (code d'erreur 89)","severity":"danger","meaning":"Une opération réseau lors de la communication avec le serveur ne s'est pas terminée dans le temps imparti et a expiré. La connexion était établie, mais la réponse n'est pas arrivée à temps.","cause":"Réseau lent ou instable, serveur surchargé, opération de longue durée retardant la réponse, valeurs de timeout socket/connect trop basses, ou congestion réseau.","fix":"Mesurez la latence et la stabilité du réseau. Vérifiez la charge et les ressources du serveur. Définissez connectTimeoutMS et socketTimeoutMS dans le pilote à des valeurs réalistes. Interceptez les erreurs transitoires et relancez. Optimisez les requêtes lentes pour réduire les temps de réponse.","tags":["networktimeout","network timeout","89","timeout","socket","network","retry"]},{"code":"262","title":"ExceededTimeLimit (code d'erreur 262)","severity":"caution","meaning":"L'opération a dépassé la limite de temps autorisée côté serveur et a été terminée. Le dépassement de maxTimeMS ou de limites de temps internes peut provoquer cette erreur.","cause":"Requête/commande de longue durée, exécution lente due à un index manquant, agrégation lourde, parcours d'un grand jeu de données, ou forte charge serveur.","fix":"Inspectez la requête avec explain() et ajoutez un index approprié. Découpez l'opération en plus petits morceaux. Définissez maxTimeMS selon le besoin réel. Réduisez les données tôt dans le pipeline d'agrégation avec $match/$project. Vérifiez que les ressources du serveur sont suffisantes.","tags":["exceededtimelimit","time limit","262","timeout","maxtimems","slow","index"]},{"code":"292","title":"QueryExceededMemoryLimitNoDiskUseAllowed (code d'erreur 292)","severity":"caution","meaning":"Une requête ou une étape d'agrégation a dépassé la limite de mémoire autorisée (100 MB par défaut) et a échoué, car l'écriture sur disque n'était pas autorisée. S'observe généralement dans les étapes de tri ou de regroupement.","cause":"Tri en mémoire sur de grandes données non indexées, grandes étapes d'agrégation $group/$sort, très grands jeux de résultats intermédiaires, paramètre allowDiskUse:false.","fix":"Autorisez le débordement sur disque dans l'agrégation avec l'option allowDiskUse:true : db.collection.aggregate(pipeline, {allowDiskUse:true}). Réduisez l'utilisation de la mémoire en indexant les champs triés. Filtrez tôt le pipeline avec $match pour réduire les données traitées. Paginez les résultats si nécessaire. Dans MongoDB 6.0+, l'utilisation du disque est activée par défaut dans la plupart des cas.","tags":["memory limit","memory limit","292","allowdiskuse","sort","aggregation","group"]},{"code":"26","title":"NamespaceNotFound (code d'erreur 26)","severity":"safe","meaning":"L'espace de noms référencé (database.collection) est introuvable. La collection ou l'index indiqué n'existe pas.","cause":"Nom de collection mal orthographié, collection pas encore créée, mauvais contexte de base de données, ou tentative d'opération drop/index sur une collection inexistante.","fix":"Vérifiez le nom de la collection et la base de données : show collections. N'oubliez pas que les noms sont sensibles à la casse. MongoDB crée automatiquement une collection lors de la première écriture ; si nécessaire, créez-la explicitement avec db.createCollection(). Pour des opérations comme drop, vérifiez au préalable que la collection existe.","tags":["namespacenotfound","namespace","collection missing","26","collection","not found","not found"]},{"code":"43","title":"CursorNotFound (code d'erreur 43)","severity":"caution","meaning":"Le curseur côté serveur n'existe plus. Lorsque le client demande le lot suivant (getMore), l'erreur est renvoyée, car le curseur est introuvable.","cause":"Le curseur a expiré (10 minutes d'inactivité par défaut), attente trop longue entre les lots, redémarrage du serveur, ou curseur fermé explicitement.","fix":"Traitez les résultats du curseur plus rapidement ; n'attendez pas longtemps entre les lots. Pour les opérations de longue durée, utilisez l'option noCursorTimeout avec prudence (mongosh : cursor.noCursorTimeout() ; PyMongo : no_cursor_timeout=True) et fermez toujours le curseur ; un curseur à l'intérieur d'une session est fermé à l'expiration de la session (localLogicalSessionTimeoutMinutes, 30 min par défaut) même avec cette option. Pour les grands jeux de résultats, itérez avec un batchSize plus petit ou utilisez une pagination par plage. Relancez l'opération et interrogez à nouveau.","tags":["cursornotfound","cursor","cursor","43","getmore","timeout","batch","pagination"]},{"code":"211","title":"KeyNotFound (code d'erreur 211)","severity":"caution","meaning":"Une clé/valeur attendue est introuvable. Indique généralement une incohérence liée à la gestion interne des clés, aux clés de session ou à la collection oplog/keys.","cause":"Clés de signature manquantes/supprimées dans la collection admin.system.keys, problèmes de synchronisation d'horloge dans le replica set/le cluster shardé, problèmes de keyfile/de rotation des clés, ou état interne corrompu.","fix":"Vérifiez la synchronisation d'horloge (NTP) entre les membres du replica set. Vérifiez le contenu de admin.system.keys et la génération des clés. Revoyez l'état du cluster (rs.status(), sh.status()). Si le problème persiste, analysez les journaux mongod et revoyez la configuration keyfile/d'identité. Assurez-vous que le primaire fonctionne correctement afin que les clés puissent être régénérées.","tags":["keynotfound","key","211","keys","session","ntp","cluster","cluster"]}]},"errors/redis.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"WRONGTYPE","title":"WRONGTYPE Operation against a key holding the wrong kind of value (opération sur une clé contenant un type de valeur incorrect)","severity":"caution","meaning":"Une commande a été exécutée sur une clé dont le type de données ne lui convient pas. Par exemple, application d'une commande de liste (LPUSH) ou de hash (HSET) à une clé contenant une chaîne.","cause":"La clé est d'un type différent de celui attendu, le même nom de clé est utilisé avec différents types de données, confusion de types dans la logique applicative, ou clé ancienne/résiduelle écrasée.","fix":"Vérifiez le type de la clé : TYPE mykey. Choisissez la commande selon le type réel de la clé. Distinguez les types dans le nommage des clés (par ex. un set pour user:1:tags, une chaîne pour user:1:name). Si nécessaire, supprimez l'ancienne clé (DEL) et recréez-la avec le bon type. Imposez la cohérence des types dans la couche applicative.","tags":["wrongtype","wrong type","type","data type","key","key"]},{"code":"OOM","title":"OOM command not allowed when used memory > 'maxmemory' (commande refusée : mémoire utilisée supérieure à maxmemory)","severity":"danger","meaning":"La mémoire utilisée par Redis a atteint la limite maxmemory et les commandes d'écriture sont rejetées, car la politique d'éviction ne peut pas libérer d'espace pour de nouvelles données. La mémoire est pleine ; les écritures sont impossibles.","cause":"Limite maxmemory dépassée, politique noeviction sélectionnée (aucune clé n'est jamais supprimée), données sans TTL en croissance continue, fuite mémoire, ou RAM insuffisante pour la limite.","fix":"Inspectez l'utilisation de la mémoire : INFO memory et MEMORY USAGE. Choisissez une politique d'éviction appropriée (par ex. allkeys-lru ou volatile-lru) : CONFIG SET maxmemory-policy allkeys-lru. Définissez des TTL sur les clés pour qu'elles soient nettoyées automatiquement. Augmentez maxmemory/la RAM. Trouvez et réduisez les grosses clés (redis-cli --bigkeys). Purgez les données inutiles.","tags":["oom","out of memory","memory","maxmemory","eviction","memory","danger"]},{"code":"NOAUTH","title":"NOAUTH Authentication required (authentification requise)","severity":"caution","meaning":"Redis exige une authentification, mais le client a tenté d'exécuter une commande avant de s'authentifier. Il doit d'abord se connecter avec AUTH.","cause":"Un mot de passe requirepass/ACL est défini sur le serveur, le client n'envoie pas la commande AUTH, le mot de passe est absent de la chaîne de connexion, ou AUTH n'est pas répété après le rétablissement de la connexion.","fix":"Authentifiez-vous avant d'exécuter des commandes : AUTH password (avec les ACL, AUTH app_user password). Ajoutez le mot de passe au client/à la chaîne de connexion. Si vous utilisez un pool de connexions, assurez-vous qu'AUTH est effectué sur chaque nouvelle connexion. Stockez le mot de passe de façon sécurisée (variable d'environnement/gestionnaire de secrets).","tags":["noauth","authentication","authentication","auth","password","requirepass","acl"]},{"code":"WRONGPASS","title":"WRONGPASS invalid username-password pair or user is disabled (paire utilisateur/mot de passe invalide ou utilisateur désactivé)","severity":"caution","meaning":"La paire nom d'utilisateur/mot de passe fournie dans la commande AUTH est invalide, ou l'utilisateur ACL est désactivé. L'authentification a été rejetée.","cause":"Mot de passe erroné, nom d'utilisateur ACL erroné, utilisateur désactivé dans l'ACL, valeur requirepass modifiée, ou problème d'échappement/d'encodage dans le mot de passe.","fix":"Saisissez le bon nom d'utilisateur et le bon mot de passe. Inspectez l'utilisateur ACL : ACL LIST et ACL GETUSER app_user. Si l'utilisateur est désactivé, activez-le (ACL SETUSER app_user on ...). Vérifiez le mot de passe défini avec requirepass (CONFIG GET requirepass). Lors de la réinitialisation du mot de passe, assurez-vous que tous les clients sont mis à jour.","tags":["wrongpass","password","invalid password","auth","acl","user","invalid"]},{"code":"READONLY","title":"READONLY You can't write against a read only replica (écriture impossible sur un réplica en lecture seule)","severity":"caution","meaning":"Une commande d'écriture a été envoyée à un nœud réplica (slave). Les réplicas sont en lecture seule par défaut ; les écritures ne peuvent être effectuées que sur le master.","cause":"Le client se connecte à un réplica au lieu du master, l'ancien master est devenu réplica après un failover, mauvais routage vers le mauvais nœud dans Sentinel/Cluster, ou load balancer transmettant les écritures à un réplica.","fix":"Routez les requêtes d'écriture vers le nœud master. Si vous utilisez Sentinel, interrogez Sentinel pour obtenir le master actuel (SENTINEL get-master-addr-by-name). Dans Cluster, écrivez sur le bon master de shard. Configurez la séparation lecture/écriture dans la bibliothèque cliente. Après un failover, rafraîchissez les connexions pour qu'elles pointent vers le nouveau master.","tags":["readonly","read only","replica","slave","write","write","master","failover"]},{"code":"MASTERDOWN","title":"MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no' (lien avec le master coupé)","severity":"danger","meaning":"Le lien entre le réplica et le master est coupé et, comme replica-serve-stale-data est défini sur 'no', le réplica refuse de répondre aux requêtes avec des données obsolètes.","cause":"Nœud master planté ou injoignable, panne/partition réseau, lien de réplication master-réplica coupé, ou master incapable de répondre sous forte charge.","fix":"Vérifiez l'état du nœud master et la connectivité réseau. Inspectez l'état de la réplication : INFO replication. Remettez le master en service, ou laissez un failover se produire via Sentinel/Cluster. Si servir des données obsolètes est acceptable, envisagez replica-serve-stale-data yes (attention au risque de cohérence). Utilisez Sentinel/Cluster pour la haute disponibilité.","tags":["masterdown","master down","replication","replication","stale","failover","sentinel","danger"]},{"code":"CLUSTERDOWN","title":"CLUSTERDOWN The cluster is down / Hash slot not served (cluster indisponible / hash slot non servi)","severity":"danger","meaning":"Redis Cluster n'est pas pleinement opérationnel. Certains hash slots ne sont servis par aucun nœud ; le cluster rejette donc les requêtes.","cause":"Pas assez de nœuds master joignables, un ou plusieurs slots non attribués/non couverts, perte de la majorité (quorum), crashs de nœuds, ou certains slots injoignables alors que cluster-require-full-coverage vaut yes.","fix":"Vérifiez l'état du cluster : CLUSTER INFO et CLUSTER NODES. Remettez en service les masters arrêtés, ou laissez les réplicas être promus. Réattribuez les slots non attribués (redis-cli --cluster fix ou CLUSTER ADDSLOTS). Vérifiez que tous les slots sont couverts. Si un fonctionnement partiel est acceptable, envisagez cluster-require-full-coverage no.","tags":["clusterdown","cluster","cluster","hash slot","slot","quorum","coverage","danger"]},{"code":"LOADING","title":"LOADING Redis is loading the dataset in memory (Redis charge le jeu de données en mémoire)","severity":"caution","meaning":"Redis démarre et charge le jeu de données (RDB/AOF) depuis le disque en mémoire. La plupart des commandes sont temporairement rejetées jusqu'à la fin du chargement.","cause":"Le serveur vient de démarrer, un gros fichier RDB/AOF est en cours de chargement, un réplica effectue une synchronisation complète depuis le master, ou processus de récupération après un redémarrage.","fix":"Attendez la fin du chargement ; la durée dépend de la taille des données. Surveillez l'état : INFO persistence (attendez tant que loading:1 ; prêt lorsque 0). Dans l'application, interceptez cette erreur transitoire et relancez à intervalles courts. Pour réduire le temps de chargement, revoyez le jeu de données/la stratégie de persistance (RDB ou AOF). Prévoyez suffisamment de RAM et des disques rapides.","tags":["loading","loading","startup","startup","rdb","aof","sync","persistence"]},{"code":"BUSY","title":"BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE (Redis occupé par l'exécution d'un script)","severity":"caution","meaning":"Un script Lua ou une fonction de longue durée est en cours d'exécution et a dépassé busy-reply-threshold (nommé lua-time-limit avant Redis 7 ; 5000 ms par défaut). Redis étant mono-thread, il ne peut pas traiter d'autres commandes pendant ce temps ; seules SCRIPT KILL (FUNCTION KILL pour les fonctions) ou SHUTDOWN NOSAVE sont acceptées.","cause":"Boucle infinie ou script/fonction Lua de très longue durée, calcul lourd, script bouclant sur de grandes données, ou appel EVAL mal écrit.","fix":"Si le script n'a pas modifié de données, arrêtez-le : SCRIPT KILL. Si le script a déjà effectué des écritures, SCRIPT KILL ne fonctionne pas ; en dernier recours, pour préserver la cohérence des données, SHUTDOWN NOSAVE peut être nécessaire (les données non sauvegardées sont perdues ; soyez prudent). Écrivez des scripts courts et déterministes. Découpez les opérations longues en morceaux. Revoyez le paramètre busy-reply-threshold (anciennement lua-time-limit).","tags":["busy","busy server","lua","script","script kill","eval","time limit"]},{"code":"MISCONF","title":"MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk (persistance sur disque impossible)","severity":"danger","meaning":"Redis est configuré pour prendre des instantanés RDB, mais ne peut pas écrire sur le disque. Dans cet état, si stop-writes-on-bgsave-error est activé, Redis rejette toutes les commandes d'écriture.","cause":"Disque plein, problèmes de permissions du système de fichiers, répertoire non accessible en écriture, erreurs de sauvegarde en arrière-plan (BGSAVE), échec de fork dû à une mémoire insuffisante, ou problème matériel disque.","fix":"Libérez de l'espace disque et vérifiez l'autorisation d'écriture sur le répertoire RDB (dir). Inspectez la dernière erreur BGSAVE : INFO persistence et les journaux Redis. Pour autoriser temporairement les écritures jusqu'à la correction du problème, stop-writes-on-bgsave-error no peut être défini (attention au risque pour la durabilité). Assurez une mémoire suffisante pour le fork et le paramètre vm.overcommit_memory=1. Vérifiez la configuration de persistance (save/appendonly).","tags":["misconf","persist","durability","rdb","bgsave","disk","snapshot","danger"]},{"code":"CROSSSLOT","title":"CROSSSLOT Keys in request don't hash to the same slot (les clés de la requête ne sont pas dans le même slot)","severity":"caution","meaning":"Dans Redis Cluster, une commande multi-clés (par ex. MGET, MSET, SUNION) a été utilisée, mais les clés tombent dans des hash slots différents. Les opérations multi-clés exigent que toutes les clés soient dans le même slot.","cause":"En mode cluster, des commandes accédant à plusieurs clés à la fois sont appelées avec des clés réparties sur différents slots. Les clés sont distribuées sur différents nœuds, car elles n'utilisent pas de hash tag.","fix":"Utilisez un hash tag pour placer les clés liées dans le même slot : fixez une partie du nom de la clé avec des accolades (par ex. {user1}:profile et {user1}:settings vont dans le même slot). N'utilisez les commandes multi-clés qu'avec des clés partageant le même hash tag. Vous pouvez aussi découper les opérations en commandes mono-clé.","tags":["crossslot","cross slot","cluster","cluster","hash tag","slot","mget","mset"]},{"code":"MOVED","title":"MOVED slot ip:port (redirection du cluster)","severity":"safe","meaning":"Dans Redis Cluster, le hash slot contenant la clé demandée appartient à un autre nœud. Le serveur redirige définitivement le client vers le bon nœud. Il s'agit d'une redirection plutôt que d'une erreur.","cause":"Un client non compatible cluster envoie une requête au mauvais nœud, les slots ont été redistribués entre les nœuds (resharding), ou la carte des slots mise en cache par le client est obsolète.","fix":"Utilisez une bibliothèque cliente compatible cluster ; celles-ci suivent automatiquement la réponse MOVED, redirigent vers le bon nœud et mettent à jour la carte des slots. Utilisez l'option -c lors de la connexion avec redis-cli. Assurez-vous que votre client rafraîchit périodiquement la topologie des slots (CLUSTER SLOTS/SHARDS).","tags":["moved","cluster","cluster","redirect","redirect","slot","resharding"]},{"code":"ASK","title":"ASK slot ip:port (redirection du cluster)","severity":"safe","meaning":"Dans Redis Cluster, un slot est en cours de migration vers un autre nœud, et la clé demandée peut se trouver temporairement sur le nœud cible. Le serveur redirige le client vers le nœud cible pour cette requête uniquement (redirection temporaire).","cause":"Accès à une clé déjà transférée au sein d'un slot en cours de migration lors d'un resharding. Contrairement à MOVED, ce n'est pas permanent ; cela ne s'applique qu'à ce moment-là.","fix":"Les clients compatibles cluster gèrent automatiquement les redirections ASK : ils envoient la commande ASKING au nœud cible et relancent la requête, mais ne mettent pas à jour la carte des slots de façon permanente. Utilisez une bibliothèque cliente adaptée, et l'option -c pour redis-cli. Lorsque le resharding est terminé, les redirections deviennent MOVED et permanentes.","tags":["ask","asking","cluster","cluster","migration","migration","redirect","redirect"]},{"code":"NOSCRIPT","title":"NOSCRIPT No matching script. Please use EVAL (aucun script correspondant)","severity":"caution","meaning":"Le condensé SHA1 du script Lua appelé avec EVALSHA est introuvable dans le cache de scripts du serveur. Redis ne connaît aucun script correspondant à ce hash.","cause":"Le serveur a été redémarré ou le cache de scripts a été vidé avec SCRIPT FLUSH, le script n'a jamais été chargé, ou le cache est vide sur le nouveau nœud après un failover.","fix":"Exécutez le script avec son corps complet via EVAL (Redis le met automatiquement en cache), ou chargez-le d'abord avec SCRIPT LOAD et utilisez le SHA renvoyé dans EVALSHA. Dans l'application, interceptez l'erreur NOSCRIPT et implémentez une logique de repli d'EVALSHA vers EVAL. De nombreuses bibliothèques clientes Redis le font automatiquement.","tags":["noscript","script","evalsha","eval","lua","sha","script load","cache"]},{"code":"EXECABORT","title":"EXECABORT Transaction discarded because of previous errors (transaction abandonnée en raison d'erreurs précédentes)","severity":"caution","meaning":"Une transaction dans un bloc MULTI/EXEC a été abandonnée en raison d'erreurs survenues lors de la mise en file d'attente. EXEC rejette la transaction sans exécuter aucune commande.","cause":"Une commande entre MULTI et EXEC contient une erreur de syntaxe, ou erreurs détectées lors de la mise en file d'attente, comme un nom de commande inexistant ou un nombre d'arguments erroné. De telles erreurs invalident toute la transaction.","fix":"Corrigez la syntaxe et les arguments des commandes du bloc MULTI. Vérifiez les réponses lors de la mise en file d'attente des commandes ; si une commande renvoie une erreur au lieu de QUEUED, la transaction est abandonnée à l'EXEC. Reconstruisez la transaction avec des commandes valides. Remarque : les transactions Redis ne sont pas annulées (pas de rollback) ; seules les erreurs de mise en file d'attente entraînent EXECABORT, et les erreurs d'exécution n'arrêtent pas les autres commandes.","tags":["execabort","transaction","transaction","multi","exec","discard","queue","queue"]}]},"errors/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"cluster_block_exception","title":"ClusterBlockException[blocked by: [TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block]] (before 7.x: FORBIDDEN/12/index read-only / allow delete (api)) (index bloqué par le flood stage watermark)","severity":"danger","meaning":"Comme l'utilisation du disque a dépassé le flood stage watermark (95 % par défaut ; sur les grands disques, déterminé par max_headroom), Elasticsearch a placé un bloc read_only_allow_delete sur les index ayant un shard sur le nœud concerné, afin d'éviter que le disque ne se remplisse complètement. En raison de ce bloc, les écritures dans l'index (indexation/mise à jour/suppression de documents) sont impossibles ; seule la suppression de l'index lui-même est autorisée. ES 8.x renvoie cette erreur avec HTTP 429.","cause":"L'utilisation du disque sur au moins un nœud a dépassé le seuil cluster.routing.allocation.disk.watermark.flood_stage. Même si le blocage est levé manuellement, Elasticsearch le réapplique tant que le disque est encore plein. Les causes typiques sont de gros index sans rotation des journaux, des snapshots non nettoyés ou une capacité disque insuffisante.","fix":"Libérez d'abord de l'espace disque : supprimez les index anciens/inutiles (DELETE /old-index), prenez un snapshot et retirez les index, ou agrandissez le disque. En 7.4+, le blocage est levé automatiquement lorsque l'utilisation du disque repasse sous le high watermark ; pour le lever immédiatement sans attendre, retirez-le manuellement : PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}. En correction permanente, supprimez/archivez automatiquement les anciennes données avec ILM (Index Lifecycle Management) et revoyez les paramètres de watermark (disk.watermark.low/high/flood_stage).","tags":["disk","read-only","watermark","flood-stage","cluster-block","danger"]},{"code":"index_read_only_allow_delete","title":"TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block (legacy: FORBIDDEN/12/index read-only / allow delete (api)) (index en lecture seule après dépassement du flood stage)","severity":"danger","meaning":"L'index est en lecture seule, car index.blocks.read_only_allow_delete vaut true ; de nouveaux documents ne peuvent pas être écrits ni mis à jour, seul l'index peut être supprimé. Elasticsearch le définit généralement automatiquement lorsque le flood stage watermark du disque est dépassé ; ES 8.x renvoie HTTP 429 aux requêtes d'écriture.","cause":"Le blocage a été appliqué automatiquement, car l'utilisation du disque a dépassé le seuil flood stage, ou le paramètre a été défini manuellement sur true. Si le blocage est levé sans libérer d'espace disque, Elasticsearch le réapplique.","fix":"Libérez suffisamment d'espace disque. En 7.4+, un bloc défini automatiquement se lève de lui-même lorsque l'utilisation du disque repasse sous le high watermark ; pour un bloc défini manuellement, ou pour le lever sans attendre, retirez le bloc : PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}. Pour un seul index : PUT /index-name/_settings {\"index.blocks.read_only_allow_delete\": null}. Si le problème se reproduit, augmentez la capacité disque, mettez en place une politique ILM et surveillez avec _cat/allocation quel nœud se remplit.","tags":["read-only","disk","forbidden","watermark","block","danger"]},{"code":"high_disk_watermark_exceeded","title":"high disk watermark [90%] exceeded / flood stage disk watermark [95%] exceeded (seuil disque high / flood stage dépassé)","severity":"danger","meaning":"L'utilisation du disque d'un nœud a dépassé le seuil high watermark (90 % par défaut) ou flood stage (95 % par défaut). Lorsque le high watermark est dépassé, Elasticsearch tente de déplacer des shards de ce nœud vers d'autres nœuds ; lorsque le flood stage est dépassé, il passe les index en lecture seule.","cause":"Le disque se remplit : index de journaux/métriques en croissance continue, anciens index jamais supprimés, dépôt de snapshots non nettoyé, disque sous-dimensionné, ou un seul nœud surchargé en raison d'une répartition déséquilibrée des shards.","fix":"Inspectez l'utilisation du disque : GET _cat/allocation?v. Supprimez les anciens index ou prenez-en un snapshot puis retirez-les, et agrandissez le disque. Vous pouvez relever temporairement les seuils (avec prudence ; en 8.x, définissez les trois ensemble en raison de la validation max_headroom) : PUT _cluster/settings {\"persistent\":{\"cluster.routing.allocation.disk.watermark.low\":\"88%\",\"cluster.routing.allocation.disk.watermark.high\":\"93%\",\"cluster.routing.allocation.disk.watermark.flood_stage\":\"96%\"}}. Une fois le problème résolu, revenez aux valeurs par défaut en définissant ces mêmes paramètres à null (les paramètres transient sont dépréciés ; ne les utilisez pas). Correction permanente : gestion du cycle de vie avec ILM, nettoyage régulier des index et planification de la capacité. Si des index sont passés en lecture seule à cause du flood stage, retirez le bloc séparément.","tags":["disk","watermark","flood-stage","allocation","capacity","danger"]},{"code":"circuit_breaking_exception","title":"CircuitBreakingException[[parent] Data too large, data for [<...>] would be [...], which is larger than the limit of [...]] (données trop volumineuses : limite du circuit breaker dépassée)","severity":"danger","meaning":"Une requête a été rejetée, car elle dépasserait la limite mémoire (heap) du circuit breaker. Elasticsearch arrête la requête en amont pour éviter que le nœud ne plante avec une OutOfMemoryError. Le breaker [parent] protège l'utilisation totale de la mémoire ; le breaker [fielddata] protège la mémoire fielddata des champs text.","cause":"Heap JVM insuffisant, agrégations très grandes/complexes, grands jeux de résultats, grosses requêtes bulk, fielddata chargé sur des champs text, ou nombreuses requêtes lourdes simultanées remplissant le heap. C'est généralement un symptôme de pression sur le heap.","fix":"Allégez les requêtes : réduisez la taille des agrégations, la taille des lots bulk et les champs renvoyés. Agrégez sur le sous-champ keyword (.keyword) plutôt que sur le fielddata des champs text. Augmentez le heap JVM (~50 % de la RAM du serveur, au plus ~31GB). Surveillez la pression sur le heap : GET _cat/nodes?v&h=name,heap.percent. Corrigez la cause racine (mémoire/requête) plutôt que de relever temporairement les limites des breakers. Vérifiez quel breaker s'est déclenché avec GET _nodes/stats/breaker.","tags":["circuit-breaker","data-too-large","heap","memory","fielddata","oom","danger"]},{"code":"mapper_parsing_exception","title":"mapper_parsing_exception: failed to parse field [...] of type [...] (échec de l'analyse du champ)","severity":"caution","meaning":"Lors de l'indexation d'un document, la valeur envoyée pour un champ ne correspondait pas au type défini pour ce champ dans le mapping et n'a pas pu être analysée. Par exemple, du texte envoyé à un champ défini comme integer, ou un format de date invalide envoyé à un champ défini comme date.","cause":"Incohérence de type de champ (une chaîne pour un champ numérique, un format mal formé pour un champ date), date ne correspondant pas au format de date attendu, JSON mal formé/incomplet, champs typés à tort dynamiquement, ou valeur scalaire envoyée là où un objet est attendu.","fix":"Vérifiez le nom du champ et le type attendu dans le message d'erreur : GET /index-name/_mapping. Faites correspondre la valeur du document au type du champ, ou redéfinissez le mapping avec le bon type. Pour les champs date, faites correspondre le paramètre format au format du document. Le type d'un champ existant ne peut pas être modifié ; si nécessaire, créez un nouvel index et déplacez les données avec _reindex. Validez les données dans la couche applicative.","tags":["mapping","parse","mapper","field-type","indexing","caution"]},{"code":"strict_dynamic_mapping_exception","title":"strict_dynamic_mapping_exception: mapping set to strict, dynamic introduction of [...] within [...] is not allowed (mapping strict ; nouveau champ non autorisé)","severity":"caution","meaning":"Comme le mapping de l'index ou de l'objet est défini sur \"dynamic\": \"strict\", un document contenant un nouveau champ non défini dans le mapping a été rejeté. Le mode strict empêche l'ajout silencieux de champs hors schéma.","cause":"Un champ absent du mapping a été envoyé dans le document (faute de frappe, nouveau champ, champ inattendu) alors que le mapping est en dynamic:strict. C'est généralement une protection volontaire pour garder un contrôle strict du schéma.","fix":"Corrigez ou supprimez le champ en trop/mal orthographié dans le document. Si le champ est réellement nécessaire, ajoutez-le d'abord au mapping : PUT /index-name/_mapping {\"properties\": {\"new_field\": {\"type\": \"...\"}}}. Si vous souhaitez que les nouveaux champs soient ajoutés automatiquement, définissez dynamic sur \"true\", ou sur \"false\" s'ils doivent être stockés mais non indexés. Utilisez le mode strict délibérément pour préserver la discipline du schéma.","tags":["mapping","strict","dynamic","schema","field","caution"]},{"code":"illegal_argument_exception_fielddata","title":"illegal_argument_exception: Fielddata is disabled on text fields by default. Set fielddata=true on [your_field] in order to load fielddata in memory (fielddata désactivé par défaut sur les champs text)","severity":"caution","meaning":"Une agrégation, un tri ou un script a été tenté sur un champ text analysé. Ces opérations sont désactivées par défaut sur les champs text, car elles nécessitent un fielddata coûteux en mémoire.","cause":"Une agrégation/un tri a été appliqué directement à un champ text. Dans Elasticsearch, text sert à la recherche plein texte et keyword à l'agrégation/au tri sur valeur exacte ; mélanger les deux produit cette erreur.","fix":"Utilisez le sous-champ keyword du champ pour l'agrégation/le tri (par ex. \"my_field.keyword\"). Si le champ est uniquement text, ajoutez un sous-champ keyword au mapping (multi-field) et exécutez _reindex. Activer le fielddata sur un champ text (fielddata=true) est possible mais déconseillé en raison de la forte consommation mémoire et du risque de circuit breaker ; ne l'utilisez qu'en cas de nécessité absolue.","tags":["fielddata","text","keyword","aggregation","sorting","mapping","caution"]},{"code":"search_phase_execution_exception","title":"search_phase_execution_exception: all shards failed / Failed to execute phase [query] (échec de la recherche sur tous les shards)","severity":"danger","meaning":"Une requête de recherche a échoué sur un ou sur tous les shards requis. Il s'agit d'une erreur de recherche générique qui enveloppe la cause sous-jacente réelle ; la section \"caused by\" indique le vrai problème (erreur d'analyse, breaker, type de champ inadapté, shard injoignable, etc.).","cause":"Syntaxe de requête invalide, agrégation sur un mauvais type de champ, too_many_clauses, déclenchement d'un circuit breaker, shard non attribué/injoignable, délai de requête dépassé ou crash d'un nœud. La cause racine se trouve dans l'exception interne de la réponse.","fix":"Trouvez l'exception réelle dans les détails \"caused by\" / \"failed_shards\" de la réponse et corrigez en conséquence (corriger la requête, utiliser un champ keyword, paramètres breaker/heap, allocation des shards). Vérifiez l'état du cluster : GET _cluster/health et GET _cat/shards?v. Si les résultats partiels sont autorisés et que certains shards ont échoué, revoyez le comportement allow_partial_search_results.","tags":["search","shard","search","all-shards-failed","query","danger"]},{"code":"too_many_clauses","title":"too_many_clauses: maxClauseCount is set to 1024 (query contains too many clauses) (la requête contient trop de clauses)","severity":"caution","meaning":"Une requête a dépassé le nombre maximal autorisé de clauses Lucene. En ES 8.x, cette limite n'est pas fixe ; elle est calculée automatiquement à partir de la taille du heap du nœud et du nombre de threads de recherche (elle peut descendre à 1024 sur les nœuds à petit heap), et le message d'erreur indique la valeur actuelle. Les très grandes requêtes bool et les requêtes qui s'étendent à de nombreux termes la déclenchent.","cause":"Requêtes bool générées par programme avec des milliers de clauses should/must ; requêtes wildcard/prefix/fuzzy/query_string s'étendant à de nombreux termes ; structures bool profondément imbriquées. Remarque : la requête terms (une liste de valeurs sur un seul champ) n'est pas soumise à cette limite ; son plafond est index.max_terms_count (65,536 par défaut).","fix":"Simplifiez la requête : convertissez de nombreuses clauses term sur un même champ en une seule requête terms (ou un terms lookup pour les grandes listes). Utilisez des types de requêtes plus spécifiques ou des champs index_prefixes/keyword au lieu de wildcard/prefix. En ES 8.x, le paramètre indices.query.bool.max_clause_count est déprécié et ignoré ; le moyen de relever la limite est d'augmenter le heap du nœud. Revoyez la logique de génération des requêtes.","tags":["too-many-clauses","max-clause-count","bool","terms","query","caution"]},{"code":"version_conflict_engine_exception","title":"version_conflict_engine_exception: [_doc][id]: version conflict, current version [x] is different than the one provided [y] (HTTP 409) (conflit de version du document)","severity":"caution","meaning":"Une requête de mise à jour/suppression de document a été rejetée par le contrôle de concurrence optimiste. La version du document (ou _seq_no/_primary_term) ne correspond pas à celle de la requête ; autrement dit, quelqu'un d'autre a modifié le document après votre lecture. Renvoie HTTP 409.","cause":"Mises à jour concurrentes du même document, écriture tentée avec une version/un seq_no obsolète, tentative de création (op_type=create) d'un document qui existe déjà, ou condition de concurrence lors d'un _bulk.","fix":"Relisez le document et relancez la requête avec les _seq_no et _primary_term (ou la version) actuels (boucle lecture-modification-écriture). Pour des relances automatiques, utilisez le paramètre retry_on_conflict de la requête _update ; dans _update_by_query/_delete_by_query, passez conflicts=proceed pour ignorer les documents en conflit et continuer. Si l'intention est une création uniquement, le conflit est normal ; interceptez 409 dans l'application et traitez-le de façon appropriée (relance ou abandon).","tags":["version-conflict","409","concurrency","optimistic","seq-no","caution"]},{"code":"document_missing_exception","title":"document_missing_exception: [_doc][id]: document missing (document introuvable)","severity":"caution","meaning":"Le document à mettre à jour (_update) est introuvable dans l'index. Une mise à jour nécessite un document existant ; si le document n'existe pas, cette exception se produit.","cause":"Le document a été supprimé auparavant, n'a jamais été créé, le mauvais _id a été utilisé, la requête est allée vers le mauvais index, ou un document écrit avec un routage personnalisé a été accédé sans routage. (GET et _update sont en temps réel ; le délai de refresh ne provoque pas cette erreur.)","fix":"Vérifiez que l'_id et l'index cible sont corrects : GET /index-name/_doc/id. Si vous souhaitez un comportement « mise à jour s'il existe / création s'il manque », utilisez upsert (ou doc_as_upsert:true) dans la requête _update. Créez directement le document avec index (PUT). Dans l'application, interceptez cette exception et basculez vers une logique d'upsert.","tags":["document-missing","update","upsert","document","not-found","caution"]},{"code":"index_not_found_exception","title":"index_not_found_exception: no such index [index_name] (HTTP 404) (index introuvable)","severity":"caution","meaning":"L'index (ou l'alias) demandé n'existe pas. L'opération a été rejetée avec HTTP 404, car aucun index portant le nom donné n'a été trouvé.","cause":"L'index n'a jamais été créé, a été supprimé, le nom de l'index/de l'alias comporte une faute de frappe, le client est connecté au mauvais environnement (cluster), ou une écriture dans un index inexistant a été tentée alors que action.auto_create_index est désactivé.","fix":"Listez les index existants : GET _cat/indices?v. Vérifiez le nom et l'alias : GET _alias. Créez l'index : PUT /index-name (avec les mappings/settings requis), ou configurez la création automatique avec un index template. Si la création automatique est souhaitée, vérifiez le paramètre action.auto_create_index. Vérifiez que vous n'êtes pas connecté au mauvais cluster.","tags":["index-not-found","404","index","alias","not-found","caution"]},{"code":"resource_already_exists_exception","title":"resource_already_exists_exception: index [index_name/uuid] already exists (HTTP 400) (l'index existe déjà)","severity":"safe","meaning":"L'index (ou la ressource) à créer existe déjà. La requête de création a été rejetée, car un index portant le même nom existe. Dans les versions plus anciennes, elle apparaît aussi sous la forme index_already_exists_exception.","cause":"Tentative de créer deux fois le même index, scripts d'installation non idempotents, requêtes de création concurrentes, ou tentative de recréer le même nom avec des paramètres différents.","fix":"Si l'index existe déjà, il n'est pas nécessaire de le créer ; continuez. S'il doit être recréé, supprimez-le d'abord (DELETE /index-name), puis créez-le (attention : perte de données). Rendez les scripts d'installation idempotents : vérifiez d'abord l'existence avec GET _cat/indices. Évitez les conflits en utilisant des index templates ou ILM/rollover pour la gestion du schéma.","tags":["already-exists","resource-already-exists","index","create","idempotent","safe"]},{"code":"cluster_health_red","title":"cluster health status: RED / unassigned shards (état du cluster RED / shards non attribués)","severity":"danger","meaning":"L'état RED du cluster signifie qu'au moins un shard primaire n'est pas attribué et est indisponible. C'est un état critique dans lequel certaines données ne peuvent pas être lues/écrites, avec un risque de perte partielle de données. (YELLOW signifie que seuls des réplicas ne sont pas attribués ; les primaires sont intacts.)","cause":"Un nœud a planté/quitté le cluster et les shards primaires qu'il hébergeait se sont retrouvés sans copie, allocation bloquée par le disk watermark, shard corrompu, erreur lors d'une restauration de snapshot, ou paramètres d'allocation empêchant l'attribution des shards.","fix":"Déterminez pourquoi les shards ne sont pas attribués : GET _cluster/allocation/explain ou GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason. Remettez en service les nœuds plantés ; s'il s'agit d'un problème de disque, libérez de l'espace (watermark). Si l'allocation a été bloquée temporairement, déclenchez une réallocation : POST _cluster/reroute?retry_failed=true. Si un primaire est complètement perdu, restaurez à partir d'un snapshot. En état RED, concentrez-vous d'abord sur la récupération des primaires.","tags":["cluster-health","red","unassigned","shard","primary","danger"]},{"code":"no_shard_available_action_exception","title":"no_shard_available_action_exception: No shard available for [...] (aucun shard disponible)","severity":"danger","meaning":"Aucune copie de shard active capable de servir une requête n'a été trouvée. La requête ne peut pas être satisfaite, car le shard concerné n'est pas attribué, est en cours de récupération, ou son nœud hôte est injoignable.","cause":"Crash de nœud/partition réseau, shards non attribués, récupération de shard en cours, blocage de l'allocation par le disk watermark, ou cluster à l'état RED/YELLOW.","fix":"Inspectez l'état du cluster et des shards : GET _cluster/health et GET _cat/shards?v. Déterminez pourquoi les shards ne sont pas attribués : GET _cluster/allocation/explain. Remettez en service les nœuds plantés, corrigez le problème de disque et attendez la fin de la récupération : GET _cat/recovery?v&active_only=true. Si nécessaire, relancez l'allocation avec POST _cluster/reroute?retry_failed=true.","tags":["no-shard-available","shard","unassigned","recovery","cluster","danger"]},{"code":"es_rejected_execution_exception","title":"es_rejected_execution_exception: rejected execution of ... on ... (queue capacity ...) (HTTP 429) (exécution rejetée : file d'attente pleine)","severity":"danger","meaning":"Une opération a été rejetée, car la capacité de la file d'attente du thread pool concerné (search, write/bulk, etc.) est pleine. En cas de surcharge, Elasticsearch se protège en refusant des requêtes (backpressure) et renvoie HTTP 429 (Too Many Requests).","cause":"Volume de requêtes simultanées très élevé, requêtes bulk très grosses ou très fréquentes, forte charge d'agrégation/de recherche, ressources nœud/CPU insuffisantes, ou file d'attente du thread pool trop petite pour la charge.","fix":"Relancez côté client avec un backoff exponentiel ; 429 est normalement transitoire. Réduisez la taille des lots bulk et le débit d'envoi, et diminuez la concurrence. Surveillez les opérations rejetées : GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected. Pour une charge soutenue, ajoutez des nœuds/ressources (scalabilité horizontale). Augmenter la taille de la file d'attente est un dernier recours ; la cause racine est la capacité.","tags":["rejected-execution","429","thread-pool","queue","bulk","backpressure","danger"]},{"code":"master_not_discovered_exception","title":"master_not_discovered_exception / MasterNotDiscoveredException","severity":"danger","meaning":"Le cluster n'a pas pu élire/trouver un nœud master actif. Sans master, l'allocation des shards, les modifications de mapping et les mises à jour du cluster state sont impossibles ; le cluster devient incapable de traiter les opérations.","cause":"Une majorité (quorum) des nœuds éligibles master est injoignable, partition réseau (prévention du split brain), configuration discovery.seed_hosts / cluster.initial_master_nodes incorrecte, absence de bootstrap sur une nouvelle installation, ou trop peu de nœuds éligibles master.","fix":"Vérifiez que les nœuds éligibles master fonctionnent et se voient mutuellement : GET _cat/nodes?v et GET _cluster/state. Vérifiez les paramètres discovery.seed_hosts et cluster.initial_master_nodes (lors de la première installation). Vérifiez le réseau/pare-feu et la connectivité entre nœuds (transport 9300). Maintenez en fonctionnement suffisamment de nœuds éligibles master (de préférence 3). Inspectez les journaux à la recherche de messages d'élection du master.","tags":["master","discovery","quorum","split-brain","cluster","danger"]},{"code":"security_exception","title":"security_exception: unable to authenticate / action [...] is unauthorized for user [...] (HTTP 401/403) (authentification ou autorisation refusée)","severity":"danger","meaning":"L'authentification (401 Unauthorized) ou l'autorisation (403 Forbidden) a échoué. 401 indique des identifiants absents/incorrects ; 403 indique que l'utilisateur est authentifié mais ne dispose pas du rôle/privilège requis pour cette action/cet index.","cause":"Nom d'utilisateur/mot de passe ou clé API erronés, en-tête Authorization manquant, jeton expiré/invalide, utilisateur sans le privilège d'index ou de cluster concerné, ou attribution de rôle absente/incorrecte.","fix":"Pour 401 : envoyez les bons identifiants/la bonne clé API et vérifiez l'en-tête Authorization. Pour 403 : inspectez les rôles et privilèges de l'utilisateur (GET _security/user/app_user, GET _security/role/app_role) et ajoutez les privilèges d'index/de cluster requis. Appliquez le principe du moindre privilège. Si vous utilisez une clé API, vérifiez son expiration et ses autorisations.","tags":["security","401","403","authentication","authorization","security","danger"]},{"code":"validation_exception","title":"validation_exception / illegal_argument_exception: Validation Failed: ... (échec de validation)","severity":"caution","meaning":"Les paramètres ou réglages d'une requête ont échoué à la validation. Elasticsearch a rejeté sans l'exécuter la requête contenant des valeurs invalides ou incohérentes (par ex. nombre de shards invalide, combinaison de paramètres incompatible, champ obligatoire manquant).","cause":"Paramètre d'index invalide (par ex. number_of_shards négatif/nul), tentative de mise à jour d'un paramètre non modifiable sur un index existant, combinaison de paramètres incompatible, champ obligatoire manquant, ou valeur hors limites.","fix":"Lisez le détail de validation dans le message d'erreur ; il indique clairement quel paramètre est invalide et pourquoi. Ramenez la valeur dans une plage/un format valide. Spécifiez les paramètres non modifiables (statiques) lors de la création d'un nouvel index, et non sur un index existant (_reindex si nécessaire). Fournissez les champs obligatoires attendus par l'API. Vérifiez les valeurs valides documentées du paramètre.","tags":["validation","validation","settings","illegal-argument","invalid","caution"]},{"code":"task_cancelled_exception","title":"task_cancelled_exception: Task was cancelled / TaskCancelledException (la tâche a été annulée)","severity":"caution","meaning":"Une tâche en cours (search, _reindex, _update_by_query, etc.) a été annulée. L'annulation peut provenir d'une demande utilisateur à _tasks/_cancel, de la fermeture de la connexion HTTP par le client (délai client/abandon ; Elasticsearch annule automatiquement la recherche associée), ou de l'annulation d'une tâche parente ou d'une recherche asynchrone expirée.","cause":"L'utilisateur a annulé la tâche avec POST _tasks/<task_id>/_cancel (X-Opaque-Id sert uniquement à identifier et retrouver la tâche ; il ne l'annule pas), un délai côté client a coupé la connexion, requêtes coûteuses de longue durée, ou la gestion adaptative des ressources a arrêté une tâche lourde.","fix":"Si la tâche a été annulée de manière inattendue, revoyez les paramètres de délai et de connexion du client ; optimisez la requête lourde (moins de données, filtres plus spécifiques). Pour les opérations longues, exécutez _reindex/_update_by_query de façon asynchrone (wait_for_completion=false) et surveillez avec GET _tasks. Affichez les tâches actives : GET _tasks?detailed=true. Si nécessaire, découpez les requêtes coûteuses ou utilisez scroll/PIT.","tags":["task-cancelled","task","timeout","reindex","cancel","caution"]}]},"errors/oracle-more.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00604","title":"error occurred at recursive SQL level (erreur au niveau du SQL récursif)","severity":"danger","meaning":"Une erreur s'est produite lors du SQL récursif qu'Oracle exécute en interne en arrière-plan. Il s'agit d'une erreur d'enveloppe (externe) qui masque l'erreur réelle ; la vraie cause se trouve dans le second code d'erreur affiché juste en dessous.","cause":"Accès au dictionnaire de données, trigger DDL en échec, tablespace SYSTEM plein, espace temporaire insuffisant, objet du dictionnaire de données corrompu, ou trigger LOGON en échec exécuté à la connexion de la session.","fix":"Lisez la deuxième ligne qui suit ORA-00604 dans la pile d'erreurs ; c'est la vraie cause, corrigez-la. Par exemple, si vous voyez ORA-01653, étendez le tablespace. Si un trigger DDL/LOGON est suspect, désactivez-le temporairement : ALTER TRIGGER trigger_name DISABLE; Vérifiez l'utilisation des tablespaces SYSTEM et TEMP avec DBA_FREE_SPACE.","tags":["recursive","recursion","dictionary","data dictionary","trigger","wrapper"]},{"code":"ORA-01438","title":"value larger than specified precision allowed for this column (valeur supérieure à la précision autorisée pour la colonne)","severity":"caution","meaning":"Un nombre dépassant les limites de précision et d'échelle définies a été écrit dans une colonne NUMBER. Par exemple, une valeur dont la partie entière compte 3 chiffres ne tient pas dans une colonne NUMBER(4,2).","cause":"La définition NUMBER(p,s) de la colonne est trop étroite pour les données ; un résultat de calcul est plus grand que prévu ; l'échelle décimale réduit les chiffres restants pour la partie entière (NUMBER(5,3) n'autorise que 2 chiffres entiers).","fix":"Élargissez la capacité de la colonne pour correspondre à la plage réelle des données : ALTER TABLE my_table MODIFY (my_column NUMBER(12,2)); Arrondissez la valeur dans l'application, ou ajustez-la à l'échelle avec ROUND/TRUNC. Pour trouver la ligne en dépassement, affinez votre requête lors des tests, et ajoutez une validation des saisies.","tags":["number","precision","precision","scale","scale","overflow","numeric"]},{"code":"ORA-01400","title":"cannot insert NULL into (column) (insertion de NULL impossible dans la colonne)","severity":"caution","meaning":"Une valeur NULL a été insérée dans une colonne soumise à une contrainte NOT NULL, ou affectée à celle-ci. Un champ obligatoire ne peut pas rester vide.","cause":"Aucune valeur fournie pour une colonne obligatoire dans un INSERT, UPDATE mettant la colonne à NULL, aucun DEFAULT défini pour la colonne, ou valeur vide provenant d'une sous-requête/de l'application.","fix":"Fournissez une valeur valide pour la colonne indiquée dans le message d'erreur. Définissez une valeur par défaut pertinente pour la colonne : ALTER TABLE my_table MODIFY (my_column DEFAULT 0); (DEFAULT ne s'applique que lorsque la colonne est entièrement omise de l'INSERT ; pour les valeurs NULL envoyées explicitement, utilisez DEFAULT ON NULL en 12c+ : ALTER TABLE my_table MODIFY (my_column DEFAULT ON NULL 0);) Si la règle n'est pas réellement obligatoire, supprimez la contrainte : ALTER TABLE my_table MODIFY (my_column NULL); Validez les valeurs vides dans l'application avant de les envoyer.","tags":["not null","null","empty","insert","constraint","constraint","mandatory"]},{"code":"ORA-02291","title":"integrity constraint violated - parent key not found (contrainte d'intégrité violée : clé parente introuvable)","severity":"danger","meaning":"Une contrainte de clé étrangère a été violée. Aucune ligne parente correspondant à la valeur insérée dans la table enfant n'a été trouvée ; autrement dit, l'enregistrement référencé n'existe pas.","cause":"INSERT/UPDATE référençant un enregistrement parent inexistant, ligne de la table parente pas encore insérée, mauvais ordre de chargement (enfant avant parent), ou correspondance en échec en raison de différences de casse ou d'espaces.","fix":"Insérez d'abord l'enregistrement concerné dans la table parente, puis dans la table enfant. Vérifiez que la référence existe : SELECT * FROM parent_table WHERE key_col = value; Lors des chargements en masse, traitez les tables dans le bon ordre (parent d'abord). Pour trouver la ligne fautive, comparez la valeur de la table enfant avec la table parente à l'aide de NOT IN / d'une anti-jointure.","tags":["fk","foreign key","foreign key","constraint","constraint","parent","reference"]},{"code":"ORA-02292","title":"integrity constraint violated - child record found (contrainte d'intégrité violée : enregistrement enfant trouvé)","severity":"danger","meaning":"Une ligne parente a été supprimée ou sa clé modifiée, mais la contrainte de clé étrangère ne l'a pas permis, car des enregistrements enfants dépendants existent.","cause":"Tentative de DELETE d'une ligne parente ayant des enregistrements dans la table enfant ; FK définie sans ON DELETE CASCADE ; enregistrements dépendants non nettoyés au préalable.","fix":"Supprimez d'abord les enregistrements enfants dépendants ou rattachez-les à un autre enregistrement parent, puis supprimez la ligne parente. Si la règle métier le permet, recréez la clé étrangère avec ON DELETE CASCADE : ALTER TABLE child DROP CONSTRAINT fk_name; ALTER TABLE child ADD CONSTRAINT fk_name FOREIGN KEY (my_column) REFERENCES parent(key_col) ON DELETE CASCADE; Pour trouver les lignes dépendantes, interrogez la table enfant par la clé parente.","tags":["fk","foreign key","foreign key","child","child record","delete","cascade"]},{"code":"ORA-00979","title":"not a GROUP BY expression (n'est pas une expression GROUP BY)","severity":"caution","meaning":"Une colonne non agrégée a été utilisée dans la liste SELECT avec une fonction d'agrégation (SUM, COUNT, etc.), mais la colonne n'a pas été ajoutée à la clause GROUP BY. Oracle ne sait pas comment regrouper cette colonne.","cause":"Colonne présente dans le SELECT mais pas dans le GROUP BY ; expressions hors fonctions d'agrégation absentes de la liste GROUP BY ; incohérence dans HAVING ou dans les expressions du select.","fix":"Ajoutez toutes les colonnes non agrégées à la liste GROUP BY : SELECT department, COUNT(*) FROM employee GROUP BY department; Vous pouvez aussi encapsuler cette colonne dans une fonction d'agrégation (MAX, MIN), ou repenser le regroupement avec une fonction analytique (OVER()).","tags":["group by","grouping","aggregate","aggregation","sum","count","query"]},{"code":"ORA-00918","title":"column ambiguously defined (colonne définie de façon ambiguë)","severity":"caution","meaning":"Une colonne portant le même nom existe dans plusieurs tables de la requête et, comme elle est utilisée sans préfixe de table/d'alias, Oracle ne peut pas déterminer à quelle table elle appartient.","cause":"Les deux tables jointes ont une colonne de même nom (par ex. ID dans les deux) ; la colonne est utilisée sans qualification (sans préfixe) dans SELECT, WHERE ou ORDER BY.","fix":"Qualifiez chaque colonne ambiguë avec le nom de la table ou l'alias : SELECT a.id, b.id FROM tableA a JOIN tableB b ON a.id = b.a_id; Donnez aux tables des alias courts et préfixez toutes les colonnes pour améliorer la lisibilité et la clarté.","tags":["ambiguous","ambiguous column","column","column","join","alias","query"]},{"code":"ORA-06550","title":"line/column PLS-... PL/SQL compilation error (erreur de compilation PL/SQL)","severity":"caution","meaning":"Une erreur de syntaxe ou sémantique a été trouvée lors de la compilation d'un bloc PL/SQL, d'une procédure ou d'un bloc anonyme. Le code a échoué à l'étape de compilation avant de pouvoir s'exécuter.","cause":"Fautes de frappe, référence à une variable ou à un objet non déclaré, appel avec un nombre/type de paramètres erroné, point-virgule manquant, appel à une procédure/un package inexistant, ou privilège manquant.","fix":"Rendez-vous au numéro de ligne et de colonne indiqué dans le message d'erreur ; lisez le sous-code PLS-XXXX. Pour voir toutes les erreurs de compilation : SHOW ERRORS; ou SELECT * FROM user_errors WHERE name = 'OBJECT_NAME'; Vérifiez les noms d'objets, les signatures de paramètres et les GRANT requis, puis recompilez.","tags":["plsql","compilation","compile","pls","syntax","procedure","code"]},{"code":"ORA-04068","title":"existing state of packages has been discarded (l'état existant des packages a été supprimé)","severity":"caution","meaning":"Alors qu'une session en cours utilisait l'état d'un package, le package a été recompilé ; cet état a donc été invalidé. L'instance du package détenue par la session n'est plus valide.","cause":"Un corps ou une spécification de package a été recompilé avec ALTER/CREATE OR REPLACE alors que des sessions qui en dépendent étaient actives ; cela se produit généralement lors d'un déploiement applicatif sur un système en production. S'observe avec ORA-04061/04065.","fix":"Réexécuter l'appel (relance) le résout généralement ; à la seconde tentative, le package se charge sous sa nouvelle forme. Interceptez ORA-04068 dans l'application et relancez automatiquement. Appliquez les modifications de packages pendant les fenêtres de maintenance où l'activité des utilisateurs est faible. Une conception de package sans état réduit ce problème.","tags":["package","package","state","package state","recompile","compile","retry"]},{"code":"ORA-01427","title":"single-row subquery returns more than one row (une sous-requête mono-ligne renvoie plusieurs lignes)","severity":"caution","meaning":"Une sous-requête censée renvoyer une seule ligne en a renvoyé plusieurs. Par exemple, la sous-requête à droite d'une comparaison d'égalité (=) a produit plusieurs valeurs.","cause":"Sous-requête renvoyant plusieurs lignes là où = ou une affectation est attendu ; condition WHERE insuffisamment restrictive ; correspondance non unique dans la corrélation ; sous-requête scalaire de la liste SELECT renvoyant plusieurs lignes.","fix":"Restreignez la sous-requête pour qu'elle renvoie une seule ligne (WHERE supplémentaire, clé unique). Si plusieurs valeurs sont réellement attendues, utilisez IN au lieu de = : WHERE id IN (SELECT id FROM ...). Ou ajoutez une agrégation à la sous-requête (MAX, MIN), ou réduisez-la à une ligne avec ROWNUM = 1. Exécutez d'abord la sous-requête seule et vérifiez combien de lignes elle renvoie.","tags":["subquery","subquery","single row","single row","in","scalar","query"]},{"code":"ORA-12899","title":"value too large for column (valeur trop grande pour la colonne)","severity":"caution","meaning":"Une valeur plus longue que la longueur maximale définie a été écrite dans une colonne de caractères/de données. Par exemple, un texte de 15 caractères ne tient pas dans une colonne VARCHAR2(10).","cause":"La largeur de la colonne est insuffisante pour les données ; dans un jeu de caractères multi-octets (UTF8), le nombre de caractères dépasse la limite en octets (sémantique BYTE) ; jeu de caractères différent lors d'une migration de données ; résultat de concaténation devenant plus long.","fix":"Élargissez la colonne : ALTER TABLE my_table MODIFY (my_column VARCHAR2(100)); Pour un contenu multi-octets, utilisez la sémantique CHAR : my_column VARCHAR2(100 CHAR). Si les valeurs longues doivent être raccourcies, tronquez-les avec SUBSTR. Le message d'erreur indique la longueur réelle et la longueur autorisée ; trouvez l'enregistrement en dépassement avec LENGTH/LENGTHB.","tags":["varchar2","length","length","too large","column","column","charset"]},{"code":"ORA-30926","title":"unable to get a stable set of rows in the source tables (impossible d'obtenir un ensemble stable de lignes dans les tables source)","severity":"danger","meaning":"Dans une instruction MERGE (ou une jointure modifiable), le côté source a produit plusieurs lignes correspondantes pour la même ligne cible. Oracle a arrêté l'opération, car il ne peut pas décider avec quelle valeur source effectuer la mise à jour.","cause":"La source de MERGE ... USING contient des correspondances en double pour la condition ON ; la requête source n'est pas unique sur la clé cible ; plusieurs lignes source correspondent à la même ligne cible.","fix":"Rendez la requête source unique sur la clé cible. Trouvez les correspondances en double : SELECT key_col, COUNT(*) FROM (source_query) GROUP BY key_col HAVING COUNT(*) > 1; Dédoublonnez la source avec DISTINCT ou une fonction d'agrégation/analytique (ROW_NUMBER), puis relancez le MERGE. Assurez-vous que la condition ON est suffisamment sélective.","tags":["merge","merge","duplicate","duplicate rows","source","source","stable"]},{"code":"ORA-08103","title":"object no longer exists (l'objet n'existe plus)","severity":"danger","meaning":"Un objet (table, index, segment LOB) accédé par une requête en cours n'existe plus, ou sa structure physique a changé. Généralement, un DDL/TRUNCATE a eu lieu sur l'objet pendant l'exécution de la requête.","cause":"Le même objet a subi un TRUNCATE/DROP/SHRINK alors que la requête était ouverte ; déplacement (MOVE) ou maintenance de partition ; index/segment corrompu ; disparition de la structure sous-jacente pendant l'accès à un segment LOB ; conflit avec un DDL concurrent.","fix":"Vérifiez que l'objet existe toujours et est valide : SELECT status FROM user_objects WHERE object_name = 'NAME'; Reconstruisez l'index/l'objet concerné (REBUILD) ou relancez la requête. Évitez TRUNCATE/DROP sur des objets pendant que des requêtes de longue durée les utilisent. En cas de suspicion de corruption, vérifiez avec ANALYZE ... VALIDATE STRUCTURE.","tags":["object","object","truncate","drop","ddl","corruption","segment"]},{"code":"ORA-01843","title":"not a valid month (mois invalide)","severity":"caution","meaning":"Lors de la conversion d'un texte en DATE, la partie mois n'a pas pu être interprétée comme un mois valide. Généralement, le format de date ne correspond pas au format des données.","cause":"Aucun masque de format passé à TO_DATE et le NLS_DATE_FORMAT de la session ne correspond pas aux données ; jour et mois inversés (confusion DD/MM et MM/DD) ; différence de langue/de paramètres régionaux (dans les noms de mois) ; texte de date corrompu ou inattendu.","fix":"Passez toujours un format explicite dans les appels TO_DATE : TO_DATE('2026-07-04', 'YYYY-MM-DD'). Pour un texte contenant des noms de mois, indiquez la langue : TO_DATE(str, 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH'). Définissez le format de la session si nécessaire : ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'. Validez les textes de date invalides avant le chargement.","tags":["date","date","to_date","nls","format","month","month"]},{"code":"ORA-14400","title":"inserted partition key does not map to any partition (la clé de partition insérée ne correspond à aucune partition)","severity":"danger","meaning":"La valeur de la clé de partition de la ligne insérée ne correspond à aucune partition définie sur la table. Aucune partition par plage ou par liste ne couvre cette valeur.","cause":"En partitionnement RANGE, une date/valeur supérieure à la plage la plus haute a été insérée et il n'existe pas de partition MAXVALUE ; en partitionnement LIST, une nouvelle valeur non définie ; la nouvelle partition n'a pas été créée à l'avance à une limite de mois/d'année.","fix":"Ajoutez la partition manquante : ALTER TABLE my_table ADD PARTITION p_2026_08 VALUES LESS THAN (TO_DATE('2026-09-01','YYYY-MM-DD')); Pour RANGE, ajoutez une partition MAXVALUE comme filet de sécurité. Définissez une partition DEFAULT pour LIST afin de capter les valeurs inattendues. Utilisez le partitionnement automatique (INTERVAL partitioning) pour que les nouvelles partitions soient créées automatiquement.","tags":["partition","partition","partition key","range","list","maxvalue","insert"]},{"code":"ORA-01536","title":"space quota exceeded for tablespace (quota d'espace dépassé pour le tablespace)","severity":"danger","meaning":"Le quota d'espace de l'utilisateur sur le tablespace indiqué est épuisé. Même si le tablespace dispose d'espace libre, aucun nouvel espace ne peut être alloué, car la limite accordée à l'utilisateur est dépassée.","cause":"L'utilisateur a un quota limité (non UNLIMITED) ; le quota s'est rempli avec la croissance des données ; chargement excessif dans une table/un index ; écriture dans le mauvais tablespace ; segments croissant plus vite que prévu.","fix":"Augmentez le quota de l'utilisateur : ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; ou indiquez une valeur précise : QUOTA 500M ON users. Consultez l'utilisation actuelle : SELECT * FROM dba_ts_quotas WHERE username = 'APP_USER'. Archivez/purgez les segments inutilement volumineux ou déplacez les objets vers un tablespace approprié.","tags":["quota","quota","tablespace","space","space","user","user"]},{"code":"direct path read","title":"Attente : session lisant des blocs directement dans la PGA en contournant le buffer cache","severity":"caution","meaning":"La session passe du temps dans cet événement d'attente lorsqu'elle lit des blocs directement dans la mémoire PGA plutôt que via le buffer cache de la base de données. Il est généralement associé aux grands full table scans et aux requêtes parallèles.","cause":"Nombreux blocs lus depuis le disque en raison de parcours complets de grandes tables, requêtes parallèles, opérations de tri/hachage débordant sur le tablespace temporaire, décision de serial direct path read pour les parcours complets en série, et requêtes ne pouvant pas utiliser d'index.","fix":"Identifiez avec AWR/ASH le SQL effectuant de lourds parcours complets et ajoutez des index appropriés. Réduisez les parcours inutiles de grandes tables par l'optimisation des requêtes. Examinez la latence du stockage (E/S) et passez à des disques plus rapides si nécessaire. Revoyez le degré de parallélisme. Pour les opérations débordant sur TEMP, évaluez la taille de PGA_AGGREGATE_TARGET.","tags":["wait","wait","direct path read","full scan","full table scan","io","pga"]},{"code":"latch: cache buffers chains","title":"Attente : contention sur le latch cache buffers chains","severity":"danger","meaning":"Il y a une contention sur le latch cache buffers chains (CBC), qui protège les blocs du buffer cache. Cette attente augmente lorsque de nombreuses sessions tentent d'atteindre les mêmes blocs fréquemment accédés (chauds).","cause":"Blocs chauds fréquemment lus ; SQL inefficace générant de nombreuses lectures logiques ; accès répétés aux mêmes blocs racine d'index dans des jointures nested loop ; accès concurrents intenses mal répartis ; petites tables mais très lues.","fix":"Trouvez le SQL générant le plus de lectures logiques (V$SQL, ASH) et réduisez les accès aux buffers en améliorant les index/le plan d'accès. Identifiez les blocs chauds (X$BH / vues associées). Envisagez de convertir les nested loops inutiles en jointures par hachage. Pour les petites tables de référence très lues, répartissez le schéma d'accès ou mettez-les en cache.","tags":["latch","cache buffers chains","cbc","contention","contention","hot block","wait","wait"]}]},"errors/postgres-more.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"42883","title":"undefined_function","severity":"caution","meaning":"La fonction (ou l'opérateur) appelée dans la requête est introuvable avec ce nom et ces types d'arguments. PostgreSQL résout les fonctions à la fois par le nom et par les types d'arguments ; si la signature ne correspond pas, cette erreur est levée.","cause":"Nom de fonction mal orthographié ; types d'arguments ne correspondant pas à la signature définie (par ex. un nombre au lieu de texte) ; cast explicite manquant ; schéma de la fonction absent du search_path ; extension requise non installée.","fix":"Vérifiez que la fonction existe et contrôlez sa signature : \\df function_name. Convertissez explicitement les arguments : SELECT fn(value::text). Ajoutez le préfixe de schéma ou définissez le search_path. Si une extension est requise, installez-la : CREATE EXTENSION extension_name; Pour les erreurs d'opérateur, faites correspondre les types des deux côtés.","tags":["function","function not found","undefined","operator","cast","type conversion","42883"]},{"code":"42804","title":"datatype_mismatch","severity":"caution","meaning":"Le type de données attendu et le type de données fourni ne correspondent pas. Par exemple, affectation d'une valeur de type erroné à une colonne, branches CASE renvoyant des types différents, ou branches UNION dont les types ne s'alignent pas.","cause":"Affectation incompatible avec le type de la colonne ; branches CASE/COALESCE produisant des types différents ; colonnes correspondantes d'une UNION de types différents ; valeur renvoyée (RETURN) ne correspondant pas au type de retour de la fonction ; valeur non booléenne là où un booléen est attendu.","fix":"Convertissez l'expression dans le bon type : value::integer ou CAST(value AS numeric). Faites renvoyer le même type à toutes les branches CASE. Dans une UNION, alignez les types des colonnes correspondantes des deux côtés. Assurez-vous que la condition WHERE produit un booléen.","tags":["type","data type","mismatch","type mismatch","cast","union","42804"]},{"code":"22P02","title":"invalid_text_representation","severity":"caution","meaning":"Une valeur texte n'était pas une représentation valide pour le type de données cible lors de la conversion. Par exemple, tenter de convertir le texte 'abc' en integer lève cette erreur.","cause":"Texte non numérique envoyé à une colonne/un paramètre numérique ; chaîne vide ('') convertie en nombre/date ; texte invalide pour un UUID ou un enum ; JSON mal formé ; données d'entrée au format inattendu.","fix":"Vérifiez que la valeur convertie est réellement valide. Validez les saisies dans l'application avant la conversion de type. Convertissez les chaînes vides en NULL : NULLIF(value, ''). Pour une conversion sûre sur PostgreSQL 16+, testez la valeur au préalable avec pg_input_is_valid('abc', 'integer') et utilisez pg_input_error_info() pour le détail de l'erreur ; sur les versions plus anciennes, envisagez un contrôle de validation (regex). Identifiez la ligne erronée.","tags":["cast","type conversion","invalid text","invalid input","integer","uuid","22P02"]},{"code":"22003","title":"numeric_value_out_of_range","severity":"caution","meaning":"Une valeur numérique est hors de la plage autorisée par le type cible. Par exemple, écriture d'une valeur supérieure à 2 milliards dans une colonne integer, ou d'un nombre trop grand pour une colonne numeric(5,2).","cause":"Dépassement des limites de smallint/integer/bigint ; dépassement de la précision/échelle de numeric(p,s) ; débordement arithmétique ; somme/produit dépassant la capacité du type ; séquence atteignant la borne supérieure de son type.","fix":"Passez la colonne/le paramètre à un type plus large (bigint au lieu d'integer, augmentation de la précision de numeric) : ALTER TABLE my_table ALTER COLUMN my_column TYPE bigint; Arrondissez (ROUND) ou validez la valeur selon la règle métier. Si un calcul intermédiaire déborde, calculez l'expression en numeric. En cas de débordement de séquence, élargissez le type.","tags":["numeric","number","out of range","overflow","numeric overflow","integer","22003"]},{"code":"23514","title":"check_violation","severity":"caution","meaning":"Une contrainte CHECK a été violée. La ligne insérée ou mise à jour ne satisfait pas une condition logique définie sur la table (par ex. price > 0).","cause":"Données enfreignant une règle métier (prix négatif, code de statut invalide, violation de plage de dates) ; absence de validation côté application ; contrainte trop stricte pour les données ; enregistrements enfreignant la règle lors d'un chargement en masse.","fix":"Utilisez le nom de la contrainte dans le message d'erreur pour savoir quelle condition a été violée ; affichez la définition : \\d table_name. Interrogez les lignes fautives en inversant la condition. Corrigez les données ou, si la règle a réellement changé, mettez à jour la contrainte : ALTER TABLE my_table DROP CONSTRAINT name; ALTER TABLE my_table ADD CONSTRAINT name CHECK (new_condition);","tags":["check","constraint","check constraint","violation","constraint violation","validation","23514"]},{"code":"22012","title":"division_by_zero","severity":"caution","meaning":"Une expression arithmétique a tenté une division (ou un modulo) par zéro. PostgreSQL n'autorise pas la division par zéro et abandonne la requête.","cause":"La colonne/l'expression diviseur vaut 0 ; dénominateur nul dans un calcul de ratio/de pourcentage ; division par un COUNT ou une SUM nul ; valeurs nulles inattendues dans des données non filtrées.","fix":"Utilisez NULLIF pour renvoyer NULL lorsque le dénominateur est nul : numerator / NULLIF(denominator, 0). Si souhaité, transformez le résultat en valeur par défaut avec COALESCE : COALESCE(numerator / NULLIF(denominator,0), 0). Ou utilisez CASE WHEN denominator = 0 THEN NULL ELSE numerator/denominator END. Filtrez au préalable les cas nuls dans les données.","tags":["division","divide","zero","divide by zero","nullif","arithmetic","22012"]},{"code":"42601","title":"syntax_error","severity":"caution","meaning":"Une erreur de syntaxe a été trouvée dans l'instruction SQL. PostgreSQL n'a pas pu analyser la requête et indique l'emplacement approximatif (position) de l'erreur.","cause":"Virgule ou parenthèse manquante/en trop ; mot-clé réservé (comme user, order) utilisé comme nom de colonne sans guillemets ; point-virgule manquant ; commande mal orthographiée ; littéral de chaîne non terminé ; utilisation d'une syntaxe propre à une version.","fix":"Utilisez la valeur 'position' du message d'erreur pour localiser le problème ; inspectez la partie qui la précède immédiatement. Encadrez les mots réservés de guillemets doubles : \"order\". Vérifiez l'équilibre des parenthèses et des guillemets. Testez une requête complexe en la découpant en morceaux. Consultez la documentation de votre version pour la syntaxe correcte.","tags":["syntax","syntax error","parse","parser","keyword","position","42601"]},{"code":"42P07","title":"duplicate_table","severity":"caution","meaning":"La table (ou un objet similaire) en cours de création existe déjà. CREATE a échoué, car un objet portant le même nom est présent.","cause":"Recréation d'une table existante avec CREATE TABLE ; exécution du script deux fois ; réapplication de migrations ; conflit de nom avec une vue/séquence/un index du même nom.","fix":"Utilisez IF NOT EXISTS pour ignorer la création lorsque l'objet existe déjà : CREATE TABLE IF NOT EXISTS my_table (...). Si vous voulez vraiment la recréer, supprimez-la d'abord : DROP TABLE IF EXISTS my_table; Rendez les migrations réexécutables sans risque (idempotentes). Vérifiez l'objet existant avec \\dt.","tags":["duplicate","duplicate object","table","relation","create","already exists","42P07"]},{"code":"42701","title":"duplicate_column","severity":"caution","meaning":"Le même nom de colonne a été utilisé plusieurs fois dans une définition de table ou une opération ALTER, ou une colonne déjà existante a été ajoutée à nouveau.","cause":"Écriture du même nom de colonne deux fois dans CREATE TABLE ; ajout d'une colonne déjà existante avec ALTER TABLE ADD COLUMN ; erreur de copier-coller ; migration exécutée à nouveau.","fix":"Supprimez ou renommez le nom de colonne en double dans la définition de la table. Vérifiez d'abord pour éviter de rajouter une colonne existante : \\d table_name. Lorsque la version le permet, ajoutez de façon sûre : ALTER TABLE my_table ADD COLUMN IF NOT EXISTS my_column type; Assurez-vous que les migrations sont idempotentes.","tags":["duplicate","duplicate column","column","column exists","alter","add column","42701"]},{"code":"3D000","title":"invalid_catalog_name","severity":"danger","meaning":"La base de données à laquelle vous tentez de vous connecter ou d'accéder n'existe pas. Aucune base de données portant le nom donné n'a été trouvée.","cause":"Nom de base de données mal orthographié dans la chaîne de connexion ; base de données pas encore créée ou supprimée ; sensibilité à la casse ; connexion au mauvais serveur ; nom erroné dans la configuration d'environnement (env).","fix":"Listez les bases de données existantes : psql -l ou SELECT datname FROM pg_database. Corrigez le nom de la base de données dans la chaîne de connexion. Créez-la si nécessaire : CREATE DATABASE database_name; Vérifiez que vous vous connectez au bon serveur/port. Vérifiez le nom de la base dans la configuration de l'application.","tags":["database","database not found","catalog","does not exist","missing","connection","3D000"]},{"code":"3F000","title":"invalid_schema_name","severity":"caution","meaning":"Le schéma référencé n'existe pas ou est introuvable dans le search_path. Il n'existe aucun schéma portant le nom donné.","cause":"Nom de schéma mal orthographié ; schéma jamais créé ; search_path n'incluant pas le schéma ; différence de casse ; mauvais préfixe lors de l'accès à un objet avec préfixe de schéma.","fix":"Listez les schémas existants : \\dn ou SELECT schema_name FROM information_schema.schemata. Corrigez le nom du schéma. Créez-le si nécessaire : CREATE SCHEMA schema_name; Définissez le search_path : SET search_path TO schema_name, public; Accédez aux objets avec le bon préfixe de schéma.","tags":["schema","schema not found","search_path","does not exist","missing","invalid","3F000"]},{"code":"0A000","title":"feature_not_supported","severity":"caution","meaning":"La fonctionnalité que vous tentez d'utiliser n'est pas prise en charge dans ce contexte ou dans cette version de PostgreSQL. Même si l'opération demandée peut être valide, elle ne peut pas être appliquée dans la situation actuelle.","cause":"Syntaxe/fonctionnalité pas encore disponible dans la version ; ALTER non pris en charge pour un type de colonne/table particulier ; opération non prise en charge par un foreign data wrapper (FDW) ; restrictions sur les combinaisons réplication logique/contraintes ; conversions non autorisées pour certains types de données.","fix":"Lisez le message d'erreur complet ; il indique généralement clairement ce qui n'est pas pris en charge. Utilisez une approche alternative (par ex. nouvelle colonne + copie + renommage au lieu de modifier directement le type de la colonne). Si la fonctionnalité est arrivée dans une version plus récente, envisagez une mise à niveau. Si vous atteignez des limites de FDW/d'extension, consultez la documentation.","tags":["not supported","unsupported","feature","unsupported feature","version","upgrade","0A000"]},{"code":"57P03","title":"cannot_connect_now","severity":"danger","meaning":"Le serveur ne peut pas accepter de connexions pour le moment ; généralement, la base de données est encore en cours de démarrage ou de récupération. Les connexions pourront être ouvertes une fois le système prêt.","cause":"PostgreSQL vient d'être démarré et est encore en phase de démarrage/récupération ; récupération après crash ; restauration depuis une sauvegarde en cours ; standby pas encore prêt à accepter des requêtes ; arrêt en cours.","fix":"Patientez brièvement et reconnectez-vous ; le serveur accepte les connexions une fois le démarrage terminé. Surveillez la progression de la récupération dans le journal du serveur. Si cela prend du temps, il peut s'agir d'un important rejeu de WAL ou d'un problème de récupération. Ajoutez une logique de relance dans l'application.","tags":["connection","connection refused","starting up","startup","recovery","crash recovery","57P03"]},{"code":"2BP01","title":"dependent_objects_still_exist","severity":"caution","meaning":"La suppression a été bloquée, car d'autres objets dépendent de l'objet à supprimer. DROP échoue tant que des objets dépendants existent.","cause":"Tentative de suppression d'une table alors que des vues/clés étrangères/fonctions en dépendent ; objets dépendant d'un type ou d'un schéma ; DROP sans CASCADE ; colonne/séquence référencée par d'autres objets.","fix":"Supprimez d'abord les objets dépendants ou supprimez avec CASCADE : DROP TABLE my_table CASCADE; (n'oubliez pas que CASCADE supprime aussi les objets dépendants ; soyez prudent). Pour voir les dépendances, lisez le détail de l'erreur (ligne DETAIL) ou inspectez pg_depend/les catalogues concernés. Planifiez correctement l'ordre de suppression.","tags":["dependency","dependent objects","cascade","drop","objects","object","2BP01"]},{"code":"55006","title":"object_in_use","severity":"danger","meaning":"L'opération ne peut pas être effectuée sur l'objet cible (généralement une base de données), car il est utilisé par d'autres sessions. Par exemple, une base de données ayant des connexions actives ne peut pas être supprimée.","cause":"Connexions ouvertes sur la base de données pendant DROP DATABASE ; opération nécessitant un verrou exclusif sur un objet en cours d'utilisation ; autre session détenant l'objet.","fix":"Trouvez et terminez les connexions ouvertes sur la base de données cible : SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'db_name' AND pid <> pg_backend_pid(); Bloquez les nouvelles connexions (si nécessaire). Sur PostgreSQL 13+, supprimez-la en fermant de force les connexions : DROP DATABASE db_name WITH (FORCE);","tags":["in use","being accessed","object","object in use","drop database","connection","55006"]},{"code":"22007","title":"invalid_datetime_format","severity":"caution","meaning":"Une valeur texte n'était pas dans un format valide lors de sa conversion en type date/heure (date, time, timestamp). La valeur ne correspond pas au format date-heure attendu.","cause":"Texte de date dans un format inattendu (confusion de l'ordre jour/mois/année) ; valeurs de jour/d'heure invalides ; conversion d'une chaîne vide ('') en date ; composants manquants ou en trop ; incohérence de paramètres régionaux/de format ; mauvais masque de format pour to_timestamp/to_date.","fix":"Convertissez le texte de date avec le bon format : to_date('2026-07-04', 'YYYY-MM-DD') ou to_timestamp(str, 'YYYY-MM-DD HH24:MI:SS'). Privilégiez le format ISO 8601 (YYYY-MM-DD). Transformez les valeurs vides en NULL avec NULLIF(value,''). Validez les données d'entrée avant le chargement ; identifiez la ligne erronée.","tags":["date","date format","datetime","format","timestamp","to_date","22007"]}]},"errors/mysql-more.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1052","title":"Column 'x' in field list is ambiguous (colonne ambiguë dans la liste des champs)","severity":"caution","meaning":"Une colonne portant le même nom existe dans plusieurs tables de la requête et, comme elle est utilisée sans préfixe de table/d'alias, MySQL ne peut pas savoir à quelle table elle appartient.","cause":"Les deux tables jointes ont une colonne de même nom (par ex. id dans les deux) ; colonne écrite sans qualification (sans préfixe) dans SELECT, WHERE, ON, GROUP BY ou ORDER BY.","fix":"Qualifiez chaque colonne ambiguë avec le nom de la table ou l'alias : SELECT a.id, b.id FROM a JOIN b ON a.id = b.a_id; Donnez aux tables des alias courts et préfixez toutes les colonnes en conflit. Réduisez l'ambiguïté des jointures sur des colonnes communes avec USING(my_column).","tags":["ambiguous","ambiguous column","column","column name","join","alias","1052"]},{"code":"1136","title":"Column count doesn't match value count (le nombre de colonnes ne correspond pas au nombre de valeurs)","severity":"caution","meaning":"Le nombre de colonnes de l'instruction INSERT ne correspond pas au nombre de valeurs de la liste VALUES. Chaque colonne doit recevoir exactement une valeur.","cause":"Liste de colonnes et liste de valeurs d'un INSERT de longueurs différentes ; dans un INSERT sans liste de colonnes, valeurs ne correspondant pas au nombre de colonnes de la table ; virgule manquante/en trop ; structure de la table modifiée sans mise à jour du script.","fix":"Rendez égaux le nombre de colonnes et le nombre de valeurs. Utilisez toujours une liste de colonnes explicite : INSERT INTO my_table (a, b, c) VALUES (1, 2, 3); Vérifiez la structure de la table : DESCRIBE my_table; Faites correspondre une à une les valeurs et les colonnes et corrigez les virgules en trop/manquantes.","tags":["column count","column count mismatch","insert","values","mismatch","value count","1136"]},{"code":"1048","title":"Column 'x' cannot be null (la colonne ne peut pas être NULL)","severity":"caution","meaning":"Tentative d'insertion de NULL dans une colonne définie NOT NULL, ou de mise à NULL de cette colonne. Un champ obligatoire ne peut pas rester vide.","cause":"Aucune valeur fournie pour une colonne obligatoire dans l'INSERT ; UPDATE mettant la colonne à NULL ; absence de DEFAULT pour la colonne ; valeur vide provenant de l'application/d'une sous-requête ; mode SQL strict qui ne remplit pas silencieusement la valeur vide.","fix":"Fournissez une valeur valide pour la colonne obligatoire. Définissez une valeur par défaut pertinente pour la colonne : ALTER TABLE my_table MODIFY my_column INT NOT NULL DEFAULT 0; Si la règle est inutile, autorisez NULL : ALTER TABLE my_table MODIFY my_column INT NULL; Validez les valeurs vides dans l'application avant de les envoyer.","tags":["not null","null","empty value","insert","cannot be null","mandatory","1048"]},{"code":"1292","title":"Incorrect datetime value / truncated incorrect value (valeur datetime incorrecte / valeur incorrecte tronquée)","severity":"caution","meaning":"Une valeur était invalide ou mal formée lors de sa conversion vers le type de la colonne cible (notamment date/heure ou numérique). En mode strict, MySQL la rejette comme une erreur.","cause":"Dates ou valeurs invalides comme '0000-00-00' ; mauvais format de date ; texte non numérique dans une colonne numérique ; mode SQL strict (STRICT_TRANS_TABLES) actif ; problèmes de conversion dus à un jeu de caractères/une collation différent(e).","fix":"Mettez la valeur dans un format valide pour le type de la colonne ('YYYY-MM-DD' pour les dates). Utilisez NULL au lieu de dates vides/invalides. Si nécessaire, donnez un format explicite avec STR_TO_DATE : STR_TO_DATE(str, '%Y-%m-%d'). Si une tolérance temporaire est requise, revoyez sql_mode, mais corrigez de préférence les données. Affinez la recherche pour trouver la ligne fautive.","tags":["datetime","date","truncated","incorrect value","strict mode","format","1292"]},{"code":"1406","title":"Data too long for column (données trop longues pour la colonne)","severity":"caution","meaning":"Une valeur plus longue que la longueur maximale définie de la colonne a été écrite. Par exemple, une chaîne de 20 caractères ne tient pas dans une colonne VARCHAR(10).","cause":"Largeur de colonne trop petite pour les données ; caractères d'un jeu multi-octets (utf8mb4) occupant plus d'espace que prévu ; résultat de concaténation devenant plus long ; mode SQL strict actif (sinon la valeur est tronquée silencieusement).","fix":"Élargissez la colonne : ALTER TABLE my_table MODIFY my_column VARCHAR(255); Pour un contenu très long, envisagez le type TEXT. Si la valeur doit être raccourcie, tronquez-la dans l'application ou avec SUBSTRING. Trouvez la ligne en dépassement : SELECT * FROM my_table WHERE CHAR_LENGTH(my_column) > 10; Ajoutez une validation des saisies.","tags":["data too long","length","varchar","column","column length","truncate","1406"]},{"code":"1071","title":"Specified key was too long; max key length is 767 bytes (clé trop longue ; longueur maximale de 767 octets)","severity":"caution","meaning":"L'index (ou la clé) en cours de création a dépassé la longueur maximale autorisée en octets. Avec les jeux de caractères multi-octets en particulier, les index VARCHAR atteignent rapidement cette limite.","cause":"Index sur une colonne VARCHAR large en utf8mb4 (4 octets par caractère) ; ancien format de ligne InnoDB (REDUNDANT/COMPACT ; en 5.7 et antérieures, également innodb_large_prefix désactivé) ; longueur totale d'un index composite multi-colonnes dépassant la limite ; grande clé primaire.","fix":"Raccourcissez la longueur de la colonne indexée ou utilisez un index sur préfixe : CREATE INDEX idx ON my_table (my_column(191)); Passez à la limite de 3072 octets avec le format de ligne DYNAMIC/COMPRESSED (DYNAMIC est le format par défaut dans MySQL 8.0 ; innodb_large_prefix a été supprimé en 8.0 et n'est nécessaire qu'en 5.7 et antérieures). Si nécessaire, revoyez le jeu de caractères/la longueur de la colonne.","tags":["index","index length","key too long","key","prefix","utf8mb4","1071"]},{"code":"1050","title":"Table already exists (la table existe déjà)","severity":"caution","meaning":"La table en cours de création existe déjà. CREATE TABLE a échoué, car une table portant le même nom est présente.","cause":"Recréation d'une table existante ; script exécuté deux fois ; migrations réappliquées ; autre objet (une vue) utilisant le même nom.","fix":"Utilisez IF NOT EXISTS pour ne créer la table que si elle est absente : CREATE TABLE IF NOT EXISTS my_table (...); Si vous voulez vraiment la recréer, supprimez-la d'abord : DROP TABLE IF EXISTS my_table; Listez les tables existantes : SHOW TABLES; Rendez les migrations réexécutables sans risque (idempotentes).","tags":["table exists","table already exists","duplicate","duplicate table","create table","1050"]},{"code":"1051","title":"Unknown table 'x' (table inconnue)","severity":"caution","meaning":"La table référencée n'existe pas. Aucune table portant le nom donné n'a été trouvée lors d'une suppression, d'une modification ou d'une requête.","cause":"Nom de table mal orthographié ; table jamais créée ou déjà supprimée ; mauvaise base de données (schéma) sélectionnée ; sensibilité à la casse (les noms de tables sont sensibles à la casse sous Linux) ; DROP TABLE sur une table inexistante.","fix":"Listez les tables existantes : SHOW TABLES; et sélectionnez la bonne base de données : USE mydb; Corrigez le nom de la table et sa casse. Pour éviter l'erreur lors de la suppression : DROP TABLE IF EXISTS my_table; Si elle se trouve dans un autre schéma, qualifiez-la sous la forme schema.table.","tags":["unknown table","table not found","does not exist","drop","not exists","1051"]},{"code":"1091","title":"Can't DROP 'x'; check that column/key exists (DROP impossible ; vérifiez que la colonne/clé existe)","severity":"caution","meaning":"La colonne, l'index ou la clé étrangère supprimé avec ALTER TABLE n'existe pas. Un objet inexistant ne peut pas être supprimé.","cause":"Nom d'objet inexistant indiqué dans DROP COLUMN/INDEX/FOREIGN KEY ; nom mal orthographié ; objet déjà supprimé ; confusion entre le nom de la clé étrangère et le nom de l'index ; migration exécutée une seconde fois.","fix":"Vérifiez le nom réel de l'objet : SHOW CREATE TABLE my_table; ou SHOW INDEX FROM my_table; Supprimez-le en utilisant le bon nom. Pour une clé étrangère, utilisez son nom de symbole : ALTER TABLE my_table DROP FOREIGN KEY fk_name; MySQL ne dispose pas de la syntaxe DROP COLUMN IF EXISTS (seul MariaDB la possède) ; dans les migrations idempotentes, vérifiez d'abord l'existence de la colonne via information_schema.columns : SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'my_table' AND column_name = 'my_column';","tags":["cant drop","cannot drop","column","index","foreign key","alter","1091"]},{"code":"1093","title":"You can't specify target table 'x' for update in FROM clause (table cible de la mise à jour interdite dans la clause FROM)","severity":"caution","meaning":"Dans une instruction UPDATE ou DELETE, la table mise à jour/supprimée ne peut pas elle-même être utilisée directement comme source dans une sous-requête (dans FROM). MySQL n'autorise pas une même table à être à la fois cible et source de cette manière.","cause":"Sous-requête lisant directement la même table cible, comme dans UPDATE my_table ... WHERE id IN (SELECT ... FROM my_table ...) ; sous-requête référençant la même table dans un DELETE.","fix":"Encapsulez la sous-requête dans une table dérivée et donnez-lui un nouvel alias pour que MySQL la traite comme une copie distincte : UPDATE my_table SET c = 1 WHERE id IN (SELECT id FROM (SELECT id FROM my_table WHERE condition) AS t); Vous pouvez aussi l'écrire sous forme d'auto-jointure : UPDATE my_table a JOIN my_table b ON ... SET a.c = ...;","tags":["update","delete","target table","same table","subquery","derived table","1093"]},{"code":"1305","title":"PROCEDURE does not exist (la procédure n'existe pas)","severity":"caution","meaning":"La procédure stockée ou la fonction appelée est introuvable dans la base de données indiquée. Aucune routine portant ce nom n'est définie.","cause":"Nom de procédure mal orthographié ; procédure dans la mauvaise base de données ou jamais créée ; mauvais schéma sélectionné avec USE ; absence de privilège EXECUTE pour appeler la procédure ; sensibilité à la casse.","fix":"Vérifiez que la procédure existe et dans quel schéma : SHOW PROCEDURE STATUS WHERE Db = 'mydb'; Sélectionnez la bonne base de données : USE mydb; ou appelez-la avec son nom entièrement qualifié : CALL mydb.my_procedure(...); Accordez les privilèges requis : GRANT EXECUTE ON PROCEDURE mydb.my_procedure TO app_user;","tags":["procedure","routine","does not exist","not found","stored procedure","call","1305"]},{"code":"1227","title":"Access denied; you need (at least one of) the PRIVILEGE (accès refusé ; privilège requis)","severity":"danger","meaning":"L'utilisateur ne dispose pas du privilège requis pour l'opération tentée. L'opération exige un droit qui dépasse les autorisations accordées au compte.","cause":"Privilège d'administration manquant, tel que SUPER, PROCESS, RELOAD ou CREATE USER ; autorisation nécessaire à la lecture d'une vue/table globale non accordée ; opération spécifique (SET GLOBAL, accès au binlog) nécessitant des privilèges élevés.","fix":"Affichez les privilèges actuels : SHOW GRANTS FOR CURRENT_USER(); Accordez l'autorisation requise avec un compte privilégié : GRANT PROCESS ON *.* TO 'app_user'@'host'; (GRANT prend effet immédiatement ; FLUSH PRIVILEGES n'est pas nécessaire). Dans la mesure du possible, utilisez des privilèges dynamiques (par ex. SYSTEM_VARIABLES_ADMIN) au lieu de SUPER. Appliquez le principe du moindre privilège.","tags":["access denied","privilege","missing privilege","permission","grant","access","1227"]},{"code":"3730","title":"Cannot drop table referenced by a foreign key constraint (suppression impossible d'une table référencée par une clé étrangère)","severity":"danger","meaning":"La table ne peut pas être supprimée, car la clé étrangère d'une autre table la référence. Tant que la référence existe, DROP est bloqué.","cause":"Tentative de suppression d'une table parente alors que des tables enfants qui lui sont liées par une FK existent encore ; non-respect de l'ordre des dépendances ; clés étrangères non supprimées au préalable.","fix":"Supprimez d'abord la clé étrangère qui référence la table : ALTER TABLE child_table DROP FOREIGN KEY fk_name; puis supprimez la table. Trouvez les tables/contraintes dépendantes : SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME = 'my_table'; Dans les opérations en masse, SET FOREIGN_KEY_CHECKS = 0; peut être utilisé temporairement, mais soyez prudent et remettez-le à 1 ensuite.","tags":["foreign key","fk","drop table","referenced","reference","constraint","3730"]},{"code":"2013","title":"Lost connection to MySQL server during query (connexion au serveur MySQL perdue pendant la requête)","severity":"danger","meaning":"La connexion entre le client et le serveur MySQL a été coupée pendant l'exécution de la requête. Elle a été interrompue avant que le serveur puisse répondre ou renvoyer les résultats.","cause":"Requête de longue durée dépassant net_read_timeout/net_write_timeout ; serveur terminant la requête/la connexion pour cause de mémoire insuffisante (OOM) ; panne réseau ; dépassement de max_allowed_packet ; crash ou redémarrage du serveur ; très grand jeu de résultats.","fix":"Vérifiez la stabilité du réseau et le journal d'erreurs du serveur ; voyez s'il y a eu un OOM ou un crash. Augmentez les délais : SET GLOBAL net_read_timeout = 120; Découpez les grosses requêtes en lots. Augmentez max_allowed_packet. Ajoutez une logique de reconnexion dans l'application. Optimisez les requêtes longues.","tags":["lost connection","connection dropped","timeout","network","network outage","oom","2013"]},{"code":"1153","title":"Got a packet bigger than 'max_allowed_packet' bytes (paquet plus grand que max_allowed_packet)","severity":"danger","meaning":"Le client ou le serveur a tenté d'envoyer un paquet de données plus grand que la taille de paquet maximale autorisée (max_allowed_packet). Cela se produit généralement avec de grandes valeurs BLOB/TEXT ou de très gros INSERT en masse.","cause":"Insertion de données BLOB/TEXT très volumineuses ; énorme INSERT unique ou INSERT multi-lignes en masse ; requête renvoyant un grand résultat ; petite valeur de max_allowed_packet ; import de grandes lignes avec mysqldump.","fix":"Augmentez max_allowed_packet (côté serveur et côté client) : SET GLOBAL max_allowed_packet = 67108864; (64MB) et reconnectez-vous. Pour le rendre permanent, ajoutez max_allowed_packet = 64M sous [mysqld] dans my.cnf. Découpez les grosses opérations en masse en lots plus petits. Évaluez si vous avez réellement besoin de stocker des valeurs très volumineuses.","tags":["max_allowed_packet","packet","packet size","blob","large data","insert","1153"]},{"code":"1969","title":"MariaDB: Query execution was interrupted (max_statement_time exceeded) (exécution de la requête interrompue)","severity":"caution","meaning":"Propre à MariaDB (ER_STATEMENT_TIMEOUT) : la requête a été interrompue, car elle a dépassé le temps d'exécution maximal défini par max_statement_time. MySQL ne possède pas ce code ; l'équivalent MySQL est l'erreur 3024 (ER_QUERY_TIMEOUT, max_execution_time).","cause":"Dans MariaDB, dépassement de la limite max_statement_time de session/globale ou d'une limite définie avec SET STATEMENT max_statement_time=N FOR ... ; plan inefficace, index manquant, parcours d'un grand volume de données ou attentes de verrous.","fix":"Analysez la requête avec EXPLAIN et accélérez-la en ajoutant des index et en améliorant le plan. Si la requête doit réellement s'exécuter longtemps, relevez la limite dans MariaDB : SET SESSION max_statement_time = 60; (secondes) ou SET STATEMENT max_statement_time = 60 FOR SELECT ...; Si vous utilisez MySQL, consultez l'entrée 3024 (max_execution_time, millisecondes ; hint MAX_EXECUTION_TIME). Découpez les opérations volumineuses.","tags":["timeout","statement timeout","interrupted","query interrupted","max_execution_time","query","1969"]}]},"errors/mssql-more.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"8134","title":"Divide by zero error encountered (erreur de division par zéro)","severity":"caution","meaning":"Une erreur de division par zéro s'est produite, car le diviseur d'une expression arithmétique est nul. SQL Server ne peut pas effectuer la division et arrête la requête.","cause":"Colonne ou expression utilisée comme diviseur valant 0, dénominateur nul dans des calculs de moyenne/de ratio, division sans NULLIF, ou valeurs nulles inattendues dans les données.","fix":"Protégez le dénominateur avec NULLIF (par ex. value / NULLIF(denominator, 0)) ; le résultat devient NULL et une valeur par défaut peut être fournie avec ISNULL/COALESCE. Utilisez CASE WHEN denominator = 0 THEN NULL ELSE value/denominator END. Filtrez les valeurs nulles avec WHERE avant la division. Si nécessaire, évaluez le comportement de SET ARITHABORT/ANSI_WARNINGS.","tags":["math","arithmetic","divide by zero","division by zero","8134"]},{"code":"245","title":"Conversion failed when converting the value to data type (échec de la conversion vers le type de données)","severity":"caution","meaning":"Une valeur n'a pas pu être convertie vers le type de données cible. Par exemple, une conversion implicite/explicite a échoué en raison d'un contenu inadapté lors de la conversion d'une chaîne en int ou en date.","cause":"Conversion d'un texte non numérique en int/decimal, format de date invalide, conversion de type implicite dans des comparaisons de colonnes, types incompatibles dans une condition WHERE ou JOIN, ou données d'entrée mal formées.","fix":"Utilisez les variantes de conversion sûres : TRY_CAST ou TRY_CONVERT (renvoient NULL en cas d'échec). Identifiez les lignes erronées avec ISNUMERIC/TRY_CONVERT. Alignez les types de colonnes et éliminez les conversions implicites. Validez les saisies et utilisez un format explicite pour les dates (par ex. un style CONVERT).","tags":["conversion","type conversion","cast","convert","data type","245"]},{"code":"515","title":"Cannot insert the value NULL into column (insertion de la valeur NULL impossible dans la colonne)","severity":"caution","meaning":"Tentative d'insertion ou de mise à jour d'une valeur NULL dans une colonne définie NOT NULL. L'opération a été rejetée, car la colonne n'autorise pas les valeurs nulles.","cause":"Colonne obligatoire omise de l'instruction INSERT, champs manquants/vides dans les données source, colonne NOT NULL sans valeur par défaut, ou NULL envoyé par l'application.","fix":"Fournissez une valeur valide pour les colonnes obligatoires. Ajoutez des contraintes DEFAULT aux colonnes appropriées. Validez les données source pour détecter les champs manquants et attribuez des valeurs par défaut avec ISNULL/COALESCE. Si la colonne n'a pas réellement besoin d'être obligatoire, revoyez le schéma pour autoriser NULL.","tags":["null","not null","insert","constraint","constraint violation","515"]},{"code":"8115","title":"Arithmetic overflow error converting expression to data type (erreur de dépassement arithmétique lors de la conversion)","severity":"caution","meaning":"La valeur produite par une opération arithmétique ou une conversion a dépassé la plage que peut contenir le type de données cible. Un dépassement s'est produit, car le nombre est trop grand.","cause":"Somme/produit (SUM) dépassant la plage d'int, grande valeur affectée à une colonne de petit type, précision/échelle decimal insuffisante, ou valeur IDENTITY atteignant la limite du type.","fix":"Utilisez un type plus large pour les calculs intermédiaires (par ex. SUM avec CAST(my_column AS bigint)). Augmentez la précision/l'échelle decimal. Agrandissez le type de colonne pour qu'il contienne les données. Convertissez avant le calcul les expressions à risque de dépassement et limitez les plages de saisie.","tags":["overflow","arithmetic overflow","arithmetic","math","int","bigint","8115"]},{"code":"512","title":"Subquery returned more than 1 value (la sous-requête a renvoyé plus d'une valeur)","severity":"caution","meaning":"Une sous-requête utilisée dans un contexte attendant une valeur unique (=, <, >, comparaison ou affectation) a renvoyé plusieurs lignes. SQL Server attendait une seule valeur scalaire mais a rencontré plusieurs valeurs.","cause":"Sous-requête supposée renvoyer une seule valeur pour la comparaison renvoyant plusieurs lignes, condition de correspondance non unique, filtre manquant, ou requête corrélée mal construite.","fix":"Réduisez la sous-requête à une seule ligne (par ex. TOP 1, MAX/MIN, ou un GROUP BY approprié). Si plusieurs valeurs sont attendues, utilisez IN ou EXISTS au lieu de =. Réécrivez avec un JOIN. Examinez pourquoi la sous-requête renvoie plusieurs lignes et corrigez les conditions de filtre/de clé.","tags":["subquery","scalar subquery","more than one","512","in","exists"]},{"code":"207","title":"Invalid column name (nom de colonne invalide)","severity":"caution","meaning":"Le nom de colonne référencé dans la requête est introuvable dans la table/la vue. Aucune colonne ne porte ce nom, ou elle est hors de portée.","cause":"Erreur d'orthographe/de casse, mauvais alias de table, colonne pas encore ajoutée ou renommée, mauvais contexte de schéma/table, ou omission des crochets au lieu des guillemets.","fix":"Vérifiez le nom de la colonne et la table (consultez sys.columns ou l'Explorateur d'objets). Utilisez le bon alias et le bon schéma. Pour les colonnes nouvellement ajoutées, assurez-vous que l'objet est à jour ; recompilez la procédure stockée si nécessaire. Utilisez des crochets pour les noms contenant des espaces/caractères spéciaux.","tags":["column","column name","invalid column","invalid column name","alias","207"]},{"code":"156","title":"Incorrect syntax near the keyword (syntaxe incorrecte près du mot-clé)","severity":"caution","meaning":"Il y a une erreur de syntaxe près d'un mot-clé. SQL Server n'a pas pu analyser l'instruction, car un mot réservé est utilisé à une position inattendue.","cause":"Utilisation d'un mot réservé (par ex. ORDER, USER, TABLE) comme nom de colonne/d'objet sans crochets, virgule/parenthèse manquante, clauses dans le mauvais ordre, ou clause mal placée (WHERE, GROUP BY).","fix":"Inspectez la zone autour du mot-clé indiqué par l'erreur. Si vous utilisez des mots réservés comme noms d'objets, encadrez-les de crochets (par ex. [User]). Corrigez les virgules, parenthèses et l'ordre des clauses manquants. Testez la syntaxe en découpant la requête en parties.","tags":["syntax","syntax error","keyword","reserved word","156"]},{"code":"2812","title":"Could not find stored procedure (procédure stockée introuvable)","severity":"caution","meaning":"La procédure stockée appelée avec EXEC est introuvable dans la portée de la base de données/du schéma courant. Aucune procédure ne porte ce nom.","cause":"Faute de frappe dans le nom de la procédure, préfixe de schéma manquant ou erroné (par ex. dbo. omis), procédure située dans une autre base de données, pas encore créée, ou non visible en raison des autorisations.","fix":"Vérifiez le nom et le schéma de la procédure ; utilisez le nom entièrement qualifié dans l'appel (par ex. EXEC dbo.ProcedureName). Assurez-vous d'être dans la bonne base de données (USE DatabaseName). Vérifiez que la procédure existe (sys.procedures / OBJECT_ID). Accordez l'autorisation EXECUTE requise et vérifiez que le script de déploiement s'est exécuté.","tags":["stored procedure","procedure","sp","not found","procedure not found","2812"]},{"code":"8623","title":"The query processor ran out of internal resources and could not produce a query plan (ressources internes du processeur de requêtes épuisées ; aucun plan produit)","severity":"danger","meaning":"Le processeur de requêtes a épuisé ses ressources internes pour une requête trop complexe et n'a pas pu produire de plan d'exécution. La requête ne peut pas être optimisée, car elle est trop volumineuse/complexe.","cause":"Requêtes avec de très longues listes IN ou de nombreuses clauses OR/UNION, nombre excessif de JOIN, énormes requêtes générées automatiquement, requêtes avec trop de paramètres, ou arbres d'expressions excessivement profonds.","fix":"Simplifiez la requête : déplacez les longues listes IN dans une table temporaire/un paramètre table (table-valued parameter), réduisez les conditions OR avec UNION ALL ou une réécriture, et découpez la requête complexe en parties. Réduisez le nombre de JOIN. Traitez par lots les requêtes générées automatiquement. Revoyez les index et la conception des requêtes si nécessaire.","tags":["query processor","internal resources","plan","complex query","8623","danger"]},{"code":"4104","title":"The multi-part identifier could not be bound (l'identificateur en plusieurs parties n'a pas pu être lié)","severity":"caution","meaning":"Un identificateur en plusieurs parties séparées par des points (par ex. table.column ou alias.column) n'a pas pu être résolu. SQL Server n'a pas pu lier ce nom à une table/colonne de la requête.","cause":"Alias de table erroné ou non défini, référence à une table absente de FROM/JOIN, colonne attribuée à la mauvaise table, utilisation du nom réel de la table après la définition d'un alias, ou faute de frappe.","fix":"Vérifiez que les alias utilisés sont définis dans la clause FROM/JOIN. Qualifiez la colonne avec la bonne table/le bon alias. Si vous avez donné un alias à une table, utilisez cet alias partout (et non le nom réel). Ajoutez la table manquante au JOIN et vérifiez les noms.","tags":["identifier","multi-part","multi-part identifier","bind","alias","4104"]},{"code":"2714","title":"There is already an object named in the database (un objet portant ce nom existe déjà dans la base)","severity":"caution","meaning":"Un objet portant le même nom que celui en cours de création (table, vue, procédure, etc.) existe déjà dans la base de données. L'instruction CREATE a échoué en raison d'un conflit de noms.","cause":"Exécution du même script CREATE une seconde fois, objet existant non supprimé au préalable, hypothèse de schéma différente, scripts de déploiement/migration non idempotents, ou conflit de nommage.","fix":"Vérifiez d'abord si l'objet existe : créez-le conditionnellement avec IF OBJECT_ID('dbo.Name') IS NULL, ou utilisez CREATE OR ALTER (pour les procédures/vues/fonctions). Si nécessaire, supprimez (DROP) l'objet existant et recréez-le. Écrivez des scripts de migration idempotents. Vérifiez que vous travaillez dans le bon schéma/la bonne base de données.","tags":["already exists","object exists","create","object","duplicate object","2714"]},{"code":"601","title":"Could not continue scan with NOLOCK due to data movement (poursuite du parcours NOLOCK impossible en raison d'un déplacement de données)","severity":"caution","meaning":"Lors d'un parcours avec NOLOCK (READ UNCOMMITTED), le parcours n'a pas pu continuer en raison d'un déplacement concurrent de données (fractionnements/déplacements de pages). La lecture sans verrou a rencontré un état incohérent.","cause":"Pages parcourues déplacées par des mises à jour/suppressions/fractionnements de pages concurrents lors d'une lecture sous le hint NOLOCK ou le niveau d'isolation READ UNCOMMITTED.","fix":"Relancez la requête (c'est généralement transitoire). Évitez le hint NOLOCK dans la mesure du possible ; utilisez l'isolation READ COMMITTED SNAPSHOT (RCSI) pour des lectures non bloquantes mais cohérentes. Sur les tables à forte charge d'écriture, privilégiez l'isolation snapshot aux lectures sales (dirty reads).","tags":["nolock","scan","data movement","601","isolation","read uncommitted"]},{"code":"50000","title":"Erreur personnalisée levée avec RAISERROR","severity":"caution","meaning":"Il s'agit du numéro par défaut (50000) des erreurs personnalisées (définies par l'utilisateur) levées avec RAISERROR sans indiquer d'ID de message. La logique de l'application/de la procédure a volontairement levé une erreur.","cause":"Erreur personnalisée levée avec RAISERROR ou THROW dans une procédure stockée ou un script lors d'une violation de règle métier, d'un échec de validation, d'un contrôle de flux volontaire, ou de messages d'exception personnalisés.","fix":"Lisez le texte du message RAISERROR/THROW pour comprendre la violation réelle de la règle métier. Interceptez cette erreur côté application avec TRY...CATCH et obtenez les détails avec ERROR_MESSAGE()/ERROR_NUMBER(). Définissez des messages avec sp_addmessage pour donner des numéros explicites à vos erreurs personnalisées. Corrigez la condition déclenchante dans la logique métier.","tags":["raiserror","custom error","user-defined error","throw","50000","try catch"]},{"code":"3902","title":"The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION (COMMIT TRANSACTION sans BEGIN TRANSACTION correspondant)","severity":"caution","meaning":"Un COMMIT TRANSACTION a été exécuté, mais il n'existe aucun BEGIN TRANSACTION ouvert correspondant. Aucune transaction à valider n'a été trouvée, car le compteur de transactions (@@TRANCOUNT) vaut zéro.","cause":"Utilisation déséquilibrée de BEGIN/COMMIT/ROLLBACK, transaction déjà annulée après une erreur, gestion incorrecte des compteurs de transactions imbriquées, ou nouvel appel à COMMIT après un ROLLBACK dans un bloc CATCH.","fix":"Équilibrez le nombre d'instructions BEGIN et COMMIT. Vérifiez @@TRANCOUNT avant de valider (IF @@TRANCOUNT > 0 COMMIT). Dans TRY...CATCH, en cas d'erreur, effectuez le ROLLBACK en fonction de XACT_STATE()/@@TRANCOUNT et évitez un COMMIT inutile. Revoyez la logique des transactions imbriquées.","tags":["transaction","commit","begin","trancount","transaction count","3902"]}]},"errors/mongodb-more.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"121","title":"DocumentValidationFailure","severity":"caution","meaning":"Le document inséré ou mis à jour a été rejeté, car il ne satisfait pas les règles de validation JSON Schema (validator) définies sur la collection.","cause":"Le document n'est pas conforme au schéma : champ obligatoire manquant, type BSON erroné, valeur hors de la plage/du motif autorisé, champs supplémentaires interdits, ou violation de règle alors que validationAction vaut 'error'.","fix":"Inspectez les détails de l'erreur (errInfo) pour voir quelle règle a été violée. Rendez le document conforme au schéma : ajoutez les champs obligatoires et corrigez les types. Revoyez le validator de la collection avec collMod. validationLevel/validationAction peuvent être assouplis temporairement, mais conserver les règles est préférable pour la qualité des données.","tags":["validation","validation","schema","schema","121","document"]},{"code":"40","title":"ConflictingUpdateOperators","severity":"caution","meaning":"Une mise à jour a appliqué plusieurs opérateurs de mise à jour en conflit au même champ (chemin). MongoDB ne peut pas modifier deux fois le même chemin dans une seule opération.","cause":"Opérateurs en conflit ciblant le même champ dans un document de mise à jour (par ex. à la fois $set et $inc, ou modification simultanée d'un chemin parent et d'un chemin enfant), mise à jour mal construite, ou conflit de chemin imbriqué.","fix":"Fusionnez les opérateurs en conflit sur le même champ en un seul opérateur, ou découpez la mise à jour en opérations distinctes. Ne modifiez pas simultanément un chemin parent et un chemin enfant (par ex. a et a.b). Revoyez le document de mise à jour et assurez-vous que chaque champ n'est modifié qu'une seule fois.","tags":["update","update","operator","conflict","conflict","40"]},{"code":"66","title":"ImmutableField (impossible de mettre à jour _id)","severity":"caution","meaning":"Tentative de mise à jour d'un champ immuable, généralement le champ _id du document. _id ne peut pas être modifié après la création du document.","cause":"Mise à jour tentant de modifier le champ _id, mise à jour non autorisée d'un champ de shard key, ou affectation d'une nouvelle valeur à un champ considéré comme immuable.","fix":"Ne mettez pas à jour la valeur _id ; si un autre _id est nécessaire, insérez le document avec le nouvel _id et supprimez l'ancien (insert + delete). Respectez les règles de MongoDB pour les mises à jour de shard key. Retirez les champs immuables du document de mise à jour et ne définissez que les champs modifiables.","tags":["immutable","_id","immutable","update","shard key","66"]},{"code":"2","title":"BadValue","severity":"caution","meaning":"Une valeur invalide ou inattendue a été passée à une commande ou à une requête. MongoDB n'a pas pu interpréter l'argument comme une valeur acceptable.","cause":"Argument d'opérateur invalide, nombre hors limites, valeur d'option non prise en charge, spécification de tri mal formée, expression de requête mal formée, ou combinaison de paramètres inappropriée.","fix":"Corrigez l'argument en vous basant sur l'indication champ/valeur du message d'erreur. Validez les options des commandes et requêtes par rapport à la documentation. Vérifiez le format des expressions sort/projection/aggregate. Ramenez les plages de valeurs et les types dans les limites valides.","tags":["badvalue","invalid value","argument","2","query"]},{"code":"14","title":"TypeMismatch","severity":"caution","meaning":"Le type de la valeur fournie ne correspond pas au type BSON attendu. Un autre type de données était attendu pour un champ ou un argument.","cause":"Texte fourni là où un nombre est attendu, chaîne utilisée au lieu d'une date, valeur unique envoyée là où un tableau est attendu, argument de type erroné passé à un opérateur d'agrégation/de requête, ou incohérence de types dans le schéma.","fix":"Fournissez le bon type BSON pour le champ/l'argument. Convertissez les types dans la couche applicative avant d'enregistrer les données (par ex. écrivez les nombres sous forme de vraies valeurs Number). Vérifiez les types attendus par les opérateurs de requête et d'agrégation. Imposez la cohérence des types avec un validator de schéma.","tags":["typemismatch","type mismatch","bson","type","14"]},{"code":"10334","title":"BSONObjectTooLarge","severity":"danger","meaning":"Un document BSON a dépassé la taille maximale autorisée (16 MB). MongoDB ne peut pas stocker ni renvoyer un document unique au-delà de cette limite.","cause":"Dépassement de la limite de 16 MB par ajout continu de données/d'éléments de tableau à un document, très grands tableaux imbriqués, énormes documents produits par une agrégation, ou grandes données binaires stockées directement dans le document.","fix":"Revoyez le modèle de données : séparez les grands tableaux dans des collections distinctes et utilisez un modèle par référence. Utilisez GridFS pour les gros fichiers binaires. Bornez les tableaux non bornés (par ex. le modèle capped/bucket). Maîtrisez la taille des documents dans les étapes d'agrégation.","tags":["bson","16mb","too large","large document","gridfs","10334","danger"]},{"code":"46","title":"LockBusy","severity":"caution","meaning":"Le verrou ou la ressource demandé n'a pas pu être acquis immédiatement, car il était détenu par une autre opération, et l'opération a été rejetée au lieu d'attendre. Les attentes de verrou qui se terminent par un délai expiré ont un code distinct (24 LockTimeout).","cause":"Opérations d'administration/DDL concurrentes sur la même collection ou base de données (par ex. deux constructions d'index simultanées, collMod, drop ou rename), conflit avec une opération de maintenance en cours, ou opération interne ne pouvant pas attendre, même brièvement, un verrou et rencontrant une ressource occupée.","fix":"Trouvez l'opération en conflit : utilisez db.currentOp() pour inspecter les opérations DDL ou de maintenance en cours sur le même espace de noms. Relancez la commande une fois l'opération en conflit terminée. Sérialisez les opérations d'administration/DDL et ne les exécutez pas en parallèle sur la même collection. Si le problème persiste, recherchez dans les journaux mongod l'opération qui détient la ressource.","tags":["lock","lock","lockbusy","busy","ddl","46"]},{"code":"96","title":"OperationFailed","severity":"danger","meaning":"Échec générique d'opération ; une erreur inattendue s'est produite pendant l'exécution de la commande et l'opération n'a pas pu se terminer. C'est l'erreur générique utilisée lorsqu'aucun code plus spécifique n'existe.","cause":"Erreur interne côté serveur, épuisement des ressources, état incohérent, défaillance d'un sous-système (stockage, réplication), ou diverses conditions d'échec propres à une commande particulière.","fix":"Déterminez la cause racine en examinant le texte complet du message d'erreur et les journaux du serveur (mongod.log). Relancez l'opération ; la condition peut être transitoire. Vérifiez les ressources (disque, mémoire) et l'état du replica set. Si le problème se reproduit, revoyez la commande et ses données d'entrée.","tags":["operationfailed","operation failed","generic error","96","danger"]},{"code":"72","title":"InvalidOptions","severity":"caution","meaning":"Des options invalides ou mutuellement incompatibles ont été passées à une commande. La combinaison d'options indiquée n'est pas prise en charge pour cette commande.","cause":"Nom d'option non pris en charge, options mutuellement exclusives utilisées ensemble, option non valide pour la version en cours d'exécution, combinaison write concern/read concern invalide, ou conflit d'options d'index/de collation.","fix":"Vérifiez dans la documentation les options acceptées par la commande et supprimez celles qui sont incompatibles. Assurez-vous que votre version prend en charge l'option. Revoyez les paramètres write/read concern et de collation. N'utilisez que des combinaisons d'options valides.","tags":["invalidoptions","invalid option","options","72","command"]},{"code":"9","title":"FailedToParse","severity":"caution","meaning":"Le serveur n'a pas pu analyser la commande, la requête ou l'expression envoyée. La structure est syntaxiquement invalide ou n'est pas au format attendu.","cause":"Structure JSON/BSON mal formée, champs manquants ou en trop, utilisation incorrecte d'opérateurs, syntaxe de pipeline d'agrégation invalide, ou document de commande ne correspondant pas au format attendu.","fix":"Corrigez la structure de la commande/requête/du pipeline conformément à la documentation. Validez la structure JSON (accolades/virgules manquantes, noms de champs erronés). Vérifiez la syntaxe des étapes d'agrégation. Assurez-vous que les opérateurs sont utilisés correctement et que les arguments sont fournis au format attendu.","tags":["failedtoparse","parse","parsing","syntax","9","query"]},{"code":"134","title":"ReadConcernMajorityNotAvailableYet","severity":"caution","meaning":"Une lecture avec le read concern majority a été demandée, mais aucun snapshot validé par la majorité n'est encore disponible. Le serveur ne peut pas fournir ce niveau de cohérence pour le moment.","cause":"Le replica set vient de démarrer ou est instable, les écritures majority n'ont pas encore été validées, retard de réplication, pas assez de membres joignables, ou cluster encore en phase de démarrage.","fix":"Patientez brièvement et relancez ; la lecture réussit dès que le snapshot majority est disponible. Vérifiez l'état du replica set et le retard de réplication (rs.status()). Assurez-vous que suffisamment de membres sont démarrés. Si ce n'est pas requis, envisagez un read concern inférieur (par ex. local).","tags":["readconcern","majority","majority","replica","134","consistency"]},{"code":"202","title":"NetworkInterfaceExceededTimeLimit","severity":"danger","meaning":"Une requête réseau (par ex. communication entre membres ou connexion client) a dépassé sa limite de temps. L'opération n'a pas pu se terminer en raison d'un délai réseau expiré.","cause":"Latence/panne réseau, membre cible lent ou injoignable, charge excessive, valeurs de délai mal configurées, problèmes DNS/de connexion, ou membres géographiquement éloignés.","fix":"Vérifiez la connectivité et la latence du réseau ; vérifiez l'accessibilité entre les membres. Inspectez l'état et la charge du membre cible. Configurez les délais (connect/socket timeout) pour votre environnement. Ajoutez une logique de relance. Si le problème persiste, revoyez la configuration de l'infrastructure/du réseau.","tags":["network","network","timeout","timeout","202","danger"]}]},"errors/redis-more.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"NOPERM","title":"NOPERM this user has no permissions to run the command or access one of the keys (l'utilisateur n'a pas l'autorisation d'exécuter la commande ou d'accéder à une clé)","severity":"caution","meaning":"L'utilisateur ACL authentifié n'est pas autorisé pour la commande qu'il veut exécuter ou la clé à laquelle il accède. Redis rejette l'opération selon les règles ACL.","cause":"L'utilisateur ACL ne dispose pas de l'autorisation de commande/catégorie, le motif de clé ne figure pas dans sa liste d'autorisations, une restriction de canal (pub/sub) s'applique, ou l'utilisateur a reçu des privilèges de portée étroite.","fix":"Inspectez les autorisations de l'utilisateur : ACL GETUSER app_user. Ajoutez les commandes/catégories et motifs de clés requis (par ex. ACL SETUSER app_user +get +set ~app:*). Selon le principe du moindre privilège, n'accordez que les autorisations nécessaires. Vérifiez que vous êtes connecté avec le bon utilisateur.","tags":["noperm","acl","privilege","permission denied","permission","user"]},{"code":"ERR value is not an integer or out of range","title":"ERR value is not an integer or out of range (la valeur n'est pas un entier ou est hors limites)","severity":"caution","meaning":"Une commande attendant un entier (par ex. INCR, EXPIRE, le count de LPOP) a reçu une valeur non numérique ou hors limites. Redis n'a pas pu interpréter la valeur comme un entier.","cause":"La clé ciblée par INCR/DECR contient une valeur texte (non entière), argument fourni sous forme de texte au lieu d'un nombre, nombre trop grand/petit pour la plage, ou valeur négative là où ce n'est pas autorisé.","fix":"Vérifiez la valeur de la clé : GET mykey et TYPE mykey. Pour les opérations numériques, assurez-vous que la clé contient une valeur entière. Vérifiez que les arguments sont des entiers valides dans la bonne plage. Si nécessaire, réinitialisez la valeur (SET mykey 0) ou recréez-la avec le bon type.","tags":["err","integer","not an integer","range","incr","value"]},{"code":"ERR value is not a valid float","title":"ERR value is not a valid float (la valeur n'est pas un flottant valide)","severity":"caution","meaning":"Une commande attendant un nombre décimal (float) (par ex. INCRBYFLOAT, un score ZADD) a reçu une valeur qui n'est pas un nombre à virgule flottante valide. Redis n'a pas pu analyser la valeur comme un float.","cause":"La clé ciblée par INCRBYFLOAT ne contient pas de float valide, argument non numérique, mauvais séparateur décimal (un point est requis, pas une virgule) ou nombre mal formé.","fix":"Vérifiez la valeur de la clé avec GET et assurez-vous qu'elle contient un float valide. Utilisez un point comme séparateur décimal (par ex. 3.14). Vérifiez que l'argument est numérique et correctement formaté. Si nécessaire, réinitialisez la valeur à un float valide.","tags":["err","float","decimal","incrbyfloat","zadd","value"]},{"code":"ERR increment or decrement would overflow","title":"ERR increment or decrement would overflow (l'incrément ou le décrément provoquerait un dépassement)","severity":"caution","meaning":"Un INCR/DECR (ou INCRBY/DECRBY) a été rejeté, car le résultat dépasserait la plage des entiers signés sur 64 bits. La valeur dépasserait la borne supérieure ou inférieure.","cause":"Valeur de la clé très proche de la limite int64 (environ 9,2 trillions) de sorte que l'incrément/le décrément déborde, argument INCRBY très grand, ou compteur croissant sans contrôle.","fix":"Concevez les valeurs de compteur pour qu'elles restent dans les bornes int64. Pour les grands comptages, appliquez une mise à l'échelle ou des réinitialisations périodiques. Maintenez l'incrément (l'argument d'INCRBY) dans une plage raisonnable. Si de très grandes valeurs sont nécessaires, utilisez une autre stratégie de comptage dans la couche applicative.","tags":["err","overflow","integer overflow","incr","decr","int64"]},{"code":"ERR max number of clients reached","title":"ERR max number of clients reached (nombre maximal de clients atteint)","severity":"danger","meaning":"Le nombre de clients connectés a atteint la limite maxclients et les nouvelles connexions ne peuvent pas être acceptées. Redis refuse toute nouvelle connexion client.","cause":"Fuite de connexions (connexions jamais fermées), valeur de maxclients insuffisante, pool de connexions mal configuré, afflux soudain de clients, ou limite de descripteurs de fichiers du système d'exploitation trop basse.","fix":"Inspectez les connexions actuelles : CLIENT LIST et INFO clients. Corrigez les fuites de connexions ; dimensionnez correctement le pool client et réutilisez les connexions. Si nécessaire, relevez maxclients (CONFIG SET maxclients) et la limite ulimit (descripteurs de fichiers) du système d'exploitation. Définissez un timeout pour les connexions inactives.","tags":["err","maxclients","connection","client","limit","danger"]},{"code":"BUSYKEY","title":"BUSYKEY Target key name already exists (le nom de clé cible existe déjà)","severity":"caution","meaning":"RESTORE (ou une commande similaire) a échoué, car la clé cible existe déjà. Par défaut, Redis n'écrase pas une clé existante.","cause":"Le nom de clé restauré avec RESTORE existe déjà dans la base de données, données précédentes non nettoyées, ou tentatives de restauration multiples vers la même clé.","fix":"Si vous souhaitez écraser la clé existante, ajoutez l'option REPLACE à RESTORE. Vous pouvez aussi supprimer d'abord la clé cible (DEL mykey) ou utiliser un autre nom de clé. Vérifiez l'existence au préalable avec EXISTS mykey.","tags":["busykey","restore","already exists","target key","replace","key"]},{"code":"ERR no such key","title":"ERR no such key (clé inexistante)","severity":"caution","meaning":"La clé sur laquelle opérer est introuvable dans la base de données. Les commandes qui exigent une clé existante, comme RENAME, DUMP et MOVE, ne fonctionnent pas sur une clé inexistante.","cause":"La clé n'a jamais été créée, elle a expiré et a été supprimée en raison de son TTL, nom/orthographe de clé erroné, mauvais index de base de données (SELECT), ou clé déjà supprimée avec DEL.","fix":"Vérifiez que la clé existe : EXISTS mykey. Assurez-vous d'utiliser le bon nom de clé et la bonne base de données (SELECT db). RENAME exige que la clé source existe. Revoyez la logique pour les clés qui expirent via TTL. Recréez la clé si nécessaire.","tags":["err","no such key","key not found","rename","dump","key"]},{"code":"ERR DUMP payload version or checksum are wrong","title":"ERR DUMP payload version or checksum are wrong (version ou checksum du payload DUMP incorrect)","severity":"caution","meaning":"Les données sérialisées (sortie de DUMP) passées à RESTORE sont invalides ; la version est incompatible ou la vérification du checksum a échoué. Redis ne peut pas restaurer les données en toute sécurité.","cause":"Données DUMP corrompues/modifiées, transfert entre des versions de Redis différentes et incompatibles, corruption lors de l'encodage/du décodage (par ex. mauvaise gestion des octets), ou payload manquant/tronqué.","fix":"Assurez-vous que les données DUMP sont transportées intactes (binary-safe) ; transférez-les sous forme d'octets bruts plutôt que via un encodage texte. Vérifiez que les versions de Redis source et cible sont compatibles. Effectuez un nouveau DUMP et transférez-le à nouveau. Pour une migration, envisagez la commande MIGRATE au lieu de RESTORE.","tags":["err","dump","restore","checksum","checksum mismatch","version"]},{"code":"NOREPLICAS","title":"NOREPLICAS Not enough good replicas to write (pas assez de réplicas sains pour écrire)","severity":"danger","meaning":"En raison du paramètre min-replicas-to-write, les écritures sont rejetées, car il n'y a pas assez de réplicas sains. Redis bloque les écritures pour la sécurité des données.","cause":"Pas assez de réplicas connectés/à jour, crashs de réplicas, retard de réplication dépassant la limite min-replicas-max-lag, problèmes réseau, ou réplicas manquants après un failover.","fix":"Vérifiez l'état des réplicas : INFO replication. Remettez en service les réplicas arrêtés et réduisez le retard de réplication. Corrigez la connectivité réseau. Revoyez les paramètres min-replicas-to-write / min-replicas-max-lag pour votre environnement (équilibre entre durabilité et disponibilité). Prévoyez suffisamment de réplicas.","tags":["noreplicas","replica","write","min-replicas","replication","danger"]},{"code":"ERR This instance has cluster support disabled","title":"ERR This instance has cluster support disabled (la prise en charge du cluster est désactivée sur cette instance)","severity":"caution","meaning":"Une commande de cluster (CLUSTER ...) a été exécutée, mais cette instance Redis n'a pas été démarrée en mode cluster. La commande a été rejetée, car la prise en charge du cluster est désactivée.","cause":"cluster-enabled yes non défini, instance fonctionnant en mode standalone, commandes de cluster envoyées à un serveur standalone, ou connexion au mauvais serveur.","fix":"Si vous souhaitez utiliser les fonctionnalités de cluster, démarrez l'instance en mode cluster : définissez cluster-enabled yes dans la configuration, redémarrez, et formez le cluster à partir des nœuds. Si vous restez en mode standalone, n'utilisez pas les commandes de cluster. Vérifiez que vous êtes connecté au bon serveur (cluster).","tags":["err","cluster","cluster mode","disabled","standalone","config"]},{"code":"ERR unknown subcommand","title":"ERR unknown subcommand or wrong number of arguments (sous-commande inconnue ou nombre d'arguments erroné)","severity":"caution","meaning":"Une commande conteneur (par ex. CONFIG, CLIENT, ACL, XINFO) a reçu une sous-commande invalide, ou la sous-commande a reçu un nombre d'arguments erroné. Redis n'a pas reconnu la sous-commande.","cause":"Faute de frappe dans le nom de la sous-commande, sous-commande absente de la version de Redis utilisée, nombre d'arguments erroné/manquant, ou syntaxe de sous-commande incorrecte.","fix":"Utilisez le bon nom de sous-commande et les bons arguments ; consultez la sous-commande HELP de la commande (par ex. CONFIG HELP, CLIENT HELP). Vérifiez que votre version de Redis prend en charge cette sous-commande. Corrigez le nombre d'arguments et la syntaxe conformément à la documentation.","tags":["err","subcommand","unknown subcommand","argument","config","client"]},{"code":"ERR Client sent AUTH, but no password is set","title":"ERR AUTH <password> called without any password configured for the default user (Redis 6+) / ERR Client sent AUTH, but no password is set (Redis <6) (AUTH appelé sans mot de passe configuré)","severity":"caution","meaning":"Le client a tenté de s'authentifier avec AUTH, mais aucun mot de passe (requirepass) ni mot de passe ACL n'est défini sur le serveur. AUTH a été rejeté, car il n'y a aucun identifiant à vérifier. Dans ce cas, Redis 6+ renvoie \"ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?\" ; \"Client sent AUTH, but no password is set\" est le message antérieur à Redis 6.","cause":"requirepass vide sur le serveur, client envoyant AUTH inutilement, mot de passe défini dans la chaîne de connexion alors que le serveur n'en a pas, ou incohérence de configuration.","fix":"Si le serveur n'exige pas de mot de passe, supprimez la commande AUTH/le paramètre de mot de passe du client. Si vous souhaitez une protection par mot de passe, définissez requirepass sur le serveur (CONFIG SET requirepass password) ou définissez un mot de passe pour l'utilisateur ACL. Rendez cohérentes les configurations du client et du serveur.","tags":["err","auth","password","requirepass","authentication","acl"]}]},"errors/elasticsearch-more.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"action_request_validation_exception","title":"action_request_validation_exception","severity":"caution","meaning":"La requête a échoué à la validation avant son exécution. Un champ obligatoire est manquant ou des champs sont invalides ; Elasticsearch rejette donc l'opération.","cause":"Paramètres obligatoires manquants (par ex. aucun nom d'index indiqué), corps vide, combinaison de champs invalide, ou informations requises non fournies dans une requête bulk/index.","fix":"Revoyez la liste des champs manquants/invalides dans le message d'erreur et fournissez les champs obligatoires. Complétez le corps et les paramètres de la requête conformément à la documentation de l'API. Dans les requêtes bulk, vérifiez que chaque ligne d'action comporte des métadonnées et un corps corrects.","tags":["validation","validation error","request","request error","action"]},{"code":"index_closed_exception","title":"index_closed_exception","severity":"caution","meaning":"L'index cible est fermé. Un index fermé ne sert pas les requêtes de lecture/écriture ; l'opération n'a donc pas pu être effectuée.","cause":"L'index a été fermé auparavant avec l'API close, fermé lors d'opérations de maintenance/ILM, ou désactivé pour économiser des ressources.","fix":"Rouvrez l'index : POST /index_name/_open. Après l'ouverture, attendez que les shards soient attribués (green/yellow). Examinez pourquoi l'index a été fermé (politique ILM, action manuelle). Revoyez la stratégie consistant à garder fermés les index rarement consultés.","tags":["index closed","closed index","open","index","closed"]},{"code":"unavailable_shards_exception","title":"unavailable_shards_exception","severity":"danger","meaning":"Un ou plusieurs shards nécessaires à l'opération ne sont pas disponibles ; la requête (généralement une écriture) n'a donc pas pu se terminer. Pas assez de copies de shards actives n'ont été trouvées.","cause":"Shards non attribués en raison d'un crash/départ de nœud, dépassement du disk watermark, réplicas manquants, condition wait_for_active_shards non satisfaite, ou état du cluster red.","fix":"Vérifiez l'état du cluster et des shards : GET _cluster/health et GET _cat/shards?v. Examinez pourquoi les shards ne sont pas attribués : GET _cluster/allocation/explain. Remettez en service les nœuds arrêtés, libérez de l'espace disque (watermark) et revoyez les paramètres de réplicas. Si nécessaire, définissez wait_for_active_shards de façon appropriée.","tags":["shard","unavailable","inaccessible","cluster","allocation","danger"]},{"code":"query_shard_exception","title":"query_shard_exception","severity":"caution","meaning":"Une requête a échoué lors de son exécution sur un shard particulier. Cela se produit généralement parce que la requête est incompatible avec le mapping ou a une structure invalide.","cause":"Interrogation d'un champ inexistant ou d'un champ de type erroné, tentative d'agrégation/de tri sur un champ text (fielddata désactivé), syntaxe de requête invalide, mauvaise analyse, ou type de champ incompatible.","fix":"Revoyez le champ/la raison dans les détails de l'erreur. Vérifiez le mapping du champ interrogé : GET /index/_mapping. Pour le tri/l'agrégation, utilisez le sous-champ keyword (field.keyword) au lieu de text. Corrigez la syntaxe de la requête et assurez-vous que les types de champs sont compatibles avec la requête.","tags":["query","query error","shard","mapping","fielddata","query shard"]},{"code":"script_exception","title":"script_exception","severity":"caution","meaning":"Une erreur s'est produite lors de la compilation ou de l'exécution d'un script (généralement Painless). Le script est mal écrit ou a levé une exception à l'exécution.","cause":"Erreur de syntaxe Painless, accès à une valeur null, incohérence de types, utilisation d'un champ/d'une variable inexistant, dépassement de la limite de compilation des scripts, ou appel d'API non autorisé.","fix":"Revoyez les détails de compilation/d'exécution dans le message d'erreur. Corrigez la syntaxe du script et l'accès aux champs ; ajoutez des contrôles de null (par ex. doc['field'].size() > 0). Paramétrez les scripts fréquemment utilisés (stored/params) afin de ne pas dépasser la limite de compilation. Utilisez des noms et des types de champs corrects.","tags":["script","painless","compilation","runtime","script exception"]},{"code":"number_format_exception","title":"number_format_exception","severity":"caution","meaning":"Une valeur qui devrait être numérique n'a pas pu être convertie en nombre. Une chaîne invalide/non numérique a été fournie là où un champ ou un paramètre attend un nombre.","cause":"Texte non numérique envoyé à un champ numérique (integer/long/double), tentative de conversion d'une chaîne vide en nombre, mauvais format décimal, ou données d'entrée incompatibles avec le mapping.","fix":"Envoyez des valeurs numériques valides au champ. Validez/transformez les données source avant l'indexation. Utilisez null pour les valeurs vides ou omettez le champ. Revoyez le paramètre coerce et le type du champ dans le mapping. Utilisez un point comme séparateur décimal.","tags":["number format","number","conversion","numeric","format"]},{"code":"connect_transport_exception","title":"connect_transport_exception","severity":"danger","meaning":"Impossible de se connecter à un nœud via la couche transport (9300). La communication entre nœuds ou entre le client et le cluster n'a pas pu être établie ; l'opération n'a donc pas pu être effectuée.","cause":"Nœud cible arrêté/injoignable, problèmes réseau/pare-feu, adresse/port de transport erroné, incohérence de configuration TLS, problème DNS, ou incohérence du nom/de la version du cluster.","fix":"Vérifiez que le nœud cible fonctionne et que le port de transport (9300 par défaut) est joignable sur le réseau. Vérifiez les règles de pare-feu/réseau et la configuration de discovery. Assurez-vous que les paramètres TLS/de certificats sont cohérents sur tous les nœuds. Revoyez les journaux des nœuds et vérifiez la compatibilité de cluster.name/de la version.","tags":["transport","connection","connect","network","node","danger"]},{"code":"repository_missing_exception","title":"repository_missing_exception","severity":"caution","meaning":"Le dépôt de snapshots référencé dans une opération de snapshot/restauration est introuvable. Aucun dépôt n'est enregistré sous le nom donné.","cause":"Le dépôt n'a jamais été enregistré, faute de frappe dans le nom, dépôt supprimé, défini sur un autre cluster, ou non réenregistré après la réinitialisation du cluster state.","fix":"Listez les dépôts enregistrés : GET _snapshot/_all. Réenregistrez le dépôt avec le bon nom et les bons paramètres : PUT _snapshot/repo_name (avec type et settings). Assurez-vous que le nom du dépôt est correctement orthographié. Vérifiez que l'accès au dépôt partagé (fs/s3, etc.) fonctionne sur tous les nœuds.","tags":["repository","snapshot repository","snapshot","missing","backup","restore"]},{"code":"concurrent_snapshot_execution_exception","title":"concurrent_snapshot_execution_exception","severity":"caution","meaning":"Une nouvelle requête de snapshot/restauration a été rejetée, car une opération de snapshot en conflit s'exécute en même temps. Une autre opération de snapshot est encore en cours sur le dépôt ou l'index.","cause":"Snapshot précédent encore en cours, conflit de snapshot/restauration concurrent affectant le même index, politique SLM (snapshot lifecycle) entrant en collision avec un snapshot manuel, ou déclenchements à intervalles rapprochés.","fix":"Vérifiez les snapshots en cours : GET _snapshot/repo/_current et GET _snapshot/_status. Attendez la fin de l'opération en cours, ou annulez-la si nécessaire : DELETE _snapshot/repo/snapshot_name. Planifiez les snapshots pour qu'ils ne se chevauchent pas. Coordonnez les opérations manuelles avec SLM.","tags":["snapshot","concurrent","conflict","simultaneous","slm","backup"]},{"code":"invalid_index_name_exception","title":"invalid_index_name_exception","severity":"caution","meaning":"Le nom d'index donné ne respecte pas les règles de nommage d'Elasticsearch. Le nom contient des caractères invalides ou enfreint les règles de format.","cause":"Lettres majuscules, caractères interdits (\\ / * ? \" < > | espace , #), nom commençant par _, - ou +, nom trop long (plus de 255 octets), ou noms réservés comme . / ... (Les noms commençant par un point ne sont pas interdits, mais sont réservés aux index cachés/système et produisent un avertissement.)","fix":"Rendez le nom d'index conforme : utilisez uniquement des minuscules ; évitez les caractères interdits ; ne commencez pas le nom par _, - ou +, et n'utilisez pas de noms commençant par un point, sauf pour les index système/cachés. Gardez le nom sous 255 octets. Imposez ces règles dans l'application lors de la génération des noms d'index.","tags":["index name","index naming","invalid","invalid name","naming"]},{"code":"resource_not_found_exception","title":"resource_not_found_exception","severity":"caution","meaning":"La ressource demandée (document, index, tâche, pipeline, modèle, etc.) est introuvable. Aucune ressource n'existe pour l'ID/le nom donné.","cause":"ID de document inexistant, index/pipeline supprimé ou jamais créé, nom mal orthographié, tâche pas encore terminée ou déjà nettoyée, ou requête envoyée au mauvais cluster.","fix":"Vérifiez le nom/l'ID de la ressource et son existence (par ex. GET /index/_doc/id, GET _ingest/pipeline/name). Assurez-vous d'envoyer la requête au bon index et au bon cluster. Revoyez l'ordre des opérations de suppression/création. Pour les tâches, vérifiez l'état avec l'API _tasks.","tags":["not found","resource not found","resource","missing resource","404"]},{"code":"process_cluster_event_timeout_exception","title":"process_cluster_event_timeout_exception","severity":"danger","meaning":"Une mise à jour du cluster state (par ex. création d'index, modification de mapping) ne s'est pas terminée dans le temps imparti et a expiré. Le nœud master n'a pas pu traiter la mise à jour à temps.","cause":"Nœud master sous forte charge, cluster state très grand/complexe, modifications de mapping/template fréquentes et intensives, master lent, ou problèmes d'élection du master.","fix":"Vérifiez la charge et l'état du nœud master : GET _cluster/health, GET _cat/master, et GET _cluster/pending_tasks pour les tâches en attente. Réduisez le cluster state (moins d'index/mappings/templates inutiles, prévention de l'explosion du mapping dynamique). Exécutez les opérations lourdes de type DDL par lots pendant les périodes de faible trafic. Augmentez les ressources du master si nécessaire.","tags":["cluster event","timeout","timed out","master","cluster state","danger"]}]},"errors/oracle-more2.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00955","title":"name is already used by an existing object (nom déjà utilisé par un objet existant)","severity":"caution","meaning":"Le nom de l'objet que vous tentez de créer (table, index, vue, séquence, synonyme, etc.) est déjà utilisé par un autre objet du même schéma. Dans Oracle, les noms d'objets sont uniques au sein d'un schéma, et même des types différents comme les tables, les vues et les séquences partagent le même espace de noms.","cause":"Le nom choisi dans l'instruction CREATE est en conflit avec un objet existant. Piège fréquent : tenter de créer une séquence ou une vue portant le même nom qu'une table ; ou un nom d'index créé automatiquement (par ex. l'index de la clé primaire) entrant en collision avec le nom que vous avez indiqué.","fix":"Choisissez un autre nom ou supprimez l'objet en conflit. Pour savoir quel objet utilise ce nom : SELECT object_type, object_name FROM user_objects WHERE object_name = 'YOUR_NAME'; Si vous voulez vraiment le remplacer, supprimez (DROP) l'ancien objet (sauvegardez d'abord ses données). CREATE OR REPLACE est disponible pour les vues, synonymes, types et objets PL/SQL (procédure, fonction, package, trigger) ; il n'existe pas pour les tables, index et séquences.","tags":["ddl","create","name","conflict","duplicate","object"]},{"code":"ORA-02449","title":"unique/primary keys in table referenced by foreign keys (clés uniques/primaires de la table référencées par des clés étrangères)","severity":"caution","meaning":"La clé primaire ou unique de la table que vous voulez supprimer est référencée par des clés étrangères d'autres tables. Oracle ne vous permet pas de supprimer la table sans supprimer d'abord ces dépendances.","cause":"Vous avez exécuté DROP TABLE sans CASCADE CONSTRAINTS alors que d'autres tables référencent cette table via des FK. En appliquant l'intégrité référentielle, Oracle empêche les clés étrangères orphelines.","fix":"Pour la supprimer en même temps que les clés étrangères dépendantes : DROP TABLE table_name CASCADE CONSTRAINTS; Cela supprime aussi les contraintes FK concernées dans les autres tables (sans supprimer les données de ces tables). Pour voir quelles contraintes en dépendent : SELECT table_name, constraint_name FROM all_constraints WHERE r_constraint_name IN (SELECT constraint_name FROM user_constraints WHERE table_name = 'TABLE_NAME');","tags":["drop","foreign key","foreign key reference","constraint","cascade","reference"]},{"code":"ORA-01950","title":"no privileges on tablespace 'name' (aucun privilège sur le tablespace)","severity":"danger","meaning":"L'utilisateur n'a pas de quota sur le tablespace dans lequel il tente d'écrire. Dans Oracle, un utilisateur doit disposer d'un quota sur le tablespace pour allouer de l'espace à un objet.","cause":"Aucun quota sur le tablespace n'a été accordé à l'utilisateur ; typiquement, un nouvel utilisateur sans le privilège système UNLIMITED TABLESPACE tente de créer une table/un index ou d'insérer des données dans son tablespace par défaut. Apparaît lors d'un INSERT, d'un CREATE TABLE ou de la croissance d'un index.","fix":"En tant que DBA, accordez un quota à l'utilisateur : ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; ou une limite précise : ALTER USER app_user QUOTA 500M ON users; Vous pouvez aussi (avec prudence) accorder GRANT UNLIMITED TABLESPACE TO app_user;, mais cela donne des droits illimités sur tous les tablespaces et n'est généralement pas recommandé.","tags":["tablespace","quota","tablespace quota","privilege","privilege","insert"]},{"code":"ORA-01110","title":"data file N: 'path'","severity":"danger","meaning":"Message complémentaire (secondaire) signalant un problème sur le fichier de données portant le numéro et le chemin indiqués. Il n'apparaît jamais seul : il est précédé d'une autre erreur ORA qui décrit le problème réel, et cette ligne indique uniquement quel fichier de données est concerné.","cause":"Il accompagne généralement une erreur principale, par exemple un fichier de données offline (ORA-00376), nécessitant une media recovery (ORA-01113), un bloc corrompu (ORA-01578) ou un fichier illisible. Une panne de disque, une suppression accidentelle, un fichier déplacé ou une restauration incomplète depuis une sauvegarde peuvent le déclencher.","fix":"Lisez l'erreur ORA réelle qui précède cette ligne et résolvez-la. Vérifiez le fichier concerné : SELECT file#, name, status FROM v$datafile; Si une récupération est nécessaire : RECOVER DATAFILE 'path'; puis ALTER DATABASE DATAFILE 'path' ONLINE; Si le fichier est physiquement perdu, restaurez-le et récupérez-le avec RMAN : RESTORE DATAFILE N; RECOVER DATAFILE N;","tags":["datafile","data file","recovery","media recovery","offline","rman"]},{"code":"ORA-16038","title":"log N sequence# M cannot be archived (le journal N, séquence M, ne peut pas être archivé)","severity":"danger","meaning":"Oracle ne peut pas archiver un fichier de redo log en ligne et ne peut donc pas réutiliser ce groupe de journaux. Lorsque l'archivage est bloqué en mode ARCHIVELOG, la base peut cesser d'accepter de nouvelles transactions et se figer.","cause":"En général, la destination des journaux archivés ou la Fast Recovery Area (FRA) est pleine ; l'espace disque est épuisé ; la destination d'archivage est inaccessible ; ou il existe un problème de droits/de montage. Souvent observé avec ORA-19809 (limite dépassée pour les fichiers de récupération) ou ORA-00312.","fix":"Libérez de l'espace dans la destination d'archivage/la FRA : sauvegardez les anciens journaux archivés avec RMAN vers une destination hors FRA puis supprimez-les (DELETE ARCHIVELOG ALL COMPLETED BEFORE ...; avec Data Guard, ne supprimez pas les journaux archivés non encore transférés/appliqués sur le standby), ou augmentez db_recovery_file_dest_size. Vérifiez l'état des destinations : ARCHIVE LOG LIST; et SELECT dest_name, status, error FROM v$archive_dest WHERE status != 'INACTIVE'; Une fois le problème corrigé, l'archivage reprend là où il s'était arrêté.","tags":["archivelog","archive","redo","fra","recovery area","archiver stuck"]},{"code":"ORA-19809","title":"limit exceeded for recovery files (limite dépassée pour les fichiers de récupération)","severity":"danger","meaning":"L'espace alloué à la Fast Recovery Area (FRA) (db_recovery_file_dest_size) est plein. Faute de place pour les journaux archivés, les flashback logs et les sauvegardes, Oracle ne peut pas créer de nouveaux fichiers de récupération et la base peut se figer.","cause":"Les journaux archivés se sont accumulés dans la FRA faute de sauvegarde et de purge régulières ; l'utilisation des flashback logs a augmenté ; ou la taille allouée est trop faible pour la charge réelle. Observé avec des erreurs d'archivage telles que ORA-16038.","fix":"Examinez l'utilisation de la FRA : SELECT * FROM v$recovery_file_dest; et SELECT * FROM v$flash_recovery_area_usage; Supprimez les journaux archivés inutiles avec RMAN : DELETE ARCHIVELOG ALL BACKED UP 1 TIMES TO DISK; (avec Data Guard, vérifiez d'abord qu'ils ont été appliqués sur le standby ; CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY est recommandé) ou, comme correctif durable, augmentez la taille : ALTER SYSTEM SET db_recovery_file_dest_size = 50G SCOPE=BOTH; Mettez en place un job RMAN qui sauvegarde régulièrement les journaux archivés.","tags":["fra","recovery area","db_recovery_file_dest_size","archivelog","space","rman"]},{"code":"ORA-00376","title":"file N cannot be read at this time (le fichier N ne peut pas être lu actuellement)","severity":"danger","meaning":"Le fichier de données portant ce numéro ne peut pas être lu pour le moment car il est offline ou inaccessible. Les requêtes et opérations accédant aux données du tablespace concerné échouent avec cette erreur.","cause":"Le fichier de données a été mis offline automatiquement après une erreur ou manuellement ; il attend une media recovery ; le disque est devenu inaccessible ; ou un fichier non récupérable en mode NOARCHIVELOG a été laissé offline. Généralement accompagné d'une ligne ORA-01110 identifiant le fichier.","fix":"Consultez l'état des fichiers : SELECT file#, name, status FROM v$datafile WHERE status != 'ONLINE'; Récupérez-le et remettez-le online : RECOVER DATAFILE N; puis ALTER DATABASE DATAFILE N ONLINE; En cas de corruption/perte physique, restaurez et récupérez avec RMAN : RESTORE DATAFILE N; RECOVER DATAFILE N; Corrigez aussi le problème de disque/matériel sous-jacent.","tags":["datafile","data file","offline","recovery","media recovery","read"]},{"code":"ORA-12520","title":"TNS:listener could not find available handler for requested type of server (le listener n'a trouvé aucun gestionnaire disponible pour le type de serveur demandé)","severity":"danger","meaning":"Le listener n'a pas trouvé de processus serveur (handler) adapté au type de connexion demandé (dédiée ou partagée). Le client atteint le listener, mais la connexion ne peut pas être transmise à un processus serveur.","cause":"L'instance n'est pas enregistrée auprès du listener ou son enregistrement est défaillant ; la limite PROCESSES/SESSIONS est atteinte et aucun nouveau processus dédié ne peut être créé ; dispatchers absents/insuffisants en configuration shared server ; ou l'instance vient de démarrer et ne s'est pas encore enregistrée auprès du listener.","fix":"Vérifiez les services vus par le listener : lsnrctl services (nombre et état des handlers). Réenregistrez l'instance auprès du listener : ALTER SYSTEM REGISTER; Vérifiez la limite PROCESSES : SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); Si nécessaire, augmentez-la avec ALTER SYSTEM SET processes=... SCOPE=SPFILE; et redémarrez l'instance.","tags":["listener","tns","connection","handler","processes","dispatcher"]},{"code":"ORA-28001","title":"the password has expired (le mot de passe a expiré)","severity":"caution","meaning":"Le mot de passe de l'utilisateur de base de données avec lequel vous vous connectez a expiré, car le PASSWORD_LIFE_TIME de son profil est écoulé. Aucune session ne peut être ouverte tant que le mot de passe n'est pas modifié.","cause":"La durée de vie du mot de passe définie dans le profil de l'utilisateur est terminée et la période de grâce est également écoulée. Depuis Oracle 11g, les mots de passe du profil DEFAULT expirent tous les 180 jours ; cela provoque des interruptions inattendues, notamment pour les comptes de service applicatifs de longue durée.","fix":"Changez le mot de passe : ALTER USER app_user IDENTIFIED BY new_password; Pour éviter que le problème se répète avec les comptes applicatifs, utilisez un profil sans expiration de mot de passe : CREATE PROFILE app_profile LIMIT PASSWORD_LIFE_TIME UNLIMITED; puis ALTER USER app_user PROFILE app_profile; Une supervision qui alerte avant l'expiration est également utile. Vérifiez l'état via ACCOUNT_STATUS dans DBA_USERS.","tags":["password","password expired","expired","profile","account","login"]},{"code":"ORA-38029","title":"object statistics are locked (les statistiques de l'objet sont verrouillées)","severity":"caution","meaning":"La collecte/suppression des statistiques a été refusée car les statistiques de l'optimiseur d'une table (ou d'un schéma) sont verrouillées. Les statistiques verrouillées sont protégées contre tout recalcul par DBMS_STATS.","cause":"DBMS_STATS.LOCK_TABLE_STATS ou LOCK_SCHEMA_STATS a été appelé auparavant (par ex. pour figer les statistiques), ou un import Data Pump (notamment CONTENT=METADATA_ONLY) a laissé les statistiques verrouillées involontairement. Exécuter GATHER_TABLE_STATS alors qu'elles sont verrouillées lève cette erreur.","fix":"Déverrouillez les statistiques : EXEC DBMS_STATS.UNLOCK_TABLE_STATS('APP_SCHEMA','MY_TABLE'); ou au niveau du schéma DBMS_STATS.UNLOCK_SCHEMA_STATS('APP_SCHEMA'); Si vous ne voulez pas lever le verrou mais souhaitez tout de même collecter, utilisez FORCE=>TRUE : EXEC DBMS_STATS.GATHER_TABLE_STATS('APP_SCHEMA','MY_TABLE', force=>TRUE); Pour voir l'état du verrou : SELECT table_name, stattype_locked FROM dba_tab_statistics WHERE stattype_locked IS NOT NULL;","tags":["statistics","optimizer statistics","dbms_stats","locked","lock","optimizer"]}]},"errors/postgres-more2.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"54001","title":"statement_too_complex","severity":"caution","meaning":"L'instruction est plus complexe que ce que PostgreSQL peut traiter ; en général, des expressions très profondément imbriquées ou de nombreux opérateurs imbriqués font dépasser à l'arbre d'expression la limite de pile du parser/planner. Appartient à la Class 54 (program_limit_exceeded).","cause":"Parenthèses/sous-requêtes imbriquées trop profondes ou chaînes OR/AND ; énormes listes WHERE ... IN (...) générées automatiquement ; ou arbres d'expression très volumineux issus de vues récursives. La limite max_stack_depth est atteinte.","fix":"Simplifiez la requête : déplacez les longues listes IN dans une table temporaire ou un ensemble VALUES et faites un JOIN ; décomposez les expressions imbriquées ; revoyez les énormes requêtes générées par l'ORM. Si c'est vraiment nécessaire et que la limite de pile de l'OS le permet, augmentez prudemment max_stack_depth (par ex. SET max_stack_depth = '4MB';) en restant sous l'ulimit de l'OS.","tags":["program_limit","complex","stack","nested","query","max_stack_depth"]},{"code":"58030","title":"io_error","severity":"danger","meaning":"PostgreSQL a rencontré une erreur d'entrée/sortie (I/O) au niveau du système d'exploitation lors de la lecture ou de l'écriture d'un fichier. Cela indique un problème physique ou de système de fichiers dans l'accès aux fichiers de la base. Appartient à la Class 58 (system_error).","cause":"Panne de disque ou secteurs défectueux ; disque plein ; erreurs du système de fichiers ; panne du stockage réseau (NFS/SAN) ; problèmes de droits ; ou limites de descripteurs de fichiers/inodes de l'OS épuisées.","fix":"Vérifiez l'état et l'espace libre des disques (df -h, dmesg, données SMART). Consultez les journaux PostgreSQL pour savoir quel fichier est concerné. Corrigez les erreurs du système de fichiers ; remplacez le matériel si nécessaire. En cas de suspicion de corruption, planifiez une restauration depuis la dernière sauvegarde saine et gardez les checksums activés à l'avenir (initdb --data-checksums).","tags":["system_error","io","disk","hardware","file","corruption"]},{"code":"22004","title":"null_value_not_allowed","severity":"caution","meaning":"Un NULL a été passé là où NULL n'est pas accepté. C'est différent d'une contrainte NOT NULL de colonne (23502) ; cela provient généralement d'un NULL passé à un paramètre ou à une opération qui ne l'accepte pas dans le contexte d'une fonction ou d'une procédure. Appartient à la Class 22 (data_exception).","cause":"Affectation de NULL à une variable PL/pgSQL déclarée NOT NULL ; passage d'une chaîne de requête NULL à EXECUTE ; ou passage de NULL à des arguments de fonctions intégrées qui ne l'acceptent pas. Remarque : une fonction marquée STRICT ne lève pas d'erreur sur une entrée NULL — elle renvoie NULL sans s'exécuter.","fix":"Trouvez la ligne en échec (CONTEXT) et protégez la valeur qui y parvient par un contrôle de NULL, ou fournissez une valeur par défaut avec COALESCE : COALESCE(value, default_value). En PL/pgSQL, validez les valeurs avant de les affecter à des variables NOT NULL ; en SQL dynamique, assurez-vous que la chaîne passée à EXECUTE n'est pas NULL. Empêchez l'envoi de champs obligatoires à NULL au niveau applicatif.","tags":["data_exception","null","function","strict","parameter","coalesce"]},{"code":"42P18","title":"indeterminate_datatype","severity":"caution","meaning":"PostgreSQL n'a pas pu déduire du contexte le type de données d'une expression ou d'un paramètre. Se produit le plus souvent lorsqu'un placeholder non typé (comme $1) ou un NULL nu est utilisé sans aucune indication de type autour.","cause":"Paramètres non typés dans des requêtes préparées ; comparaisons où les deux côtés sont indéterminés, comme \"WHERE $1 = $2\" ; ou utilisation sans contexte de NULL/de littéraux de tableau. Le planificateur ne peut pas décider quel type utiliser.","fix":"Ajoutez un cast explicite à l'expression indéterminée : $1::text, $1::integer, NULL::date, etc. Par exemple : SELECT $1::int + 1; Précisez les types des paramètres du driver/de l'ORM. Dans les comparaisons de colonnes, assurez-vous qu'au moins un côté a un type connu.","tags":["syntax","type","indeterminate","cast","parameter","prepared"]},{"code":"42P02","title":"undefined_parameter","severity":"caution","meaning":"La requête référence un paramètre qui n'existe pas. PostgreSQL signale que le paramètre positionnel référencé (par ex. $2) n'est pas défini. Appartient à la Class 42 (syntax_error_or_access_rule_violation).","cause":"Référence à plus de paramètres que ceux fournis dans une instruction préparée avec PREPARE (par ex. envoi de deux paramètres et utilisation de $3) ; numérotation des paramètres avec des trous ; ou index $N erronés dans du SQL concaténé dynamiquement.","fix":"Assurez-vous que les numéros de paramètres correspondent au nombre de valeurs fournies ; vérifiez qu'ils commencent à $1 et se suivent sans trou. Définissez complètement les types des paramètres dans l'instruction PREPARE : PREPARE p(int, text) AS SELECT ... WHERE a=$1 AND b=$2; Si vous utilisez un ORM/driver, comparez le nombre de paramètres liés aux placeholders de la requête.","tags":["syntax","parameter","prepared","undefined","placeholder","sql"]},{"code":"2F003","title":"prohibited_sql_statement_attempted","severity":"caution","meaning":"Une instruction interdite dans ce contexte a été tentée à l'intérieur d'une fonction/routine SQL. Appartient à la Class 2F (sql_routine_exception). Par exemple, une fonction censée être en lecture seule qui tente de modifier des données.","cause":"PostgreSQL lui-même lève rarement ce code ; il provient généralement du code d'une extension ou d'un langage procédural. Remarque : une fonction marquée IMMUTABLE/STABLE qui exécute INSERT/UPDATE/DELETE ne lève pas 2F003 ; elle lève '... is not allowed in a non-volatile function' avec le code 0A000 (feature_not_supported).","fix":"Lisez dans le message d'erreur quelle instruction a été interdite et consultez la documentation de la routine/extension concernée. Si vous obtenez l'erreur de fonction non volatile (0A000), définissez la fonction qui modifie les données comme VOLATILE (c'est la valeur par défaut) : CREATE FUNCTION ... LANGUAGE plpgsql VOLATILE. Retirez les appels modifiant les données des endroits qui doivent rester en lecture seule (expressions d'index, contrôles de contraintes).","tags":["sql_routine","function","volatile","prohibited","immutable","side effect"]},{"code":"40003","title":"statement_completion_unknown","severity":"danger","meaning":"L'issue de la transaction est inconnue ; la connexion au serveur a été coupée avant qu'on sache si la transaction s'était terminée (notamment pendant COMMIT). Le client ne peut pas savoir avec certitude si les modifications sont durables. Appartient à la Class 40 (transaction_rollback).","cause":"Le réseau a été coupé, le serveur a planté ou un failover s'est produit après l'envoi de COMMIT mais avant la réception de la réponse. La transaction a pu être validée sur le serveur, ou annulée.","fix":"Ne relancez pas la transaction à l'aveugle ; vérifiez d'abord si elle a réellement été appliquée (par ex. en interrogeant la ligne attendue). Concevez des transactions idempotentes ; utilisez des clés métier uniques/clés d'idempotence. Implémentez une logique robuste de reconnexion et de validation dans le pool de connexions.","tags":["transaction","commit","unknown","connection","idempotent","failover"]},{"code":"08003","title":"connection_does_not_exist","severity":"danger","meaning":"Une opération a été tentée sur une connexion à la base qui n'existe pas (déjà fermée). L'application a essayé d'envoyer une requête via un handle de connexion invalide. Appartient à la Class 08 (connection_exception).","cause":"Timeout de connexion, fermeture par le serveur, coupure réseau, ou connexion prise dans le pool alors qu'elle est en réalité morte ; ou réutilisation d'une connexion fermée par close() dans le code applicatif.","fix":"Activez les contrôles de vivacité dans le pool de connexions (requête de validation / keepalive) ; configurez les paramètres qui évincent les connexions mortes du pool (par ex. connectionTestQuery/maxLifetime dans HikariCP). Dans l'application, vérifiez que la connexion est ouverte avant de l'utiliser et reconnectez-vous automatiquement en cas de coupure. Revoyez les valeurs de TCP keepalive et d'idle_in_transaction_session_timeout.","tags":["connection","connection","closed","pool","pool","timeout"]},{"code":"25006","title":"read_only_sql_transaction","severity":"caution","meaning":"Une commande modifiant les données (INSERT/UPDATE/DELETE/DDL) a été tentée dans une transaction en lecture seule. La transaction est configurée uniquement pour la lecture. Appartient à la Class 25 (invalid_transaction_state).","cause":"SET TRANSACTION READ ONLY ou le paramètre default_transaction_read_only=on ; ou, le plus souvent, une tentative d'écriture sur un réplica en lecture (hot standby / réplica en streaming). Les serveurs standby sont par définition en lecture seule.","fix":"Dirigez les écritures vers le serveur primaire ; vérifiez le routage des connexions qui sépare lectures et écritures. Si le réglage a été fait par erreur : SET TRANSACTION READ WRITE; ou, au niveau du serveur, default_transaction_read_only=off. Assurez-vous que votre application envoie le trafic en lecture-écriture vers le bon nœud.","tags":["transaction","read only","read only","replica","standby","primary"]},{"code":"21000","title":"cardinality_violation","severity":"caution","meaning":"Plus d'une ligne a été renvoyée là où une seule était attendue. Une sous-requête scalaire a renvoyé plusieurs lignes, ou MERGE/ON CONFLICT DO UPDATE a tenté d'affecter une deuxième fois la même ligne cible. Appartient à la Class 21.","cause":"Une sous-requête utilisée dans la liste SELECT ou sous la forme WHERE = (sous-requête) qui renvoie plusieurs lignes ; la même clé de conflit proposée plusieurs fois dans un seul INSERT ... ON CONFLICT DO UPDATE ('cannot affect row a second time') ; ou plusieurs lignes source correspondant à une même ligne cible dans MERGE. UPDATE ... FROM ne lève pas d'erreur en cas de correspondances multiples ; la ligne cible est mise à jour à partir d'une seule des lignes source, de façon imprévisible.","fix":"Assurez-vous que la sous-requête renvoie une seule ligne : ajoutez LIMIT 1, utilisez un agrégat (MAX/MIN) ou fournissez une condition qui rend la correspondance unique. Dans les schémas MERGE et ON CONFLICT, dédoublonnez le côté source avec DISTINCT ON/un agrégat ; faites de même pour UPDATE ... FROM afin d'éviter des résultats silencieusement faux. Pour trouver les correspondances multiples inattendues, exécutez la sous-requête seule et vérifiez le nombre de lignes.","tags":["cardinality","subquery","subquery","single row","join","scalar"]}]},"errors/mysql-more2.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1927","title":"MariaDB: ER_CONNECTION_KILLED: Connection was killed (la connexion a été tuée)","severity":"danger","meaning":"Spécifique à MariaDB : la connexion/le thread en cours a été terminé par le serveur, la requête en cours a été interrompue et la connexion client a été coupée. Ce code n'existe pas dans MySQL ; la même situation y apparaît sous la forme 1317 (ER_QUERY_INTERRUPTED) lorsque la requête est interrompue, et 2013 (Lost connection) côté client lorsque la connexion est coupée.","cause":"Un administrateur ou un outil de supervision qui envoie une commande KILL ; l'arrêt du serveur ; des limites de connexions inactives propres à MariaDB comme idle_transaction_timeout/wait_timeout ; ou le serveur qui coupe la connexion sous pression de ressources. Également observé lors de maintenances dans des environnements cloud/proxy.","fix":"Recherchez pourquoi la requête a été tuée : SHOW PROCESSLIST et le slow query log pour les requêtes longues ; calendriers de maintenance/redémarrage du serveur ; politiques de kill des connexions. Interceptez cette erreur dans l'application et relancez en toute sécurité (si l'opération est idempotente). Optimisez et découpez les requêtes longues ; utilisez la reconnexion automatique dans le pool de connexions.","tags":["connection","connection killed","kill","thread","shutdown","retry"]},{"code":"1290","title":"ER_OPTION_PREVENTS_STATEMENT: running with the --read-only option (le serveur tourne avec l'option --read-only)","severity":"caution","meaning":"Le serveur MySQL a refusé une instruction modifiant les données (INSERT/UPDATE/DELETE/DDL) car il tourne avec l'option --read-only ou --super-read-only. Sous read_only, les utilisateurs disposant de CONNECTION_ADMIN/SUPER peuvent encore écrire ; avec super_read_only activé, eux non plus, et le message indique '--super-read-only option'.","cause":"read_only=ON ou super_read_only=ON ; le plus souvent une tentative d'écriture accidentelle sur un réplica. Les réplicas et les secondaires Group Replication sont maintenus en lecture seule pour la cohérence des données ; l'automatisation du failover met l'ancien primaire en super_read_only. Écrire sur le mauvais nœud pendant un failover/switchover le déclenche aussi.","fix":"Dirigez les écritures vers le serveur primaire (source). Si ce serveur doit réellement devenir le nouveau primaire : SET GLOBAL read_only = OFF; (désactiver read_only désactive automatiquement super_read_only ; activer super_read_only active aussi read_only). Ne le modifiez pas manuellement sur les nœuds gérés par votre outil HA (Orchestrator/Group Replication/InnoDB Cluster). Pour vérifier l'état : SELECT @@read_only, @@super_read_only;","tags":["read only","super_read_only","read-only mode","replica","primary","write","failover","1290"]},{"code":"1836","title":"ER_READ_ONLY_MODE: Running in read-only mode (fonctionnement en mode lecture seule)","severity":"caution","meaning":"InnoDB fonctionne en mode lecture seule (innodb_read_only=ON) ou le répertoire de données/le support est en lecture seule ; les écritures et le DDL sur les tables InnoDB sont refusés. Remarque : une tentative d'écriture alors que read_only/super_read_only est activé renvoie 1290 (ER_OPTION_PREVENTS_STATEMENT), et non ce code.","cause":"Le serveur a été démarré avec innodb_read_only=ON (par ex. support en lecture seule, répertoire de données partagé, copie destinée à l'analyse) ; dans ce mode, des modifications telles que INSERT/UPDATE/DELETE et CREATE/DROP/ALTER TABLE ne peuvent pas être appliquées aux tables InnoDB.","fix":"Si des écritures sont nécessaires, démarrez le serveur sans innodb_read_only : supprimez la ligne innodb_read_only=ON de my.cnf et redémarrez (cette variable n'est pas dynamique et exige un redémarrage). Pour vérifier l'état : SELECT @@innodb_read_only; Vérifiez que le répertoire de données et le système de fichiers sont accessibles en écriture. Si vous écrivez sur un réplica/secondaire HA (1290), dirigez les écritures vers le primaire.","tags":["innodb_read_only","read only","read-only mode","innodb","1836"]},{"code":"3819","title":"ER_CHECK_CONSTRAINT_VIOLATED: Check constraint is violated (contrainte CHECK violée)","severity":"caution","meaning":"La ligne que vous insérez ou mettez à jour viole une contrainte CHECK définie sur la table. Depuis MySQL 8.0.16, les contraintes CHECK sont réellement appliquées (les versions antérieures les ignoraient).","cause":"La valeur de la colonne ne satisfait pas la condition de l'expression CHECK (par ex. saisie d'un prix négatif alors que CHECK (price >= 0) existe). Après une montée de version d'anciennes versions de MySQL vers 8.0.16+, l'erreur peut apparaître de façon inattendue car des contraintes autrefois ignorées sont désormais appliquées.","fix":"Assurez-vous que la valeur satisfait la condition de la contrainte ; ajoutez une validation côté application. Pour voir la définition de la contrainte : SHOW CREATE TABLE my_table; Si la contrainte est vraiment inutile : ALTER TABLE my_table DROP CHECK constraint_name; ou temporairement ALTER TABLE my_table ALTER CHECK constraint_name NOT ENFORCED; Corrigez aussi la source des données erronées.","tags":["check","constraint","check constraint","validation","8.0","violation"]},{"code":"1118","title":"ER_TOO_BIG_ROWSIZE: Row size too large (taille de ligne trop grande)","severity":"caution","meaning":"La taille de ligne de la table que vous définissez dépasse la taille de ligne maximale autorisée par le moteur de stockage. La taille de ligne maximale pour les colonnes non BLOB/TEXT est de 65535 octets ; InnoDB impose des limites supplémentaires qui dépendent de la taille de page.","cause":"Définition de nombreuses colonnes larges (notamment de longs VARCHAR/CHAR) ; chaque caractère occupant jusqu'à 4 octets dans les jeux de caractères multioctets comme utf8mb4 ; ou grandes colonnes stockées en ligne dans le format de ligne COMPACT/REDUNDANT d'InnoDB.","fix":"Convertissez certaines grandes colonnes en TEXT/BLOB (elles peuvent être stockées hors page) ; réduisez les colonnes inutiles ou très larges, ou scindez la table en deux. Dans InnoDB, utiliser ROW_FORMAT=DYNAMIC ou COMPRESSED aide en déplaçant les grandes colonnes hors page : ALTER TABLE my_table ROW_FORMAT=DYNAMIC; Tenez aussi compte d'innodb_page_size.","tags":["row size","row size too large","innodb","varchar","row_format","limit"]},{"code":"1170","title":"ER_BLOB_KEY_WITHOUT_LENGTH: BLOB/TEXT column used in key specification without a key length (colonne BLOB/TEXT utilisée dans une clé sans longueur de clé)","severity":"caution","meaning":"Vous avez utilisé une colonne BLOB ou TEXT dans une définition d'index/de clé sans préciser de longueur de préfixe. Ces types ne peuvent pas être indexés intégralement ; seuls les N premiers caractères/octets peuvent l'être.","cause":"Définition d'un INDEX/UNIQUE/PRIMARY KEY sur une colonne TEXT/BLOB sans longueur dans CREATE TABLE ou CREATE INDEX. MySQL exige une longueur de préfixe car il ne peut pas indexer intégralement ces valeurs potentiellement très volumineuses.","fix":"Précisez une longueur de préfixe dans l'index, par exemple INDEX (description(255)). Exemple : ALTER TABLE my_table ADD INDEX idx_description (description(191)); (191 est couramment utilisé avec utf8mb4 en raison de la limite de préfixe de clé d'InnoDB). Si une recherche plein texte est nécessaire, envisagez un index FULLTEXT. Pour des champs qui doivent être courts et uniques, convertir la colonne en VARCHAR peut être plus approprié.","tags":["index","key","blob","text","prefix","prefix length"]},{"code":"1592","title":"ER_BINLOG_UNSAFE_STATEMENT: Unsafe statement written to the binary log (instruction non sûre écrite dans le binary log)","severity":"caution","meaning":"Avec le format de binlog STATEMENT, une instruction au résultat non déterministe (pouvant produire un résultat différent sur le réplica) a été exécutée. MySQL émet un avertissement en l'écrivant dans le binlog car il existe un risque d'incohérence des données entre la source et le réplica.","cause":"Avec binlog_format=STATEMENT : fonctions non déterministes telles que UUID(), SYSDATE() ou USER() (NOW() se réplique sans risque en STATEMENT), UPDATE/DELETE avec LIMIT mais sans ORDER BY, INSERT combinés à AUTO_INCREMENT, ou certaines fonctions définies par l'utilisateur/système. Ces instructions peuvent ne pas produire le même résultat sur chaque serveur.","fix":"Passez le format de binlog en mode ligne (recommandé ; ROW est la valeur par défaut depuis MySQL 5.7.7) : binlog_format=ROW dans my.cnf ou SET PERSIST binlog_format = ROW; (la variable binlog_format est dépréciée depuis 8.0.34 ; la modification s'applique aux nouvelles sessions). Le format ROW réplique sans risque les instructions non déterministes. Si vous devez rester en STATEMENT, remplacez les fonctions/expressions non déterministes par des valeurs constantes et ajoutez un ORDER BY déterministe au DML avec LIMIT.","tags":["binlog","replication","unsafe","statement","row","nondeterministic"]},{"code":"3140","title":"ER_INVALID_JSON_TEXT: Invalid JSON text (texte JSON invalide)","severity":"caution","meaning":"Un texte JSON invalide (syntaxiquement incorrect) a été passé à une colonne JSON ou à une fonction JSON. MySQL refuse l'opération car il ne peut pas analyser la valeur en tant que JSON.","cause":"Guillemet/crochet non fermé, virgule finale, guillemets simples, erreurs de caractères d'échappement ou texte brut envoyé à une colonne JSON. Apparaît lorsqu'un texte mal formé est passé à des fonctions telles que JSON_SET, JSON_INSERT, ou à une colonne de type JSON.","fix":"Corrigez le texte JSON pour obtenir une syntaxe valide : utilisez des guillemets doubles, supprimez les virgules finales, vérifiez les échappements. Testez la validité du texte : SELECT JSON_VALID('{...}'); (renvoie 1 s'il est valide). Utilisez une bibliothèque fiable pour sérialiser le JSON dans la couche applicative et validez-le avant de l'envoyer au serveur.","tags":["json","invalid","syntax","json_valid","parse","data"]},{"code":"1526","title":"ER_NO_PARTITION_FOR_GIVEN_VALUE: Table has no partition for value (aucune partition de la table pour cette valeur)","severity":"caution","meaning":"La valeur de la clé de partitionnement de la ligne insérée ne correspond à aucune partition définie. En partitionnement RANGE ou LIST, l'insertion échoue car aucune partition ne couvre la valeur reçue.","cause":"En partitionnement RANGE, insertion d'une valeur au-delà de la borne la plus haute sans partition MAXVALUE définie (par ex. une date future) ; en partitionnement LIST, arrivée d'une valeur non listée. Fréquent dans les tables partitionnées par période lorsque la partition de la nouvelle période n'est pas ajoutée à temps.","fix":"Ajoutez une partition qui couvre la valeur. Nouvelle partition haute pour RANGE : ALTER TABLE my_table ADD PARTITION (PARTITION p2026 VALUES LESS THAN (2027)); Pour une partition fourre-tout, définissez MAXVALUE pour RANGE et les valeurs concernées pour LIST. Pour les tables basées sur le temps, mettez en place un job de maintenance (event/scheduler) qui ajoute automatiquement les partitions.","tags":["partition","partitioning","range","list","maxvalue","insert"]}]},"errors/mssql-more2.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"1101","title":"Could not allocate a new page because of insufficient disk space in filegroup (impossible d'allouer une nouvelle page : espace disque insuffisant dans le filegroup)","severity":"danger","meaning":"SQL Server a tenté d'allouer une nouvelle page pour un objet, mais le filegroup manque d'espace disque. Les fichiers de données ne peuvent ni croître ni trouver de page libre, donc l'écriture échoue.","cause":"Le disque hébergeant le fichier de données est plein ; l'autogrow est désactivé sur le fichier ou MAXSIZE est atteint ; ou chaque fichier du filegroup a atteint son plafond de taille. Fréquent lors de gros INSERT/créations d'index.","fix":"Libérez de l'espace sur le disque ou ajoutez de l'espace au filegroup. Agrandissez le fichier ou activez l'autogrow : ALTER DATABASE db MODIFY FILE (NAME = logical_name, SIZE = 20GB, MAXSIZE = UNLIMITED, FILEGROWTH = 512MB); Vous pouvez aussi ajouter un nouveau fichier au filegroup (sur un autre disque). Pour voir l'utilisation des fichiers, consultez sys.database_files et l'espace libre avec DBCC SQLPERF.","tags":["disk","filegroup","file group","autogrow","space","allocation"]},{"code":"3729","title":"Cannot DROP because it is being referenced by object (DROP impossible : l'objet est référencé par un autre objet)","severity":"caution","meaning":"L'objet que vous voulez supprimer (table, type, fonction, etc.) est référencé par un autre objet, et cette dépendance empêche la suppression. Les objets liés au schéma (schema-bound) sont notamment protégés de cette façon.","cause":"Tentative de suppression d'une table/d'un objet dont dépend une vue ou une fonction créée WITH SCHEMABINDING ; suppression d'une table référencée par une clé étrangère ; ou retrait d'un type défini par l'utilisateur encore utilisé par une table/un paramètre.","fix":"Supprimez d'abord les objets qui le référencent ou retirez la dépendance. Pour trouver les dépendances : SELECT referencing_schema_name, referencing_entity_name FROM sys.dm_sql_referencing_entities('app_schema.object', 'OBJECT'); Pour les vues schema-bound, retirez d'abord SCHEMABINDING avec ALTER VIEW ou supprimez la vue, puis supprimez la table.","tags":["drop","reference","schemabinding","view","dependencies","dependency"]},{"code":"3961","title":"Snapshot isolation transaction failed because the object was modified by a DDL statement (échec de la transaction en snapshot isolation : objet modifié par une instruction DDL)","severity":"caution","meaning":"Une transaction en snapshot isolation a échoué car un objet auquel elle accédait a été modifié par une instruction DDL (changement de schéma) dans une autre transaction concurrente. Les métadonnées ne sont pas versionnées, la transaction snapshot ne peut donc pas continuer.","cause":"Pendant qu'une transaction démarrée en isolation SNAPSHOT (SET TRANSACTION ISOLATION LEVEL SNAPSHOT) s'exécute, une autre session lance du DDL comme ALTER TABLE/CREATE INDEX sur une table à laquelle elle accède. Les transactions snapshot voient les versions de lignes mais pas les versions du schéma. Cette erreur ne se produit pas sous READ COMMITTED SNAPSHOT (RCSI) ; RCSI prend un nouveau snapshot à chaque instruction.","fix":"Relancez la transaction ; elle réussit généralement une fois le DDL terminé. Comme correctif durable, effectuez les changements de schéma (DDL) pendant des fenêtres de maintenance sans charge snapshot importante. Interceptez cette erreur dans l'application et implémentez une logique de relance automatique. Raccourcissez les transactions snapshot de longue durée.","tags":["snapshot","isolation level","ddl","concurrency","retry","isolation"]},{"code":"1785","title":"Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths (la contrainte FOREIGN KEY peut créer des cycles ou plusieurs chemins de cascade)","severity":"caution","meaning":"La clé étrangère que vous ajoutez pourrait créer un cycle ou plusieurs chemins de suppression/mise à jour en cascade vers la même table lors des opérations en cascade (ON DELETE/UPDATE CASCADE). SQL Server la refuse pour éviter un comportement de cascade ambigu.","cause":"Une FK avec action en cascade créant une référence circulaire entre tables (A->B->A) ou plusieurs chemins de cascade vers une même table. SQL Server l'interdit pour éviter un ordre de cascade ambigu et des boucles infinies.","fix":"Définissez la clé étrangère en ON DELETE NO ACTION / ON UPDATE NO ACTION (supprimez la cascade) : ALTER TABLE child ADD CONSTRAINT fk ... FOREIGN KEY (...) REFERENCES parent(...) ON DELETE NO ACTION; Implémentez la suppression en cascade requise avec un trigger INSTEAD OF DELETE : le trigger supprime d'abord les lignes enfants, puis la ligne parente (par ex. DELETE c FROM child c JOIN deleted d ON c.parent_id = d.id; DELETE e FROM parent e JOIN deleted d ON e.id = d.id;). Un trigger AFTER DELETE ne fonctionne pas ; le contrôle de FK s'exécute avant le trigger, la suppression est donc refusée avec l'erreur 547. Revoyez les dépendances circulaires du modèle de données.","tags":["foreign key","fk","cascade","circular reference","cycle","constraint"]},{"code":"9421","title":"XML parsing: illegal xml character (analyse XML : caractère XML illégal)","severity":"caution","meaning":"SQL Server a rencontré un caractère non autorisé (invalide) lors de l'analyse d'un texte en XML. La conversion vers le type XML ou l'affectation à une colonne/variable XML a échoué à cause de ce caractère.","cause":"Caractères de contrôle interdits en XML 1.0 (par ex. certains caractères de la plage 0x00-0x1F), mauvais encodage (encoding mismatch), ou données corrompues/non nettoyées provenant d'une autre source. Apparaît généralement lors d'un CAST(... AS XML) ou d'un INSERT dans une colonne XML.","fix":"Supprimez les caractères invalides du texte avant de le convertir en XML. Rendez l'encodage source (UTF-8/UTF-16) compatible avec la cible ; déclarez le bon encodage dans la déclaration XML. Supprimez les caractères de contrôle avec REPLACE/NCHAR, ou générez et validez le XML avec une bibliothèque standard dans la couche applicative. Si vous devez stocker le texte brut, envisagez NVARCHAR(MAX).","tags":["xml","parse","character","encoding","invalid","cast"]},{"code":"15151","title":"Cannot find the object/principal because it does not exist or you do not have permission (objet/principal introuvable : inexistant ou droits insuffisants)","severity":"caution","meaning":"L'objet ou le principal de sécurité (utilisateur, rôle, login) référencé n'existe pas, ou vous n'avez pas le droit d'y accéder/d'agir dessus. Pour des raisons de sécurité, SQL Server regroupe les deux cas dans un seul message.","cause":"Utilisation d'un nom d'utilisateur, de rôle, de schéma ou d'objet inexistant dans une commande comme GRANT/ALTER/DROP ; exécution dans le mauvais contexte de base (l'objet se trouve dans une autre base) ; ou absence du privilège requis pour l'opération. Fautes de frappe, casse et préfixes de schéma sont des causes fréquentes.","fix":"Vérifiez que le nom de l'objet/du principal est correctement orthographié et que vous êtes dans la bonne base. Pour les utilisateurs : SELECT name, type_desc FROM sys.database_principals; pour les objets, interrogez sys.objects. Assurez-vous que le compte d'exécution dispose des droits nécessaires (par ex. ALTER ANY USER, CONTROL). Utilisez le bon préfixe de schéma (comme dbo.object).","tags":["permission","principal","privilege","not found","grant","security"]},{"code":"4834","title":"You do not have permission to use the bulk load statement (vous n'avez pas le droit d'utiliser l'instruction de chargement en masse)","severity":"caution","meaning":"Vous ne disposez pas du droit nécessaire pour effectuer un chargement en masse avec BULK INSERT (ou OPENROWSET BULK). Le chargement en masse accède directement au système de fichiers et aux ressources du serveur, il exige donc un privilège distinct.","cause":"Le login ne dispose pas du droit ADMINISTER BULK OPERATIONS au niveau serveur ou n'est pas membre du rôle serveur bulkadmin (Azure SQL Database exige plutôt ADMINISTER DATABASE BULK OPERATIONS au niveau base). Le droit INSERT sur la table cible et l'accès en lecture au fichier source pour le compte de service SQL Server sont également requis.","fix":"Accordez le droit nécessaire : GRANT ADMINISTER BULK OPERATIONS TO login_name; (niveau serveur) ou ajoutez l'utilisateur au rôle approprié. Azure SQL DB utilise ADMINISTER DATABASE BULK OPERATIONS. Assurez-vous aussi que le compte de service SQL Server dispose du droit de lecture sur le fichier/partage cible. Selon le principe du moindre privilège, ne l'accordez qu'aux comptes qui en ont besoin.","tags":["bulk insert","privilege","permission","bulk load","bulkadmin","file"]},{"code":"10928","title":"Resource limit for the database has been reached (Azure SQL) (limite de ressources de la base atteinte)","severity":"danger","meaning":"Dans Azure SQL Database, la base a atteint une limite de ressources (requêtes/workers concurrents ou sessions, par exemple) et la nouvelle requête a été refusée. Cela indique que le plafond du niveau de service choisi (DTU ou vCore) est atteint.","cause":"Dépassement du nombre maximal de requêtes concurrentes (workers) ou de sessions autorisé par le niveau choisi ; pic de charge soudain ; requêtes inefficaces/longues qui consomment les threads worker ; ou pool de connexions surdimensionné.","fix":"Réduisez la concurrence : optimisez les requêtes pour raccourcir leur durée, fermez les transactions ouvertes inutiles, gardez une taille de pool de connexions raisonnable. Pour une charge soutenue, montez en niveau de service (DTU/vCore supérieur). Surveillez l'utilisation des ressources avec sys.dm_db_resource_stats. Pour les erreurs pouvant être relancées, implémentez des relances avec backoff exponentiel dans l'application.","tags":["azure","limit","resource","dtu","vcore","concurrency"]},{"code":"40544","title":"The database has reached its size quota (Azure SQL) (la base a atteint son quota de taille)","severity":"danger","meaning":"Azure SQL Database a atteint le quota de taille de données maximal défini pour le niveau de service. La base ne peut plus croître, les nouvelles écritures échouent donc.","cause":"Données ayant grossi jusqu'à la limite du niveau ; index/journal volumineux consommant de l'espace ; ou petit MAXSIZE permis par le niveau choisi (par ex. Basic/Standard série S). Des données historiques non archivées remplissent aussi rapidement le quota.","fix":"Libérez de l'espace : supprimez/archivez les données inutiles, supprimez les index non utilisés, compressez les tables (data compression). Comme correctif durable, augmentez MAXSIZE ou passez à un niveau de service supérieur : ALTER DATABASE db MODIFY (MAXSIZE = 250 GB); Surveillez l'utilisation de l'espace avec sys.database_files et les DMV associées. Anticipez la montée en charge selon la tendance de croissance.","tags":["azure","size","quota","maxsize","size quota","scaling"]},{"code":"40613","title":"Database is not currently available (Azure SQL) (base actuellement indisponible)","severity":"danger","meaning":"Dans Azure SQL, la base cible est injoignable pour le moment. Généralement transitoire ; apparaît brièvement pendant que la plateforme déplace, reconfigure ou récupère la base.","cause":"Mise à l'échelle/failover automatique, application de correctifs, équilibrage de charge ou relocalisation de ressources côté Azure ; rarement un événement de plateforme plus long. Les connexions sont refusées jusqu'au retour en ligne de la base.","fix":"Patientez brièvement et réessayez ; il s'agit d'une erreur transitoire. Implémentez une relance automatique avec backoff exponentiel dans l'application ; les SDK Azure/bibliothèques de relance reconnaissent ce code d'erreur comme transitoire. Si le problème persiste, consultez Azure Service Health et les incidents régionaux. Pour la haute disponibilité, envisagez la géo-réplication/un failover group.","tags":["azure","temporary","transient","availability","retry","failover"]}]},"errors/mongodb-more2.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"31 RoleNotFound","title":"Role not found","severity":"caution","meaning":"Le rôle référencé n'est pas défini dans MongoDB. Un nom de rôle inexistant a été utilisé lors de l'attribution d'un rôle à un utilisateur, de la mise à jour d'un rôle ou de la consultation des privilèges d'un rôle.","cause":"Nom de rôle mal orthographié ou jamais créé dans grantRolesToUser/createUser ; recherche du rôle dans la mauvaise base (les rôles sont liés à une base précise) ; ou rôle personnalisé (non intégré) pas encore défini avec createRole.","fix":"Vérifiez le nom du rôle et la base à laquelle il appartient. Listez les rôles existants : db.getRoles({showBuiltinRoles: false}) ou la requête d'administration équivalente sur toutes les bases. Si le rôle personnalisé n'existe pas, créez-le d'abord : db.createRole({role:'reader', privileges:[...], roles:[]}). Indiquez la bonne db lors de l'attribution : db.grantRolesToUser('app_user', [{role:'reader', db:'app'}]).","tags":["role","roles","authorization","access control","createrole","user"]},{"code":"263 OperationNotSupportedInTransaction","title":"Operation not supported inside a multi-document transaction (opération non prise en charge dans une transaction multi-documents)","severity":"caution","meaning":"Une opération non prise en charge dans ce contexte a été tentée dans une transaction multi-documents. Certaines commandes (notamment certaines opérations DDL et de métadonnées) ne peuvent pas s'exécuter dans une transaction.","cause":"Opérations non prises en charge dans la transaction, comme la création de collections/d'index (selon la version) ; certaines commandes de type count/list ; ou des commandes d'administration non valides dans une session transactionnelle. Les anciennes versions de MongoDB ne prennent pas en charge le DDL dans les transactions (une partie est arrivée avec 4.4+).","fix":"Sortez l'opération non prise en charge de la transaction (par ex. créez les collections/index nécessaires avant le début de la transaction). Vérifiez votre version de MongoDB ; 4.4+ prend en charge une partie du DDL dans les transactions. Exécutez l'opération dans une session distincte du driver. Limitez la transaction aux opérations CRUD prises en charge.","tags":["transaction","multi-document transaction","ddl","unsupported","session","crud"]},{"code":"20 IllegalOperation","title":"Illegal operation","severity":"caution","meaning":"L'opération demandée est invalide/interdite pour l'état actuel du serveur ou le type de l'objet cible. MongoDB renvoie cette erreur générique lorsqu'il refuse une action logiquement interdite.","cause":"Tentative d'une opération réservée aux collections sur une vue ; renommage invalide entre deux bases ; modification interdite sur des collections système ; ou action d'administration non autorisée par la configuration du replica set/du sharding. Varie selon le contexte.","fix":"Lisez le détail du message d'erreur ; il indique généralement explicitement quelle opération a été interdite et pourquoi. Adaptez l'opération au type de l'objet cible (par ex. travaillez sur la collection sous-jacente plutôt que sur la vue). Pour les opérations de renommage/déplacement, consultez les restrictions de MongoDB (comme renameCollection au sein de la même base). Vérifiez l'état du cluster (replica set/sharding).","tags":["illegal","invalid","operation","view","rename","system"]},{"code":"48 NamespaceExists","title":"Namespace (collection) already exists (le namespace/la collection existe déjà)","severity":"safe","meaning":"La collection (namespace) que vous essayez de créer existe déjà. MongoDB signale sa présence ; il n'est donc pas nécessaire de créer une nouvelle collection avec le même nom et les mêmes options.","cause":"Tentative de recréer une collection existante avec createCollection (éventuellement avec d'autres options) ; scripts de schéma/d'installation exécutés plusieurs fois ; ou tentative de redéfinir un nom existant en capped collection ou avec des options spéciales.","fix":"Généralement sans risque à ignorer ; cela signifie que la collection existe déjà. Rendez vos scripts d'installation idempotents : vérifiez l'existence avant de créer (db.getCollectionNames().includes('name')). Pour changer les options, vous devez supprimer puis recréer la collection (sauvegardez d'abord les données) : db.my_collection.drop() puis createCollection.","tags":["namespace","collection","collection exists","createcollection","already exists","idempotent"]},{"code":"11601 Interrupted","title":"Operation was interrupted","severity":"caution","meaning":"L'opération en cours a été interrompue. MongoDB l'a arrêtée avant son achèvement en raison d'une intervention externe ou d'un changement d'état du serveur.","cause":"Un administrateur/outil qui tue l'opération avec db.killOp() ; l'arrêt du serveur ; un changement de primaire dans le replica set (stepdown/failover). (Le dépassement de maxTimeMS renvoie 50 MaxTimeMSExpired, et non cette erreur.) Les requêtes longues peuvent être arrêtées de cette façon.","fix":"Recherchez pourquoi l'opération a été interrompue : historique currentOp/killOp, événements de failover, maintenance du serveur. Si l'opération est idempotente, relancez-la en toute sécurité. Optimisez et raccourcissez les requêtes longues ; ajoutez des index appropriés. Utilisez la logique de relance des erreurs transitoires du driver ; assurez la reconnexion automatique lors des failovers du replica set.","tags":["interrupted","interruption","killop","failover","stepdown","retry"]},{"code":"85 IndexOptionsConflict","title":"Index already exists with different options / different name (un index existe déjà avec d'autres options / un autre nom)","severity":"caution","meaning":"L'index que vous créez entre en conflit avec un index existant : un index du même nom a des options différentes, ou un index équivalent avec la même clé et les mêmes options existe déjà sous un autre nom. MongoDB ne permet pas de recréer le même index avec une définition conflictuelle.","cause":"Création d'un index avec le même nom et la même clé mais des options différentes (par ex. expireAfterSeconds, partialFilterExpression, collation) ; ou redéfinition de l'équivalent d'un index existant sous un autre nom ('Index already exists with a different name'). La dérive des définitions d'index au fil du temps dans les scripts applicatifs/de migration est la cause typique. Un même nom avec une clé différente donne plutôt 86 IndexKeySpecsConflict.","fix":"Consultez l'index existant : db.my_collection.getIndexes(). Si vous voulez vraiment changer les options, supprimez l'ancien index et créez le nouveau : db.my_collection.dropIndex('index_name') puis createIndex(...). Sinon, alignez exactement la définition sur l'index existant ou donnez un autre nom au nouvel index. Rendez les scripts de migration idempotents.","tags":["index","indexes","options","conflict","unique","createindex"]},{"code":"67 CannotCreateIndex","title":"Cannot create index","severity":"caution","meaning":"La création de l'index a échoué en raison d'une contrainte ou d'une limite. MongoDB n'a pas pu construire l'index demandé à cause des données existantes ou de la configuration.","cause":"Dépassement du nombre maximal d'index autorisé par collection (64) ; clé trop grande ; ou combinaisons d'options incompatibles (par ex. type de champ inadapté pour un index TTL). Remarque : construire un index unique alors que les données existantes contiennent des doublons lève E11000 DuplicateKey, et non ce code.","fix":"Si vous atteignez la limite du nombre d'index, supprimez les index inutilisés (analysez l'utilisation avec $indexStats). Vérifiez la compatibilité taille/type de la clé et la combinaison d'options. Sur les grandes collections, construisez l'index en période de faible charge (depuis 4.2, la construction d'index ne bloque de toute façon pas longtemps les écritures ; l'option background est ignorée).","tags":["index","indexes","unique","duplicate","limit","ttl"]},{"code":"27 IndexNotFound","title":"Index not found","severity":"safe","meaning":"L'index référencé est introuvable sur la collection. Apparaît généralement lors d'une tentative de suppression ou de renommage d'un index inexistant.","cause":"Nom d'index ou spécification de clé erroné dans une commande comme dropIndex/hideIndex ; index déjà supprimé auparavant ; ou nom d'index généré automatiquement différent de celui attendu (MongoDB génère des noms comme field_1_field2_-1).","fix":"Listez les index existants et leurs noms exacts : db.my_collection.getIndexes(). Répétez l'opération avec le bon nom ou le bon document de clé : db.my_collection.dropIndex({field: 1}) ou db.my_collection.dropIndex('field_1'). Écrivez les scripts de suppression de sorte qu'ils n'échouent pas si l'index est absent (avec un contrôle d'existence).","tags":["index","indexes","dropindex","not found","getindexes","name"]},{"code":"79 UnknownReplWriteConcern","title":"Unknown replica set write concern","severity":"caution","meaning":"Le write concern indiqué n'est pas reconnu. La valeur w utilisée dans l'opération (nom de write concern personnalisé/étiqueté ou valeur numérique) n'est pas définie dans la configuration du replica set.","cause":"Utilisation d'un nom de write concern personnalisé non défini dans getLastErrorModes (par ex. une faute de frappe au lieu de 'majority', ou un nom de tag personnalisé). La configuration et la requête ne correspondent pas. Remarque : demander un w numérique supérieur au nombre de membres votants est une erreur différente (100 UnsatisfiableWriteConcern).","fix":"Assurez-vous que le nom de write concern utilisé est défini dans les paramètres du replica set. Consultez la configuration actuelle : rs.conf() (settings.getLastErrorModes). Si vous utilisez un mode personnalisé, définissez-le d'abord, ou passez aux valeurs standard ('majority' ou un w numérique valide). Revoyez le paramètre writeConcern dans le driver/la chaîne de connexion de l'application.","tags":["write concern","write acknowledgement","replica set","majority","getlasterrormodes","config"]}]},"errors/redis-more2.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"ERR DEBUG command not allowed","title":"DEBUG command not allowed","severity":"caution","meaning":"Vous avez tenté d'exécuter la commande DEBUG, mais le serveur ne l'autorise pas. DEBUG est une commande de diagnostic de bas niveau dangereuse ; pour des raisons de sécurité, elle est désactivée ou restreinte par défaut dans les versions récentes de Redis.","cause":"Dans Redis 7+, les sous-commandes DEBUG ne s'exécutent pas sans le paramètre enable-debug-command ; les services Redis cloud managés désactivent généralement complètement la commande ; ou la commande est refusée par une ACL. Les scripts de test/diagnostic (par ex. DEBUG SLEEP, DEBUG OBJECT) rencontrent souvent cette erreur.","fix":"Évitez DEBUG en production. Si elle est réellement nécessaire dans un environnement local/de test, activez-la dans la configuration : enable-debug-command yes (ou 'local') dans redis.conf. Sur les services cloud, la commande ne peut généralement pas être activée du tout ; utilisez des commandes sûres comme MEMORY USAGE, OBJECT ENCODING et INFO pour obtenir des informations équivalentes. Si une restriction ACL s'applique, revoyez les droits.","tags":["debug","command","prohibited","security","acl","config"]},{"code":"ERR Can't save in background: fork failed","title":"Background save failed: fork failed (échec de la sauvegarde en arrière-plan : échec du fork)","severity":"danger","meaning":"Redis n'a pas pu créer de processus enfant (fork) pour un snapshot RDB en arrière-plan ou une réécriture AOF. La persistance ne pouvant pas s'exécuter, les données ne peuvent pas être écrites sur disque.","cause":"Le système d'exploitation ne peut pas allouer assez de mémoire pour le fork ; en général overcommit_memory est désactivé et le système refuse lorsque Redis tente de réserver environ une copie de la mémoire qu'il utilise. Une mémoire physique/virtuelle insuffisante ou des limites du nombre de processus peuvent aussi en être la cause.","fix":"Activez l'overcommit mémoire sous Linux : sysctl vm.overcommit_memory=1 (ajoutez-le à /etc/sysctl.conf pour le rendre permanent). Ajoutez de la mémoire au serveur ou réduisez l'utilisation mémoire de Redis (maxmemory). Prévoyez un espace de swap suffisant. Vérifiez les limites de processus/descripteurs de fichiers (ulimit). Si la persistance est critique, ne passez pas en production sans ces réglages.","tags":["fork","rdb","aof","persistence","overcommit","memory"]},{"code":"NOPROTO unsupported protocol version","title":"Unsupported RESP protocol version","severity":"caution","meaning":"Le client a demandé une version du protocole RESP que le serveur ne prend pas en charge. Généralement renvoyé lorsque RESP3 (version 3) est demandé avec la commande HELLO et que le serveur ne prend en charge que RESP2.","cause":"HELLO 3 a été envoyé mais le serveur exécute une ancienne version de Redis (antérieure à 6.0) ou ne prend pas en charge RESP3 ; la bibliothèque cliente demande RESP3 par défaut mais le serveur est ancien. Incompatibilité de versions.","fix":"Configurez votre client pour utiliser RESP2, ou demandez une version valide avec HELLO (HELLO 2). Mettez à niveau le serveur vers une version prenant en charge RESP3 (Redis 6+). Revoyez le paramètre de version du protocole de votre bibliothèque cliente ; la plupart des bibliothèques peuvent revenir automatiquement à RESP2. Vérifiez la version du serveur : INFO server (redis_version).","tags":["resp","protocol","hello","resp3","version","compatibility"]},{"code":"ERR Unknown option or number of arguments for CONFIG SET","title":"Unknown option or number of arguments for CONFIG SET (older versions: Unsupported CONFIG parameter) (option ou nombre d'arguments inconnu pour CONFIG SET)","severity":"caution","meaning":"Le paramètre de configuration que vous avez tenté de modifier avec CONFIG SET n'est pas reconnu dans cette version de Redis, ou le nombre d'arguments est incorrect. Dans Redis 7, le message complet est \"ERR Unknown option or number of arguments for CONFIG SET - '<name>'\" ; les anciennes versions renvoient \"ERR Unsupported CONFIG parameter\". CONFIG GET ne lève pas d'erreur pour un paramètre inconnu ; il renvoie une liste vide.","cause":"Nom de paramètre mal orthographié ; utilisation d'un paramètre d'une autre version de Redis (des paramètres peuvent être ajoutés et supprimés d'une version à l'autre) ; ou service cloud managé n'autorisant pas la modification de certains paramètres. L'exécution d'anciens scripts sur des versions plus récentes/plus anciennes est la cause typique.","fix":"Vérifiez le nom de paramètre valide. Listez les réglages actuels avec un motif, par exemple CONFIG GET 'maxmemory*'. Consultez la liste des paramètres dans la documentation de votre version de Redis ; affichez la version avec INFO server. En cas de restriction sur un service cloud, utilisez l'ensemble de paramètres autorisé par le fournisseur. Mettez à jour vos scripts pour la version cible.","tags":["config","parameter","unsupported","version","config set","config get"]},{"code":"ERR MULTI calls can not be nested","title":"MULTI calls can not be nested","severity":"caution","meaning":"MULTI a été envoyé de nouveau alors qu'un bloc MULTI (transaction) était déjà ouvert. Redis ne prend pas en charge les blocs de transaction imbriqués ; un seul MULTI peut être actif à la fois.","cause":"Logique applicative qui démarre un nouveau MULTI sans fermer le précédent avec EXEC/DISCARD ; réutilisation de la même connexion d'un pool sans réinitialiser correctement l'état de transaction ; ou wrapper de bibliothèque qui appelle MULTI deux fois à votre insu.","fix":"Fermez chaque bloc MULTI par un seul EXEC (ou DISCARD pour annuler) et n'ouvrez un nouveau bloc qu'après avoir terminé le précédent. Séparez clairement le début et la fin de transaction dans le flux du code. Assurez-vous qu'aucune transaction ne reste ouverte avant de rendre une connexion au pool. Si une logique imbriquée est nécessaire, envisagez d'effectuer l'opération atomique avec un script Lua (EVAL).","tags":["multi","transaction","nested","nested","exec","discard"]},{"code":"ERR Lua script attempted to access nonexistent global variable","title":"Lua script attempted to access a nonexistent global variable (le script Lua a tenté d'accéder à une variable globale inexistante)","severity":"caution","meaning":"Un script Lua exécuté avec EVAL/EVALSHA a tenté d'accéder à une variable globale non définie. Pour protéger l'environnement de script, Redis restreint la création/la lecture de variables globales et renvoie ce type d'accès comme une erreur.","cause":"Utilisation accidentelle d'une variable globale au lieu d'une variable locale dans le script (oubli du mot-clé local) ; accès à un nom non défini à cause d'une faute de frappe ; ou tentative de conserver un état dans une variable globale. L'environnement Lua isolé (sandbox) de Redis ne le permet pas.","fix":"Déclarez toutes les variables du script avec local : local x = ... Corrigez les fautes de frappe ; assurez-vous que chaque variable est définie avant son utilisation. Les scripts doivent être sans état ; pour un état persistant, utilisez des clés Redis (KEYS/redis.call), et non des variables Lua globales. Gardez le script court et déterministe.","tags":["lua","eval","script","global","variable","local"]},{"code":"ERR unknown command","title":"Unknown command","severity":"caution","meaning":"Redis ne reconnaît pas la commande envoyée. Le nom de la commande est peut-être erroné, indisponible dans cette version, ou désactivé/renommé.","cause":"Nom de commande mal orthographié ; commande ajoutée dans une version plus récente de Redis (absente de l'ancien serveur) ; appel d'une commande nécessitant un module (par ex. RedisJSON, RediSearch) sans que le module soit chargé ; ou commande désactivée avec rename-command/une ACL. Parfois, un mauvais découpage des arguments par le client fait aussi paraître le nom de commande corrompu.","fix":"Vérifiez le nom et la syntaxe de la commande (COMMAND DOCS command ou la documentation). Vérifiez la version du serveur : INFO server. Pour une commande de module, vérifiez que le module est chargé : MODULE LIST. Si la commande a été masquée avec rename-command, revoyez la configuration. Assurez-vous que votre bibliothèque cliente encode correctement la commande.","tags":["command","unknown","unknown","version","module","acl"]},{"code":"TRYAGAIN Multiple keys request during rehashing of slot","title":"Multiple keys request during rehashing of slot (cluster) (requête multi-clés pendant la migration d'un slot)","severity":"caution","meaning":"Dans Redis Cluster, pendant le déplacement d'un hash slot entre nœuds (rehashing/resharding), une requête contenant plusieurs clés du même slot a été émise, alors que certaines clés avaient déjà été déplacées et d'autres non. Le serveur vous demande de relancer l'opération peu après.","cause":"Exécution d'une commande multi-clés (MGET, MSET, Lua multi-clés, transaction) pendant une migration de slot. Dans le slot en migration, certaines clés sont sur le nœud source et d'autres sur le nœud cible, la commande ne peut donc pas s'exécuter de façon atomique.","fix":"Interceptez cette erreur dans le client et relancez après une courte attente (TRYAGAIN est transitoire ; il disparaît à la fin du resharding). Pour les opérations multi-clés, garantissez que toutes les clés sont dans le même slot grâce à un hash tag, par exemple {app_user123}.profile, {app_user123}.settings. Effectuez le rééquilibrage du cluster (resharding) pendant les périodes de faible trafic.","tags":["cluster","slot","rehash","resharding","tryagain","hash tag"]},{"code":"ERR DISCARD without MULTI","title":"DISCARD without MULTI","severity":"caution","meaning":"DISCARD a été envoyé alors qu'aucun bloc MULTI n'était ouvert. DISCARD sert uniquement à annuler une transaction en cours ; il renvoie cette erreur lorsqu'il n'y a rien à annuler.","cause":"Appel de DISCARD sans MULTI, ou de nouveau après que la transaction a déjà été fermée par EXEC/DISCARD ; appel inconditionnel de DISCARD dans le code de gestion d'erreurs ; ou état de connexion différent de celui attendu (aucun MULTI ouvert sur la connexion prise dans le pool).","fix":"N'appelez DISCARD que lorsqu'un MULTI est réellement ouvert ; suivez l'état de la transaction dans le code. Dans les gestionnaires d'erreurs, vérifiez si une transaction est ouverte avant d'appeler DISCARD. Utilisez le wrapper de transaction de votre bibliothèque pour qu'il gère l'état automatiquement. Garantissez un état propre dans le pool de connexions.","tags":["discard","multi","transaction","cancel","state","error handling"]}]},"errors/elasticsearch-more2.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"search_context_missing_exception","title":"No search context found for id (scroll expired) (aucun contexte de recherche pour cet id : scroll expiré)","severity":"caution","meaning":"Le contexte de recherche utilisé par une requête scroll n'existe plus sur le serveur. Apparaît généralement lorsqu'on poursuit avec un scroll_id conservé après l'expiration ou l'effacement du contexte de scroll.","cause":"Le keep-alive indiqué pour le scroll a expiré (pause trop longue entre les requêtes) ; le contexte a été effacé manuellement (clear scroll) ; ou le contexte a été perdu au redémarrage d'un nœud. Les contextes de scroll sont conservés dans la mémoire du nœud, leur durée de vie est donc limitée.","fix":"Indiquez un keep-alive suffisant à chaque requête scroll (par ex. scroll=5m) et réduisez le délai entre les pages. Dans Elasticsearch moderne, utilisez search_after + Point in Time (PIT) au lieu de scroll pour la pagination profonde ; c'est plus scalable et c'est la méthode recommandée. Libérez les contextes une fois terminé (clear scroll) pour ne pas gaspiller de mémoire.","tags":["scroll","search_after","pit","context","timeout","pagination"]},{"code":"aliases_not_found_exception","title":"Aliases missing","severity":"safe","meaning":"L'alias sur lequel vous essayez d'agir n'existe pas. Généralement renvoyé lors de la suppression d'un alias inexistant ou d'une action sur celui-ci.","cause":"Le nom d'alias est mal orthographié ; l'alias a été supprimé auparavant ; ou une action remove de l'API _aliases désigne un alias qui n'a jamais été attaché. Apparaît aussi lorsqu'aucun alias ne correspond à un motif générique (wildcard).","fix":"Listez les alias existants : GET _alias, ou pour un index précis GET my-index/_alias. Relancez l'opération avec le bon nom. Écrivez les scripts de suppression de sorte qu'ils n'échouent pas si l'alias est absent ; gérez les actions de façon conditionnelle dans les opérations groupées _aliases. Pour repointer des alias de façon atomique, utilisez add et remove dans une seule requête _aliases.","tags":["alias","alias name","not found","aliases","index","routing"]},{"code":"snapshot_missing_exception","title":"Snapshot is missing","severity":"caution","meaning":"Le snapshot référencé est introuvable dans le dépôt (repository) indiqué. Cela indique qu'un nom de snapshot inexistant a été utilisé lors d'une restauration, d'une suppression ou d'une demande d'état.","cause":"Le nom du snapshot est mal orthographié ; le snapshot a été supprimé auparavant ; le mauvais dépôt a été interrogé ; ou la sauvegarde ne s'est jamais terminée. Un résultat similaire se produit lorsque la définition du dépôt a changé ou qu'il est injoignable.","fix":"Listez les snapshots du dépôt : GET _snapshot/repo_name/_all (ou pour un nom précis GET _snapshot/repo_name/snapshot_name). Relancez avec le bon dépôt et le bon nom de snapshot. Vérifiez la définition du dépôt : GET _snapshot/repo_name. Vérifiez que votre politique de sauvegarde (SLM) s'exécute correctement et produit des snapshots.","tags":["snapshot","backup","repository","repo","restore","not found"]},{"code":"index_template_missing_exception","title":"Index template is missing","severity":"safe","meaning":"L'index template référencé est introuvable. Un nom de template inexistant a été utilisé lors de la lecture, de la suppression ou d'une opération basée sur un template.","cause":"Le nom du template est mal orthographié ; le template a été supprimé auparavant ; l'API des templates legacy a été confondue avec celle des composable index templates ; ou le template n'a jamais été créé. Apparaît aussi lorsqu'un script d'installation ne s'est pas terminé.","fix":"Listez les templates existants. Pour les composable templates : GET _index_template ou GET _index_template/name. Pour les templates legacy : GET _template. Relancez avec la bonne API et le bon nom. Si le template est absent, créez-le : PUT _index_template/name {...}. Rendez les scripts de suppression/mise à jour idempotents pour qu'ils n'échouent pas si le template est absent.","tags":["index template","template","composable","legacy","not found","mapping"]},{"code":"settings_exception","title":"Invalid settings","severity":"caution","meaning":"L'opération a été refusée car le paramètre fourni est invalide. Lors de l'application de paramètres d'index ou de cluster, il y a un problème avec le nom du paramètre, sa valeur, ou la possibilité de le modifier dans ce contexte.","cause":"Nom de paramètre inconnu/mal orthographié ; valeur de mauvais type ou hors de la plage autorisée ; tentative de modifier sur un index actif un paramètre statique qui ne peut être défini qu'à la création de l'index (par ex. number_of_shards) ; ou utilisation d'un paramètre supprimé/renommé.","fix":"Validez le nom et la valeur du paramètre d'après la documentation. Mettez à jour les paramètres dynamiques sur l'index actif : PUT my-index/_settings {\"index.number_of_replicas\": 2}. Les paramètres statiques (par ex. number_of_shards) ne sont définis qu'à la création ; les modifier exige un reindex. Consultez les paramètres actuels : GET my-index/_settings.","tags":["settings","setting","invalid","static","dynamic","reindex"]},{"code":"shard_not_found_exception","title":"Shard not found","severity":"danger","meaning":"Le shard cible est introuvable. Elasticsearch n'a pas pu traiter la requête car il n'a pas pu atteindre le shard concerné de l'index indiqué.","cause":"Le shard n'est pas encore affecté (unassigned) ou est en cours de recovery ; le shard est temporairement absent car un nœud a quitté le cluster ; ou une requête de bas niveau routée directement vers un shard porte des informations de routage qui ne sont plus valides. Apparaît lorsque l'état du cluster est dégradé.","fix":"Vérifiez l'état du cluster et des shards : GET _cluster/health et GET _cat/shards?v. Voyez pourquoi des shards ne sont pas affectés : GET _cluster/allocation/explain. Si un nœud a été perdu, remettez-le en service ; revoyez les paramètres de disque/d'allocation (watermark). Si une recovery est en cours, attendez qu'elle se termine. Conservez un nombre de réplicas suffisant pour que les données restent disponibles en cas de perte d'un nœud.","tags":["shard","shard","unassigned","unassigned shard","recovery","cluster health"]},{"code":"not_x_content_exception","title":"Compressor detection error / body is not valid content (erreur de détection du compresseur / corps de requête invalide)","severity":"caution","meaning":"Elasticsearch n'a pas pu reconnaître le corps de la requête comme un contenu structuré valide (XContent comme JSON). Les données envoyées ne sont pas au format attendu ou sont corrompues.","cause":"Le corps de la requête est un JSON invalide ; en-tête Content-Type erroné ou absent (application/json non envoyé) ; corps envoyé par erreur compressé/binaire ; ou JSON simple envoyé à des endpoints comme _bulk qui attendent du JSON délimité par des retours à la ligne (NDJSON). Les erreurs d'échappement/d'encodage le déclenchent aussi.","fix":"Vérifiez que le corps de la requête est un JSON valide et envoyez l'en-tête Content-Type: application/json. Pour l'endpoint _bulk, utilisez du NDJSON, où chaque ligne est un objet JSON distinct et la dernière ligne se termine par un retour à la ligne (Content-Type: application/x-ndjson). Assurez-vous que votre bibliothèque cliente sérialise correctement le corps. Si vous utilisez la compression, envoyez l'en-tête Content-Encoding approprié.","tags":["json","xcontent","content-type","bulk","ndjson","request body"]},{"code":"document_source_missing_exception","title":"Document source is missing","severity":"caution","meaning":"L'opération a besoin du champ _source du document, mais celui-ci n'est pas stocké. Une opération nécessitant _source (par ex. mise à jour partielle, récupération de la source) a été tentée sur un index dont la source est désactivée.","cause":"Le mapping de l'index définit _source: {enabled: false} ; le JSON d'origine du document n'étant pas stocké, les mises à jour partielles, le reindex et les requêtes renvoyant _source ne peuvent pas fonctionner. Les configurations qui désactivent la source pour économiser de l'espace provoquent ce problème.","fix":"Les opérations qui ont besoin de _source exigent que la source soit activée dans le mapping. Lors de la création d'un nouvel index, laissez _source activé (il l'est par défaut). Si _source est désactivé sur un index existant, réindexez le document complet au lieu de le mettre à jour (écrasement avec l'API index). Pour économiser de l'espace, envisagez le filtrage de _source (includes/excludes) ou le synthetic _source plutôt que de désactiver entièrement la source.","tags":["_source","source","update","mapping","reindex","document source"]}]},"errors/oracle-more3.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00845","title":"MEMORY_TARGET not supported on this system (MEMORY_TARGET non pris en charge sur ce système)","severity":"danger","meaning":"L'instance ne peut pas démarrer car la gestion automatique de la mémoire demandée via MEMORY_TARGET / MEMORY_MAX_TARGET n'est pas prise en charge par la configuration de la mémoire partagée du système d'exploitation. Sous Linux, c'est généralement parce que /dev/shm est plus petit que la cible mémoire demandée.","cause":"MEMORY_TARGET est supérieur à /dev/shm (monté en tmpfs) ; ou MEMORY_TARGET (AMM) est utilisé alors que les HugePages sont activées ; AMM n'est pas compatible avec les HugePages, contrairement à ASMM (SGA_TARGET). L'Automatic Memory Management d'Oracle fonctionne en créant des fichiers dans /dev/shm, et si cet espace est insuffisant, l'instance ne démarre pas.","fix":"Rendez /dev/shm plus grand que MEMORY_TARGET : remontez le tmpfs (mount -o remount,size=8G /dev/shm) et rendez-le persistant dans /etc/fstab. Vous pouvez aussi utiliser des réglages séparés au lieu d'AMM : mettez MEMORY_TARGET et MEMORY_MAX_TARGET à 0 et définissez SGA_TARGET et PGA_AGGREGATE_TARGET. Si vous utilisez les HugePages, désactivez AMM et préférez ASMM (SGA_TARGET).","tags":["memory_target","memory","amm","dev/shm","hugepages","startup"]},{"code":"ORA-27102","title":"out of memory","severity":"danger","meaning":"Au démarrage, l'instance Oracle n'a pas pu allouer le segment de mémoire partagée (SGA) demandé au système d'exploitation. Généralement observé avec ORA-27102 et un code d'erreur OSD/Linux de plus bas niveau (par ex. Linux-x86_64 Error: 28: No space left on device).","cause":"La mémoire demandée pour la SGA dépasse les limites de mémoire partagée du noyau (shmmax, shmall) ; le serveur manque de RAM physique/de swap ; ou la configuration des HugePages est absente/insuffisante, si bien qu'une grande SGA ne peut pas être allouée. Fréquent après une augmentation de SGA_MAX_SIZE.","fix":"Augmentez les paramètres de mémoire partagée du noyau : définissez kernel.shmmax et kernel.shmall dans /etc/sysctl.conf pour couvrir la SGA et exécutez sysctl -p. Si vous utilisez les HugePages, calculez et définissez vm.nr_hugepages en fonction de la SGA. Vérifiez la RAM totale et le swap du serveur ; si nécessaire, réduisez SGA_MAX_SIZE / SGA_TARGET.","tags":["sga","memory","shmmax","hugepages","startup","osd"]},{"code":"ORA-04030","title":"out of process memory when trying to allocate N bytes (mémoire de processus épuisée lors de l'allocation de N octets)","severity":"danger","meaning":"Un processus serveur n'a pas pu allouer d'espace dans sa PGA (Program Global Area), c'est-à-dire sa mémoire de processus privée. Contrairement à la mémoire partagée de la SGA, il s'agit de l'épuisement de la mémoire propre à une seule session/un seul processus.","cause":"Des opérations de tri/hash très volumineuses, des collections PL/SQL surdimensionnées, des variables qui s'accumulent dans des boucles non maîtrisées ou des limites de mémoire de processus du système d'exploitation (ulimit -v/-d, vm.max_map_count, espace d'adressage) ont épuisé la mémoire du processus. Sur les instances 32 bits, la limite d'espace d'adressage peut aussi la déclencher.","fix":"Identifiez ce qui consomme la mémoire en examinant la catégorie d'allocation sur la deuxième ligne de l'erreur (par ex. 'pga heap', 'koh-kghu sessi'). Revoyez PGA_AGGREGATE_LIMIT / PGA_AGGREGATE_TARGET. Vérifiez les limites du système d'exploitation (ulimit -a ; memlock, as, stack pour l'utilisateur oracle). Optimisez la requête/le PL/SQL et réduisez la taille des lots en traitement de masse. Si nécessaire, contactez Oracle Support avec le fichier de trace.","tags":["pga","process memory","memory","ulimit","sort","plsql"]},{"code":"ORA-12537","title":"TNS:connection closed","severity":"danger","meaning":"La connexion TNS entre le client et le serveur a pris fin car le canal de communication a été fermé de façon inattendue. La session tombe pendant l'établissement de la connexion ou juste après.","cause":"Le listener a transmis le client à un processus serveur, mais le processus n'a pas pu être créé ou s'est terminé immédiatement : crash de l'instance, limite PROCESSES atteinte, manque de ressources du système d'exploitation, pare-feu/couche intermédiaire coupant la connexion, ou règle TCP.VALIDNODE_CHECKING/TCP.INVITED_NODES dans sqlnet.ora qui rejette le client. Parfois, un pare-feu inactif entre client et serveur coupe la connexion.","fix":"Examinez listener.log et sqlnet.log. Vérifiez les limites PROCESSES/SESSIONS : SELECT * FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); Vérifiez que les listes TCP.INVITED_NODES/TCP.EXCLUDED_NODES du sqlnet.ora côté serveur ne rejettent pas l'IP du client (une incompatibilité de version de logon lève ORA-28040, et non cette erreur). Augmentez le délai d'inactivité du pare-feu ou envoyez des keepalives avec SQLNET.EXPIRE_TIME. Consultez l'alert.log à la recherche de crashs d'instance.","tags":["tns","connection","listener","processes","firewall","sqlnet"]},{"code":"ORA-03135","title":"connection lost contact","severity":"danger","meaning":"Une session déjà établie et fonctionnelle a perdu le contact avec le serveur. Une requête longue ou une connexion inactive a été coupée car les deux côtés ne pouvaient plus se joindre.","cause":"Le processus serveur s'est terminé de façon inattendue (ORA-00600/07445, kill, limite de ressources) ; un profil RESOURCE_LIMIT a terminé la session ; une connexion inactive a été coupée par un pare-feu/load balancer ; ou il y a eu une coupure réseau/perte de paquets. Se produit typiquement avec des requêtes qui ne répondent pas pendant longtemps ou avec des connexions inactives.","fix":"Recherchez dans l'alert.log et les fichiers de trace associés pourquoi le processus s'est arrêté (ORA-00600/ORA-07445 ?). Côté réseau, augmentez le délai d'inactivité du pare-feu et activez la détection des connexions mortes avec SQLNET.EXPIRE_TIME dans sqlnet.ora. Vérifiez les limites CONNECT_TIME/IDLE_TIME du profil utilisateur. Examinez la pression sur les ressources du serveur (mémoire, CPU, PROCESSES).","tags":["connection","sqlnet","idle","firewall","dead connection","network"]},{"code":"ORA-01033","title":"ORACLE initialization or shutdown in progress (initialisation ou arrêt d'ORACLE en cours)","severity":"danger","meaning":"L'instance à laquelle vous vous connectez est en cours de démarrage (mount/open) ou d'arrêt. Les connexions utilisateur normales ne sont pas acceptées dans cet état intermédiaire.","cause":"L'instance est bloquée en NOMOUNT/MOUNT (par ex. en attente de media recovery), un SHUTDOWN est en cours, ou la base est figée avant d'atteindre OPEN à cause d'une erreur. Se connecter à une instance tout juste démarrée avant la fin du démarrage renvoie aussi cette erreur.","fix":"Connectez-vous en SYSDBA et vérifiez l'état : sqlplus / as sysdba; puis SELECT status FROM v$instance; et SELECT open_mode FROM v$database; Essayez d'ouvrir l'instance : ALTER DATABASE OPEN; Si une récupération est requise, exécutez RECOVER DATABASE; puis ouvrez-la. Trouvez dans l'alert.log l'erreur sous-jacente qui empêche l'ouverture (fichier de données/redo manquant ou corrompu). Si l'arrêt est bloqué, essayez SHUTDOWN ABORT suivi de STARTUP.","tags":["startup","shutdown","mount","open","recovery","instance"]},{"code":"ORA-00600","title":"internal error code, arguments: [...]","severity":"danger","meaning":"Un contrôle de cohérence interne du noyau Oracle a échoué. Il s'agit d'une erreur interne générique indiquant que le code du noyau a rencontré une condition inattendue ; les arguments entre crochets identifient le chemin de code déclenché.","cause":"Peut être déclenchée par une corruption de bloc/de données, un bug logiciel Oracle non corrigé, des niveaux de patch incohérents, des problèmes de mémoire/matériel ou des cas limites inattendus. Le premier argument (par ex. [ktbdchk1]) indique la couche concernée et est essentiel pour l'analyse du support.","fix":"Cette erreur doit presque toujours être traitée avec Oracle Support. Conservez le fichier de trace généré et l'entrée de l'alert.log ; recherchez le premier argument dans My Oracle Support avec l'ORA-600 Troubleshooter et l''ORA-600 Lookup Tool'. Appliquez le patch/PSU/RU approprié. En cas de suspicion de corruption, vérifiez l'intégrité des blocs avec DBVERIFY/RMAN VALIDATE. Dans les environnements critiques, n'expérimentez pas en production sans ouvrir de SR.","tags":["internal","ora-600","kernel","bug","trace","support"]},{"code":"ORA-07445","title":"exception encountered: core dump","severity":"danger","meaning":"Un processus serveur Oracle s'est arrêté brutalement après avoir reçu une exception au niveau du système d'exploitation (par ex. SIGSEGV segmentation fault, access violation) et a produit un core dump. Contrairement à ORA-00600, l'erreur est déclenchée par un signal du système d'exploitation.","cause":"Généralement un bug logiciel Oracle, une corruption mémoire, des blocs de données/d'index corrompus, des bibliothèques incompatibles ou une panne matérielle/mémoire. Le nom de la fonction et le type de signal (par ex. SIGSEGV) dans le message identifient le chemin de code qui a planté.","fix":"Conservez les fichiers de trace et d'incident générés ainsi que l'alert.log, et recherchez nom de fonction + signal dans My Oracle Support ; un patch est généralement nécessaire. Lancez des tests matériels/mémoire et alignez les niveaux de patch du système d'exploitation et d'Oracle. En cas de suspicion de corruption, exécutez RMAN VALIDATE / DBVERIFY. Si le problème se répète, ouvrez un SR et envoyez le core dump au support.","tags":["ora-7445","core dump","sigsegv","kernel","bug","trace"]},{"code":"ORA-01102","title":"cannot mount database in EXCLUSIVE mode (impossible de monter la base en mode EXCLUSIVE)","severity":"danger","meaning":"La base ne peut pas être montée en mode EXCLUSIVE car une autre instance (ou des fichiers résiduels d'une ancienne instance) semble encore détenir un verrou dessus. L'instance s'arrête à l'étape MOUNT.","cause":"L'instance précédente ne s'est pas arrêtée proprement et des fichiers de verrou (lk<SID> sous $ORACLE_HOME/dbs, ou mémoire partagée/sémaphores résiduels) subsistent ; une autre instance est déjà attachée à la même base ; ou une seconde instance a été démarrée dans une installation non RAC. Les processus/la mémoire partagée résiduels d'une instance précédente ou un fichier lk<SID> obsolète sont les causes les plus fréquentes.","fix":"Vérifiez qu'aucune autre instance ne tourne réellement : ps -ef | grep pmon et ipcs -m (segments de mémoire partagée). S'il reste des résidus, faites un ABORT de l'instance et nettoyez ; si nécessaire, supprimez les segments/sémaphores résiduels avec ipcrm. Supprimez le fichier de verrou lk<SID> sous $ORACLE_HOME/dbs (uniquement si l'instance est définitivement arrêtée). Essayez ensuite STARTUP MOUNT. Sur RAC, vérifiez CLUSTER_DATABASE et l'état des nœuds.","tags":["mount","exclusive","lock","startup","pmon","instance"]},{"code":"ORA-29913","title":"error in executing ODCIEXTTABLEOPEN callout (erreur lors de l'exécution du callout ODCIEXTTABLEOPEN)","severity":"caution","meaning":"Une erreur s'est produite lors de la lecture/de l'analyse du fichier source pendant l'accès à une table externe. Oracle a échoué dans la routine de l'access driver (ORACLE_LOADER / ORACLE_DATAPUMP) qu'il appelle pour récupérer les données de la table externe.","cause":"En général, le fichier n'est pas présent dans le chemin désigné par l'objet DIRECTORY, l'utilisateur OS d'Oracle n'a pas le droit de lecture sur le fichier/répertoire, il n'y a pas de droit d'écriture pour les fichiers log/bad, ou les paramètres d'accès (délimiteur de champ, format d'enregistrement) sont erronés. Souvent accompagné de sous-erreurs comme ORA-29400 et KUP-04xxx.","fix":"Lisez les sous-erreurs (KUP-xxxx) pour déterminer la cause racine. Vérifiez le chemin du DIRECTORY et l'existence du fichier : SELECT directory_name, directory_path FROM all_directories; Accordez à l'utilisateur OS d'Oracle le droit de lecture sur le fichier et le répertoire, et le droit d'écriture sur le répertoire log/bad. Alignez la section ACCESS PARAMETERS de la définition de la table externe sur le format du fichier source. Si nécessaire, vérifiez les droits READ/WRITE sur le DIRECTORY.","tags":["external table","external table","directory","oracle_loader","permission","kup"]}]},"errors/postgres-more3.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"08001","title":"sqlclient_unable_to_establish_sqlconnection","severity":"danger","meaning":"Le client n'a pas pu établir de connexion avec le serveur PostgreSQL. La connexion a échoué au niveau réseau/adresse, avant de devenir une session.","cause":"Hôte/port erroné ; adresse sur laquelle le serveur n'écoute pas (connexion distante alors que listen_addresses = 'localhost') ; serveur arrêté/injoignable ; pare-feu bloquant le port (5432 par défaut) ; ou échec de résolution DNS. Un pool de connexions mal configuré peut aussi la déclencher.","fix":"Vérifiez que le serveur est démarré et écoute sur la bonne adresse : contrôlez listen_addresses = '*' (ou l'IP concernée) et le paramètre port dans postgresql.conf, puis redémarrez le serveur (listen_addresses et port exigent un redémarrage ; un reload ne suffit pas). Testez l'accès depuis le client : psql -h host -p 5432 -U app_user. Ouvrez 5432/tcp dans le pare-feu. Revoyez les valeurs host/port/DNS et la chaîne de connexion.","tags":["connection","connection","network","listen_addresses","port","firewall"]},{"code":"08004","title":"sqlserver_rejected_establishment_of_sqlconnection","severity":"danger","meaning":"Le serveur a été atteint, mais il a refusé d'établir la connexion. L'accès réseau existe, mais le serveur n'a pas accepté cette connexion en vertu de sa politique.","cause":"Le serveur PostgreSQL produit rarement ce code ; il est surtout signalé par le driver client ou le pool de connexions. Les codes réels du serveur pour des refus similaires sont différents : un refus pg_hba.conf ('no pg_hba.conf entry for host', méthode reject) renvoie 28000 (invalid_authorization_specification), l'épuisement de max_connections renvoie 53300 (too_many_connections), et une connexion refusée pendant le démarrage/l'arrêt renvoie 57P03.","fix":"Lisez dans le journal du serveur le SQLSTATE réel et la raison du refus. Pour un refus pg_hba (28000), ajoutez une ligne correspondant à l'IP du client, à l'utilisateur et à la base (par ex. host all all 10.0.0.0/24 scram-sha-256) et rechargez avec SELECT pg_reload_conf();. En cas de saturation des connexions (53300), comparez les connexions client à max_connections avec SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';","tags":["connection","pg_hba","rejected","authentication","max_connections","reject"]},{"code":"57P01","title":"admin_shutdown","severity":"danger","meaning":"Votre connexion active a été terminée suite à une commande d'administrateur. Le client reçoit cette erreur lorsque le serveur s'arrête ou que la session est terminée manuellement ('terminating connection due to administrator command').","cause":"Un DBA a tué la session avec pg_terminate_backend() ; le serveur est arrêté en mode fast/immediate (le smart shutdown ne tue pas les sessions, il attend leur fin) ; ou les connexions existantes ont été coupées pour un changement de configuration/une maintenance planifiée. Dans certains scénarios de failover, l'ancien primaire coupe aussi les connexions de cette façon.","fix":"Ce n'est généralement pas une erreur permanente ; l'application doit se reconnecter (relance). Si c'est inattendu, recherchez la raison de l'arrêt/de la terminaison dans le journal du serveur. Pour les maintenances planifiées, implémentez la reconnexion automatique et les contrôles de santé du pool de connexions dans l'application. Le journal du serveur n'indique pas qui a terminé une session ; pour tracer les appels à pg_terminate_backend, conservez une piste d'audit avec log_statement (ou pgaudit).","tags":["shutdown","terminate","connection","maintenance","failover","retry"]},{"code":"53400","title":"configuration_limit_exceeded","severity":"danger","meaning":"L'opération a échoué car une limite de configuration a été dépassée. Le serveur a refusé la requête en raison d'une limite définie (par ex. temp_file_limit, nombre de replication slots ou de WAL senders).","cause":"Exemples typiques : utilisation de fichiers temporaires par une requête dépassant temp_file_limit ('temporary file size exceeds temp_file_limit') ; tous les replication slots utilisés ('all replication slots are in use', max_replication_slots) ; dépassement de la limite max_wal_senders. Remarque : l'épuisement de max_prepared_transactions renvoie 53200, et des prepared transactions désactivées (valeur 0) renvoient 55000.","fix":"Le message d'erreur indique quelle limite a été dépassée. Pour temp_file_limit, optimisez la requête (utilisez EXPLAIN ANALYZE pour trouver les étapes de tri/hash qui débordent sur disque), augmentez work_mem au niveau de la session si nécessaire, ou relevez la limite en connaissance de cause. Si les slots sont épuisés, nettoyez les slots inutilisés après avoir vérifié leurs propriétaires : SELECT slot_name, slot_type, active FROM pg_replication_slots; ou augmentez max_replication_slots / max_wal_senders et redémarrez le serveur.","tags":["limit","temp_file_limit","replication slot","max_wal_senders","configuration","resource"]},{"code":"XX001","title":"data_corrupted","severity":"danger","meaning":"PostgreSQL a détecté une corruption de données dans une page de table/heap. La requête ne peut pas se terminer en toute sécurité car le contenu du bloc lu est incohérent ('invalid page in block').","cause":"Panne de disque/stockage, corruption du système de fichiers, garanties fsync non respectées lors d'une coupure de courant soudaine, matériel défectueux (RAM/contrôleur) ou, rarement, un bug logiciel. Avec les checksums activés, la corruption est détectée plus tôt.","fix":"Vérifiez d'abord le matériel/le stockage et, si possible, prévoyez immédiatement un retour à une sauvegarde physique (PITR). Si une sauvegarde saine existe, restaurez-la. Pour la récupération, AU RISQUE DE PERDRE DES DONNÉES et uniquement en dernier recours, vous pouvez mettre à zéro les pages endommagées avec zero_damaged_pages = on et exporter les données lisibles (cela entraîne une perte de données). Activez data_checksums (sur un cluster existant en PG12+, pg_checksums --enable serveur arrêté), lancez des tests de RAM et effectuez des sauvegardes PITR régulières.","tags":["corruption","corruption","checksum","page","hardware","pitr"]},{"code":"XX002","title":"index_corrupted","severity":"danger","meaning":"Une corruption a été détectée dans une structure d'index. Les données vers lesquelles pointe l'index étant incohérentes avec les lignes réelles du heap, les requêtes utilisant l'index peuvent renvoyer des résultats faux ou échouer.","cause":"Panne matérielle/de stockage, ordre du B-tree cassé par un changement de collation (règle de tri) après une mise à niveau glibc/ICU du système d'exploitation, ou coupure soudaine. Les mises à jour de collation du système d'exploitation provoquent notamment une corruption silencieuse des index sur du texte.","fix":"Reconstruisez l'index corrompu : REINDEX INDEX idx_name; ou REINDEX TABLE my_table; Sur les grandes tables, utilisez REINDEX INDEX CONCURRENTLY pour réduire le verrouillage. Si la collation est en cause, reconstruisez tous les index texte concernés et vérifiez-les avec l'extension amcheck (bt_index_check). Si la cause racine est matérielle, remplacez le stockage.","tags":["index","index","corruption","reindex","collation","amcheck"]},{"code":"25001","title":"active_sql_transaction","severity":"caution","meaning":"Une commande qui ne peut s'exécuter qu'en dehors d'une transaction, ou à son début, a été appelée alors qu'une transaction était active. Certaines commandes ne s'exécutent pas à l'intérieur d'un bloc de transaction.","cause":"Des réglages comme SET TRANSACTION ISOLATION LEVEL doivent être émis avant la première requête du bloc ('SET TRANSACTION ISOLATION LEVEL must be called before any query'). De même, certaines commandes sont refusées dans un BEGIN/une transaction ouverte. Cela vient généralement d'une application qui désactive l'autocommit et démarre une transaction de façon implicite.","fix":"Émettez les réglages de niveau transaction tout au début de la transaction, avant l'exécution de toute requête : BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ... Vérifiez si la bibliothèque cliente ouvre une transaction implicite (paramètre autocommit). Pour les commandes qui ne peuvent pas s'exécuter dans un bloc de transaction, fermez d'abord la transaction en cours avec COMMIT/ROLLBACK.","tags":["transaction","transaction","isolation","set transaction","autocommit","begin"]},{"code":"42P16","title":"invalid_table_definition","severity":"caution","meaning":"La commande DDL a été refusée car la définition de la table est invalide. La structure de table/colonnes demandée enfreint les règles de PostgreSQL.","cause":"Situations comme la définition de plusieurs clés primaires ('multiple primary keys for table are not allowed'), des définitions en conflit avec l'héritage, des restrictions sur les colonnes generated/identity, ou une modification vers une structure qu'ALTER TABLE ne peut pas produire. Fréquent dans les scripts de migration générés automatiquement.","fix":"Lisez la raison précise dans le message d'erreur. Ne définissez qu'une seule PRIMARY KEY ; si une clé multicolonne est nécessaire, utilisez une clé composite de la forme PRIMARY KEY (a, b). Revoyez les règles des colonnes identity/generated. Vérifiez la définition actuelle de la table avec \\d my_table et corrigez le script de migration.","tags":["ddl","table","primary key","definition","alter table","migration"]},{"code":"P0001","title":"raise_exception","severity":"caution","meaning":"Une fonction ou un trigger PL/pgSQL a levé volontairement une erreur avec RAISE EXCEPTION. Cette erreur n'est pas produite par le moteur de base de données mais par du code de règles métier écrit par vous (ou par une bibliothèque).","cause":"Lorsque la logique métier échoue à une validation, le développeur a appelé RAISE EXCEPTION 'message' USING ERRCODE = ...;. Par exemple, un trigger lève une erreur personnalisée pour bloquer une mise à jour interdite ou faire respecter une règle de cohérence.","fix":"Le message d'erreur et les éventuels champs HINT/DETAIL expliquent ce que la fonction a refusé ; respectez la règle métier en conséquence. Pour trouver la source, inspectez le code de la fonction/du trigger concerné (les instructions RAISE). Si un SQLSTATE précis est nécessaire, attribuez un code significatif avec RAISE ... USING ERRCODE au lieu du générique P0001, afin que l'application puisse le distinguer.","tags":["plpgsql","raise","trigger","function","business rule","exception"]}]},"errors/mysql-more3.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1049","title":"Unknown database 'name'","severity":"caution","meaning":"La base (schéma) à laquelle vous essayez de vous connecter ou que vous voulez utiliser n'existe pas sur le serveur. L'instruction USE ou le nom de base par défaut dans la chaîne de connexion désigne un schéma inexistant.","cause":"La base n'a jamais été créée, son nom est mal orthographié (la sensibilité à la casse peut varier selon le système d'exploitation), elle a été supprimée, ou l'application se connecte au mauvais environnement (dev/prod). Des schémas non migrés lors du passage à un nouveau serveur sont une autre cause fréquente.","fix":"Listez les bases existantes : SHOW DATABASES; Vérifiez que le nom est correctement orthographié et que la casse correspond (le paramètre lower_case_table_names compte). Si elle n'existe pas, créez-la : CREATE DATABASE database_name CHARACTER SET utf8mb4; ou restaurez-la depuis une sauvegarde. Vérifiez l'environnement cible dans la configuration de connexion de l'application.","tags":["database","database","schema","use","name","connection"]},{"code":"1364","title":"Field 'name' doesn't have a default value (le champ 'name' n'a pas de valeur par défaut)","severity":"caution","meaning":"Une ligne a été insérée sans valeur pour une colonne définie NOT NULL sans DEFAULT. En mode SQL strict, MySQL lève une erreur car il ne peut pas renseigner automatiquement la colonne obligatoire manquante.","cause":"Une colonne obligatoire (NOT NULL, sans DEFAULT) a été omise de l'INSERT, et STRICT_TRANS_TABLES/STRICT_ALL_TABLES est activé dans sql_mode. Une application écrite pour l'ancien mode permissif rencontre cette erreur lorsqu'elle est déplacée sur un serveur avec le mode strict activé.","fix":"Ajoutez une valeur pour la colonne manquante dans l'INSERT, ou définissez un DEFAULT pertinent sur la colonne : ALTER TABLE my_table MODIFY my_column ... NOT NULL DEFAULT 'value'; Si la colonne est réellement facultative, autorisez NULL. sql_mode peut être assoupli comme contournement temporaire/de compatibilité, mais conserver le mode strict est recommandé pour la qualité des données.","tags":["insert","not null","default","strict mode","sql_mode","column"]},{"code":"1206","title":"The total number of locks exceeds the lock table size (le nombre total de verrous dépasse la taille de la table des verrous)","severity":"danger","meaning":"InnoDB a épuisé la mémoire allouée aux verrous qu'il détient (les structures de verrous dans le buffer pool). Cela se produit lorsqu'une seule transaction accumule trop de verrous de ligne/gap, et la transaction est annulée.","cause":"Très grosses transactions uniques : UPDATE/DELETE de millions de lignes d'un coup, clause WHERE sans index qui verrouille toute la table, ou innodb_buffer_pool_size trop petit pour la charge. Les gros traitements par lots sont le déclencheur classique.","fix":"Découpez les gros DML en petits lots (par ex. supprimez/mettez à jour 10,000 lignes à la fois avec LIMIT dans une boucle et un COMMIT après chaque lot). Indexez les colonnes du WHERE pour réduire le nombre de lignes verrouillées. Comme correctif durable, augmentez innodb_buffer_pool_size (les structures de verrous y sont allouées). Évitez les transactions inutilement longues.","tags":["innodb","lock","lock","buffer pool","batch","memory"]},{"code":"1236","title":"Got fatal error 1236 from master when reading data from binary log (erreur fatale 1236 reçue du master lors de la lecture du binary log)","severity":"danger","meaning":"Un serveur réplica (slave) a reçu une erreur fatale lors de la lecture du binary log de la source (master), et la réplication s'est arrêtée. Le réplica ne trouve pas dans le binlog de la source la position qu'il attend.","cause":"Les fichiers de binary log nécessaires ont été supprimés sur la source (binlog_expire_logs_seconds — expire_logs_days avant 8.0, supprimé en 8.4 — ou PURGE BINARY LOGS) ; trou/incohérence de GTID ; position de binlog corrompue après un crash de la source ; ou le réplica a pris tellement de retard qu'il demande un journal qui n'existe plus. 'Could not find first log file name in binary log index file' est le sous-message typique.","fix":"Examinez l'état de la réplication : SHOW REPLICA STATUS\\G (8.0.22+ ; SHOW SLAVE STATUS dans les versions plus anciennes, l'ancienne syntaxe a été supprimée en 8.4). Listez les binlogs disponibles sur la source : SHOW BINARY LOGS; Si le journal manquant a été purgé, vous devez reconstruire le réplica à partir d'une nouvelle sauvegarde/position GTID (mysqldump --source-data — --master-data avant 8.0.26, alias déprécié en 8.0.26+ — ou le plugin CLONE). Augmentez la rétention des binlogs sur la source en fonction de la charge ; si vous utilisez les GTID, vérifiez les trous de GTID.","tags":["replication","replication","binlog","master","gtid","slave"]},{"code":"1129","title":"Host 'host' is blocked because of many connection errors (l'hôte 'host' est bloqué en raison de nombreuses erreurs de connexion)","severity":"danger","meaning":"MySQL a bloqué un hôte client après que les erreurs de connexion consécutives provenant de celui-ci ont dépassé le seuil (max_connect_errors). L'hôte a été mis sur liste noire en raison des handshakes échoués jusque-là.","cause":"Un load balancer/outil de supervision envoie des handshakes incomplets ou mal formés sur le port TCP ; des problèmes réseau coupent les connexions en cours de route ; ou un client mal configuré multiplie les tentatives de connexion échouées. Lorsque le compteur d'erreurs atteint max_connect_errors, l'hôte est bloqué.","fix":"Levez immédiatement le blocage : en SQL, TRUNCATE TABLE performance_schema.host_cache; (8.0.23+ ; FLUSH HOSTS a été déprécié en 8.0.23 et supprimé en 8.4) ou en ligne de commande, mysqladmin flush-hosts. Pour voir les hôtes bloqués : SELECT host, ip, sum_connect_errors FROM performance_schema.host_cache; Corrigez la cause racine : configurez l'outil de health check/supervision pour qu'il effectue un handshake MySQL complet et vérifiez la stabilité du réseau. Dans des environnements sains, vous pouvez augmenter max_connect_errors pour accroître la tolérance, mais l'essentiel est de résoudre les erreurs de connexion réelles.","tags":["connection","host blocked","max_connect_errors","flush hosts","handshake","network"]},{"code":"1194","title":"Table 'name' is marked as crashed and should be repaired (la table 'name' est marquée comme endommagée et doit être réparée)","severity":"danger","meaning":"Une table, généralement stockée avec le moteur MyISAM (aussi Aria dans MariaDB), est marquée comme corrompue/endommagée et ne peut pas être utilisée avant d'être réparée. Les requêtes qui accèdent à la table échouent avec cette erreur.","cause":"La table MyISAM n'a pas été fermée proprement lors d'un crash soudain du serveur/d'une coupure de courant, le disque s'est rempli, une panne matérielle s'est produite ou le processus mysqld a été tué. MyISAM n'offrant ni transactions ni résistance aux crashs, cela arrive bien plus souvent qu'avec InnoDB.","fix":"Réparez la table : REPAIR TABLE table_name; ou hors ligne avec myisamchk -r. Sauvegardez toujours les fichiers de données avant de réparer. Comme correctif durable, passez les tables en InnoDB : ALTER TABLE my_table ENGINE=InnoDB; ce qui les rend résistantes aux crashs. Vérifiez l'état du disque et le paramètre myisam_recover_options.","tags":["myisam","crashed","repair","corruption","corruption","innodb"]},{"code":"1615","title":"Prepared statement needs to be re-prepared (l'instruction préparée doit être préparée à nouveau)","severity":"caution","meaning":"L'exécution d'une instruction préparée côté serveur a été refusée car elle est devenue invalide après la modification des métadonnées dont elle dépend (définition de table/vue). L'instruction doit être préparée à nouveau.","cause":"DDL (ALTER TABLE) sur la table sous-jacente après la préparation de l'instruction, table recréée, cache de métadonnées plein qui évince l'entrée, ou limites table_definition_cache/table_open_cache trop petites. Fréquent sur les systèmes avec beaucoup de DDL et dans certains scénarios ORM/pool.","fix":"La plupart des bibliothèques clientes interceptent l'erreur et préparent de nouveau l'instruction automatiquement ; dans l'application, interceptez cette erreur et relancez prepare + execute. Si elle se répète souvent, augmentez table_definition_cache et table_open_cache. Évitez le DDL inutile pendant le trafic applicatif.","tags":["prepared statement","re-prepare","ddl","metadata","cache","retry"]},{"code":"1553","title":"Cannot drop index needed in a foreign key constraint (impossible de supprimer un index requis par une contrainte de clé étrangère)","severity":"caution","meaning":"Vous essayez de supprimer un index requis par une contrainte de clé étrangère. InnoDB ne permet pas de retirer l'index sur lequel repose une FK.","cause":"Dans InnoDB, chaque clé étrangère exige un index sur sa ou ses colonnes de référence. La suppression de cet index avec DROP INDEX est refusée car elle laisserait la contrainte FK sans support. Cela survient généralement lors d'une réorganisation de table (nettoyage d'index).","fix":"Supprimez d'abord la contrainte de clé étrangère, puis l'index : ALTER TABLE my_table DROP FOREIGN KEY fk_name; puis DROP INDEX idx_name ON my_table; Si nécessaire, recréez la FK de sorte qu'elle repose sur un autre index approprié. Pour lister les FK existantes : SELECT constraint_name, table_name FROM information_schema.key_column_usage WHERE referenced_table_name IS NOT NULL;","tags":["foreign key","foreign key","index","drop index","innodb","constraint"]},{"code":"3948","title":"Loading local data is disabled; this must be enabled on both the client and server sides (le chargement de données locales est désactivé ; il doit être activé côté client et côté serveur)","severity":"caution","meaning":"Vous avez tenté de charger des données depuis un fichier côté client avec LOAD DATA LOCAL INFILE, mais la fonctionnalité est désactivée côté client et/ou côté serveur. Pour des raisons de sécurité, LOCAL est désactivé par défaut dans MySQL 8.","cause":"La variable système local_infile est à OFF sur le serveur et/ou le driver client n'a pas activé LOCAL INFILE. La fonctionnalité est restreinte par défaut car elle peut permettre à un serveur malveillant de lire les fichiers du client.","fix":"Si c'est vraiment nécessaire, activez-la des deux côtés : sur le serveur SET GLOBAL local_infile = 1; sur le client, activez l'option LOCAL INFILE du driver (par ex. l'option de connexion allowLoadLocalInfile / --local-infile=1). Du point de vue de la sécurité, l'approche préférable consiste à placer le fichier sur le serveur et à utiliser LOAD DATA INFILE sans LOCAL, ou à utiliser des outils de bulk INSERT/d'import. Désactivez de nouveau local_infile une fois terminé.","tags":["load data","local infile","local_infile","security","import","loading"]},{"code":"1075","title":"Incorrect table definition; there can be only one auto column and it must be defined as a key (définition de table incorrecte : une seule colonne auto, qui doit être définie comme clé)","severity":"caution","meaning":"La définition de la table est invalide car les règles d'AUTO_INCREMENT ont été enfreintes : une table ne peut avoir qu'une seule colonne auto-incrémentée, et cette colonne doit faire partie d'une clé (PRIMARY KEY ou UNIQUE/index).","cause":"Définition de plus d'une colonne AUTO_INCREMENT, ou colonne AUTO_INCREMENT laissée hors de toute clé (index). Se produit lors d'un CREATE TABLE ou d'un ALTER TABLE.","fix":"Conservez une seule colonne AUTO_INCREMENT et intégrez-la à une clé : CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, ...); Pour corriger une table existante, ajoutez d'abord la colonne à une clé : ALTER TABLE t ADD PRIMARY KEY (id); puis définissez AUTO_INCREMENT. Si vous avez besoin de plusieurs colonnes auto, revoyez la conception ; MySQL ne le prend pas en charge.","tags":["auto_increment","table","primary key","key","ddl","definition"]}]},"errors/mssql-more3.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"102","title":"Incorrect syntax near 'token'","severity":"caution","meaning":"Le parser Transact-SQL a trouvé une syntaxe invalide près du token indiqué et n'a pas pu compiler l'instruction. La requête est refusée avant son exécution.","cause":"Faute de frappe, virgule/parenthèse manquante, mot-clé réservé (par ex. USER, ORDER) utilisé sans crochets, littéral de chaîne non terminé, ou syntaxe incompatible copiée depuis un autre dialecte SQL. Les erreurs de concaténation en SQL dynamique sont aussi un déclencheur fréquent.","fix":"Examinez attentivement la zone autour du token signalé ; l'erreur se trouve souvent sur la ligne précédente. Entourez les mots réservés de crochets ([Order]) et vérifiez l'équilibre des parenthèses et des virgules. Si vous construisez du SQL dynamique, faites PRINT @sql pour voir le texte généré et exécutez-le. Vérifiez que la syntaxe est prise en charge par la version/le niveau de compatibilité.","tags":["syntax","syntax error","parse","tsql","dynamic sql","keyword"]},{"code":"8114","title":"Error converting data type X to Y","severity":"caution","meaning":"Une valeur n'a pas pu être convertie d'un type de données vers un autre car les types sont incompatibles. Contrairement à l'erreur 245, il s'agit généralement d'un échec de conversion de type au niveau d'un paramètre/d'une expression (par ex. varchar vers numeric).","cause":"Envoi d'un texte non numérique ou mal formé vers une colonne/un paramètre numérique ; dépassement d'échelle/de précision ; tentative de conversion d'une chaîne vide ('') en numérique ; ou passage d'un argument de mauvais type dans une requête paramétrée. Fréquent avec les conversions implicites générées par les ORM.","fix":"Vérifiez que les valeurs converties tiennent dans le type cible. Utilisez TRY_CONVERT / TRY_CAST pour une conversion sûre ; ils renvoient NULL en cas d'échec, ce qui permet de filtrer ces lignes avec WHERE. Alignez les types des paramètres sur ceux des colonnes. Parcourez les données avec ISNUMERIC/TRY_CAST pour trouver les lignes fautives, et nettoyez les chaînes vides.","tags":["convert","cast","data type","conversion","numeric","try_convert"]},{"code":"1204","title":"The instance of the SQL Server Database Engine cannot obtain a LOCK resource (l'instance du moteur SQL Server ne peut pas obtenir de ressource LOCK)","severity":"danger","meaning":"SQL Server n'a pas pu allouer la mémoire dont le gestionnaire de verrous a besoin et ne peut plus créer de verrous. L'opération échoue car le pool de mémoire des verrous est épuisé.","cause":"Trop de verrous fins (ligne/page) détenus simultanément : DML volumineux, sans index ou en masse ; escalade de verrous empêchée ; pression mémoire générale sur le serveur ; ou transactions ouvertes de longue durée. Il arrive qu'une pression mémoire externe soit le déclencheur.","fix":"Découpez les grosses opérations en lots plus petits pour réduire le nombre de verrous simultanés. Utilisez des index appropriés pour réduire le nombre de lignes parcourues/verrouillées. Revoyez les configurations qui désactivent l'escalade de verrous : trace flags 1211/1224 et ALTER TABLE ... SET (LOCK_ESCALATION = DISABLE); vérifiez avec SELECT name, lock_escalation_desc FROM sys.tables; et DBCC TRACESTATUS(-1); Vérifiez la mémoire du serveur et le paramètre max server memory, et soulagez la pression mémoire.","tags":["lock","lock memory","memory","lock escalation","batch","resource"]},{"code":"3906","title":"Failed to update database because the database is read-only (échec de la mise à jour : la base est en lecture seule)","severity":"caution","meaning":"Vous avez tenté de modifier des données (INSERT/UPDATE/DELETE/DDL) dans une base marquée en lecture seule. SQL Server a refusé l'écriture.","cause":"La base a été passée en READ_ONLY ; la connexion a abouti sur un réplica secondaire d'un Availability Group (destiné aux lectures) ; la base est restée en lecture seule après un snapshot ou une restauration ; ou le système de fichiers est en lecture seule. Une application routée par erreur vers un réplica secondaire est la cause typique.","fix":"Si elle doit vraiment être accessible en écriture : ALTER DATABASE mydb SET READ_WRITE; Dans un Availability Group, dirigez l'application vers le réplica primaire (vérifiez le routage des connexions ReadWrite / ApplicationIntent). Un snapshot en lecture seule ne peut pas être modifié ; ciblez plutôt la base source. Vérifiez aussi les droits sur les fichiers et le système de fichiers.","tags":["read only","read-only database","availability group","replica","read_write","write"]},{"code":"15023","title":"User already exists in the current database (l'utilisateur existe déjà dans la base courante)","severity":"caution","meaning":"Lors de l'ajout d'un nouvel utilisateur à la base, un utilisateur du même nom existait déjà dans cette base. Apparaît aussi lors du mappage d'un login vers un utilisateur existant (utilisateurs orphelins).","cause":"Typiquement, lorsqu'une base est restaurée depuis une sauvegarde sur un autre serveur, les SID des logins ne correspondent pas aux utilisateurs de la base (utilisateurs orphelins). Tenter d'ajouter de nouveau le même nom avec CREATE USER, ou de mettre en place manuellement le mappage login-utilisateur, lève cette erreur.","fix":"Remappez l'utilisateur orphelin sur le login existant : ALTER USER app_user WITH LOGIN = login_name; (recommandé plutôt que l'ancien sp_change_users_login). Si l'utilisateur est réellement superflu : DROP USER app_user; Pour trouver les orphelins : SELECT dp.name FROM sys.database_principals dp LEFT JOIN sys.server_principals sp ON dp.sid = sp.sid WHERE sp.sid IS NULL AND dp.type IN ('S','U');","tags":["user","database user","login","orphaned","restore","orphaned user"]},{"code":"18452","title":"Login failed. The login is from an untrusted domain and cannot be used with Windows authentication (échec de connexion : login provenant d'un domaine non approuvé, inutilisable avec l'authentification Windows)","severity":"danger","meaning":"Une tentative de connexion avec l'authentification Windows a été refusée car elle provenait d'un domaine non approuvé (ou l'authentification Windows a été utilisée là où l'authentification SQL est requise). Le serveur ne fait pas confiance à cette identité.","cause":"Le client se connecte avec une identité Windows d'un domaine/groupe de travail que SQL Server n'approuve pas ; les machines sont dans des domaines différents/non approuvés ; la configuration Kerberos/SPN est erronée ; ou la chaîne de connexion utilise Integrated Security alors que l'authentification SQL est attendue. Une tentative de login SQL alors que le mode mixte est désactivé présente un symptôme similaire.","fix":"Si l'authentification SQL est requise, passez le serveur en Mixed Mode et utilisez un nom d'utilisateur/mot de passe dans la chaîne de connexion (Integrated Security=false). Si l'authentification Windows est requise, vérifiez la relation d'approbation entre domaines et la configuration DNS/SPN Kerberos. Assurez-vous que le client et le serveur sont dans le même domaine ou dans un domaine approuvé. Affinez le diagnostic avec la valeur 'state' du journal d'erreurs.","tags":["login","authentication","windows","domain","kerberos","mixed mode"]},{"code":"605","title":"Attempt to fetch logical page belongs to a different allocation unit (la page logique lue appartient à une autre unité d'allocation)","severity":"danger","meaning":"Lors de la lecture d'une page, SQL Server a trouvé une page appartenant à un autre objet au lieu de l'objet/de l'unité d'allocation attendus. C'est un indicateur fort d'une corruption physique/logique grave de la base.","cause":"Chaînage de pages rompu à cause d'une panne de stockage/disque, d'un bug de contrôleur ou de pilote, d'une corruption du cache d'écriture lors d'une coupure de courant soudaine, ou d'un système de fichiers endommagé. Un « 605 transitoire » peut parfois provenir de problèmes d'I/O temporaires, mais les cas persistants sont une vraie corruption.","fix":"Exécutez immédiatement DBCC CHECKDB ('mydb') WITH NO_INFOMSGS, ALL_ERRORMSGS pour déterminer l'étendue des dégâts. Si possible, restaurez depuis une sauvegarde saine connue (une PITR minimisant la perte de données est préférable). Testez le stockage/le matériel et vérifiez le paramètre PAGE_VERIFY CHECKSUM. Les options REPAIR peuvent entraîner une perte de données ; ne les utilisez qu'en dernier recours et après avoir effectué une sauvegarde ; dans les environnements critiques, faites appel au support Microsoft.","tags":["corruption","database corruption","page","allocation unit","dbcc checkdb","storage"]},{"code":"4922","title":"ALTER TABLE ALTER COLUMN failed because one or more objects access this column (échec d'ALTER TABLE ALTER COLUMN : un ou plusieurs objets accèdent à cette colonne)","severity":"caution","meaning":"Vous avez tenté de modifier le type/une propriété d'une colonne, mais des objets dépendant de cette colonne (index, contrainte, colonne calculée, statistiques, vue schema-bound, etc.) l'en ont empêché. La colonne ne peut pas être modifiée tant que les dépendances ne sont pas retirées.","cause":"La colonne est utilisée dans un index, une contrainte PRIMARY KEY/UNIQUE/CHECK/DEFAULT, une expression de colonne calculée, une vue/fonction définie WITH SCHEMABINDING, ou un objet de statistiques. Un changement de type/de taille invaliderait ces objets, l'ALTER est donc refusé.","fix":"Trouvez d'abord les objets dépendants et supprimez-les ou désactivez-les temporairement, modifiez la colonne, puis recréez-les. Interrogez sys.sql_expression_dependencies et sys.indexes/sys.default_constraints pour voir les dépendances. Ordre typique : DROP CONSTRAINT/INDEX -> ALTER COLUMN -> CREATE de nouveau. Pour les vues schema-bound, retirez temporairement SCHEMABINDING puis rajoutez-le.","tags":["alter column","dependency","index","constraint","schemabinding","ddl"]},{"code":"7202","title":"Could not find server 'name' in sys.servers (serveur 'name' introuvable dans sys.servers)","severity":"caution","meaning":"Le nom de serveur référencé dans la requête via un linked server ou un nom en quatre parties (server.database.schema.object) est introuvable parmi les linked servers configurés. SQL Server n'a pas pu résoudre le nom.","cause":"Le linked server n'a jamais été défini ; le nom est mal orthographié ; un linked server présent dans un autre environnement (dev/prod) manque en production ; ou OPENQUERY/le nom en quatre parties désigne un mauvais nom de serveur. Fréquent lorsque des scripts sont déplacés d'un serveur à l'autre.","fix":"Listez les linked servers définis : SELECT name FROM sys.servers; ou EXEC sp_linkedservers. Ajoutez-le si nécessaire : EXEC sp_addlinkedserver @server='SERVER', ...; et configurez le mappage des logins avec sp_addlinkedsrvlogin. Vérifiez que le nom en quatre parties de la requête utilise le bon nom de linked server ; si vous accédez uniquement à un objet local, retirez le préfixe de serveur.","tags":["linked server","remote server","sys.servers","openquery","four-part name","configuration"]},{"code":"40501","title":"The service is currently busy. Retry the request after 10 seconds (le service est actuellement occupé ; réessayez dans 10 secondes)","severity":"danger","meaning":"Dans Azure SQL Database, le service a temporairement refusé la requête en raison d'une pression sur les ressources (throttling du moteur). Le système est occupé et le client doit réessayer peu après.","cause":"Une ressource comme le CPU, les connexions, le débit d'écriture du journal de transactions ou le nombre de workers atteint la limite du niveau de service choisi (DTU/vCore) ; pic de charge soudain ; ou requêtes longues/inefficaces qui consomment la ressource. Le 'reason code' accompagnant l'erreur indique quelle ressource est limitée.","fix":"Implémentez une logique de relance avec backoff exponentiel dans l'application ; c'est le comportement attendu pour les erreurs transitoires. Examinez l'utilisation des ressources : sys.dm_db_resource_stats et les métriques du portail Azure. Optimisez les requêtes/index et réduisez la concurrence inutile. Si la saturation persiste, montez en niveau de service (DTU/vCore).","tags":["azure","throttling","busy","retry","resource","dtu"]}]},"errors/mongodb-more3.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"10107 NotWritablePrimary","title":"not master / not writable primary","severity":"danger","meaning":"Une écriture (ou une autre opération nécessitant le primaire) a été envoyée à un membre du replica set qui n'est pas actuellement le primaire. Seul le primaire accepte les écritures, l'opération a donc été refusée.","cause":"L'application écrit sur un secondaire ; lors d'un failover (élection), l'ancien primaire est devenu secondaire et le client y est toujours connecté ; ou les informations de préférence de lecture/écriture et de topologie du driver sont obsolètes. Un paramètre replicaSet absent de la chaîne de connexion est la cause typique.","fix":"Configurez le driver avec tous les membres du replica set et le nom du replica set (replicaSet=... et tous les hôtes dans la chaîne de connexion) afin que le driver découvre automatiquement le primaire et réoriente les requêtes en cas de failover. Utilisez la préférence de lecture/écriture par défaut, qui dirige les écritures vers le primaire. Comme il s'agit d'une erreur transitoire, activez les retryable writes dans l'application. Vérifiez quel membre est PRIMARY avec rs.status().","tags":["replica set","primary","failover","write","election","retryable"]},{"code":"91 ShutdownInProgress","title":"The server is in the process of shutting down (le serveur est en cours d'arrêt)","severity":"danger","meaning":"L'opération n'a pas pu se terminer car le serveur (mongod/mongos) est en cours d'arrêt. Un nœud en cours d'arrêt refuse les nouvelles requêtes et termine les opérations en cours.","cause":"Redémarrage/maintenance planifié, arrêt précédant un stepdown du primaire dans un replica set, signal de terminaison envoyé par le conteneur/l'orchestrateur, ou processus arrêté en raison de l'épuisement des ressources. C'est attendu lors des rolling upgrades.","fix":"C'est généralement transitoire ; utilisez une logique de relance dans l'application et une connexion au replica set afin que les requêtes soient dirigées vers un autre membre sain. Si c'est inattendu, recherchez la raison de l'arrêt dans les journaux mongod (signal, OOM killer, ressources). Lors des maintenances progressives, rétrogradez proprement le primaire avant de l'arrêter (rs.stepDown()).","tags":["shutdown","shutting down","maintenance","stepdown","replica set","retry"]},{"code":"189 PrimarySteppedDown","title":"Primary stepped down while operation was in progress (le primaire a été rétrogradé pendant l'opération)","severity":"danger","meaning":"Le primaire a été rétrogradé (stepdown) pendant l'exécution de l'opération et n'accepte plus les écritures. La commande en cours a donc été abandonnée.","cause":"Une élection a été déclenchée dans le replica set : le primaire a été rétrogradé manuellement pour maintenance, a perdu le contact avec la majorité, un changement de priorité s'est produit, ou il y a eu une partition réseau. Les opérations en cours au moment du failover reçoivent cette erreur.","fix":"C'est typiquement une erreur pouvant être relancée ; avec les retryable writes/reads activés dans le driver, l'opération peut être dirigée automatiquement vers le nouveau primaire. Interceptez cette erreur dans l'application et relancez. Si cela se produit souvent, examinez la santé du replica set : vérifiez l'état des membres, la latence réseau et le retard de l'oplog avec rs.status(), et réduisez les élections inutiles (paramètres heartbeat/timeout).","tags":["stepdown","election","failover","primary","replica set","retry"]},{"code":"24 LockTimeout","title":"Operation exceeded time limit waiting for a lock (l'opération a dépassé le délai d'attente d'un verrou)","severity":"caution","meaning":"Une opération a dépassé son délai en attendant un verrou requis (par ex. au niveau base/collection) et a été abandonnée. Le verrou n'a pas pu être obtenu à temps.","cause":"Une opération longue ou une commande DDL (par ex. des opérations d'administration conflictuelles sur une collection) détient le verrou requis ; écritures concurrentes importantes ; ou une opération limitée par maxTimeMS expire en attendant un verrou. Observé avec des opérations d'index/de collection conflictuelles.","fix":"Trouvez l'opération qui détient le verrou : examinez les opérations actives et en attente avec db.currentOp() ; terminez-la avec db.killOp() si nécessaire. Déplacez les commandes d'administration conflictuelles (construction d'index, collMod, compact) en heures creuses. Gardez les opérations courtes et évitez les transactions inutilement longues. Si nécessaire, ajustez maxTimeMS à la charge.","tags":["lock","lock wait","timeout","currentop","ddl","concurrency"]},{"code":"7 HostNotFound","title":"Could not find host","severity":"danger","meaning":"MongoDB n'a pas pu résoudre/trouver un nom d'hôte auquel il tentait de se connecter. Le driver ou un membre du replica set ne peut pas atteindre l'hôte indiqué, la connexion ne peut donc pas être établie.","cause":"Échec de résolution DNS, nom d'hôte erroné/obsolète dans la chaîne de connexion, noms d'hôte des membres du replica set (rs.conf) que le client ne peut pas résoudre, ou problème réseau/d'accès. Dans les environnements de conteneurs, des noms d'hôte internes non résolubles depuis l'extérieur sont la cause typique.","fix":"Vérifiez que le nom d'hôte est correct et résoluble (testez le DNS avec ping/nslookup). Mettez à jour les hôtes dans la chaîne de connexion. Assurez-vous que les noms des membres du replica set sont résolubles à la fois par les clients et par les membres ; si nécessaire, remplacez les noms d'hôte dans rs.conf() par des FQDN/IP joignables. Si vous utilisez SRV (mongodb+srv), vérifiez les enregistrements DNS SRV.","tags":["host","dns","connection","replica set","network","srv"]},{"code":"175 QueryPlanKilled","title":"Query plan killed during execution","severity":"caution","meaning":"Le plan d'une requête en cours est devenu invalide car les données/la collection sous-jacentes ont changé, et il a été tué. La requête a été abandonnée car elle ne pouvait pas continuer en toute sécurité.","cause":"Pendant l'exécution de la requête (notamment avec un curseur ouvert), la collection a été supprimée, un index qu'elle utilisait a été supprimé, ou un renommage/une reconfiguration a eu lieu. Les changements sous-jacents pendant des appels getMore de longue durée sont le déclencheur typique.","fix":"Relancez la requête/le curseur. Assurez-vous qu'aucun DDL destructif (drop, dropIndex, rename) n'est exécuté sur la collection ou l'index pendant l'exécution des requêtes ; planifiez ce type de maintenance en dehors du trafic de requêtes. Raccourcissez les lectures longues et assurez-vous que les index nécessaires sont permanents.","tags":["query","cursor","getmore","drop","index","plan"]},{"code":"11602 InterruptedDueToReplStateChange","title":"Operation interrupted due to a replication state change (opération interrompue suite à un changement d'état de la réplication)","severity":"danger","meaning":"L'opération a été interrompue car l'état du replica set a changé (par ex. changement de primaire). La commande en cours a été terminée car elle ne pouvait pas se terminer en toute sécurité dans la nouvelle topologie.","cause":"Les opérations en cours sont interrompues lors d'une élection, d'un stepdown du primaire, de changements de rôle des membres ou d'un changement de configuration. A la même origine que 189 PrimarySteppedDown et apparaît généralement au moment du failover.","fix":"C'est une erreur pouvant être relancée ; avec les retryable writes/reads activés dans le driver, l'opération peut être relancée sur le nouveau primaire. Interceptez cette erreur dans l'application et relancez. Si elle se répète souvent, recherchez la cause des élections : instabilité réseau, timeouts de heartbeat, surcharge ou retard de l'oplog. Surveillez la santé du replica set avec rs.status().","tags":["replica set","election","failover","interrupted","retry","state change"]},{"code":"136 CappedPositionLost","title":"Capped collection position lost","severity":"caution","meaning":"Un curseur lisant une capped collection (de taille fixe), typiquement un tailable cursor, a perdu sa position. Le document sur lequel il était positionné n'existe plus car la collection l'a écrasé de façon cyclique.","cause":"Lorsqu'une capped collection est pleine, les documents les plus anciens sont supprimés et écrasés automatiquement ; un tailable cursor qui suit une collection écrite plus vite qu'il ne peut consommer prend du retard et sa position est écrasée. Observé chez les consommateurs qui suivent des structures capped comme l'oplog.","fix":"Repositionnez le curseur (ouvrez un nouveau tailable cursor) et reprenez le suivi ; interceptez cette erreur dans l'application et rafraîchissez la lecture. Si la cause racine est un consommateur qui ne suit pas le rythme d'écriture : augmentez la taille de la capped collection/de l'oplog ou accélérez le consommateur. Pour suivre les changements de façon fiable, utilisez les change streams plutôt que les tailable cursors lorsque c'est possible.","tags":["capped","tailable cursor","oplog","cursor","position","change stream"]},{"code":"28 PathNotViable","title":"Cannot use the part of the path to traverse the element (impossible d'utiliser cette partie du chemin pour parcourir l'élément)","severity":"caution","meaning":"Une mise à jour a tenté de parcourir un chemin de champ pointé (par ex. a.b.c), mais une partie du chemin a rencontré un type qui ne peut pas contenir de sous-champs (valeur scalaire ou élément de mauvais type). Le chemin n'a donc pas pu être parcouru.","cause":"Écriture dans un chemin imbriqué comme a.b.c alors que le champ 'a' du document est un scalaire (par ex. nombre/chaîne) et non un objet/tableau ; ou incohérences de type comme un objet trouvé là où un index de tableau est attendu. Fréquent dans les documents au schéma incohérent (lors de la mise à jour de chemins profonds avec $set).","fix":"Avant la mise à jour, assurez-vous que le chemin est du type attendu (objet/tableau). Corrigez les documents incompatibles ou rendez la mise à jour conditionnelle à la structure du document. Si nécessaire, convertissez d'abord le champ dans le bon type (par ex. remplacez la valeur scalaire par un objet), puis écrivez le champ imbriqué. Envisagez la validation de schéma pour la cohérence du schéma.","tags":["update","path","dotted","type","set","schema"]}]},"errors/redis-more3.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"BUSYGROUP Consumer Group name already exists","title":"Consumer group already exists","severity":"caution","meaning":"Vous avez tenté de créer un consumer group sur un stream avec XGROUP CREATE, mais un groupe du même nom existe déjà sur ce stream. Redis n'écrase pas le groupe existant.","cause":"L'application tente de créer le groupe sans condition au démarrage et le groupe subsiste d'une exécution précédente ; ou plusieurs instances tentent de créer le même groupe en parallèle. Typique d'une logique de démarrage non idempotente.","fix":"Rendez la création du groupe idempotente : interceptez et ignorez l'erreur (un groupe existant ne pose pas de problème). Utilisez XGROUP CREATE stream group $ MKSTREAM pour créer automatiquement le stream s'il n'existe pas encore. Exécutez XINFO GROUPS stream pour voir les groupes existants. Si vous voulez vraiment le réinitialiser, supprimez-le d'abord avec XGROUP DESTROY stream group.","tags":["stream","consumer group","xgroup","busygroup","idempotent","mkstream"]},{"code":"NOGROUP No such key or consumer group","title":"No such key or consumer group in stream","severity":"caution","meaning":"Vous avez exécuté une commande de consommation de stream (XREADGROUP, XACK, XCLAIM, etc.), mais la clé (stream) ou le consumer group indiqué n'existe pas. Le groupe/stream sur lequel agir est introuvable.","cause":"Le groupe n'a jamais été créé (XGROUP CREATE omis) ; la clé du stream a été supprimée ou jamais écrite ; le nom du groupe/de la clé est mal orthographié ; ou le groupe a été supprimé avec XGROUP DESTROY. Fréquent lorsque les consommateurs démarrent avant la création du groupe.","fix":"Assurez-vous d'abord que le stream et le groupe existent : XINFO STREAM stream et XINFO GROUPS stream. Si le groupe manque, créez-le : XGROUP CREATE stream group $ MKSTREAM (crée aussi le stream si nécessaire). Ajoutez au démarrage de l'application une étape qui crée le groupe (en ignorant BUSYGROUP). Vérifiez l'orthographe des noms de clé et de groupe.","tags":["stream","consumer group","nogroup","xreadgroup","xgroup","key"]},{"code":"NOTBUSY No scripts in execution right now","title":"No scripts in execution right now","severity":"safe","meaning":"Vous avez envoyé SCRIPT KILL ou FUNCTION KILL, mais aucun script/aucune fonction Lua n'est en cours d'exécution (et ne peut être tué). La commande a été refusée car il n'y a rien à terminer.","cause":"Le script était déjà terminé au moment où vous avez tenté de l'arrêter ; SCRIPT KILL a été envoyé au mauvais nœud (alors que le script s'exécute sur une autre instance) ; ou la condition BUSY s'est résolue d'elle-même avant SCRIPT KILL. Généralement un problème de timing.","fix":"Si aucun script n'est réellement bloqué, il n'y a rien à faire ; c'est une réponse sans danger/informative. Si vous obtenez une erreur BUSY (un script a dépassé busy-reply-threshold, appelé lua-time-limit avant Redis 7), essayez SCRIPT KILL (FUNCTION KILL pour les fonctions) sur le bon nœud, là où le script s'exécute. Si le script a modifié des données, SCRIPT KILL ne fonctionne pas ; en dernier recours, SHUTDOWN NOSAVE peut être nécessaire. Découpez les scripts longs et respectez la limite de temps.","tags":["lua","script kill","notbusy","busy","script","function"]},{"code":"DENIED Redis is running in protected mode","title":"Redis is running in protected mode","severity":"danger","meaning":"Redis fonctionne en protected mode et a refusé une connexion sans mot de passe provenant de l'extérieur (adresse autre que loopback). Pour des raisons de sécurité, l'accès distant non authentifié est bloqué.","cause":"Redis, lié à toutes les interfaces (ou avec un paramètre bind trop permissif), a reçu une connexion d'un client externe sans qu'aucun mot de passe (requirepass) ne soit configuré. protected-mode est activé par défaut et, dans cette situation, bloque les requêtes non loopback. Cela indique un Redis non sécurisé accidentellement exposé à Internet.","fix":"La bonne solution est de le sécuriser : définissez un mot de passe robuste avec requirepass (ou des utilisateurs ACL) et n'écoutez que sur des interfaces de confiance avec bind. Si l'accès distant est nécessaire, protégez-le par mot de passe/TLS et restreignez le port 6379 avec un pare-feu. protected-mode no peut être défini temporairement uniquement sur un réseau fermé/sécurisé, mais ce n'est JAMAIS recommandé pour des installations exposées à Internet.","tags":["protected mode","security","denied","requirepass","bind","auth"]},{"code":"ERR Can't execute '<cmd>': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context","title":"Command not allowed in subscribe context","severity":"caution","meaning":"Après le passage de la connexion en mode subscribe Pub/Sub, vous avez tenté d'exécuter une commande ordinaire (par ex. GET/SET) non autorisée dans ce mode. Une connexion en mode subscribe n'accepte qu'un ensemble limité de commandes. Dans les anciennes versions, le message était \"ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context\".","cause":"Envoi de commandes de données sur une connexion passée en mode subscribe avec SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE. Avec le protocole RESP2, une connexion abonnée n'autorise que (P|S)SUBSCRIBE, (P|S)UNSUBSCRIBE, PING, QUIT et RESET. Partager une seule connexion pour le pub/sub et le travail ordinaire est l'erreur typique.","fix":"Utilisez une connexion dédiée au Pub/Sub et ouvrez-en une autre pour les commandes ordinaires (la plupart des bibliothèques clientes le font automatiquement). Envoyez UNSUBSCRIBE/PUNSUBSCRIBE pour quitter le mode subscribe ; les commandes ordinaires fonctionnent alors. Avec le protocole RESP3 (HELLO 3), d'autres commandes sont aussi autorisées sur une connexion abonnée ; passez à RESP3 si votre driver le prend en charge.","tags":["pubsub","subscribe","context","resp","connection","hello"]},{"code":"ERR SELECT is not allowed in cluster mode","title":"SELECT is not allowed in cluster mode","severity":"caution","meaning":"En mode Redis Cluster, vous avez tenté de basculer vers une autre base logique (index de DB) avec SELECT. Le mode cluster ne prend en charge que la base 0 ; plusieurs DB ne peuvent pas être utilisées.","cause":"Une application qui bascule entre les DB 0-15 avec SELECT sur un Redis standalone continue d'envoyer la même commande après le passage à Redis Cluster. L'architecture cluster répartissant les données par slot, la notion de multiples DB n'est pas prise en charge.","fix":"Supprimez les appels SELECT en mode cluster ; seule la DB 0 est utilisée. Si vous avez besoin d'une séparation logique, utilisez des préfixes de clé (namespaces) (par ex. app1:key). Si certaines clés doivent être traitées ensemble, utilisez un hash tag ({user123}) pour qu'elles se retrouvent dans le même slot. Rendez la configuration de l'application compatible cluster.","tags":["cluster","select","database","namespace","hash tag","slot"]},{"code":"ERR wrong number of arguments for command","title":"Wrong number of arguments for command","severity":"caution","meaning":"Une commande Redis a été envoyée avec moins ou plus d'arguments que prévu. La commande a été refusée sans être traitée car sa syntaxe n'était pas respectée.","cause":"Paramètres manquants/en trop (par ex. SET key seul au lieu de SET key value) ; variables passées vides/undefined ; commande appelée avec un mauvais ordre d'arguments ; ou expansion (spread) défaillante de tableaux/d'arguments dans la bibliothèque cliente. Fréquent avec les commandes construites dynamiquement.","fix":"Vérifiez la signature correcte de la commande (par ex. SET key value [options]) ; consultez la référence officielle des commandes. Dans le code client, vérifiez que les arguments (notamment ceux issus de tableaux) ne sont pas vides/manquants. Si vous construisez des commandes à partir d'entrées utilisateur, validez au préalable le nombre d'arguments. Essayez la commande manuellement dans redis-cli pour confirmer le nombre d'arguments attendu.","tags":["syntax","arguments","argument","command","syntax","cli"]},{"code":"ERR Background save already in progress","title":"Background save already in progress","severity":"caution","meaning":"Vous avez tenté de démarrer une nouvelle sauvegarde en arrière-plan (BGSAVE) ou une réécriture AOF, mais une sauvegarde en arrière-plan est déjà en cours. Redis n'autorise pas un second processus de sauvegarde simultané.","cause":"Nouveau BGSAVE/BGREWRITEAOF déclenché avant la fin du précédent ; scripts de supervision ou application appelant BGSAVE à répétition alors que le fork/la sauvegarde prend longtemps sur un gros jeu de données. Un disque lent ou un gros fichier RDB prolonge le processus.","fix":"Attendez la fin de la sauvegarde en cours ; vérifiez son état avec les champs rdb_bgsave_in_progress et aof_rewrite_in_progress d'INFO persistence et l'horodatage LASTSAVE. Rendez la logique applicative/d'automatisation conditionnelle pour ne pas déclencher un nouveau BGSAVE avant la fin de la sauvegarde précédente. Si les sauvegardes sont trop longues, examinez la vitesse du disque et la taille du RDB.","tags":["bgsave","persistence","rdb","aof","background save","fork"]},{"code":"ERR bit offset is not an integer or out of range","title":"Bit offset is not an integer or out of range","severity":"caution","meaning":"L'offset de bit fourni à une opération sur les bits comme SETBIT/GETBIT/BITCOUNT est invalide : ce n'est pas un entier ou il est hors de la plage autorisée. Redis ne peut pas effectuer d'opération sur les bits à cet offset.","cause":"Passage d'une valeur non numérique comme offset ; utilisation d'un offset négatif ; ou offset très grand dépassant la borne supérieure autorisée (une chaîne de bits peut atteindre 512 MB, soit environ 2^32 bits). Provient d'erreurs de calcul dans des structures de compteurs/indicateurs basées sur des bitmaps.","fix":"Assurez-vous que l'offset est un entier non négatif dans la plage autorisée (0 à ~2^32-1 ; les chaînes font au plus 512 MB). Contrôlez les calculs d'offset (par ex. ceux dérivés d'identifiants utilisateur) contre les dépassements/valeurs négatives. Si de très grands offsets sont nécessaires, concevez les données pour qu'elles soient réparties sur plusieurs clés (sharding).","tags":["bitmap","setbit","offset","integer","range","bit"]}]},"errors/elasticsearch-more3.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"node_not_connected_exception","title":"Node is not connected","severity":"danger","meaning":"Un nœud n'est actuellement pas connecté à un autre nœud avec lequel il doit communiquer. La requête n'a pas pu être transmise au nœud cible car la connexion intra-cluster (couche transport) a été perdue.","cause":"Coupure réseau ou perte de paquets, crash/redémarrage du nœud cible, blocage par pare-feu/port (port transport par défaut 9300), nœud temporairement sans réponse à cause de pauses GC, ou problème de résolution DNS/d'hôte. Peut apparaître de façon transitoire lors des rolling restarts.","fix":"Examinez la santé du cluster et des nœuds : utilisez GET _cluster/health et GET _cat/nodes?v pour voir quel nœud manque. Vérifiez l'accès réseau entre nœuds et les règles de pare-feu pour le port transport (9300). Consultez les journaux du nœud concerné à la recherche de crashs, pauses GC ou OOM. Revoyez la stabilité du réseau et la configuration discovery/seed hosts.","tags":["transport","node","connection","network","9300","cluster"]},{"code":"receive_timeout_transport_exception","title":"Request timed out waiting for a response from another node (délai dépassé en attendant la réponse d'un autre nœud)","severity":"danger","meaning":"Un nœud a envoyé une requête à un autre nœud, mais la réponse n'est pas arrivée dans le délai configuré et la requête a expiré. L'appel entre nœuds (transport) n'a pas pu aboutir.","cause":"Nœud cible sous forte charge, longues pauses GC, disque lent/pression CPU, latence réseau/perte de paquets, ou nœud cible trop occupé pour répondre. Fréquent lors de requêtes lourdes, de grosses opérations bulk et du rééquilibrage des shards.","fix":"Examinez l'utilisation des ressources sur les nœuds concernés : vérifiez le heap JVM, le GC et la saturation des thread pools avec GET _nodes/stats. Vérifiez les tâches rejetées/en file d'attente avec GET _cat/thread_pool?v. Mesurez la latence réseau. Réduisez la charge (requêtes/bulk plus petits) et revoyez le heap et les ressources matérielles. Si le problème persiste, ajoutez des nœuds pour répartir la charge.","tags":["transport","timeout","gc","load","network","thread pool"]},{"code":"routing_missing_exception","title":"Routing value is required for this document (valeur de routage requise pour ce document)","severity":"caution","meaning":"Une valeur de routing est requise pour accéder au document/l'indexer, mais elle n'a pas été fournie. Sans routing, Elasticsearch n'a pas pu déterminer à quel shard appartient le document.","cause":"Le mapping de l'index définit _routing required=true ; une relation parent-join (parent/enfant) est utilisée et les documents enfants exigent un routing ; ou le paramètre routing a été omis d'une requête index/get/update/delete. Lire des documents écrits avec un routing personnalisé sans fournir ce routing est la cause typique.","fix":"Indiquez explicitement la valeur de routing dans la requête (par ex. ?routing=value dans les appels index/get/update/delete). Avec parent-join, passez l'id du parent comme routing pour les documents enfants. Utilisez pour les lectures/mises à jour/suppressions le même routing que lors de l'écriture du document. Revoyez l'exigence _routing (required) dans le mapping.","tags":["routing","shard","parent-child","join","mapping","index"]},{"code":"too_many_buckets_exception","title":"Trying to create too many buckets","severity":"caution","meaning":"Une requête d'agrégation a tenté de produire plus de buckets que la limite supérieure autorisée (search.max_buckets, 65,536 par défaut). La requête a été refusée pour protéger la mémoire.","cause":"Agrégations comme terms/date_histogram exécutées sur des champs à forte cardinalité avec un size très élevé ou un intervalle très fin ; agrégations imbriquées qui multiplient le nombre de buckets. La limite est imposée car une création excessive de buckets peut épuiser la mémoire du nœud.","fix":"Réduisez le nombre de buckets produits : diminuez le size des agrégations terms, élargissez l'intervalle de date_histogram, supprimez les agrégations imbriquées inutiles. Si de nombreux résultats sont nécessaires, paginez-les avec une composite aggregation. Relever la limite doit rester un dernier recours et comporte un risque mémoire : search.max_buckets peut être modifié avec PUT _cluster/settings, mais avec prudence.","tags":["aggregation","bucket","max_buckets","terms","composite","memory"]},{"code":"shard_lock_obtain_failed_exception","title":"Obtaining shard lock timed out","severity":"danger","meaning":"Elasticsearch n'a pas pu obtenir dans le délai imparti le verrou de répertoire local (shard lock) nécessaire pour agir sur un shard. Le shard n'a pas pu être démarré ni réaffecté.","cause":"Une copie précédente du même shard (après fermeture/relocalisation) n'a pas encore libéré le verrou ; I/O lentes/bloquées ; problèmes de disque ; ou état de shard obsolète non nettoyé après un redémarrage rapide du nœud. Généralement transitoire, mais si cela persiste, le shard reste UNASSIGNED.","fix":"Vérifiez pourquoi le shard n'est pas affecté : GET _cluster/allocation/explain. Dans la plupart des cas, le verrou est libéré rapidement et le cluster réessaie de lui-même ; si nécessaire, déclenchez une réallocation avec POST _cluster/reroute?retry_failed=true. Si le problème persiste, examinez les journaux du nœud concerné à la recherche d'erreurs d'I/O/de disque et vérifiez l'état du disque. Un redémarrage propre du nœud problématique peut libérer le verrou.","tags":["shard","lock","unassigned","allocation","io","reroute"]},{"code":"parsing_exception","title":"Failed to parse the search request / query DSL (échec de l'analyse de la requête de recherche / du query DSL)","severity":"caution","meaning":"Le corps de requête envoyé (query DSL) n'a pas pu être analysé. La requête n'a pas pu s'exécuter car la structure JSON de la requête ou la syntaxe de la requête ne correspond pas au format attendu par Elasticsearch.","cause":"JSON invalide, clés de requête mal placées/inconnues, type de requête (par ex. bool, range) utilisé avec une mauvaise structure, nom de champ supprimé/modifié d'une version à l'autre, ou accolade en trop/manquante. Fréquent avec les requêtes écrites à la main ou construites dynamiquement.","fix":"Vérifiez le corps de la requête à l'aide de l'indication de ligne/position du message d'erreur ; vérifiez que le JSON est valide. Comparez la structure du query DSL avec la référence officielle (placement de bool/must/filter, etc.). Mettez à jour la syntaxe supprimée lors des montées de version. Simplifiez la requête progressivement pour isoler la partie fautive ; utilisez le query builder de la bibliothèque cliente lorsque c'est possible.","tags":["query dsl","parse","json","syntax","search","bool"]},{"code":"max_bytes_length_exceeded_exception","title":"Document contains at least one immense term that is too long (le document contient au moins un terme démesuré, trop long)","severity":"caution","meaning":"Dans un document en cours d'indexation, un terme (notamment une valeur de champ keyword/non analysé) a dépassé la longueur maximale autorisée en octets (32766 octets). Lucene ne peut pas indexer un terme aussi long.","cause":"Une valeur texte très longue écrite (d'un seul bloc) dans un champ keyword ; par ex. le corps entier d'un document, des données base64 ou une longue URL/un long identifiant placés dans un champ keyword. Les champs keyword n'étant pas analysés, la valeur est indexée comme un seul terme et dépasse la limite.","fix":"Utilisez un champ text (analysé) pour les textes longs ; keyword est réservé aux valeurs courtes nécessitant une correspondance exacte. Pour plafonner la valeur, utilisez ignore_above dans le mapping (les valeurs plus longues ne sont pas indexées mais restent stockées). Si ce n'est pas nécessaire, définissez index:false pour les champs très longs. Réévaluez la conception du champ (text ou keyword) selon le type de données.","tags":["keyword","term","ignore_above","lucene","mapping","length"]},{"code":"illegal_argument_exception_total_fields","title":"Limit of total fields has been exceeded","severity":"caution","meaning":"Le nombre total de champs dans le mapping d'un index a dépassé la limite supérieure autorisée (index.mapping.total_fields.limit, 1000 par défaut). L'indexation échoue car de nouveaux champs ne peuvent pas être ajoutés.","cause":"Généralement une explosion du mapping : avec le dynamic mapping activé, des documents JSON non structurés/à clés variables (par ex. des objets dont les clés proviennent de données utilisateur) ajoutent chaque nouveau champ au mapping. Avec le temps, le nombre de champs augmente rapidement et atteint la limite.","fix":"Pour corriger la cause racine, modélisez les données à clés variables avec un champ flattened ou sous forme de tableau de paires clé-valeur plutôt que d'objets ; restreignez le dynamic mapping (dynamic: false ou strict). Si vous avez réellement besoin de plus de champs, relevez prudemment la limite : PUT idx/_settings {\"index.mapping.total_fields.limit\": 2000}. Surveillez l'explosion du mapping en vérifiant le nombre de champs avec GET idx/_mapping.","tags":["mapping","total_fields","dynamic mapping","flattened","limit","field"]},{"code":"verification_exception","title":"verification_exception: Found N problem(s) — Unknown column / Unknown index (ES|QL, SQL, EQL) (N problème(s) trouvé(s) — colonne / index inconnu)","severity":"caution","meaning":"La requête ES|QL (_query), SQL (_sql) ou EQL a été analysée syntaxiquement mais a échoué à la vérification sémantique : l'index, le champ (colonne) ou la fonction référencé est introuvable, ou les types sont incompatibles. Le texte d'erreur indique la position ligne/colonne en cause (par ex. \"line 1:18: Unknown column [field]\"). Un échec de vérification d'un dépôt de snapshots ne relève pas de ce code ; il s'agit de repository_verification_exception.","cause":"Nom de champ mal orthographié ou absent du mapping ; motif d'index inexistant ou ne correspondant à aucun index ; opération non prise en charge sur un champ text (par ex. regroupement sur le champ text au lieu de son sous-champ keyword) ; comparaison entre types incompatibles ou fonction inconnue.","fix":"Corrigez la requête à l'aide de l'information ligne:colonne du message d'erreur. Vérifiez les noms et types des champs : GET idx/_mapping (pour SQL, DESCRIBE idx / SHOW COLUMNS). Vérifiez le motif d'index avec GET _cat/indices. Utilisez le sous-champ keyword (field.keyword) pour les regroupements/tris ; en cas d'incohérence de type, appliquez une conversion explicite (ES|QL : TO_STRING, TO_INTEGER, etc.).","tags":["esql","sql","eql","verification","unknown column","query"]}]},"errors/oracle-more4.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-19804","title":"cannot reclaim N bytes disk space from M bytes limit (impossible de récupérer N octets d'espace disque sur la limite de M octets)","severity":"danger","meaning":"Oracle a tenté de faire de la place dans la Fast Recovery Area (FRA) pour un nouveau fichier (journal archivé, sauvegarde, flashback log), mais n'a pas pu récupérer l'espace requis car la limite logique définie par DB_RECOVERY_FILE_DEST_SIZE est atteinte.","cause":"La taille de la FRA (DB_RECOVERY_FILE_DEST_SIZE) est une borne supérieure indépendante du disque physique, et les journaux archivés, sauvegardes RMAN ou flashback logs l'ont remplie. La cause racine est généralement l'absence de sauvegarde et de suppression régulières des journaux archivés, ou des sauvegardes obsolètes non purgées.","fix":"Examinez l'utilisation de la FRA : SELECT * FROM v$recovery_area_usage; et SELECT * FROM v$recovery_file_dest; Sauvegardez les journaux archivés vers une destination hors FRA et supprimez-les (RMAN : BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT;) ou purgez les sauvegardes devenues inutiles (RMAN : DELETE OBSOLETE;). Avec Data Guard, ne supprimez pas les journaux archivés non encore appliqués sur le standby. Comme correctif durable, augmentez DB_RECOVERY_FILE_DEST_SIZE s'il reste de l'espace disque. Si Flashback Database n'est pas nécessaire, envisagez de le désactiver.","tags":["fra","recovery area","archived log","rman","db_recovery_file_dest_size","disk"]},{"code":"ORA-00494","title":"enqueue held for too long by 'inst N, osid M' (enqueue détenu trop longtemps par 'inst N, osid M')","severity":"danger","meaning":"Un enqueue de ressource critique (par ex. CF - enqueue du fichier de contrôle) a été détenu par un processus bien plus longtemps que permis, et Oracle a tué ce processus pour éviter que le système se fige. Apparaît dans l'alert.log avec une entrée 'killing enqueue blocker'.","cause":"Un processus d'arrière-plan ou utilisateur a détenu un enqueue important au-delà du délai autorisé à cause d'un goulot d'étranglement I/O, d'une forte pression CPU ou d'un blocage de l'OS (swap, disques lents) ; pour l'enqueue du fichier de contrôle (CF), ce délai est fixé par _controlfile_enqueue_timeout (900 s par défaut). Les paramètres cachés (underscore) ne doivent être modifiés que sur indication d'Oracle Support. Généralement déclenché par la latence du stockage ou des processus lents comme LGWR/CKPT.","fix":"Examinez l'alert.log et le fichier de trace du processus tué pour déterminer quel enqueue (généralement le fichier de contrôle) et quel processus d'arrière-plan sont en cause. La vraie cause est généralement la latence du stockage/des I/O : vérifiez les temps de réponse des disques, le multipath et l'état des contrôleurs. Réduisez la pression CPU/mémoire et l'utilisation du swap. Si cela se répète, partagez l'analyse de la trace avec Oracle Support.","tags":["enqueue","control file","i-o","storage","hang","background process"]},{"code":"ORA-04021","title":"timeout occurred while waiting to lock object (timeout en attendant le verrouillage de l'objet)","severity":"caution","meaning":"Un timeout s'est produit en attendant d'obtenir un library cache lock/pin sur un objet (package, procédure, type, etc.). Se produit généralement lors d'une tentative de compilation ou de modification d'un objet PL/SQL alors qu'il est exécuté ou verrouillé par une autre session.","cause":"L'objet à modifier/compiler est en cours d'exécution dans une autre session ou verrouillé par un autre DDL, le library cache lock exclusif ne peut donc pas être obtenu. Un appel de procédure de longue durée ou une session bloquée peut détenir le verrou.","fix":"Trouvez la session qui verrouille l'objet : SELECT * FROM dba_kgllock WHERE kgllkhdl IN (SELECT p1raw FROM v$session WHERE event LIKE 'library cache%'); ou identifiez la session qui utilise l'objet avec v$access et v$session. Relancez le DDL/la compilation pendant une fenêtre de maintenance à faible charge. Si nécessaire, terminez la session en toute sécurité. Arrêter brièvement l'application facilite l'obtention du verrou.","tags":["library cache","lock","plsql","ddl","compile","timeout"]},{"code":"ORA-04061","title":"existing state of package/procedure has been invalidated (l'état existant du package/de la procédure a été invalidé)","severity":"caution","meaning":"L'état existant d'un objet PL/SQL appelé (package, procédure, fonction) a été invalidé car un objet dont il dépend a changé. C'est une variante de la perte d'état accompagnée d'ORA-04068 ; l'état du package conservé par la session n'est plus valide.","cause":"Pendant qu'une session utilisait un package, un DDL a été appliqué à ce package ou à ses dépendances (modification de structure de table, recompilation, changement de droits). En conséquence, l'état du package de la session a été invalidé. Observé dans les environnements à déploiements fréquents lorsque des changements de schéma sont effectués pendant que l'application tourne.","fix":"Le même appel réussit généralement à la deuxième tentative (l'état ayant été réinitialisé) ; interceptez cette erreur dans l'application et relancez en toute sécurité. Effectuez les changements de schéma pendant un faible trafic applicatif/une fenêtre de maintenance. Compilez en masse les objets invalides : EXEC DBMS_UTILITY.COMPILE_SCHEMA('APP_SCHEMA'); Les conceptions qui minimisent l'état des packages (variables globales) réduisent cet effet.","tags":["plsql","package","invalidate","ddl","state","recompile"]},{"code":"ORA-01466","title":"unable to read data - table definition has changed (lecture des données impossible : la définition de la table a changé)","severity":"caution","meaning":"Une requête lisant des données d'un instant passé (Flashback Query, AS OF ou lecture cohérente) a échoué car la définition de la table (DDL) a changé entre l'instant passé interrogé et maintenant. Oracle ne peut pas lire en toute sécurité les données de cet instant avec la définition actuelle.","cause":"Un DDL (ajout/suppression de colonne, changement de type) a été appliqué à la table après l'horodatage visé par la requête Flashback/AS OF. Par ailleurs, des écarts d'horloge entre client et serveur ou des requêtes sur un passé très récent (quelques secondes) peuvent lever cette erreur en raison de la résolution SCN/temps.","fix":"Exécutez la requête flashback sur un instant/SCN antérieur au changement DDL de la table. Lorsque c'est possible, utilisez un SCN exact plutôt qu'un horodatage (AS OF SCN). Pour les requêtes sur un passé très récent, reculez de quelques secondes. Si un accès historique permanent est nécessaire, envisagez une conception avec versionnement ou table d'archive plutôt que du DDL.","tags":["flashback","as of","ddl","consistent read","scn","query"]},{"code":"ORA-08176","title":"consistent read failure; rollback data not available (échec de lecture cohérente ; données de rollback indisponibles)","severity":"danger","meaning":"Lors de la construction d'une image de lecture cohérente, une requête a rencontré des blocs modifiés par une opération qui ne génère pas d'undo ; aucune donnée undo/rollback n'existant pour ces blocs, l'image cohérente n'a pas pu être construite. Contrairement à ORA-01555, l'undo n'a jamais été généré plutôt qu'écrasé. (L'erreur d'isolation serializable est un code distinct : ORA-08177.)","cause":"Une opération qui ne génère pas d'undo s'exécute sur le même objet pendant la requête : chargements direct-path (INSERT /*+ APPEND */, SQL*Loader DIRECT=TRUE), CREATE INDEX / reconstruction d'index ou transactions discrètes.","fix":"Relancez la requête une fois le chargement direct-path ou l'opération d'index terminé. Ne planifiez pas de longs rapports et des jobs de chargement direct-path / de maintenance d'index sur le même objet dans la même fenêtre horaire. Si cela se répète, identifiez le job en conflit avec l'alert log et ASH et séparez les plannings.","tags":["undo","consistent read","direct path","append","create index","retry"]},{"code":"ORA-00020","title":"maximum number of processes (N) exceeded (nombre maximal de processus (N) dépassé)","severity":"danger","meaning":"Le nombre de processus de l'instance a atteint la limite supérieure définie par le paramètre d'initialisation PROCESSES, et aucune nouvelle connexion/aucun nouveau processus ne peut être créé. Les nouvelles sessions ne peuvent pas se connecter ; même un administrateur peut ne pas y parvenir.","cause":"Un pool de connexions applicatif surdimensionné, des connexions jamais fermées (fuites), plus d'utilisateurs simultanés que prévu ou un paramètre PROCESSES trop bas entraînent le dépassement de la limite. Parfois, des jobs d'arrière-plan bloqués ou des processus de requêtes parallèles consomment aussi des processus.","fix":"Vous pouvez vous connecter avec sqlplus -prelim / as sysdba, mais cette connexion ne peut pas exécuter de SQL (v$session ne peut pas être interrogée) ; elle ne sert qu'à oradebug (par ex. hanganalyze) et SHUTDOWN ABORT. Pour libérer des emplacements, tuez prudemment au niveau de l'OS les processus serveur applicatifs inactifs (ps -ef | grep 'LOCAL=NO') ; puis, avec une connexion SYSDBA normale, trouvez les sessions en fuite via v$session/v$process et fermez-les avec ALTER SYSTEM KILL SESSION. Comme correctif durable, augmentez PROCESSES et redémarrez l'instance (si SESSIONS est défini explicitement dans le spfile, il n'est pas recalculé ; mettez-le aussi à jour). Côté application, corrigez le maximum du pool de connexions et le nettoyage des connexions inactives.","tags":["processes","process","connection","limit","pool","init parameter"]},{"code":"ORA-01113","title":"file N needs media recovery (le fichier N nécessite une media recovery)","severity":"danger","meaning":"Un fichier de données est en cours de mise online ou la base est en cours d'ouverture, mais le fichier est incohérent ; une media recovery doit d'abord être appliquée avant de poursuivre. Le fichier est à un SCN plus ancien/incohérent par rapport à ce qu'attend le fichier de contrôle.","cause":"Le fichier de données a été restauré depuis une ancienne sauvegarde, le redo n'a pas été appliqué pendant qu'il était offline, ou le fichier n'est pas à jour après un crash de l'instance/du stockage. Généralement causé par l'omission de l'étape recover après une restauration, ou par un fichier resté longtemps offline.","fix":"Récupérez le fichier ou la base : RECOVER DATAFILE N; ou RECOVER DATABASE; puis ALTER DATABASE DATAFILE N ONLINE; ou ALTER DATABASE OPEN; Assurez-vous que les journaux archivés nécessaires sont accessibles. Avec RMAN : après un RESTORE si nécessaire, exécutez RECOVER DATAFILE N;. Suivez l'alert.log pour voir quels journaux sont demandés.","tags":["media recovery","datafile","redo","restore","recover","scn"]},{"code":"ORA-02393","title":"exceeded call limit on CPU usage (limite d'utilisation CPU par appel dépassée)","severity":"caution","meaning":"Le temps CPU autorisé pour un seul appel (CPU_PER_CALL), défini dans le profil de ressources attribué à la session, a été dépassé et Oracle a terminé cet appel. C'est un garde-fou de limitation des ressources.","cause":"Une limite CPU_PER_CALL est définie dans le profil de l'utilisateur et RESOURCE_LIMIT=TRUE ; un seul appel SQL/PLSQL a dépassé ce seuil CPU. Généralement déclenché par une requête inefficace/non maîtrisée qui consomme bien plus de CPU que prévu.","fix":"Optimisez la requête (index approprié, plan correct, suppression des full table scans inutiles) pour que la consommation CPU passe sous la limite. Si le besoin métier est légitime, augmentez CPU_PER_CALL dans le profil de l'utilisateur concerné ou mettez-le à UNLIMITED : ALTER PROFILE ... LIMIT CPU_PER_CALL ...; Envisagez de gérer les ressources plus finement avec Database Resource Manager plutôt qu'avec des profils.","tags":["profile","cpu_per_call","resource_limit","resource","query","optimization"]},{"code":"ORA-12801","title":"error signaled in parallel query server Pnnn (erreur signalée dans le serveur de requête parallèle Pnnn)","severity":"danger","meaning":"Dans une requête exécutée en parallèle, l'un des serveurs d'exécution parallèle (parallel query slaves) a rencontré une erreur, transmise au coordinateur de la requête. ORA-12801 est une enveloppe ; l'erreur réelle se trouve sur la deuxième ligne qui suit.","cause":"Un processus slave a rencontré une erreur réelle pendant une opération parallèle : tablespace TEMP insuffisant (ORA-01652), épuisement de la mémoire PGA (ORA-04030), corruption de données, limites de processus parallèles ou autre erreur SQL sous-jacente. ORA-12801 indique seulement qu'elle s'est produite dans un contexte parallèle.","fix":"Concentrez-vous sur le code d'erreur réel qui suit immédiatement ORA-12801 et résolvez-le (par ex. agrandir TEMP, ajuster la PGA, réparer un bloc corrompu). Si nécessaire, isolez l'erreur réelle en exécutant la requête en série, en retirant les hints PARALLEL ou en réduisant le degré de parallélisme. Revoyez PARALLEL_MAX_SERVERS et l'utilisation des ressources.","tags":["parallel","parallel query","slave","temp","pga","wrapper"]}]},"errors/postgres-more4.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"23P01","title":"exclusion_violation","severity":"danger","meaning":"Une contrainte EXCLUDE (d'exclusion) a été violée. Ce type de contrainte empêche deux lignes d'entrer en conflit selon des opérateurs précis (par ex. chevauchement de plages &&) ; la ligne insérée/mise à jour a été refusée car elle entre en conflit avec une ligne existante.","cause":"Typiquement, une contrainte EXCLUDE basée sur GiST est définie (par ex. interdire les chevauchements de périodes avec tstzrange && pour des réservations de rendez-vous/salles) et la nouvelle ligne a une plage/valeur qui chevauche une ligne existante. Peut aussi se produire lorsque deux transactions concurrentes insèrent des plages qui se chevauchent.","fix":"Vérifiez le chevauchement par une requête avant l'insertion, ou interceptez l'erreur dans l'application et affichez un message explicite à l'utilisateur. Interrogez la plage/condition concernée pour trouver la ligne existante en conflit. Si la règle métier autorise les chevauchements, assouplissez la contrainte ; sinon corrigez la saisie. En cas de concurrence, relancez la transaction en toute sécurité pour la sérialiser.","tags":["exclude","constraint violation","gist","range","overlap","constraint"]},{"code":"23001","title":"restrict_violation","severity":"danger","meaning":"Le code restrict_violation du standard SQL. Important : PostgreSQL signale la violation d'une clé étrangère définie ON DELETE/ON UPDATE RESTRICT ou NO ACTION non pas avec ce code mais avec 23503 (foreign_key_violation) ; en pratique, vous ne verrez pas ce code provenant de PostgreSQL lui-même.","cause":"Lorsqu'une ligne parente ayant des lignes enfants dépendantes est supprimée ou que sa clé est modifiée, PostgreSQL renvoie 23503. 23001 apparaît généralement dans du code applicatif porté depuis une autre base ou comme erreur personnalisée d'une extension. Contrairement à NO ACTION, RESTRICT ne peut pas différer le contrôle jusqu'à la fin de la transaction et échoue immédiatement.","fix":"Supprimez/mettez à jour d'abord les lignes enfants dépendantes ou rattachez-les à un autre parent ; traitez ensuite la ligne parente. Si la règle métier le permet, envisagez de redéfinir la clé étrangère avec ON DELETE CASCADE ou SET NULL. Lors de suppressions en masse, respectez l'ordre de suppression (enfant d'abord, puis parent).","tags":["foreign key","restrict","fk","delete","reference","constraint"]},{"code":"42P17","title":"invalid_object_definition","severity":"danger","meaning":"La définition d'un objet (fonction, collation, trigger, règle, etc.) est invalide. PostgreSQL indique que la définition fournie est incohérente en elle-même ou inacceptable ; l'objet ne peut pas être créé/modifié tel qu'il est écrit.","cause":"Causes fréquentes : combinaisons d'attributs contradictoires dans une fonction (par ex. marquée IMMUTABLE mais au comportement volatile), définition de collation/de jeu de caractères invalide, ou définition de trigger/règle inadaptée. Des options incohérentes dans la row-level security (RLS) et certains DDL peuvent aussi la déclencher.","fix":"Lisez les détails de l'erreur (DETAIL/HINT) pour déterminer quelle partie de la définition a été refusée. Alignez les étiquettes de volatilité des fonctions sur leur comportement réel (VOLATILE/STABLE/IMMUTABLE). Vérifiez que le nom de collation existe dans pg_collation. Corrigez la définition selon la syntaxe/les règles documentées et exécutez-la de nouveau.","tags":["function","collation","definition","volatility","ddl","object"]},{"code":"42710","title":"duplicate_object","severity":"caution","meaning":"L'objet en cours de création (rôle, type, contrainte, extension, schéma, nom d'index, etc.) existe déjà. PostgreSQL n'autorise pas un second objet avec le même nom/la même définition.","cause":"Une commande comme CREATE ROLE, CREATE TYPE, CREATE EXTENSION ou ADD CONSTRAINT a été exécutée alors qu'un objet du même nom existait déjà. Généralement causé par un script exécuté deux fois, des migrations non idempotentes, ou deux sessions concurrentes tentant de créer le même objet.","fix":"Vérifiez si l'objet existe (pg_roles pour les rôles, pg_type pour les types, pg_extension pour les extensions). Écrivez des migrations idempotentes : utilisez CREATE ... IF NOT EXISTS lorsque c'est disponible, ou exécutez d'abord DROP ... IF EXISTS. En cas de conflit de contrainte, renommez la contrainte existante ou supprimez-la et recréez-la.","tags":["duplicate","role","type","extension","constraint","migration"]},{"code":"22008","title":"datetime_field_overflow","severity":"caution","meaning":"Lors du calcul ou de l'analyse d'une valeur date/heure, l'un des champs est sorti de sa plage valide. Par exemple, une composante impossible comme le mois 13 ou le jour 32, ou un résultat au-delà de la plage temporelle prise en charge, a été produit.","cause":"Saisie de date invalide (par ex. '2026-02-30'), composante hors plage, ou ajout d'INTERVAL dépassant la plage de dates prise en charge. L'analyse avec un mauvais format/une mauvaise locale ou l'insertion d'une saisie utilisateur non validée est la cause typique.","fix":"Validez les dates dans l'application avant insertion et envoyez-les au bon format (de préférence ISO 8601 : YYYY-MM-DD). Lors de l'analyse avec TO_DATE/TO_TIMESTAMP, alignez le masque de format sur l'entrée. Gardez les résultats arithmétiques dans la plage de dates prise en charge ; soumettez les valeurs suspectes à des contrôles de bornes.","tags":["date","time","overflow","interval","validation","format"]},{"code":"22026","title":"string_data_length_mismatch","severity":"caution","meaning":"La longueur d'une chaîne de bits ne correspond pas à la longueur fixe exigée par l'opération. Dans PostgreSQL, cela apparaît le plus souvent lorsqu'une chaîne de bits d'une autre longueur est écrite dans un type bit(n) de longueur fixe, ou lorsqu'un AND/OR/XOR bit à bit est appliqué à des chaînes de bits de longueurs différentes.","cause":"Écriture d'une chaîne de bits plus courte/plus longue que n dans une colonne bit(n) (par ex. B'101' dans une colonne bit(4) : 'bit string length 3 does not match type bit(4)'), ou application de &, |, # à des chaînes de bits de longueurs différentes ('cannot AND bit strings of different sizes'). Ce n'est pas lié au texte ni à l'encodage des caractères ; une valeur trop longue pour char(n)/varchar(n) lève plutôt 22001.","fix":"Amenez la valeur à la longueur cible : utilisez un cast explicite (B'101'::bit(4) complète avec des zéros à droite et tronque l'excédent), ou définissez la colonne en bit varying(n) si la longueur peut varier. Castez les opérandes à la même longueur avant les opérations bit à bit ; vérifiez la longueur avec length().","tags":["bit","length","bit string","varbit","conversion","validation"]},{"code":"2201B","title":"invalid_regular_expression","severity":"caution","meaning":"Le motif fourni à une opération d'expression régulière (~, ~*, SIMILAR TO, regexp_matches, substring, etc.) est invalide. PostgreSQL n'a pas pu compiler le motif car sa syntaxe est incorrecte.","cause":"Motif regex avec des parenthèses déséquilibrées, des classes de caractères invalides, de mauvaises séquences d'échappement ou une construction non prise en charge. Une cause fréquente est l'utilisation directe d'une saisie utilisateur comme motif sans échapper ses caractères spéciaux.","fix":"Corrigez le motif regex selon la syntaxe POSIX/ARE ; vérifiez parenthèses, crochets et séquences d'échappement. Si vous recherchez une saisie utilisateur comme texte littéral plutôt que comme motif : PostgreSQL n'a pas de fonction regexp_quote intégrée, échappez donc les caractères spéciaux en les préfixant d'une barre oblique inverse via regexp_replace, ou évitez totalement les regex et utilisez strpos()/position() ou LIKE ... ESCAPE. Validez le motif en le testant dans un outil séparé.","tags":["regex","regular expression","pattern","similar to","escape","syntax"]},{"code":"44000","title":"with_check_option_violation","severity":"danger","meaning":"Un INSERT/UPDATE via une vue définie WITH CHECK OPTION a été refusé car la ligne résultante ne satisfait plus la condition WHERE de la vue. La vue empêche l'écriture de lignes qui sortiraient de son propre filtre.","cause":"Une vue modifiable est définie WITH CHECK OPTION et l'écriture fait sortir la ligne de la condition de définition de la vue (par ex. passer une ligne à 'inactive' alors que la vue filtre status='active'). Un contrôle similaire peut se déclencher dans les politiques de row-level security (RLS).","fix":"Assurez-vous que les valeurs écrites satisfont la condition WHERE de la vue, ou effectuez l'opération directement sur la table de base plutôt que sur la vue (si les droits le permettent). Si la règle métier autorise la ligne à sortir du filtre, redéfinissez la vue sans WITH CHECK OPTION. Pour la RLS, revoyez l'expression WITH CHECK de la politique.","tags":["view","check option","updatable view","rls","insert","update"]},{"code":"40002","title":"transaction_integrity_constraint_violation","severity":"danger","meaning":"Le code du standard SQL pour la violation, au COMMIT, d'une contrainte d'intégrité vérifiée en fin de transaction. Important : PostgreSQL ne produit pas ce code en pratique ; il renvoie les violations de contraintes différées (DEFERRED) au COMMIT avec le code propre à la contrainte : 23503 pour les clés étrangères, 23505 pour l'unicité.","cause":"Une contrainte de clé étrangère ou d'unicité définie DEFERRABLE INITIALLY DEFERRED a été temporairement rompue lors d'étapes intermédiaires de la transaction et non rétablie au moment du COMMIT ; dans PostgreSQL, cela est signalé par 23503/23505. Par exemple, insérer dans le mauvais ordre des lignes qui se référencent mutuellement sans les corriger en est la cause.","fix":"Corrigez la logique de la transaction pour que toutes les contraintes différées soient satisfaites avant le COMMIT (ajoutez les références manquantes, résolvez les conflits). Si nécessaire, vérifiez la contrainte tôt dans la transaction avec SET CONSTRAINTS ... IMMEDIATE pour détecter plus tôt l'étape problématique. Réordonnez les insertions/mises à jour pour les références mutuelles.","tags":["deferred","constraint violation","commit","foreign key","transaction","constraint"]}]},"errors/mysql-more4.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1055","title":"Expression not in GROUP BY (only_full_group_by) (expression absente du GROUP BY)","severity":"caution","meaning":"Une colonne qui n'est ni dans le GROUP BY ni encapsulée dans une fonction d'agrégat a été utilisée dans la liste SELECT ou dans HAVING/ORDER BY. Le mode SQL ONLY_FULL_GROUP_BY étant activé, MySQL refuse cette expression non déterministe.","cause":"La requête sélectionne directement une colonne autre que les colonnes regroupées, mais cette colonne peut ne pas avoir une valeur unique par groupe. ONLY_FULL_GROUP_BY est activé par défaut dans MySQL 5.7+ ; les requêtes qui reposent sur l'ancien comportement permissif de GROUP BY lèvent cette erreur.","fix":"Ajoutez la colonne à la liste GROUP BY ou encapsulez-la dans une fonction d'agrégat (MAX(), MIN(), ANY_VALUE()). Si vous savez que la valeur est réellement unique au sein du groupe, ANY_VALUE() supprime volontairement l'ambiguïté. Désactiver le mode (retirer ONLY_FULL_GROUP_BY de sql_mode) au lieu de corriger la requête réintroduit le risque de résultats faux ; évitez-le.","tags":["group by","only_full_group_by","sql_mode","any_value","aggregate","query"]},{"code":"1267","title":"Illegal mix of collations","severity":"caution","meaning":"Dans une opération de comparaison ou de combinaison (JOIN, WHERE, UNION, CONCAT), deux chaînes de collations différentes ont été combinées et MySQL n'a pas pu déterminer quelles règles de collation appliquer.","cause":"Généralement la comparaison de colonnes de jeux de caractères/collations différents (par ex. utf8mb4_general_ci contre utf8mb4_unicode_ci, ou latin1 contre utf8mb4), ou une incohérence entre une colonne et la collation de la connexion. Fréquent lorsque les tables/bases ont été créées avec des valeurs par défaut différentes.","fix":"Amenez les deux côtés de la comparaison à la même collation : précisez une collation explicite avec COLLATE (par ex. col1 = col2 COLLATE utf8mb4_unicode_ci) ou utilisez CONVERT(... USING ...). Comme correctif durable, convertissez les colonnes/tables concernées vers un jeu de caractères et une collation uniques et cohérents. Alignez aussi la collation de la connexion (collation_connection).","tags":["collation","collation mismatch","character set","join","utf8mb4","convert"]},{"code":"1030","title":"Got error N from storage engine","severity":"danger","meaning":"Le moteur de stockage (InnoDB/MyISAM) n'a pas pu terminer une opération et a renvoyé un code d'erreur de bas niveau. C'est un signalement générique d'un problème de disque, de système de fichiers ou interne au moteur, sous la couche SQL. Le sous-code entre parenthèses/dans le message indique la cause réelle.","cause":"Causes fréquentes : disque/espace temporaire plein (par ex. errno 28 - No space left on device), problèmes de droits sur les fichiers, fichiers de table corrompus, problèmes de tablespace InnoDB ou erreurs d'I/O au niveau de l'OS. Les requêtes de tri/regroupement qui produisent de grosses tables temporaires peuvent aussi la déclencher.","fix":"Décodez le sous-code d'erreur (sous Linux, perror N affiche sa signification, par ex. perror 28). Vérifiez l'utilisation du disque et l'espace de tmpdir ; libérez de l'espace. Vérifiez le propriétaire/les droits des fichiers. Pour InnoDB, consultez les détails dans le journal d'erreurs ; en cas de suspicion de corruption, exécutez CHECK TABLE et récupérez si nécessaire. Optimisez les requêtes qui produisent d'énormes résultats temporaires.","tags":["storage engine","innodb","disk","errno","tmpdir","i-o"]},{"code":"1197","title":"Multi-statement transaction required more than 'max_binlog_cache_size' (la transaction multi-instructions a nécessité plus que 'max_binlog_cache_size')","severity":"danger","meaning":"Les données de binlog générées par une seule transaction ont dépassé la limite de mémoire/cache définie par max_binlog_cache_size. La transaction ne tenait pas dans le binary log, elle a donc échoué et a été annulée.","cause":"Une très grosse transaction (un seul INSERT/UPDATE/DELETE de millions de lignes ou un énorme chargement en masse) a rempli le cache du binlog. Avec la journalisation binaire activée (réplication/restauration à un instant donné), les grosses transactions peuvent facilement dépasser cette limite.","fix":"Découpez la grosse transaction en lots plus petits ; faites un COMMIT après chaque lot. Si de très grosses transactions sont inévitables, augmentez max_binlog_cache_size (et binlog_cache_size si nécessaire). Dans les chargements ETL/en masse, un chargement par blocs avec des COMMIT intermédiaires évite à la fois cette erreur et les verrous détenus longtemps.","tags":["binlog","max_binlog_cache_size","transaction","batch","replication","bulk"]},{"code":"1785","title":"Statement violates GTID consistency (l'instruction viole la cohérence GTID)","severity":"danger","meaning":"Avec la réplication basée sur les GTID (Global Transaction Identifier) activée, l'instruction exécutée viole les règles de cohérence GTID. Le mode GTID n'autorise pas certains types d'instructions qui ne peuvent pas être affectés en toute sécurité à un seul GTID.","cause":"1785 (ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE) se produit précisément lorsque des tables non transactionnelles (par ex. MyISAM) sont mises à jour dans la même transaction que des tables transactionnelles (InnoDB), ou dans une transaction multi-instructions sans autocommit. Dans les anciennes versions, CREATE TABLE ... SELECT (avant 8.0.21 ; autorisé en 8.0.21+ pour les moteurs prenant en charge le DDL atomique) et CREATE/DROP TEMPORARY TABLE dans une transaction (avant 8.0.13 ; autorisé en 8.0.13+ lorsque binlog_format vaut ROW/MIXED) violent aussi la cohérence GTID (ils sont signalés avec des codes d'erreur distincts). Ils sont refusés lorsque enforce_gtid_consistency=ON.","fix":"Ne mélangez pas des tables de moteurs non transactionnels avec des tables transactionnelles dans la même transaction ; mettez à jour les tables MyISAM par des instructions uniques en autocommit ou passez toutes les tables en InnoDB : ALTER TABLE my_table ENGINE=InnoDB; Dans les anciennes versions (avant 8.0.21), scindez CREATE TABLE ... SELECT en deux étapes (CREATE TABLE, puis INSERT ... SELECT) ; avant 8.0.13, créez et supprimez les tables temporaires en dehors d'une transaction explicite.","tags":["gtid","replication","consistency","temporary table","myisam","innodb"]},{"code":"3024","title":"Query execution was interrupted, maximum statement execution time exceeded (exécution de la requête interrompue : durée maximale d'exécution dépassée)","severity":"caution","meaning":"Une requête SELECT a été interrompue par MySQL car elle a dépassé la durée d'exécution maximale définie pour elle (max_execution_time / hint MAX_EXECUTION_TIME). C'est un garde-fou de timeout qui empêche les requêtes longues d'accaparer les ressources.","cause":"La requête a duré plus longtemps que la valeur max_execution_time de la session/globale ou que le hint d'optimiseur MAX_EXECUTION_TIME(N) du SELECT. Généralement causé par un plan inefficace, un index manquant, un gros parcours ou des ralentissements sous forte concurrence. (S'applique uniquement aux SELECT en lecture seule.)","fix":"Analysez la requête avec EXPLAIN et optimisez-la (index appropriés, prédicats plus sélectifs, moins de full table scans). Si la requête doit légitimement s'exécuter longtemps, augmentez le hint MAX_EXECUTION_TIME de ce SELECT ou le max_execution_time de la session. Exécutez les requêtes analytiques lourdes hors ligne/sur un réplica.","tags":["timeout","max_execution_time","query","explain","index","select"]},{"code":"1298","title":"Unknown or incorrect time zone","severity":"caution","meaning":"Le nom de fuseau horaire indiqué n'a pas été reconnu par MySQL. Un appel CONVERT_TZ(), le paramètre time_zone ou une opération faisant référence à un fuseau horaire nommé (par ex. 'Europe/Istanbul') a échoué car ce fuseau n'est pas défini sur le serveur.","cause":"Pour que MySQL utilise des fuseaux horaires nommés, les tables de fuseaux horaires du schéma mysql (time_zone, time_zone_name, etc.) doivent être chargées. Si ces tables sont vides, seuls les décalages numériques ('+03:00') fonctionnent ; les fuseaux nommés sont inconnus et cette erreur est levée.","fix":"Chargez les tables de fuseaux horaires. Sous Linux, importez les données zoneinfo du système avec mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql; puis redémarrez le serveur ou exécutez FLUSH TABLES. Si le chargement des fuseaux nommés est impossible, utilisez un décalage numérique ('+03:00'). Vérifiez l'orthographe du nom (casse, 'Europe/Istanbul').","tags":["time zone","timezone","convert_tz","zoneinfo","mysql_tzinfo_to_sql","offset"]},{"code":"1418","title":"Function has none of DETERMINISTIC, NO SQL, or READS SQL DATA (binary logging enabled) (la fonction n'a ni DETERMINISTIC, ni NO SQL, ni READS SQL DATA ; journalisation binaire activée)","severity":"caution","meaning":"Une fonction stockée était en cours de création, mais la journalisation binaire est activée et la fonction n'est déclarée avec aucune des caractéristiques de déterminisme/d'accès aux données. MySQL refuse une telle fonction car elle est ambiguë du point de vue de la sûreté de la réplication.","cause":"La fonction a été définie sans les caractéristiques DETERMINISTIC, NO SQL ou READS SQL DATA alors que log_bin vaut ON et log_bin_trust_function_creators=OFF. Sans ces caractéristiques, MySQL ne peut pas être sûr que la fonction produit le même résultat sur le réplica.","fix":"Déclarez le comportement de la fonction avec les bonnes caractéristiques : ajoutez DETERMINISTIC si elle est réellement déterministe, NO SQL si elle n'accède pas aux données, READS SQL DATA si elle ne fait que lire. Si vous êtes sûr que les fonctions sont sans risque, vous pouvez définir log_bin_trust_function_creators=1 comme politique centrale ; toutefois, déclarer correctement la caractéristique est l'approche la plus sûre. Assurez-vous que la caractéristique correspond au comportement réel de la fonction.","tags":["stored function","deterministic","binary log","replication","log_bin_trust_function_creators","reads sql data"]},{"code":"1231","title":"Variable can't be set to the value of ...","severity":"caution","meaning":"La valeur affectée à une variable système/de session (avec SET) est invalide. La valeur a été refusée car elle est hors de l'ensemble autorisé, de mauvais type ou dans un format inadapté.","cause":"Exemples fréquents : affectation d'un nom de mode invalide/mal orthographié à sql_mode, option non définie donnée à une variable de type enum, ou affectation de NULL à une variable qui ne l'accepte pas. Des valeurs numériques dans une mauvaise unité/plage peuvent aussi la déclencher.","fix":"Vérifiez les valeurs valides et le type acceptés par la variable ; pour les variables de type ENUM/SET (par ex. sql_mode, qui est un SET), orthographiez exactement les noms de valeurs/modes et séparez les valeurs multiples par des virgules. Vérifiez la plage de valeurs valides dans la documentation. Pour des listes comme sql_mode, lisez la valeur actuelle (SELECT @@sql_mode) et ajoutez/retirez des éléments en toute sécurité à partir de celle-ci.","tags":["set","variable","sql_mode","enum","value","invalid"]}]},"errors/mssql-more4.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"8645","title":"A time out occurred while waiting for memory resources to execute the query (timeout en attendant les ressources mémoire nécessaires à l'exécution de la requête)","severity":"danger","meaning":"Une requête a expiré en attendant sur le resource semaphore l'octroi de mémoire (memory grant) dont elle a besoin pour s'exécuter. La pression mémoire ayant empêché l'allocation d'un octroi, l'opération a été annulée.","cause":"Des requêtes concurrentes gourmandes en mémoire (gros tris, hash joins, hash aggregates) ont consommé tout le pool de mémoire des requêtes ; les nouvelles requêtes se sont mises en file d'attente sur le sémaphore et ont expiré. Des estimations d'octroi de mémoire surdimensionnées (statistiques obsolètes, mauvais plans) ou une mémoire serveur insuffisante sont la cause racine.","fix":"Identifiez les requêtes qui attendent ou détiennent de la mémoire avec sys.dm_exec_query_memory_grants et les DMV du resource semaphore. Mettez à jour les statistiques et corrigez les plans qui provoquent des octrois de mémoire gonflés (index appropriés, meilleures estimations). Réduisez la concurrence des requêtes gourmandes en mémoire ; si nécessaire, plafonnez les octrois de mémoire avec Resource Governor. Revoyez la configuration mémoire du serveur (max server memory).","tags":["memory grant","resource semaphore","memory","timeout","hash join","statistics"]},{"code":"8651","title":"Could not perform the operation because the requested memory grant was not available (opération impossible : l'octroi de mémoire demandé n'était pas disponible)","severity":"danger","meaning":"L'octroi de mémoire exigé par la requête n'était pas disponible et l'opération n'a pas pu être effectuée. Contrairement à 8645, il ne s'agit pas d'une attente mais d'une impossibilité pure et simple de fournir le gros bloc de mémoire demandé ; cela indique généralement une forte pression mémoire.","cause":"La mémoire de requête disponible sur le serveur ne peut pas satisfaire l'octroi demandé par un seul opérateur (par ex. un très gros tri/hash). Des estimations de mémoire excessives, un max server memory bas, une pression mémoire externe ou une limite Resource Governor basse, REQUEST_MAX_MEMORY_GRANT_PERCENT (workload group) ou MAX_MEMORY_PERCENT (resource pool), peuvent en être la cause.","fix":"Optimisez les requêtes qui gonflent les octrois de mémoire (mise à jour des statistiques, indexation appropriée, découpage de la requête). Surveillez la mémoire totale et disponible ; vérifiez le paramètre max server memory et la pression mémoire des applications externes. Si Resource Governor est utilisé, revoyez les paramètres REQUEST_MAX_MEMORY_GRANT_PERCENT du workload group et MAX_MEMORY_PERCENT du pool. Déplacez les traitements par lots gourmands en mémoire en heures creuses.","tags":["memory grant","memory","resource governor","statistics","hash","pressure"]},{"code":"5061","title":"ALTER DATABASE failed because a lock could not be placed on database (échec d'ALTER DATABASE : impossible de poser un verrou sur la base)","severity":"danger","meaning":"Une opération ALTER DATABASE (par ex. passage en mode mono-utilisateur, modification d'un fichier, réglage d'une option) n'a pas pu obtenir le verrou requis sur la base car d'autres connexions/sessions actives l'utilisent.","cause":"L'opération a été tentée alors que la base était utilisée ; le verrou exclusif requis pour SET SINGLE_USER, RESTRICTED_USER ou des changements d'options similaires n'a pas pu être obtenu à cause des connexions utilisateur actives. Fréquent lors de déploiements/maintenances fréquents alors que des connexions applicatives sont ouvertes.","fix":"Identifiez les autres sessions connectées à la base (sys.dm_exec_sessions, sp_who2) et fermez-les en toute sécurité. Pour une maintenance, changez le mode en annulant les transactions ouvertes avec l'option ROLLBACK : ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; Effectuez l'opération lorsque le trafic applicatif est faible ou pendant une fenêtre de maintenance. Pensez à repasser en MULTI_USER une fois terminé.","tags":["alter database","lock","single_user","connection","maintenance","rollback"]},{"code":"3702","title":"Cannot drop database because it is currently in use (impossible de supprimer la base : elle est en cours d'utilisation)","severity":"caution","meaning":"Une tentative de suppression ou de renommage d'une base a été refusée car la base est encore utilisée par des connexions actives. SQL Server ne peut pas supprimer une base en cours d'utilisation.","cause":"D'autres sessions (un pool applicatif, un onglet SSMS ouvert, des jobs d'arrière-plan) sont connectées à la base à supprimer. Même votre propre session ayant cette base comme contexte (USE) peut le déclencher. Les connexions ouvertes par des outils de supervision/sauvegarde sont aussi une cause fréquente.","fix":"Basculez vers une autre base avant la suppression (USE master;). Trouvez et fermez les sessions connectées, ou passez d'abord la base en mode mono-utilisateur : ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; puis exécutez DROP DATABASE [db]; Arrêtez les pools de connexions applicatifs et assurez-vous que les outils de supervision n'ouvrent pas de connexions.","tags":["drop database","connection","in use","single_user","use","maintenance"]},{"code":"15138","title":"The database principal owns a schema in the database, and cannot be dropped (le principal de base de données possède un schéma et ne peut pas être supprimé)","severity":"caution","meaning":"Une tentative de suppression d'un utilisateur/rôle de base de données (database principal) a échoué car le principal possède un ou plusieurs schémas dans la base. Il ne peut pas être supprimé tant que les schémas qu'il possède ne sont pas transférés à un autre principal.","cause":"L'utilisateur à supprimer est OWNER d'au moins un schéma (par ex. un schéma créé par défaut à son nom, ou attribué via AUTHORIZATION). SQL Server bloque la suppression pour ne pas laisser de schémas orphelins.","fix":"Trouvez les schémas possédés par l'utilisateur : SELECT name FROM sys.schemas WHERE principal_id = USER_ID('app_user'); Transférez la propriété de ces schémas à un autre principal (par ex. dbo) : ALTER AUTHORIZATION ON SCHEMA::[app_schema] TO dbo; Si le schéma n'est plus nécessaire (et qu'il est vide), DROP SCHEMA. Une fois la propriété transférée, exécutez de nouveau DROP USER.","tags":["schema","principal","user","owner","alter authorization","drop user"]},{"code":"1934","title":"INSERT/UPDATE failed because the following SET options have incorrect settings (échec d'INSERT/UPDATE : les options SET suivantes sont mal réglées)","severity":"danger","meaning":"Une opération DML (INSERT/UPDATE/DELETE) a échoué car des options SET requises étaient mal réglées. Se produit typiquement lorsque les réglages SET obligatoires pour les vues indexées, les index sur colonnes calculées, les index filtrés ou les index spatiaux/XML ne sont pas respectés.","cause":"La table/vue concernée comporte un objet qui exige des options SET précises (notamment QUOTED_IDENTIFIER et ANSI_NULLS à ON, NUMERIC_ROUNDABORT à OFF, etc.), mais les options SET actuelles de la session ne satisfont pas cette exigence. D'anciens drivers clients ou des scripts qui définissent explicitement de mauvaises options SET la déclenchent.","fix":"Définissez les options requises sur la connexion/la session : SET QUOTED_IDENTIFIER ON; SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET CONCAT_NULL_YIELDS_NULL ON; SET ARITHABORT ON; SET NUMERIC_ROUNDABORT OFF; Assurez-vous que le driver de l'application envoie correctement ces options à la connexion. Le message d'erreur nomme les options incorrectes ; corrigez-les.","tags":["set options","indexed view","computed column","quoted_identifier","ansi_nulls","dml"]},{"code":"468","title":"Cannot resolve the collation conflict","severity":"caution","meaning":"Dans une opération (JOIN, UNION, comparaison WHERE ou égalité), deux expressions de chaîne de collations différentes se sont rencontrées et SQL Server n'a pas pu déterminer quelle collation appliquer. L'opération a été annulée en raison de l'ambiguïté.","cause":"Les colonnes comparées sont définies avec des collations différentes ; le plus souvent observé lorsque la collation de la base diffère de celle du serveur/de tempdb, ou lors de jointures entre tables de bases aux collations différentes. Joindre des tables temporaires (collation de tempdb) à des tables permanentes est un déclencheur classique.","fix":"Précisez une collation explicite dans la comparaison : ... WHERE a.col = b.col COLLATE DATABASE_DEFAULT (ou un nom de collation concret). Utilisez COLLATE DATABASE_DEFAULT lors de la création des colonnes de tables temporaires. Comme correctif durable, alignez les colonnes/bases concernées sur une collation unique et cohérente. Si les incohérences avec tempdb sont fréquentes, DATABASE_DEFAULT est la solution la plus pratique.","tags":["collation","collation","join","tempdb","database_default","conflict"]},{"code":"4864","title":"Bulk load data conversion error (type mismatch or invalid character) (erreur de conversion lors du chargement en masse : type incompatible ou caractère invalide)","severity":"caution","meaning":"Lors d'un chargement en masse (BULK INSERT, bcp ou OPENROWSET(BULK...)), un champ du fichier source n'a pas pu être converti dans le type de données de la colonne cible. Les données de la ligne/colonne signalée ne correspondent pas au type attendu.","cause":"Une valeur du fichier de données source ne convient pas au type de la colonne cible : texte dans une colonne numérique, mauvais format de date, séparateur décimal/des milliers inattendu, mauvais encodage des caractères (UTF-8/UTF-16), ou colonnes décalées à cause de mauvais terminateurs de champ/ligne. Ne pas ignorer la ligne d'en-tête est aussi une cause fréquente.","fix":"Allez à la ligne et à la colonne signalées par l'erreur et inspectez les données source. Alignez les terminateurs de champ (FIELDTERMINATOR) et de ligne (ROWTERMINATOR) sur le fichier ; utilisez FIRSTROW=2 pour ignorer la ligne d'en-tête. Définissez CODEPAGE ou DATAFILETYPE pour la bonne page de codes/le bon encodage. Rendez les formats de date/nombre compatibles avec le type cible. Utiliser un format file fiabilise le mappage.","tags":["bulk insert","bcp","conversion","fieldterminator","encoding","format file"]},{"code":"916","title":"The server principal is not able to access the database under the current security context (le principal serveur ne peut pas accéder à la base dans le contexte de sécurité actuel)","severity":"caution","meaning":"Un login a tenté d'accéder à une base précise mais n'y a pas accès dans le contexte de sécurité actuel. Le login peut se connecter au serveur mais n'a pas d'utilisateur/de droit correspondant dans la base cible.","cause":"Le login n'a pas d'utilisateur mappé dans la base (orphelin ou jamais créé), l'accès à la base est restreint, ou le compte guest est désactivé. Un cas fréquent est l'Object Explorer de SSMS qui tente de lire les métadonnées d'une base à laquelle le login n'a pas accès.","fix":"Créez un utilisateur pour le login dans la base cible et accordez les droits nécessaires : USE [db]; CREATE USER [login] FOR LOGIN [login]; et attribuez les rôles/droits appropriés. S'il existe un utilisateur orphelin, mappez-le avec ALTER USER ... WITH LOGIN = ... Si l'accès est réellement nécessaire, ajoutez-le au rôle de base de données approprié ; sinon, l'erreur est le comportement attendu (pas d'accès à cette base).","tags":["login","user","access","privilege","security context","orphaned user"]}]},"errors/mongodb-more4.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"216 ElectionInProgress","title":"ElectionInProgress","severity":"caution","meaning":"Une élection de primaire est en cours dans le replica set. Tant que l'élection n'est pas terminée, le primaire est indéterminé, donc les écritures (et certaines lectures) sont temporairement refusées.","cause":"Le cluster élit un nouveau primaire après la chute du primaire actuel, une partition réseau, un stepdown pour maintenance ou un redémarrage de nœud. Une élection prend généralement quelques secondes ; les opérations arrivant dans cette fenêtre rencontrent cette condition.","fix":"C'est transitoire ; activez la fonction de relance de votre driver (retryable writes/reads) pour que l'opération soit relancée automatiquement une fois l'élection terminée. Si les élections sont fréquentes ou longues, recherchez la cause racine : stabilité du réseau, santé des nœuds, configuration des votes et paramètres de timeout d'élection (settings.electionTimeoutMillis). Inspectez l'état du cluster avec rs.status().","tags":["replica set","election","primary","retry","step-down","high availability"]},{"code":"225 TransactionTooOld","title":"TransactionTooOld","severity":"caution","meaning":"Une commande a été exécutée dans une transaction multi-documents, mais son numéro de transaction est plus ancien que le dernier numéro de transaction vu par le serveur pour cette session. Autrement dit, une transaction plus récente a été démarrée, donc cette ancienne transaction n'est plus valide.","cause":"Une nouvelle transaction a été démarrée dans la même session avant la fin de l'ancienne, ou les numéros de transaction ont avancé dans le désordre à cause de la concurrence. Cela arrive généralement lorsqu'un objet session est partagé de façon incorrecte ou que plusieurs threads utilisent la même session.","fix":"Gérez correctement le cycle de vie de la session pour chaque transaction logique ; ne partagez pas une session simultanément entre plusieurs threads. Exécutez les transactions avec l'API withTransaction/callback recommandée par le driver (elle gère les relances et la numérotation). Fermez la transaction précédente par commit/abort avant d'en démarrer une nouvelle.","tags":["transaction","session","session misuse","concurrency","withtransaction","multi-document transaction"]},{"code":"246 SnapshotUnavailable","title":"SnapshotUnavailable","severity":"caution","meaning":"Une lecture avec le read concern snapshot demandé (readConcern: snapshot) n'a pas pu être effectuée car le serveur ne peut pas fournir de snapshot cohérent pour cet instant. Généralement lié à un horodatage de lecture qui n'a pas suffisamment avancé ou qui a pris du retard.","cause":"readConcern 'snapshot' est utilisé (surtout dans les transactions multi-documents) mais une vue cohérente pour l'horodatage atLeast/at demandé n'est pas encore disponible ; par ex. une collection/un index nouvellement créé, un changement du catalogue ou un retard de réplication. Le serveur refuse alors la lecture.","fix":"C'est généralement transitoire ; relancez l'opération après un court délai (le mécanisme de relance des erreurs de transaction transitoires du driver couvre ce cas). Si vous avez récemment exécuté du DDL (création de collection/d'index), démarrez la transaction après. Vérifiez le retard de réplication et la santé des nœuds. Si une lecture snapshot n'est pas réellement nécessaire, choisissez un readConcern plus adapté.","tags":["snapshot","readconcern","transaction","timestamp","retry","replication"]},{"code":"257 TransactionTooLarge","title":"TransactionTooLarge","severity":"danger","meaning":"Une transaction multi-documents a dépassé les limites autorisées de taille/de nombre d'opérations. La transaction a été refusée car les modifications accumulées (entrée d'oplog ou utilisation mémoire) ont dépassé la limite supérieure.","cause":"La modification/l'insertion d'un grand nombre de documents dans une seule transaction a fait trop grossir le total des données produites par la transaction (par ex. le volume de modifications à conserver dans le cache WiredTiger, ou des limites serveur comme transactionLimitBytes). Depuis 4.2, une transaction peut être répartie sur plusieurs entrées d'oplog, la limite de 16 MB par entrée d'oplog n'est donc pas à elle seule déterminante. Utiliser des transactions pour une migration de données en masse/de l'ETL atteint facilement cette limite.","fix":"Découpez la transaction en morceaux plus petits ; validez chaque lot dans une transaction distincte. Les transactions multi-documents sont conçues pour être courtes et petites ; préférez effectuer les grosses mises à jour en masse sans transaction (de façon idempotente, bloc par bloc) ou les découper avec bulk write. Évitez de modifier des milliers de documents dans une seule transaction.","tags":["transaction","size","oplog","batch","bulk","bulk write"]},{"code":"276 IndexBuildAborted","title":"IndexBuildAborted","severity":"danger","meaning":"Une construction d'index en cours a été abandonnée/arrêtée avant son achèvement. L'index cible n'a pas été créé avec succès.","cause":"La construction d'index a pu être abandonnée en raison d'un disque/d'une mémoire insuffisants sur le nœud, d'un abandon explicite via dropIndexes, d'un stepdown du primaire/changement d'état du réplica, d'une violation d'unicité (documents en conflit lors de la construction d'un index unique) ou d'un redémarrage du nœud.","fix":"Trouvez la raison de l'abandon dans les journaux mongod (espace disque, mémoire, conflit d'unicité, etc.) et résolvez-la. Si vous construisez un index unique, nettoyez d'abord les documents en conflit. Prévoyez suffisamment de disque/mémoire et assurez-vous que l'état du réplica est stable. Après correction, reconstruisez l'index avec createIndex ; sur les grandes collections, choisissez une période de faible charge.","tags":["index","index build","aborted","unique","disk","replica set"]},{"code":"86 IndexKeySpecsConflict","title":"IndexKeySpecsConflict","severity":"caution","meaning":"L'index en cours de création a la même spécification de clé (mêmes champs/même ordre) qu'un index existant, mais des options ou un nom différents. MongoDB n'autorise pas deux définitions d'index conflictuelles avec la même clé.","cause":"Tentative de créer un second index sur le(s) même(s) champ(s) avec un autre nom ou d'autres options (par ex. collation, partialFilterExpression ou réglage unique différents). Cela arrive généralement lorsqu'une définition d'index est modifiée dans une migration/le code puis réexécutée.","fix":"Inspectez les index existants : db.collection.getIndexes() Trouvez l'index en conflit ayant la même clé. Pour passer aux options souhaitées, supprimez d'abord l'ancien index (dropIndex), puis recréez-le avec la nouvelle définition. Si seul le nom diffère, continuez à utiliser l'index existant ; ne créez pas de seconde définition inutile.","tags":["index","getindexes","collation","unique","conflict","createindex"]},{"code":"166 CommandNotSupportedOnView","title":"CommandNotSupportedOnView","severity":"caution","meaning":"La commande n'est pas prise en charge sur une vue. Les vues MongoDB sont en lecture seule et n'acceptent que certaines opérations de lecture ; les écritures ou commandes non prises en charge sont refusées.","cause":"Des opérations d'écriture comme insert/update/delete, ou certaines commandes non prises en charge sur les vues (par ex. certaines opérations text/geo, création directe d'index), ont été tentées sur une vue. Observé lorsque l'application tente d'écrire dans une vue en la prenant pour une collection.","fix":"Effectuez les écritures sur la collection sous-jacente (source) sur laquelle repose la vue ; les vues ne servent qu'à la lecture. Vérifiez si la commande est prise en charge sur les vues et utilisez les opérations de lecture/d'agrégation prises en charge. Si vous avez besoin d'une structure modifiable, concevez une collection matérialisée ($merge/$out) plutôt qu'une vue.","tags":["view","read-only view","read only","aggregation","write","collection"]},{"code":"168 InvalidPipelineOperator","title":"InvalidPipelineOperator","severity":"caution","meaning":"Un opérateur/stage non reconnu ou invalide a été utilisé dans un pipeline d'agrégation. MongoDB n'a pas pu résoudre le nom d'opérateur indiqué et a refusé le pipeline.","cause":"Nom d'opérateur mal orthographié (par ex. '$groups' au lieu de '$group'), nouvel opérateur indisponible dans la version de MongoDB utilisée, ou expression utilisée dans un mauvais contexte. Un préfixe $ manquant ou un opérateur utilisé à une position invalide le déclenche aussi.","fix":"Vérifiez l'orthographe des noms d'opérateurs/de stages et le préfixe $. Assurez-vous que l'opérateur est pris en charge par la version de MongoDB du serveur (les nouveaux opérateurs n'existent pas dans les anciennes versions). Comparez le pipeline à la syntaxe documentée ; utilisez l'expression dans le bon stage (par ex. $expr, $project). Isolez le stage et testez-le sur un petit échantillon.","tags":["aggregation","pipeline","operator","syntax","version","expr"]},{"code":"118 NamespaceNotSharded","title":"NamespaceNotSharded","severity":"caution","meaning":"Une opération qui n'a de sens que sur des collections shardées a été exécutée sur une collection/un namespace pas encore shardé. La commande n'a pas pu être appliquée car la collection cible n'est pas shardée.","cause":"moveChunk, split ou certaines commandes d'administration basées sur la shard key ont été appelées sur une collection sans sharding activé. Cela arrive généralement parce que la collection n'a pas été shardée avec shardCollection ou que le mauvais namespace a été ciblé.","fix":"Vérifiez l'état de sharding de la collection : sh.status() ou config.collections. Si l'opération exige réellement le sharding, activez d'abord le sharding sur la base (sh.enableSharding) et shardez la collection (sh.shardCollection avec une shard key adaptée). Sinon, ces commandes d'administration ne sont pas nécessaires sur des collections non shardées ; vérifiez que vous ciblez le bon namespace.","tags":["sharding","shard","namespace","shardcollection","movechunk","cluster"]}]},"errors/redis-more4.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"ERR Protocol error: invalid multibulk length","title":"Protocol error: invalid multibulk length (erreur de protocole : longueur multibulk invalide)","severity":"danger","meaning":"Le serveur Redis n'a pas pu analyser le flux d'octets du client selon le RESP (Redis Serialization Protocol). Le nombre d'éléments déclaré par une commande multibulk étant invalide, la connexion a été fermée avec une erreur de protocole.","cause":"Généralement déclenché par un client non Redis (par ex. une requête HTTP, une application connectée au mauvais port) envoyant des données brutes sur le port Redis, une bibliothèque cliente défaillante/incomplète, un framing erroné dans un pipeline, ou d'énormes requêtes dépassant les limites du protocole (proto-max-bulk-len).","fix":"Vérifiez que seul un véritable client Redis se connecte au port Redis (pas de trafic HTTP/d'autre protocole parasite). Mettez à jour la bibliothèque cliente et évitez la génération de commandes personnalisée/manuelle qui casse le framing RESP. Si vous envoyez de très grandes valeurs, revoyez la limite proto-max-bulk-len et la taille des requêtes. Assurez-vous qu'aucun proxy intermédiaire du réseau ne corrompt les données.","tags":["protocol","resp","multibulk","client","pipeline","connection"]},{"code":"ERR Number of keys can't be greater than number of args","title":"EVAL/EVALSHA: numkeys exceeds argument count (numkeys dépasse le nombre d'arguments)","severity":"caution","meaning":"Lors de l'exécution d'un script Lua (EVAL/EVALSHA/FCALL), le nombre de clés indiqué (numkeys) est supérieur au nombre d'arguments réellement passés à la commande. Redis refuse la commande car il ne peut pas séparer les tableaux KEYS et ARGV.","cause":"La syntaxe d'EVAL est 'EVAL script numkeys key [key ...] arg [arg ...]'. La valeur numkeys a été mal calculée (trop élevée) ou les clés attendues n'ont jamais été ajoutées à la commande. Erreur de programmation courante.","fix":"Faites en sorte que numkeys soit exactement égal au nombre de clés réellement passées à la commande. Utilisez le wrapper EVAL de votre bibliothèque cliente (qui prend généralement KEYS et ARGV comme listes séparées) pour que numkeys soit calculé automatiquement. Lors de l'appel du script, répartissez correctement les clés dans KEYS et les autres paramètres dans ARGV.","tags":["lua","eval","evalsha","numkeys","keys","script"]},{"code":"ERR WATCH inside MULTI is not allowed","title":"WATCH inside MULTI is not allowed","severity":"caution","meaning":"WATCH a été envoyé à l'intérieur d'un bloc MULTI ouvert. Pour le verrouillage optimiste, WATCH doit placer les clés sous surveillance AVANT le début de la transaction ; appelé après MULTI, il est refusé. L'erreur n'annule pas la transaction ; seul WATCH échoue et rien n'est surveillé.","cause":"WATCH et MULTI écrits dans l'ordre inverse dans le code (MULTI d'abord, puis WATCH) ; appel de WATCH dans le wrapper pipeline/transaction de la bibliothèque cliente ; ou utilisation d'une connexion du pool laissée en état MULTI. Dans ce cas, EXEC s'exécute même si les clés censées être surveillées ont changé ; la protection attendue contre les conflits ne s'applique pas.","fix":"Ordre correct : WATCH key -> (lecture des valeurs) -> MULTI -> commandes -> EXEC. Si EXEC renvoie nil, une clé surveillée a changé ; relancez l'opération depuis le début. Utilisez l'API WATCH/transaction de la bibliothèque cliente (par ex. helpers watch + pipeline). Remarque : les commandes pub/sub comme SUBSCRIBE ne sont pas refusées dans MULTI ; elles sont mises en file et la connexion passe en mode subscribe après EXEC ; c'est également déconseillé, utilisez une connexion dédiée au pub/sub.","tags":["multi","watch","transaction","optimistic locking","exec","connection"]},{"code":"ERR WAIT cannot be used with replica instances","title":"WAIT cannot be used with replica instances","severity":"caution","meaning":"WAIT a été exécuté sur une instance réplica (replica/slave). WAIT, qui attend qu'une écriture atteigne un nombre donné de réplicas, n'a de sens que sur le primaire (master) ; il ne peut pas être utilisé sur un réplica.","cause":"L'application a envoyé WAIT par erreur sur une connexion à un réplica. Cela arrive généralement parce que la connexion est routée vers un réplica en lecture, ou parce que le client est toujours connecté à un réplica après un changement de rôle (failover).","fix":"N'envoyez WAIT qu'au nœud primaire. Assurez-vous que la configuration du client/du proxy dirige les écritures et les appels WAIT vers le master. Détectez les changements de rôle après un failover et redirigez les connexions vers le nouveau primaire (vous pouvez vérifier le rôle avec la commande ROLE).","tags":["wait","replica","master","write","failover","replication"]},{"code":"ERR Background append only file rewriting already in progress","title":"AOF rewrite already in progress","severity":"caution","meaning":"Une tentative de démarrage d'une réécriture AOF (Append Only File) (BGREWRITEAOF) a eu lieu, mais une réécriture AOF est déjà en cours. Deux réécritures AOF ne peuvent pas s'exécuter en même temps.","cause":"Une nouvelle demande de réécriture est arrivée alors qu'un BGREWRITEAOF précédent (manuel, ou automatique via le déclencheur auto-aof-rewrite-percentage) était encore en cours. Sur de gros jeux de données, une réécriture AOF peut durer longtemps et provoquer ce conflit.","fix":"Attendez la fin de la réécriture en cours ; surveillez le champ aof_rewrite_in_progress dans la sortie d'INFO persistence. Si l'opération s'exécute légitimement, n'envoyez pas d'autre demande. Si les conflits sont fréquents, revoyez les seuils de réécriture automatique (auto-aof-rewrite-percentage/min-size) et les performances d'I/O disque.","tags":["aof","bgrewriteaof","persistence","rewrite","info","disk"]},{"code":"IOERR error or timeout reading from target instance","title":"MIGRATE: I/O error or timeout to target (erreur d'I/O ou timeout vers la cible)","severity":"danger","meaning":"Une commande MIGRATE (généralement lors du déplacement de slots/clés dans Redis Cluster) a rencontré une erreur d'I/O ou un timeout lors de la connexion à l'instance cible ou de la lecture de sa réponse. La migration de la clé n'a pas pu être menée à bien en toute sécurité.","cause":"Latence/coupure réseau entre les nœuds source et cible, nœud cible surchargé ou sans réponse, timeout de MIGRATE trop court, ou clé volumineuse ne pouvant pas être transférée dans le délai imparti. Fréquent lors du rééquilibrage du cluster (resharding).","fix":"Vérifiez la connectivité et la latence réseau entre les nœuds source et cible. Indiquez un timeout plus long pour MIGRATE (notamment pour les grosses clés). Vérifiez la santé/la charge et l'état mémoire du nœud cible. Lors du déplacement de grosses clés pendant le resharding, choisissez des heures de faible charge ; relancez la migration si nécessaire.","tags":["migrate","cluster","resharding","timeout","network","i-o"]},{"code":"ERR Error compiling script (new function)","title":"Error compiling Lua script","severity":"caution","meaning":"Le script Lua envoyé avec EVAL n'a pas pu être compilé par l'interpréteur Lua intégré de Redis. Il a été refusé avant le début de l'exécution car le script contient une erreur de syntaxe.","cause":"Erreurs de syntaxe dans le script Lua : parenthèses/guillemets non fermés, mots-clés mal orthographiés, 'end' manquant, opérateurs invalides ou construction Lua non prise en charge. Une concaténation/un échappement incorrect du script côté client peut aussi la déclencher.","fix":"Corrigez la syntaxe Lua à l'aide des informations de ligne/caractère du message d'erreur (vérifiez la correspondance des parenthèses, des guillemets et des 'end'). Validez le script en le compilant dans un environnement Lua séparé. Assurez-vous que le client construit correctement le script (pas d'erreurs d'échappement/de concaténation). Préchargez les scripts validés avec SCRIPT LOAD et appelez-les avec EVALSHA.","tags":["lua","eval","compilation","syntax","script load","script"]},{"code":"ERR FAILOVER requires connected replicas.","title":"FAILOVER requires connected replicas","severity":"caution","meaning":"FAILOVER a été exécuté pour démarrer un failover coordonné (graceful), mais il a été refusé car aucun réplica approprié connecté au primaire n'est disponible pour prendre le relais. Le failover ne peut pas avoir lieu sans cible à qui transmettre le rôle.","cause":"Le primaire n'a actuellement aucun réplica connecté (réplicas arrêtés, déconnectés du réseau ou non synchronisés), ou le réplica cible indiqué n'est pas éligible/connecté. Lors de l'appel manuel de FAILOVER (Redis 6.2+), la topologie de réplication ne remplit pas la condition requise. Remarque : FAILOVER ne fonctionne qu'avec la réplication standalone (master/replica) ; dans Redis Cluster, on utilise CLUSTER FAILOVER sur le réplica.","fix":"Assurez-vous qu'il existe au moins un réplica sain, connecté et à jour (vérifiez connected_slaves et l'état de synchronisation avec INFO replication). Corrigez les problèmes de réseau/d'accès des réplicas pour qu'ils se connectent au primaire. Pour basculer vers une cible précise, utilisez FAILOVER TO host port et vérifiez que ce réplica est éligible.","tags":["failover","replica","master","replication","info replication","high availability"]},{"code":"ERR invalid expire time in 'set' command","title":"invalid expire time in SET","severity":"caution","meaning":"Un délai d'expiration invalide a été fourni à SET (avec les options EX/PX/EXAT/PXAT). Redis a refusé la commande car il n'accepte pas une durée de vie dénuée de sens comme zéro ou une valeur négative.","cause":"Envoi de 0 ou d'une valeur négative avec EX/PX, ou application qui calcule mal l'expiration et produit un nombre invalide. Des commandes comme SETEX/GETEX renvoient cette erreur de la même façon pour des durées non positives. Certains bugs clients proviennent d'une confusion entre secondes et millisecondes.","fix":"Envoyez toujours l'expiration sous forme d'entier strictement positif ; EX attend des secondes, PX des millisecondes. Validez le calcul de la durée dans l'application (il ne doit pas produire de résultat négatif/nul) et ne confondez pas les unités (secondes et millisecondes). Pour supprimer une clé immédiatement, utilisez DEL/UNLINK au lieu de définir une expiration. Pour un temps absolu, passez à EXAT/PXAT un timestamp Unix futur valide.","tags":["set","expire","ttl","ex","px","expiration"]}]},"errors/elasticsearch-more4.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"bind_transport_exception","title":"bind_transport_exception (Address already in use)","severity":"danger","meaning":"Au démarrage, un nœud Elasticsearch n'a pas pu lier un socket pour écouter sur le port transport (communication entre nœuds) ou HTTP. Accompagné généralement de la sous-erreur 'Address already in use', et le nœud ne peut pas démarrer.","cause":"Le port demandé (par défaut transport 9300, HTTP 9200) est déjà utilisé par un autre processus ; le plus souvent, une instance précédente du même nœud ne s'est pas arrêtée proprement, une autre instance/un autre service ES sur le même hôte entre en conflit, ou network.host/port est mal configuré. Des restrictions de droits/d'accès peuvent aussi empêcher la liaison.","fix":"Identifiez qui détient le port (Linux : ss -ltnp | grep 9300 ou lsof -i:9300) et terminez le processus en conflit, ou attribuez un autre port au nœud (transport.port / http.port). Vérifiez que le processus ES précédent s'est réellement terminé. Assurez-vous que network.host et les plages de ports sont corrects et utilisables. Redémarrez ensuite le nœud.","tags":["transport","port","9300","bind","network.host","startup"]},{"code":"translog_corrupted_exception","title":"translog_corrupted_exception","severity":"danger","meaning":"Le journal de transactions (translog) d'un shard n'a pas pu être lu car il est corrompu. Lors de la recovery, Elasticsearch a détecté que le translog avait échoué à son contrôle d'intégrité, et le shard n'a pas pu démarrer.","cause":"Les fichiers translog sont généralement corrompus par une coupure de courant soudaine, un disque plein, une erreur du système de fichiers, une panne matérielle/de disque ou des opérations externes sur le répertoire de données (copie, intervention manuelle). Des écritures partiellement terminées lors d'un arrêt forcé (kill -9) peuvent aussi en être la cause.","fix":"Essayez d'abord de récupérer les données depuis un réplica ou un snapshot ; si une copie saine existe, reconstruire le shard corrompu à partir de celle-ci est la voie la plus sûre. S'il n'existe pas d'autre copie et que vous acceptez une perte de données, arrêtez le nœud et nettoyez le translog/les données corrompus avec bin/elasticsearch-shard remove-corrupted-data --index <index> --shard-id <n> (cela perd définitivement les données non encore validées ; l'ancien outil elasticsearch-translog a été supprimé en 7.0). L'outil affiche la commande POST _cluster/reroute (allocate_stale_primary, accept_data_loss: true) nécessaire pour affecter le shard après le redémarrage du nœud. Sauvegardez le répertoire de données avant l'opération. Vérifiez l'état et l'espace libre du disque.","tags":["translog","shard","recovery","corruption","snapshot","disk"]},{"code":"recovery_engine_exception","title":"recovery_engine_exception","severity":"danger","meaning":"Une erreur au niveau du moteur s'est produite pendant la recovery d'un shard, et le shard n'a pas pu être ouvert. La recovery est l'étape effectuée lors d'un redémarrage de nœud, d'une affectation de réplica ou d'une restauration de snapshot.","cause":"Un problème sous-jacent pendant la recovery : segments Lucene ou translog corrompus, espace disque insuffisant, droits sur les fichiers, interruption réseau entre nœuds source et cible, ou problèmes de version/compatibilité. Encapsule généralement une sous-erreur plus précise (corruption, I/O).","fix":"Examinez l'erreur sous-jacente (caused_by) pour identifier le vrai problème (corruption, disque, réseau, etc.). Vérifiez l'espace disque et les droits sur les fichiers. Si l'affectation d'un réplica échoue, voyez pourquoi avec _cluster/allocation/explain et déclenchez une réallocation si nécessaire. En cas de corruption, récupérez depuis un réplica/snapshot sain. Si c'est lié au réseau, vérifiez la connectivité entre nœuds et les paramètres de recovery.","tags":["recovery","shard","engine","allocation","corruption","disk"]},{"code":"snapshot_in_progress_exception","title":"snapshot_in_progress_exception","severity":"caution","meaning":"L'opération demandée a été refusée car elle entre en conflit avec un snapshot en cours. Par exemple, une tentative de suppression/fermeture d'un index ou de modification du dépôt pendant la prise d'un snapshot est bloquée.","cause":"Alors qu'un snapshot était encore en cours, une tentative a été faite de supprimer/fermer des index inclus dans ce snapshot, de supprimer/modifier le dépôt, ou de démarrer une autre opération de snapshot/restauration conflictuelle. Elasticsearch n'autorise pas ces opérations conflictuelles afin de préserver la cohérence.","fix":"Attendez la fin du snapshot en cours : suivez sa progression avec GET _snapshot/_status. Si l'opération doit vraiment être annulée, arrêtez-la en supprimant le snapshot : DELETE _snapshot/repo/snapshot_name (cela interrompt le snapshot en cours). Une fois le snapshot terminé ou annulé, relancez votre opération initiale (suppression d'index, modification du dépôt).","tags":["snapshot","repository","restore","index deletion","conflict","backup"]},{"code":"repository_verification_exception","title":"repository_verification_exception","severity":"danger","meaning":"Lors de l'enregistrement ou de la vérification d'un dépôt de snapshots, le contrôle visant à s'assurer que tous les nœuds peuvent accéder au dépôt a échoué. La vérification du dépôt n'ayant pas abouti, les opérations de snapshot/restauration ne peuvent pas être effectuées de façon fiable.","cause":"Un dépôt sur système de fichiers partagé (fs) n'est pas monté/accessible au même chemin sur tous les nœuds, path.repo est absent de la configuration, les droits sur les fichiers sont insuffisants, ou les stockages cloud/objet (S3, GCS, Azure) ont des identifiants/un accès réseau incorrects. L'incapacité de certains nœuds à écrire dans le dépôt est la cause typique.","fix":"Pour un dépôt fs partagé, assurez-vous que path.repo est défini dans elasticsearch.yml sur tous les nœuds et que le même chemin est monté en lecture/écriture sur chaque nœud. Corrigez les droits sur les fichiers (utilisateur elasticsearch). Pour les dépôts cloud, vérifiez les identifiants (keystore), le nom du bucket et les droits réseau/d'accès. Revérifiez le dépôt avec POST _snapshot/repo/_verify.","tags":["repository","snapshot","path.repo","permission","s3","verification"]},{"code":"reduce_search_phase_exception","title":"reduce_search_phase_exception","severity":"danger","meaning":"Une erreur s'est produite pendant la phase reduce d'une recherche. Le nœud coordinateur a rencontré un problème lors de la fusion (reduce) des résultats partiels/agrégations provenant de différents shards et n'a pas pu produire le résultat final.","cause":"Généralement une limite de mémoire/de ressources dépassée lors de la fusion des résultats (par ex. agrégations très volumineuses, déclenchement d'un circuit breaker), une structure d'agrégation incompatible/invalide, ou un résultat partiel renvoyé par un shard qui ne peut pas être fusionné. Fréquent avec les agrégations à très forte cardinalité.","fix":"Lisez le détail caused_by pour identifier la cause racine. Allégez les agrégations lourdes : diminuez size sur les agrégations terms à forte cardinalité, paginez avec une composite aggregation et réduisez les sous-agrégations inutiles. Surveillez la pression mémoire et découpez la requête en parties plus petites si nécessaire. Si un circuit breaker se déclenche, réduisez le coût de la requête.","tags":["search","reduce","aggregation","coordinating node","memory","cardinality"]},{"code":"aggregation_execution_exception","title":"aggregation_execution_exception","severity":"caution","meaning":"Une erreur s'est produite pendant l'exécution d'une agrégation. Bien que la définition de l'agrégation ait été analysée, un problème est survenu à l'exécution lors du traitement des champs/valeurs cibles.","cause":"L'agrégation est incompatible avec le type de champ (par ex. agrégation d'un champ text sans fielddata), valeurs invalides lors du tri/du calcul, pipeline aggregation référençant un chemin inexistant (bucket path), ou combinaisons de types de champs non prises en charge. Les erreurs d'exécution dans les agrégations basées sur des scripts la déclenchent aussi.","fix":"Examinez le nom du champ/de l'agrégation dans le détail de l'erreur. Assurez-vous que les champs agrégés sont d'un type adapté (keyword/numérique plutôt que text ; utilisez si nécessaire des champs avec doc_values activé). Dans les pipeline aggregations, vérifiez que buckets_path correspond aux noms réels des buckets. Ajoutez des contrôles de null/de type dans les agrégations par script. Corrigez le mapping avec des types de champs appropriés.","tags":["aggregation","execution","fielddata","keyword","pipeline","mapping"]},{"code":"failed_node_exception","title":"failed_node_exception","severity":"danger","meaning":"Lors d'une opération à l'échelle du cluster (par ex. recherche, statistiques, commande d'administration), un nœud précis du cluster n'a pas pu traiter la requête ou y répondre. Le nœud coordinateur encapsule l'échec de ce nœud dans cette exception.","cause":"Le nœud a planté, est sorti du réseau, n'a pas pu répondre à cause d'une surcharge/de pauses GC, de problèmes de disque/de ressources, ou d'une erreur de shard sur ce nœud. Peut aussi apparaître de façon transitoire lors d'une reconfiguration du cluster (nœuds qui partent/rejoignent).","fix":"Utilisez caused_by pour déterminer sur quel nœud et pourquoi l'échec s'est produit. Vérifiez la santé du nœud : GET _cat/nodes, GET _nodes/stats (CPU, mémoire, GC, disque). Si le nœud est sous pression de ressources, revoyez la charge/les paramètres de heap ; vérifiez la connectivité réseau. Si le nœud a quitté le cluster, attendez qu'il le rejoigne et que l'affectation des shards se rétablisse (GET _cluster/health).","tags":["node","cluster","network","gc","shard","resources"]},{"code":"elasticsearch_parse_exception","title":"elasticsearch_parse_exception","severity":"caution","meaning":"Elasticsearch a rencontré une erreur d'analyse générique lors de l'analyse du corps de la requête (requête JSON, paramètres ou configuration). La requête ne correspond pas structurellement au format attendu.","cause":"JSON mal formé/invalide (virgule manquante, accolade non fermée), champs mal placés, types de valeurs inattendus (par ex. du texte là où un nombre est attendu), ou expression de temps/de plage invalide. Une imbrication incorrecte d'une requête DSL produit aussi cette exception.","fix":"Passez le corps de la requête dans un validateur JSON ; vérifiez accolades, guillemets et virgules. Vérifiez dans la documentation du DSL Elasticsearch que les champs sont au bon endroit et du bon type. Utilisez l'indication de position/de champ du message d'erreur pour corriger la partie en cause. Si vous utilisez une bibliothèque cliente, assurez-vous que la sérialisation produit un JSON valide.","tags":["parse","json","dsl","query","parsing","request"]}]},"health/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"checks":[{"id":"version-uptime","group":"Général","title":"Version et uptime","command":"SELECT i.instance_name, i.version_full, i.status, i.startup_time, ROUND(SYSDATE - i.startup_time, 2) AS uptime_days FROM v$instance i;","expect":"L'état de l'instance doit être OPEN. Si startup_time est anormalement récent (démarrage récent), un redémarrage non planifié a pu se produire ; investiguez. La colonne version_full existe en 18c+ ; en 12c et antérieur, utilisez la colonne version.","severity":"safe"},{"id":"alertlog-errors","group":"Général","title":"Erreurs critiques récentes dans l'alert log (ORA-)","command":"SELECT originating_timestamp, message_text FROM v$diag_alert_ext WHERE message_text LIKE '%ORA-%' AND originating_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY ORDER BY originating_timestamp DESC FETCH FIRST 50 ROWS ONLY;","expect":"Aucune erreur ORA- ne doit être renvoyée pour les dernières 24 heures. Des entrées comme ORA-00600, ORA-07445 et ORA-01578 (corruption de bloc) exigent une action immédiate.","severity":"danger"},{"id":"invalid-objects","group":"Général","title":"Objets INVALID","command":"SELECT owner, object_type, COUNT(*) AS invalid_count FROM dba_objects WHERE status = 'INVALID' GROUP BY owner, object_type ORDER BY invalid_count DESC;","expect":"Idéalement, il ne doit y avoir aucun objet INVALID. S'il y en a, recompilez avec EXEC DBMS_UTILITY.COMPILE_SCHEMA ou utlrp.sql ; les objets invalides sous SYS/SYSTEM sont prioritaires.","severity":"caution"},{"id":"tablespace-usage","group":"Capacité","title":"Utilisation des tablespaces","command":"SELECT df.tablespace_name, ROUND(df.total_mb, 1) AS total_mb, ROUND(df.total_mb - NVL(fs.free_mb, 0), 1) AS used_mb, ROUND((df.total_mb - NVL(fs.free_mb, 0)) / df.total_mb * 100, 1) AS pct_used FROM (SELECT tablespace_name, SUM(bytes)/1024/1024 total_mb FROM dba_data_files GROUP BY tablespace_name) df LEFT JOIN (SELECT tablespace_name, SUM(bytes)/1024/1024 free_mb FROM dba_free_space GROUP BY tablespace_name) fs ON df.tablespace_name = fs.tablespace_name ORDER BY pct_used DESC;","expect":"Un pct_used supérieur à 85 % est le seuil d'alerte, au-delà de 90 % le seuil critique. Les tablespaces sans autoextend risquent davantage de se remplir ; ajoutez un datafile ou activez autoextend.","severity":"danger"},{"id":"largest-segments","group":"Capacité","title":"Plus grands segments (tables/index)","command":"SELECT owner, segment_name, segment_type, ROUND(bytes/1024/1024/1024, 2) AS size_gb FROM dba_segments ORDER BY bytes DESC FETCH FIRST 20 ROWS ONLY;","expect":"Affiche les segments qui consomment le plus d'espace. Une croissance inattendue peut indiquer un besoin d'archivage/de purge ou de partitionnement.","severity":"safe"},{"id":"undo-temp-usage","group":"Capacité","title":"Utilisation des tablespaces UNDO et TEMP","command":"SELECT ts.tablespace_name, ts.contents, ROUND(SUM(ss.used_blocks) * ts.block_size / 1024 / 1024, 1) AS used_mb FROM v$sort_segment ss JOIN (SELECT tablespace_name, contents, block_size FROM dba_tablespaces WHERE contents = 'TEMPORARY') ts ON ss.tablespace_name = ts.tablespace_name GROUP BY ts.tablespace_name, ts.contents, ts.block_size UNION ALL SELECT tablespace_name, 'UNDO', ROUND(SUM(bytes)/1024/1024, 1) FROM dba_undo_extents WHERE status = 'ACTIVE' GROUP BY tablespace_name;","expect":"Si l'utilisation de TEMP atteint constamment le plafond, il y a de grosses opérations de tri/hash. Si l'espace UNDO actif ne peut pas satisfaire la rétention, il existe un risque d'ORA-01555 (snapshot too old).","severity":"caution"},{"id":"sessions-vs-limit","group":"Capacité","title":"Nombre de sessions vs limite","command":"SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('sessions','processes','transactions');","expect":"Si current_utilization approche limit_value (surtout au-delà de 85 %), de nouvelles connexions peuvent être refusées. max_utilization indique le pic historique ; revoyez le paramètre processes.","severity":"caution"},{"id":"fra-usage","group":"Capacité","title":"Utilisation de la Flash Recovery Area (FRA)","command":"SELECT name, ROUND(space_limit/1024/1024/1024, 2) AS limit_gb, ROUND(space_used/1024/1024/1024, 2) AS used_gb, ROUND(space_used/space_limit*100, 1) AS pct_used FROM v$recovery_file_dest;","expect":"Lorsque la FRA atteint 85 %, un avertissement est écrit dans l'alert log ; à 100 %, l'écriture des archive logs s'arrête et la base se fige (archiver hung, ORA-00257). Nettoyez les sauvegardes/archives RMAN ou augmentez db_recovery_file_dest_size.","severity":"danger"},{"id":"buffer-cache-hit","group":"Performances","title":"Taux de succès du buffer cache","command":"SELECT ROUND((1 - (phy.value - dir.value) / (con.value + cur.value)) * 100, 2) AS buffer_hit_ratio FROM v$sysstat con, v$sysstat cur, v$sysstat phy, v$sysstat dir WHERE con.name = 'consistent gets' AND cur.name = 'db block gets' AND phy.name = 'physical reads' AND dir.name = 'physical reads direct';","expect":"Sur les systèmes OLTP, on attend généralement plus de 90 %. Des valeurs durablement faibles peuvent indiquer une SGA/un buffer cache sous-dimensionné ou une indexation insuffisante ; interprétez-le avec les événements d'attente, pas isolément.","severity":"safe"},{"id":"top-sql","group":"Performances","title":"Instructions SQL les plus coûteuses (temps écoulé)","command":"SELECT sql_id, ROUND(elapsed_time/1000000, 1) AS elapsed_sec, executions, ROUND(elapsed_time/1000000/GREATEST(executions,1), 3) AS sec_per_exec, SUBSTR(sql_text, 1, 100) AS sql_text FROM v$sqlarea ORDER BY elapsed_time DESC FETCH FIRST 15 ROWS ONLY;","expect":"Requêtes ayant le temps écoulé total le plus élevé. Celles avec un sec_per_exec élevé ou un nombre d'executions très élevé sont candidates à l'optimisation ; examinez-les avec AWR/SQL Tuning Advisor.","severity":"safe"},{"id":"long-active-sessions","group":"Performances","title":"Sessions actives de longue durée","command":"SELECT s.sid, s.serial#, s.username, s.status, s.last_call_et AS active_seconds, s.event, SUBSTR(q.sql_text, 1, 80) AS sql_text FROM v$session s LEFT JOIN v$sqlarea q ON s.sql_id = q.sql_id WHERE s.status = 'ACTIVE' AND s.type = 'USER' AND s.last_call_et > 60 ORDER BY s.last_call_et DESC;","expect":"Les sessions actives avec un last_call_et (secondes) très élevé peuvent être bloquées ou exécuter une requête lourde. La colonne event indique la ressource attendue.","severity":"caution"},{"id":"blocking-sessions","group":"Performances","title":"Sessions bloquantes et en attente (verrous)","command":"SELECT bs.sid AS blocker_sid, bs.username AS blocker_user, ws.sid AS waiter_sid, ws.username AS waiter_user, ws.seconds_in_wait, ws.event FROM v$session ws JOIN v$session bs ON ws.blocking_session = bs.sid WHERE ws.blocking_session IS NOT NULL ORDER BY ws.seconds_in_wait DESC;","expect":"Aucune ligne ne doit être renvoyée. Les sessions bloquantes provoquent des latences/interblocages applicatifs ; si blocker_sid détient un verrou longtemps (seconds_in_wait élevé), évaluez la situation avec l'équipe applicative et, si nécessaire, exécutez ALTER SYSTEM KILL SESSION.","severity":"danger"},{"id":"rman-last-backup","group":"Sauvegarde et restauration","title":"État de la dernière sauvegarde RMAN réussie","command":"SELECT input_type, status, start_time, end_time, ROUND(output_bytes/1024/1024/1024, 2) AS output_gb FROM v$rman_backup_job_details ORDER BY start_time DESC FETCH FIRST 10 ROWS ONLY;","expect":"La dernière sauvegarde doit avoir status = COMPLETED et un start_time assez récent pour votre politique de RPO (par ex. dans les dernières 24 heures). Les entrées FAILED ou 'COMPLETED WITH WARNINGS' doivent être investiguées.","severity":"danger"},{"id":"archivelog-mode","group":"Sauvegarde et restauration","title":"Mode archivelog et état de l'archiver","command":"SELECT log_mode, force_logging, (SELECT COUNT(*) FROM v$archived_log WHERE first_time > SYSDATE - 1) AS logs_last_24h FROM v$database;","expect":"Une base de production doit avoir log_mode = ARCHIVELOG (sinon la restauration à un instant donné est impossible). Si aucune archive n'a été produite dans les dernières 24 heures, l'archiver est peut-être bloqué.","severity":"danger"},{"id":"locked-expired-accounts","group":"Sécurité","title":"Comptes verrouillés ou expirés","command":"SELECT username, account_status, lock_date, expiry_date, profile FROM dba_users WHERE account_status != 'OPEN' ORDER BY username;","expect":"Des comptes applicatifs critiques LOCKED/EXPIRED provoquent des interruptions inattendues. Que les comptes par défaut (SCOTT, HR, etc.) soient verrouillés/désactivés est une bonne chose. EXPIRED(GRACE) signale une expiration de mot de passe imminente.","severity":"caution"},{"id":"privileged-users","group":"Sécurité","title":"Utilisateurs à privilèges élevés (rôle DBA)","command":"SELECT grantee, granted_role, admin_option FROM dba_role_privs WHERE granted_role IN ('DBA','PDB_DBA') AND grantee NOT IN ('SYS','SYSTEM') ORDER BY grantee;","expect":"Le rôle DBA ne doit être détenu que par des comptes nécessaires et connus. Les bénéficiaires inattendus ou ceux avec admin_option = YES présentent un risque d'élévation de privilèges ; examinez-les selon le principe du moindre privilège. Pour les comptes dotés des privilèges SYSDBA/SYSOPER, vérifiez aussi : SELECT username, sysdba, sysoper FROM v$pwfile_users;","severity":"caution"},{"id":"default-password-users","group":"Sécurité","title":"Comptes ouverts avec mot de passe par défaut inchangé","command":"SELECT du.username, du.account_status, du.profile, CASE WHEN dp.username IS NOT NULL THEN 'YES' ELSE 'NO' END AS has_default_password FROM dba_users du LEFT JOIN (SELECT username FROM dba_users_with_defpwd) dp ON du.username = dp.username WHERE du.account_status = 'OPEN' ORDER BY du.username;","expect":"Les comptes avec has_default_password = YES doivent voir leur mot de passe changé immédiatement ou être verrouillés (ALTER USER ... ACCOUNT LOCK). Pour la politique de durée de vie des mots de passe, vérifiez aussi PASSWORD_LIFE_TIME dans DBA_PROFILES.","severity":"caution"},{"id":"dataguard-lag","group":"Réplication","title":"Retard du standby Data Guard","command":"SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN ('apply lag','transport lag');","expect":"L'apply lag et le transport lag doivent être aussi proches de 0 que possible. Un retard de l'ordre de minutes/heures signale un problème réseau ou de MRP (Managed Recovery Process) ; la cible de failover est en danger.","severity":"danger"},{"id":"dataguard-mrp","group":"Réplication","title":"État du processus d'application du standby (MRP)","command":"SELECT name, role, action, thread#, sequence#, block# FROM v$dataguard_process WHERE name LIKE 'MRP%' OR role LIKE 'RFS%' ORDER BY name;","expect":"Sur un standby physique, le processus MRP0 doit être visible avec l'action APPLYING_LOG ou WAIT_FOR_LOG. S'il n'y a pas de MRP, l'application est arrêtée ; s'il n'y a pas de RFS, les journaux n'arrivent pas du primaire. v$dataguard_process existe en 12.2+ ; en 12.1 et antérieur, utilisez v$managed_standby (désormais dépréciée ; colonnes process, status).","severity":"danger"}]},"health/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"checks":[{"id":"version-uptime","group":"Général","title":"Version et uptime","command":"SELECT version() AS pg_version, pg_postmaster_start_time() AS started_at, date_trunc('second', now() - pg_postmaster_start_time()) AS uptime;","expect":"Affiche la version et l'heure de démarrage. Si l'uptime est anormalement court (démarrage récent), un redémarrage non planifié ou un crash a pu se produire ; consultez les journaux.","severity":"safe"},{"id":"recent-errors","group":"Général","title":"Statistiques de checkpoint","command":"-- PG17+:\nSELECT num_timed, num_requested, buffers_written, stats_reset FROM pg_stat_checkpointer;\n-- PG16 and earlier:\nSELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, stats_reset FROM pg_stat_bgwriter;","expect":"Si le nombre de checkpoints demandés (num_requested / checkpoints_req) est élevé par rapport aux checkpoints planifiés (num_timed / checkpoints_timed), le WAL se remplit trop vite ; augmentez max_wal_size. En PG17, les compteurs sont passés de pg_stat_bgwriter à pg_stat_checkpointer.","severity":"caution"},{"id":"table-bloat","group":"Général","title":"Tables gonflées et taux de tuples morts","command":"SELECT schemaname, relname, n_live_tup, n_dead_tup, CASE WHEN n_live_tup > 0 THEN round(n_dead_tup * 100.0 / n_live_tup, 1) ELSE 0 END AS dead_pct, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 1000 ORDER BY n_dead_tup DESC LIMIT 20;","expect":"Les tables avec un dead_pct élevé (par ex. au-delà de 20 %) sont gonflées ; cela affecte les performances de VACUUM et l'utilisation du disque. Si last_autovacuum est très ancien, revoyez les réglages d'autovacuum ou lancez un VACUUM (ANALYZE) manuel.","severity":"caution"},{"id":"database-size","group":"Capacité","title":"Taille des bases","command":"SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size, pg_database_size(datname) AS size_bytes FROM pg_database WHERE datistemplate = false ORDER BY pg_database_size(datname) DESC;","expect":"Affiche la taille des bases. Évaluez toute croissance inattendue avec la capacité disque (df -h) ; PostgreSQL arrête les écritures lorsque le disque est plein.","severity":"danger"},{"id":"largest-tables","group":"Capacité","title":"Plus grandes tables (index compris)","command":"SELECT schemaname, relname, pg_size_pretty(pg_total_relation_size(relid)) AS total_size, pg_size_pretty(pg_relation_size(relid)) AS table_size, pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS index_toast_size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;","expect":"Affiche les tables qui occupent le plus d'espace. Si index_toast_size est bien plus grand que la taille de la table, il peut y avoir des index excessifs/inutilisés ; envisagez l'archivage ou le partitionnement.","severity":"safe"},{"id":"temp-files","group":"Capacité","title":"Utilisation des fichiers temporaires","command":"SELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_size, stats_reset FROM pg_stat_database WHERE temp_files > 0 ORDER BY temp_bytes DESC;","expect":"Des temp_files/temp_bytes élevés indiquent que work_mem est insuffisant et que les opérations de tri/hash débordent sur disque. Envisagez d'augmenter work_mem ou d'optimiser les requêtes.","severity":"caution"},{"id":"connections-vs-limit","group":"Capacité","title":"Nombre de connexions vs max_connections","command":"SELECT (SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend') AS current_connections, (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections, round((SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend') * 100.0 / (SELECT setting::int FROM pg_settings WHERE name = 'max_connections'), 1) AS pct_used;","expect":"Seules les connexions client sont comptées (le filtre backend_type = 'client backend' est nécessaire car pg_stat_activity inclut aussi les processus d'arrière-plan). Au-delà de 80 % de pct_used, les nouvelles connexions risquent d'être rejetées (surtout une fois superuser_reserved_connections atteint). Un pooler de connexions (PgBouncer) est recommandé.","severity":"caution"},{"id":"wal-directory-size","group":"Capacité","title":"Taille du répertoire WAL et rétention par les replication slots","command":"SELECT (SELECT count(*) FROM pg_ls_waldir()) AS wal_file_count, pg_size_pretty((SELECT sum(size) FROM pg_ls_waldir())) AS wal_total_size, slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal FROM pg_replication_slots ORDER BY slot_name;","expect":"Les replication slots inactifs (active = false) font s'accumuler le WAL et remplissent le répertoire pg_wal. Si retained_wal est important, vérifiez le propriétaire du slot (slot_type, database, plugin) : active = false peut aussi indiquer un standby temporairement déconnecté ou un slot de souscripteur logique/CDC (par ex. Debezium). S'il est réellement inutilisé, supprimez-le avec pg_drop_replication_slot ; un standby qui dépendait du slot supprimé doit être reconstruit, et le côté souscripteur/CDC doit être resynchronisé. Pour une protection permanente, fixez une limite supérieure avec max_slot_wal_keep_size (PG13+).","severity":"danger"},{"id":"cache-hit-ratio","group":"Performances","title":"Taux de succès du buffer cache","command":"SELECT sum(heap_blks_read) AS heap_read, sum(heap_blks_hit) AS heap_hit, round(sum(heap_blks_hit) * 100.0 / NULLIF(sum(heap_blks_hit) + sum(heap_blks_read), 0), 2) AS cache_hit_ratio FROM pg_statio_user_tables;","expect":"Pour les charges OLTP, la cible est généralement supérieure à 99 %. Une valeur constamment faible peut indiquer que shared_buffers est trop petit ou que l'ensemble de travail ne tient pas en mémoire.","severity":"safe"},{"id":"top-queries","group":"Performances","title":"Requêtes les plus coûteuses (pg_stat_statements)","command":"SELECT round(total_exec_time::numeric, 1) AS total_ms, calls, round(mean_exec_time::numeric, 2) AS mean_ms, round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 1) AS pct, left(query, 100) AS query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 15;","expect":"pg_stat_statements doit figurer dans shared_preload_libraries (nécessite un redémarrage) et être installé dans la base (CREATE EXTENSION pg_stat_statements). Les noms total_exec_time/mean_exec_time valent pour PG13+ ; PG12 utilise total_time/mean_time. Les requêtes qui consomment le plus de temps total sont candidates à l'optimisation ; exécutez EXPLAIN ANALYZE pour celles qui ont un mean_ms élevé.","severity":"safe"},{"id":"long-running-queries","group":"Performances","title":"Requêtes actives de longue durée","command":"SELECT pid, usename, state, now() - query_start AS duration, wait_event_type, wait_event, left(query, 80) AS query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '1 minute' AND backend_type = 'client backend' ORDER BY duration DESC;","expect":"Les requêtes actives de plus d'une minute peuvent être bloquées ou lourdes. wait_event indique ce qui est attendu. Cette requête ne liste que les requêtes actives ; vérifiez séparément les sessions 'idle in transaction' (qui empêchent VACUUM de supprimer les tuples morts) avec state = 'idle in transaction' et xact_start.","severity":"caution"},{"id":"blocking-locks","group":"Performances","title":"Sessions bloquantes et verrous en attente","command":"SELECT blocked.pid AS blocked_pid, blocked.usename AS blocked_user, blocking.pid AS blocking_pid, blocking.usename AS blocking_user, now() - blocked.query_start AS blocked_duration, left(blocked.query, 60) AS blocked_query FROM pg_stat_activity blocked JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid)) WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0;","expect":"Aucune ligne ne doit être renvoyée. Les sessions bloquantes (blocking_pid) provoquent des retards et des timeouts applicatifs ; investiguez une transaction bloquante de longue durée et, si nécessaire, terminez-la avec pg_terminate_backend.","severity":"danger"},{"id":"archiver-status","group":"Sauvegarde et restauration","title":"Santé de l'archivage WAL (pg_stat_archiver)","command":"SELECT archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, now() - last_archived_time AS since_last_archive FROM pg_stat_archiver;","expect":"failed_count doit valoir 0 et last_failed_time doit être antérieur à last_archived_time. Si since_last_archive est très grand, l'archivage s'est arrêté (échec d'archive_command) ; la chaîne de sauvegarde PITR est rompue.","severity":"danger"},{"id":"last-basebackup","group":"Sauvegarde et restauration","title":"Fraîcheur de la dernière sauvegarde et de l'archive WAL","command":"-- Base backup time is not stored in the database; read it from the backup tool: pgbackrest info / barman list-backup <server>\nSELECT pg_is_in_recovery() AS in_recovery, last_archived_wal, last_archived_time, now() - last_archived_time AS since_last_archive, failed_count, last_failed_time FROM pg_stat_archiver;","expect":"Vérifiez l'heure de la dernière sauvegarde de base dans l'outil de sauvegarde (journaux des jobs pgBackRest / Barman / pg_basebackup) ; elle doit être assez récente pour votre politique de RPO. Pour le PITR, l'archive WAL doit aussi être continue : si since_last_archive est grand ou si failed_count augmente, la chaîne d'archives est peut-être rompue.","severity":"danger"},{"id":"locked-expired-roles","group":"Sécurité","title":"Rôles de connexion expirés ou bientôt expirés","command":"SELECT rolname, rolcanlogin, rolvaliduntil, rolconnlimit FROM pg_roles WHERE rolcanlogin = true AND (rolvaliduntil IS NOT NULL AND rolvaliduntil < now() + interval '7 days') ORDER BY rolvaliduntil;","expect":"Les rôles applicatifs dont rolvaliduntil est dépassé ne peuvent plus se connecter, ce qui provoque une interruption. Planifiez à l'avance le renouvellement des mots de passe qui expirent dans les 7 prochains jours.","severity":"caution"},{"id":"superuser-roles","group":"Sécurité","title":"Rôles à privilèges élevés (SUPERUSER / privilèges de création)","command":"SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolbypassrls FROM pg_roles WHERE rolsuper = true OR rolcreaterole = true OR rolbypassrls = true ORDER BY rolsuper DESC, rolname;","expect":"SUPERUSER ne doit exister que sur des comptes nécessaires et connus. Les rôles avec rolbypassrls = true contournent la sécurité au niveau ligne (RLS) ; les privilèges élevés inattendus doivent être révoqués selon le principe du moindre privilège.","severity":"caution"},{"id":"hba-trust-auth","group":"Sécurité","title":"Règles d'authentification faibles (trust)","command":"SELECT type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method IN ('trust','password') ORDER BY line_number;","expect":"auth_method = trust autorise l'accès sans mot de passe et est dangereux en production ; 'password' envoie le mot de passe en clair. Remplacez-les par scram-sha-256. (Nécessite PG 10+.)","severity":"danger"},{"id":"replication-lag","group":"Réplication","title":"Retard de la streaming replication (côté primary)","command":"SELECT client_addr, state, sync_state, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)) AS sent_lag, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_lag, write_lag, flush_lag, replay_lag AS replay_time_lag FROM pg_stat_replication ORDER BY client_addr;","expect":"À exécuter sur le primaire. state doit valoir streaming et replay_lag (octets/temps) doit être faible. Si le standby n'apparaît pas du tout, la connexion est coupée ; si sync_state = async, la réplication synchrone ne fonctionne pas comme prévu.","severity":"danger"},{"id":"replica-lag-standby","group":"Réplication","title":"Retard de réplication (côté standby)","command":"SELECT pg_is_in_recovery() AS in_recovery, CASE WHEN pg_is_in_recovery() THEN date_trunc('second', now() - pg_last_xact_replay_timestamp()) ELSE NULL END AS replication_delay, pg_last_wal_receive_lsn() AS receive_lsn, pg_last_wal_replay_lsn() AS replay_lsn;","expect":"À exécuter sur le serveur standby. in_recovery doit valoir true. Si replication_delay est de l'ordre de minutes/heures, il peut y avoir un retard d'application ; cette valeur augmente toutefois aussi en l'absence d'écritures sur le primaire (fausse alerte), interprétez-la donc avec l'écart de LSN ; si l'écart entre receive_lsn et replay_lsn est grand, le rejeu du WAL ne suit pas.","severity":"danger"}]},"health/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"checks":[{"id":"version-uptime","group":"Général","title":"Version et uptime","command":"SELECT VERSION() AS mysql_version, VARIABLE_VALUE AS uptime_seconds, ROUND(VARIABLE_VALUE/86400, 2) AS uptime_days FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime';","expect":"Affiche la version et l'uptime en secondes. Si uptime_seconds est anormalement faible, un redémarrage non planifié ou un crash a pu se produire ; consultez le journal d'erreurs.","severity":"safe"},{"id":"error-log-status","group":"Général","title":"Entrées récentes du journal d'erreurs (performance_schema)","command":"SELECT logged, prio, error_code, subsystem, LEFT(data, 120) AS message FROM performance_schema.error_log WHERE prio IN ('Error','Warning') ORDER BY logged DESC LIMIT 30;","expect":"Affiche les erreurs et avertissements récents (nécessite MySQL 8.0+). Investiguez les entrées prio = 'Error' (en particulier les erreurs InnoDB, de verrou et de disque). Sur les versions plus anciennes, consultez le fichier log_error du serveur.","severity":"danger"},{"id":"crashed-tables","group":"Général","title":"Tables corrompues / tables à vérifier","command":"SELECT table_schema, table_name, engine, table_rows, update_time FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') AND engine IS NOT NULL ORDER BY table_schema, table_name;","expect":"Liste le moteur de chaque table et l'heure de sa dernière mise à jour. Les tables MyISAM peuvent se corrompre ; en cas de doute, vérifiez l'intégrité avec CHECK TABLE ... ou mysqlcheck. InnoDB est recommandé pour les données critiques.","severity":"caution"},{"id":"schema-sizes","group":"Capacité","title":"Taille des schémas (bases)","command":"SELECT table_schema, ROUND(SUM(data_length + index_length)/1024/1024, 1) AS size_mb, ROUND(SUM(data_free)/1024/1024, 1) AS free_mb FROM information_schema.tables GROUP BY table_schema ORDER BY SUM(data_length + index_length) DESC;","expect":"Affiche la taille des schémas et l'espace data_free récupérable. Évaluez avec la capacité disque ; si free_mb est élevé, l'espace peut être récupéré avec OPTIMIZE TABLE.","severity":"danger"},{"id":"largest-tables","group":"Capacité","title":"Plus grandes tables (data_free / fragmentation comprises)","command":"SELECT table_schema, table_name, engine, ROUND((data_length + index_length)/1024/1024, 1) AS total_mb, ROUND(data_free/1024/1024, 1) AS data_free_mb, table_rows FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') ORDER BY (data_length + index_length) DESC LIMIT 20;","expect":"Affiche les plus grandes tables. Les tables avec un data_free_mb élevé sont fragmentées ; l'espace peut être récupéré avec OPTIMIZE TABLE. Envisagez le partitionnement pour les très grandes tables.","severity":"safe"},{"id":"innodb-tablespace","group":"Capacité","title":"Utilisation du tablespace InnoDB et de l'espace temporaire","command":"SELECT FILE_NAME, TABLESPACE_NAME, ROUND(TOTAL_EXTENTS * EXTENT_SIZE / 1024 / 1024, 1) AS total_mb, ROUND((TOTAL_EXTENTS - FREE_EXTENTS) * EXTENT_SIZE / 1024 / 1024, 1) AS used_mb FROM information_schema.files WHERE FILE_TYPE = 'TABLESPACE' ORDER BY TOTAL_EXTENTS DESC LIMIT 20;","expect":"Indique le taux de remplissage des datafiles InnoDB. Si innodb_file_per_table est désactivé, ibdata1 ne cesse de croître et ne rétrécit jamais. Pour le gonflement du tablespace temporaire, vérifiez aussi la sortie de SHOW ENGINE INNODB STATUS.","severity":"caution"},{"id":"connections-vs-limit","group":"Capacité","title":"Nombre de connexions et limite max_connections","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_connected') AS connected, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'max_connections') AS max_connections, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Max_used_connections') AS max_used, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Aborted_connects') AS aborted_connects;","expect":"Si max_used approche max_connections (au-delà de 85 %), il existe un risque d'erreurs 'Too many connections'. Un aborted_connects élevé signale des problèmes d'authentification/réseau ; utilisez un pool de connexions.","severity":"caution"},{"id":"binlog-size","group":"Capacité","title":"Fichiers binary log et taille totale","command":"SHOW BINARY LOGS;","expect":"Liste les fichiers binlog et leur taille. Si la taille totale pèse sur l'espace disque, revoyez binlog_expire_logs_seconds (expire_logs_days avant 8.0 ; cette variable a été dépréciée en 8.0 et supprimée en 8.4) ; si la réplication est utilisée, ne purgez pas avant que les réplicas les aient lus.","severity":"caution"},{"id":"buffer-pool-hit","group":"Performances","title":"Taux de succès du buffer pool InnoDB","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') AS read_requests, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') AS disk_reads, ROUND((1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') / (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')) * 100, 3) AS hit_ratio_pct;","expect":"La cible pour hit_ratio_pct est généralement supérieure à 99 %. S'il reste faible, innodb_buffer_pool_size est insuffisant ; envisagez de l'augmenter jusqu'à ~70-80 % de la RAM sur un serveur dédié (moins sur un serveur partagé).","severity":"safe"},{"id":"top-queries","group":"Performances","title":"Requêtes les plus coûteuses (sys.statement_analysis)","command":"SELECT query, db, exec_count, total_latency, avg_latency, rows_sent_avg, rows_examined_avg FROM sys.statement_analysis ORDER BY total_latency DESC LIMIT 15;","expect":"Le schéma sys et performance_schema doivent être activés. Les requêtes qui consomment le plus de total_latency sont candidates à l'optimisation. Si rows_examined_avg est bien plus grand que rows_sent_avg, un index manque ; examinez-la avec EXPLAIN.","severity":"safe"},{"id":"long-running-queries","group":"Performances","title":"Requêtes actives de longue durée","command":"SELECT id, user, host, db, command, time AS seconds, state, LEFT(info, 80) AS query FROM information_schema.processlist WHERE command != 'Sleep' AND time > 60 ORDER BY time DESC;","expect":"Les requêtes de plus de 60 secondes peuvent être bloquées ou lourdes. La colonne state indique ce qui est attendu. Les longues sessions à l'état 'Sleep' épuisent aussi le pool de connexions ; utilisez KILL <id> si nécessaire.","severity":"caution"},{"id":"blocking-locks","group":"Performances","title":"Sessions bloquantes et verrous en attente","command":"SELECT r.trx_id AS waiting_trx, r.trx_mysql_thread_id AS waiting_thread, TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS wait_seconds, LEFT(r.trx_query, 60) AS waiting_query, b.trx_mysql_thread_id AS blocking_thread, LEFT(b.trx_query, 60) AS blocking_query FROM performance_schema.data_lock_waits w JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id ORDER BY wait_seconds DESC;","expect":"Ne doit renvoyer aucune ligne. Une transaction bloquante (blocking_thread) provoque des latences applicatives et des erreurs lock wait timeout. Investiguez une transaction bloquante de longue durée et terminez-la avec KILL si nécessaire. (Syntaxe MySQL 8.0.)","severity":"danger"},{"id":"binlog-enabled","group":"Sauvegarde et restauration","title":"Réglages du binary log et de la récupération","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'log_bin') AS log_bin, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'binlog_format') AS binlog_format, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'sync_binlog') AS sync_binlog, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'innodb_flush_log_at_trx_commit') AS flush_log_commit;","expect":"log_bin doit valoir ON pour la restauration à un instant donné. Pour la durabilité, sync_binlog = 1 et innodb_flush_log_at_trx_commit = 1 sont recommandés (sinon des transactions peuvent être perdues lors d'un crash). binlog_format = ROW est le plus sûr pour la réplication.","severity":"danger"},{"id":"last-backup-note","group":"Sauvegarde et restauration","title":"Heure de la dernière sauvegarde (indicateur binlog / outil)","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime') AS uptime_seconds, (SELECT MAX(update_time) FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')) AS last_data_change;","expect":"MySQL ne suit pas lui-même l'état des sauvegardes ; vérifiez l'heure de la dernière sauvegarde réussie dans les journaux des outils mysqldump / MySQL Shell dump / Percona XtraBackup (mysqlpump a été déprécié en 8.0.34 et supprimé en 8.4). La sauvegarde doit être aussi récente que l'exige votre politique de RPO (par ex. dans les dernières 24 heures).","severity":"danger"},{"id":"locked-expired-accounts","group":"Sécurité","title":"Comptes verrouillés ou à mot de passe expiré","command":"SELECT user, host, account_locked, password_expired, password_lifetime FROM mysql.user ORDER BY user, host;","expect":"Les comptes applicatifs critiques avec account_locked = 'Y' ou password_expired = 'Y' provoquent des interruptions. Si password_lifetime vaut NULL et que l'expiration globale est désactivée, le mot de passe n'expire jamais ; cela peut constituer une lacune de politique.","severity":"caution"},{"id":"anonymous-nopassword-users","group":"Sécurité","title":"Utilisateurs anonymes et utilisateurs sans mot de passe","command":"SELECT user, host, plugin, IF(authentication_string = '', 'EMPTY_PASSWORD', 'set') AS password_status FROM mysql.user WHERE user = '' OR authentication_string = '' ORDER BY host;","expect":"Ne doit renvoyer aucune ligne. Les utilisateurs anonymes (user = '') et les comptes sans mot de passe sont une grave faille de sécurité ; supprimez-les avec mysql_secure_installation ou attribuez-leur des mots de passe robustes.","severity":"danger"},{"id":"super-privilege-users","group":"Sécurité","title":"Utilisateurs à privilèges élevés (SUPER / GRANT)","command":"SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE privilege_type IN ('SUPER','GRANT OPTION','SHUTDOWN','FILE','CREATE USER') ORDER BY grantee, privilege_type;","expect":"Les privilèges comme SUPER, FILE et GRANT OPTION ne doivent exister que sur des comptes nécessaires et connus. Les bénéficiaires inattendus présentent un risque d'élévation de privilèges ; soyez particulièrement attentif aux comptes à privilèges élevés définis avec % (n'importe quel hôte).","severity":"caution"},{"id":"replica-status","group":"Réplication","title":"État et retard du réplica (standby)","command":"SHOW REPLICA STATUS;","expect":"À exécuter sur le réplica (MySQL 8.0.22+). Replica_IO_Running et Replica_SQL_Running doivent tous deux valoir 'Yes'. Seconds_Behind_Source doit être faible ; s'il vaut NULL, la réplication est arrêtée. Vérifiez le champ Last_Error. (SHOW SLAVE STATUS sur les versions plus anciennes.)","severity":"danger"},{"id":"replication-threads","group":"Réplication","title":"État de la connexion de réplication et de l'applier (performance_schema)","command":"SELECT c.channel_name, c.service_state AS io_state, a.service_state AS sql_state, c.last_error_message AS io_error, w.last_error_message AS sql_error FROM performance_schema.replication_connection_status c JOIN performance_schema.replication_applier_status a ON c.channel_name = a.channel_name LEFT JOIN performance_schema.replication_applier_status_by_coordinator w ON w.channel_name = a.channel_name;","expect":"io_state et sql_state doivent valoir 'ON'. Si io_error/sql_error est renseigné, la réplication a échoué et s'est arrêtée ; dans les configurations multi-source, chaque channel_name doit être sain individuellement.","severity":"danger"}]},"health/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"checks":[{"id":"version-uptime","group":"Général","title":"Version et uptime","command":"SELECT @@VERSION AS version, sqlserver_start_time AS start_time, DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) AS uptime_hours FROM sys.dm_os_sys_info;","expect":"Vérifiez la version et le niveau de patch. Si l'uptime est très court (moins de quelques heures), un redémarrage inattendu a pu se produire ; examinez le journal d'erreurs SQL Server.","severity":"safe"},{"id":"database-states","group":"Général","title":"Bases en état problématique (SUSPECT / RECOVERY_PENDING)","command":"SELECT name AS database_name, state_desc AS state, recovery_model_desc AS recovery_model FROM sys.databases WHERE state_desc <> N'ONLINE' ORDER BY name;","expect":"Toutes les bases de production doivent être ONLINE. SUSPECT, RECOVERY_PENDING ou EMERGENCY exigent une action immédiate ; lancez la récupération et examinez le journal d'erreurs.","severity":"danger"},{"id":"recent-errors","group":"Général","title":"Erreurs significatives récentes (ring buffer mémoire/erreurs)","command":"SELECT TOP 20 CAST(record AS XML).value('(//Record/@id)[1]', 'int') AS record_id, DATEADD(ms, timestamp - (SELECT ms_ticks FROM sys.dm_os_sys_info), GETDATE()) AS event_time, CAST(record AS XML) AS record_xml FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION' ORDER BY timestamp DESC;","expect":"Examinez les enregistrements récents d'exceptions/erreurs. Des erreurs 823/824/825 (cohérence d'E/S) fréquentes signalent une corruption disque. Pour le détail, lisez aussi le journal d'erreurs sur le serveur avec xp_readerrorlog.","severity":"caution"},{"id":"data-log-file-usage","group":"Capacité","title":"Utilisation des fichiers de données et de journal","command":"SELECT DB_NAME() AS database_name, name AS logical_name, type_desc AS file_type, size/128.0 AS allocated_mb, CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT)/128.0 AS used_mb, (size - CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT))/128.0 AS free_mb, CASE WHEN max_size = -1 THEN N'Unlimited' WHEN max_size = 268435456 THEN N'2 TB (log)' ELSE CAST(max_size/128 AS VARCHAR(20)) + N' MB' END AS max_size_limit FROM sys.database_files;","expect":"À exécuter dans chaque base. Si l'espace utilisé est très proche de l'espace alloué (85 %+) et que l'autogrowth est plafonné, planifiez la croissance ou ajoutez du disque. Si le fichier journal est constamment plein, revoyez les réglages de sauvegarde/modèle de récupération.","severity":"caution"},{"id":"largest-tables","group":"Capacité","title":"Plus grandes tables (taille et nombre de lignes)","command":"SELECT TOP 20 s.name AS schema_name, t.name AS table_name, p.rows AS row_count, SUM(a.total_pages)*8/1024 AS total_mb, SUM(a.used_pages)*8/1024 AS used_mb FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id JOIN sys.indexes i ON t.object_id = i.object_id JOIN sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id JOIN sys.allocation_units a ON p.partition_id = a.container_id WHERE i.index_id <= 1 GROUP BY s.name, t.name, p.rows ORDER BY total_mb DESC;","expect":"Identifiez les tables qui consomment le plus d'espace. Une croissance inattendue peut indiquer un besoin d'archivage, de maintenance des index ou de partitionnement.","severity":"safe"},{"id":"tempdb-usage","group":"Capacité","title":"Utilisation de l'espace tempdb","command":"SELECT SUM(user_object_reserved_page_count)*8/1024 AS user_objects_mb, SUM(internal_object_reserved_page_count)*8/1024 AS internal_objects_mb, SUM(version_store_reserved_page_count)*8/1024 AS version_store_mb, SUM(unallocated_extent_page_count)*8/1024 AS free_mb FROM sys.dm_db_file_space_usage;","expect":"Si tempdb est sur le point de manquer d'espace libre (unallocated faible), des requêtes peuvent échouer. Si le version store ne cesse de croître, il y a des transactions ouvertes de longue durée ; investiguez avec sys.dm_tran_active_snapshot_database_transactions.","severity":"caution"},{"id":"connection-count","group":"Capacité","title":"Nombre et répartition des connexions","command":"SELECT DB_NAME(dbid) AS database_name, COUNT(*) AS connection_count, SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) AS active FROM sys.sysprocesses WHERE dbid > 0 GROUP BY dbid ORDER BY connection_count DESC;","expect":"Suivez le nombre de connexions par application/base. Un nombre anormalement élevé de connexions depuis une même application peut signaler une fuite du pool de connexions. N'approchez pas la limite max worker threads.","severity":"safe"},{"id":"vlf-count","group":"Capacité","title":"Nombre de VLF du fichier journal (fragmentation)","command":"SELECT DB_NAME(database_id) AS database_name, COUNT(*) AS vlf_count FROM sys.dm_db_log_info(DB_ID()) GROUP BY database_id;","expect":"Un nombre élevé de VLF (plus de quelques centaines, par ex. 500+) signifie que le fichier journal a grandi par très petits incréments ; cela ralentit la récupération et les sauvegardes. Réduisez le journal puis faites-le croître en une seule grande étape.","severity":"caution"},{"id":"buffer-cache-ple","group":"Performances","title":"Buffer Cache Hit Ratio et Page Life Expectancy","command":"SELECT (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio') AS hit_ratio_raw, (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio base') AS hit_ratio_base, (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Page life expectancy' AND object_name LIKE '%Buffer Manager%') AS page_life_expectancy_sec;","expect":"Le buffer cache hit ratio devrait idéalement être de 99 %+ (hit_ratio_raw / hit_ratio_base). Si la Page Life Expectancy est constamment faible (règle empirique : moins de 300 s par tranche de 4 GB par nœud NUMA), il y a une pression mémoire ; davantage de RAM ou une optimisation des requêtes est nécessaire.","severity":"caution"},{"id":"expensive-queries","group":"Performances","title":"Requêtes les plus coûteuses (CPU et lectures)","command":"SELECT TOP 15 qs.total_worker_time/1000 AS total_cpu_ms, qs.execution_count AS execution_count, qs.total_logical_reads AS total_logical_reads, qs.total_worker_time/qs.execution_count/1000 AS avg_cpu_ms, SUBSTRING(st.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY qs.total_worker_time DESC;","expect":"Identifiez les requêtes les plus coûteuses en CPU et en lectures logiques. Les requêtes à lectures élevées signalent des index manquants ; examinez leurs plans d'exécution et ajoutez des index ou réécrivez les requêtes.","severity":"safe"},{"id":"blocking-sessions","group":"Performances","title":"Sessions bloquantes","command":"SELECT r.session_id AS blocked_session, r.blocking_session_id AS blocking_session, r.wait_type AS wait_type, r.wait_time/1000 AS wait_sec, r.status AS status, t.text AS query FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.blocking_session_id <> 0 ORDER BY r.wait_time DESC;","expect":"Une session bloquante (blocking_session_id <> 0) signifie que des requêtes se font mutuellement attendre. Un blocage prolongé provoque des timeouts applicatifs ; trouvez le bloqueur de tête et envisagez de terminer la transaction longue.","severity":"danger"},{"id":"wait-stats","group":"Performances","title":"Principales statistiques d'attente (wait stats)","command":"SELECT TOP 15 wait_type AS wait_type, waiting_tasks_count AS waiting_tasks, wait_time_ms/1000 AS total_wait_sec, (wait_time_ms - signal_wait_time_ms)/1000 AS resource_wait_sec, signal_wait_time_ms/1000 AS cpu_wait_sec FROM sys.dm_os_wait_stats WHERE wait_type NOT IN (N'CLR_SEMAPHORE', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'LAZYWRITER_SLEEP', N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'WAITFOR', N'BROKER_TASK_STOP', N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CHKPT', N'REQUEST_FOR_DEADLOCK_SEARCH', N'XE_TIMER_EVENT', N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'BROKER_TO_FLUSH', N'DISPATCHER_QUEUE_SEMAPHORE', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_WORK_QUEUE', N'HADR_TIMER_TASK', N'HADR_CLUSAPI_CALL', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', N'DIRTY_PAGE_POLL', N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SOS_WORK_DISPATCHER', N'LOGMGR_QUEUE', N'ONDEMAND_TASK_QUEUE', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'PWAIT_EXTENSIBILITY_CLEANUP_TASK', N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'STARTUP_DEPENDENCY_MANAGER', N'PREEMPTIVE_XE_DISPATCHER', N'PVS_PREALLOCATE', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_HOST_WAIT', N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE', N'PARALLEL_REDO_TRAN_LIST', N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK') AND wait_type NOT LIKE N'SLEEP[_]%' AND wait_type NOT LIKE N'QDS[_]%' AND waiting_tasks_count > 0 ORDER BY wait_time_ms DESC;","expect":"Interprétez les types d'attente dominants : PAGEIOLATCH = goulot d'E/S disque, CXPACKET/CXCONSUMER = parallélisme, LCK_M = verrouillage, WRITELOG = disque du journal, RESOURCE_SEMAPHORE = mémoire. Un pourcentage élevé de signal wait indique une pression CPU.","severity":"safe"},{"id":"last-backup","group":"Sauvegarde et restauration","title":"Heures des dernières sauvegardes (full / différentielle / log)","command":"SELECT d.name AS database_name, d.recovery_model_desc AS recovery_model, MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS last_full_backup, MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS last_differential, MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS last_log_backup FROM sys.databases d LEFT JOIN msdb.dbo.backupset b ON d.name = b.database_name WHERE d.database_id > 4 GROUP BY d.name, d.recovery_model_desc ORDER BY last_full_backup;","expect":"Chaque base de production doit avoir une sauvegarde complète récente. Si last_full_backup vaut NULL ou est très ancien (au-delà du RPO), le job de sauvegarde échoue. En mode de récupération FULL, sans sauvegardes du journal, le fichier journal ne cesse de croître.","severity":"danger"},{"id":"recovery-model","group":"Sauvegarde et restauration","title":"Cohérence du modèle de récupération","command":"SELECT name AS database_name, recovery_model_desc AS recovery_model, log_reuse_wait_desc AS log_reuse_wait FROM sys.databases WHERE database_id > 4 ORDER BY name;","expect":"Les bases critiques doivent utiliser FULL pour la restauration à un instant donné. log_reuse_wait_desc = LOG_BACKUP signifie qu'une sauvegarde du journal est en attente ; ACTIVE_TRANSACTION signale une transaction de longue durée.","severity":"caution"},{"id":"sysadmin-members","group":"Sécurité","title":"Membres du rôle sysadmin","command":"SELECT p.name AS login_name, p.type_desc AS type, p.is_disabled AS disabled FROM sys.server_role_members rm JOIN sys.server_principals r ON rm.role_principal_id = r.principal_id JOIN sys.server_principals p ON rm.member_principal_id = p.principal_id WHERE r.name = N'sysadmin' ORDER BY p.name;","expect":"L'appartenance à sysadmin doit être réduite au minimum. Les logins inattendus (notamment des comptes applicatifs ou des utilisateurs individuels) présentent un risque d'élévation de privilèges ; appliquez le principe du moindre privilège.","severity":"danger"},{"id":"sa-and-password-policy","group":"Sécurité","title":"Compte sa et logins sans politique de mot de passe","command":"SELECT name AS login_name, is_disabled AS disabled, is_policy_checked AS password_policy_on, is_expiration_checked AS expiration_check, LOGINPROPERTY(name, 'PasswordLastSetTime') AS last_password_change FROM sys.sql_logins WHERE is_policy_checked = 0 OR name = N'sa' ORDER BY name;","expect":"Le compte sa doit être désactivé ou renommé. Les logins SQL avec is_policy_checked = 0 présentent un risque de mot de passe faible/ancien ; activez la politique de mot de passe (CHECK_POLICY).","severity":"caution"},{"id":"alwayson-sync","group":"Réplication","title":"État de synchronisation et files d'attente Always On","command":"SELECT ag.name AS availability_group, ar.replica_server_name AS replica_server, DB_NAME(drs.database_id) AS database_name, drs.synchronization_state_desc AS sync_state, drs.synchronization_health_desc AS health, drs.log_send_queue_size AS log_send_queue_kb, drs.redo_queue_size AS redo_queue_kb FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id JOIN sys.availability_groups ag ON ar.group_id = ag.group_id ORDER BY ag.name, replica_server;","expect":"synchronization_state_desc doit valoir SYNCHRONIZED (synchrone) ou SYNCHRONIZING (asynchrone), et synchronization_health doit valoir HEALTHY. Si log_send_queue ou redo_queue augmente, le réplica prend du retard ; le risque de perte de données/RTO lors d'un failover augmente.","severity":"caution"}]},"health/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"checks":[{"id":"version-uptime","group":"Général","title":"Version et uptime","command":"db.serverStatus().version; db.serverStatus().uptime","expect":"Vérifiez que la version est prise en charge et patchée. uptime est en secondes ; s'il est très faible (quelques milliers de secondes), un redémarrage inattendu s'est produit ; examinez les journaux mongod.","severity":"safe"},{"id":"replset-status","group":"Général","title":"États des membres du replica set (santé)","command":"rs.status().members.forEach(function(m) { print(m.name + \" | \" + m.stateStr + \" | health=\" + m.health); })","expect":"Chaque membre doit avoir health=1 et il doit y avoir exactement un PRIMARY. health=0 ou les états (not reachable/healthy), DOWN et RECOVERING signalent une défaillance. S'il y a plus d'un PRIMARY ou aucun, il y a un problème d'élection.","severity":"danger"},{"id":"dbstats-sizes","group":"Capacité","title":"Taille des bases (données / stockage / index)","command":"db.stats(1024*1024)","expect":"Surveillez dataSize, storageSize et indexSize (MB). Si storageSize est bien plus grand que dataSize, il peut y avoir de la fragmentation. Garder indexSize dans la RAM (cache WiredTiger) est essentiel pour les performances.","severity":"safe"},{"id":"disk-space","group":"Capacité","title":"Utilisation de l'espace disque (fsUsedSize / fsTotalSize)","command":"var st = db.stats(); printjson({fsUsedSize_MB: (st.fsUsedSize/1048576).toFixed(0), fsTotalSize_MB: (st.fsTotalSize/1048576).toFixed(0), usedPct: (100*st.fsUsedSize/st.fsTotalSize).toFixed(1)})","expect":"Si le rapport fsUsedSize / fsTotalSize dépasse 85 %, le disque est sur le point d'être plein ; MongoDB arrête les écritures lorsque le disque est plein. Prévoyez d'ajouter du disque ou d'archiver des données.","severity":"caution"},{"id":"largest-collections","group":"Capacité","title":"Plus grandes collections","command":"db.getCollectionNames().map(function(c) { var s = db.getCollection(c).stats(); return {collection: c, size_MB: (s.size/1048576).toFixed(1), storage_MB: (s.storageSize/1048576).toFixed(1), index_MB: (s.totalIndexSize/1048576).toFixed(1), docs: s.count}; }).sort(function(a,b){return b.size_MB - a.size_MB;})","expect":"Identifiez les collections qui utilisent le plus d'espace. Une croissance inattendue peut indiquer un besoin d'archivage, d'index TTL ou de sharding. Examinez les collections dont la taille des index dépasse celle des données.","severity":"safe"},{"id":"connections","group":"Capacité","title":"Nombre de connexions (current / available)","command":"db.serverStatus().connections","expect":"Si current approche available, la limite de connexions est sur le point d'être atteinte et les nouvelles connexions seront rejetées. Vérifiez les réglages du pool de connexions de l'application et d'éventuelles fuites.","severity":"caution"},{"id":"wiredtiger-cache","group":"Performances","title":"Utilisation du cache WiredTiger et éviction","command":"var wt = db.serverStatus().wiredTiger.cache; printjson({used_MB: (wt['bytes currently in the cache']/1048576).toFixed(0), max_MB: (wt['maximum bytes configured']/1048576).toFixed(0), dirty_MB: (wt['tracked dirty bytes in the cache']/1048576).toFixed(0), pages_evicted: wt['unmodified pages evicted']})","expect":"L'éviction se déclenche lorsque la mémoire utilisée dépasse 80 % du cache maximal ; au-delà de 95 %, les threads applicatifs sont contraints de participer à l'éviction et les performances chutent. Un taux élevé de pages dirty indique une pression en écriture.","severity":"caution"},{"id":"slow-queries-profiler","group":"Performances","title":"Requêtes lentes (profiler) et COLLSCAN","command":"printjson(db.getProfilingStatus()); db.system.profile.find({millis: {$gt: 100}}).sort({ts: -1}).limit(10).forEach(function(d){ print(d.ts + \" | \" + d.millis + \"ms | \" + (d.planSummary || '') + \" | \" + JSON.stringify(d.command)); })","expect":"Ce contrôle est en lecture seule : il affiche d'abord l'état du profiler (was : 0 désactivé, 1 opérations lentes uniquement, 2 toutes), puis liste les opérations lentes enregistrées. Si le profiler est désactivé (was : 0), system.profile reste vide ; pour le diagnostic, activez-le dans une étape distincte avec db.setProfilingLevel(1, {slowms: 100}) et désactivez-le avec db.setProfilingLevel(0) une fois terminé. Les requêtes affichant COLLSCAN dans planSummary effectuent un parcours complet de la collection et manquent d'un index. Optimisez par l'indexation les requêtes ayant des valeurs millis élevées. Le profiler ajoute de la surcharge, utilisez-le donc avec prudence en production.","severity":"caution"},{"id":"long-running-ops","group":"Performances","title":"Opérations de longue durée (currentOp)","command":"db.getSiblingDB('admin').aggregate([{$currentOp: {}}, {$match: {active: true, secs_running: {$gt: 5}}}, {$project: {opid: 1, secs_running: 1, ns: 1, op: 1, planSummary: 1, waitingForLock: 1}}])","expect":"Les opérations actives avec des valeurs secs_running élevées peuvent provoquer des blocages. Terminez-les avec db.killOp(opid) si nécessaire. Les opérations avec waitingForLock=true attendent un verrou ; investiguez l'opération à l'origine.","severity":"danger"},{"id":"oplog-window","group":"Sauvegarde et restauration","title":"Fenêtre de l'oplog (historique de réplication)","command":"rs.printReplicationInfo()","expect":"La fenêtre de l'oplog (taille configurée de l'oplog et durée du journal en heures) doit être suffisamment longue. Si la fenêtre est courte (quelques heures), un secondaire lent peut basculer en resynchronisation complète (initial sync). Les stratégies de sauvegarde et de restauration à un instant donné dépendent de l'oplog.","severity":"caution"},{"id":"backup-note","group":"Sauvegarde et restauration","title":"État des sauvegardes et note sur la dernière sauvegarde","command":"var r = db.getSiblingDB('local').getCollectionNames().includes('oplog.rs') ? db.getReplicationInfo() : null; printjson(r ? {oplogWindowHours: r.timeDiffHours, tFirst: r.tFirst, tLast: r.tLast} : 'standalone: no oplog'); print('Backup verification: check mongodump/mongorestore or Ops Manager / Percona Backup (PBM) status externally.')","expect":"MongoDB n'a pas de sauvegarde planifiée intégrée ; on utilise mongodump, des snapshots du système de fichiers, Ops Manager ou PBM. Vérifiez dans l'outil externe l'heure de la dernière sauvegarde réussie et le test de restauration. Sur un replica set, la fenêtre de l'oplog (oplogWindowHours) limite jusqu'où le PITR basé sur l'oplog peut remonter ; elle ne doit pas être plus courte que l'intervalle de sauvegarde. Vérifiez que les objectifs de RPO/RTO sont atteints.","severity":"danger"},{"id":"users-roles","group":"Sécurité","title":"Liste des utilisateurs et rôles","command":"db.getSiblingDB('admin').system.users.find({}, {user: 1, db: 1, roles: 1, _id: 0}).forEach(printjson)","expect":"Examinez les utilisateurs définis et leurs rôles. Les comptes inattendus ou les rôles trop larges (root, __system, dbOwner) présentent un risque d'élévation de privilèges. Appliquez le principe du moindre privilège.","severity":"caution"},{"id":"auth-enabled","group":"Sécurité","title":"L'authentification (authorization) est-elle activée","command":"db.getSiblingDB('admin').runCommand({getCmdLineOpts: 1}).parsed.security","expect":"security.authorization doit valoir 'enabled'. S'il est désactivé (ou s'il n'y a pas de bloc security), la base est accessible sans authentification ; c'est une grave faille de sécurité, activez-la immédiatement.","severity":"danger"},{"id":"secondary-lag","group":"Réplication","title":"Retard de réplication des secondaires","command":"rs.printSecondaryReplicationInfo()","expect":"La valeur 'replication lag' de chaque secondaire doit être faible (quelques secondes). Si le retard est important (minutes), le secondaire ne suit pas la charge ou il y a un problème réseau/disque ; le risque de perte de données et d'incohérence en lecture lors d'un failover augmente.","severity":"caution"},{"id":"member-health","group":"Réplication","title":"Santé des membres et état de l'élection","command":"rs.status().members.map(function(m){ return {member: m.name, state: m.stateStr, health: m.health, configVersion: m.configVersion, last_heartbeat: m.lastHeartbeatMessage || 'OK'}; })","expect":"Tous les membres doivent être sains (health=1) et dans l'état attendu (PRIMARY/SECONDARY). Un message d'erreur dans lastHeartbeatMessage signale une partition réseau ou un membre injoignable. La perte de la majorité arrête les écritures.","severity":"caution"}]},"health/redis.json":{"engineId":"redis","engine":"Redis","order":6,"checks":[{"id":"server-version-uptime","group":"Général","title":"Version et uptime","command":"redis-cli INFO server | grep -E \"redis_version|uptime_in_days|redis_mode|os\"","expect":"redis_version doit être une version prise en charge et patchée. Si uptime_in_days est très faible, un redémarrage inattendu s'est produit ; redis_mode (standalone/sentinel/cluster) confirme la configuration.","severity":"safe"},{"id":"persistence-status","group":"Général","title":"État de la persistance (RDB / AOF)","command":"redis-cli INFO persistence | grep -E \"rdb_last_bgsave_status|aof_enabled|aof_last_bgrewrite_status|aof_last_write_status|rdb_bgsave_in_progress|loading\"","expect":"rdb_last_bgsave_status et aof_last_write_status doivent valoir 'ok'. Une valeur 'err' signale un problème de disque/permissions ou un disque plein et comporte un risque de perte de données. loading:1 signifie que le serveur charge encore les données.","severity":"danger"},{"id":"memory-usage","group":"Capacité","title":"Utilisation de la mémoire (used_memory / maxmemory)","command":"redis-cli INFO memory | grep -E \"used_memory_human|used_memory:|maxmemory_human|maxmemory:|maxmemory_policy\"","expect":"Si used_memory approche maxmemory (85 %+), il existe un risque d'éviction ou d'OOM. Si maxmemory=0, il n'y a pas de limite et Redis peut consommer toute la RAM et faire tomber le système ; définissez toujours une limite et une maxmemory_policy appropriée.","severity":"caution"},{"id":"memory-fragmentation","group":"Capacité","title":"Taux de fragmentation mémoire","command":"redis-cli INFO memory | grep -E \"mem_fragmentation_ratio|mem_allocator\"","expect":"mem_fragmentation_ratio se situe idéalement entre 1.0 et 1.5. Au-delà de 1.5, la fragmentation est élevée (mémoire gaspillée). En dessous de 1.0, Redis est en swap sur disque (grave problème de performances) ; vérifiez la RAM du système.","severity":"caution"},{"id":"evicted-keys","group":"Capacité","title":"Clés évincées (evicted_keys)","command":"redis-cli INFO stats | grep -E \"evicted_keys|expired_keys|keyspace_misses\"","expect":"Si evicted_keys ne cesse d'augmenter, Redis a atteint sa limite mémoire et écarte des données ; cela peut entraîner une perte de données non souhaitée. Envisagez d'ajouter de la mémoire, d'utiliser des TTL ou de revoir maxmemory_policy.","severity":"caution"},{"id":"dbsize-keyspace","group":"Capacité","title":"Nombre de clés et répartition du keyspace","command":"redis-cli DBSIZE; redis-cli INFO keyspace","expect":"Surveillez le nombre total de clés (DBSIZE) et la répartition par base (INFO keyspace : keys, expires, avg_ttl). Un keyspace qui croît sans TTL (expires faible) provoque un gonflement de la mémoire. Des sauts brusques du nombre de clés peuvent signaler un comportement anormal.","severity":"safe"},{"id":"hit-ratio","group":"Performances","title":"Taux de succès du cache (keyspace hits/misses)","command":"redis-cli INFO stats | grep -E \"keyspace_hits|keyspace_misses\"","expect":"Taux de succès = hits / (hits + misses). Pour un usage en cache, le taux doit être élevé (80 %+). Un taux faible indique un cache inefficace, des TTL inadaptés ou un schéma de cache-miss ; assurez-vous que les données fréquemment demandées sont mises en cache.","severity":"safe"},{"id":"slowlog","group":"Performances","title":"Journal des commandes lentes (SLOWLOG)","command":"redis-cli SLOWLOG GET 10","expect":"Examinez les commandes lentes (seuil par défaut 10ms). Les commandes O(N) (KEYS, SMEMBERS, HGETALL volumineux, SORT) bloquent Redis, mono-thread, et retardent chaque requête. Remplacez-les par des alternatives comme SCAN.","severity":"caution"},{"id":"clients","group":"Performances","title":"Clients connectés et bloqués (clients)","command":"redis-cli INFO clients | grep -E \"connected_clients|blocked_clients|maxclients|client_recent_max_input_buffer\"","expect":"Si connected_clients approche maxclients, les nouvelles connexions sont rejetées. Un nombre élevé de blocked_clients (en attente sur BLPOP/BRPOP) peut être voulu, mais une hausse inattendue signale une congestion. Vérifiez l'absence de fuites de connexions.","severity":"safe"},{"id":"ops-per-sec","group":"Performances","title":"Débit instantané (ops/sec)","command":"redis-cli INFO stats | grep -E \"instantaneous_ops_per_sec|total_commands_processed|rejected_connections|total_net_input_bytes\"","expect":"instantaneous_ops_per_sec indique la charge de travail actuelle. Des chutes/pics brusques signalent des anomalies. Si rejected_connections > 0, la limite maxclients a été atteinte. Redis étant mono-thread, un nombre d'ops très élevé peut créer un goulot CPU.","severity":"safe"},{"id":"rdb-last-save","group":"Sauvegarde et restauration","title":"Dernière sauvegarde RDB et modifications en attente","command":"redis-cli INFO persistence | grep -E \"rdb_last_save_time|rdb_changes_since_last_save|rdb_last_bgsave_status\"; redis-cli LASTSAVE","expect":"Si rdb_changes_since_last_save est très élevé, beaucoup de données se sont accumulées depuis le dernier snapshot ; ces modifications sont perdues en cas de crash. Si rdb_last_save_time (LASTSAVE, heure Unix) est très ancien, revoyez l'intervalle de sauvegarde ou la fréquence des sauvegardes.","severity":"caution"},{"id":"aof-rewrite","group":"Sauvegarde et restauration","title":"État de la réécriture AOF","command":"redis-cli INFO persistence | grep -E \"aof_enabled|aof_last_bgrewrite_status|aof_rewrite_in_progress|aof_current_size|aof_base_size\"","expect":"Dans les scénarios qui exigent la durabilité, aof_enabled=1 doit être défini. aof_last_bgrewrite_status doit valoir 'ok' ; 'err' signale un problème de disque/persistance. Si aof_current_size est bien plus grand que base_size, une réécriture est nécessaire.","severity":"caution"},{"id":"acl-auth","group":"Sécurité","title":"Réglage ACL / authentification (requirepass)","command":"redis-cli ACL LIST; redis-cli ACL GETUSER default","expect":"Il doit exister au moins un utilisateur protégé par mot de passe. Si les flags de la sortie ACL GETUSER default affichent 'on' et 'nopass' (requirepass est aussi le mot de passe de cet utilisateur), Redis est accessible sans authentification ; c'est une grave faille de sécurité. Les mots de passe ne sont affichés que sous forme de hachages ; n'utilisez pas CONFIG GET requirepass pour ce contrôle, car il affiche le mot de passe en clair. Définissez des utilisateurs à moindre privilège avec les ACL.","severity":"danger"},{"id":"dangerous-commands","group":"Sécurité","title":"Commandes dangereuses et protected-mode","command":"redis-cli CONFIG GET protected-mode; redis-cli CONFIG GET bind; redis-cli COMMAND INFO flushall flushdb config keys","expect":"protected-mode doit valoir 'yes' et bind doit être limité à des interfaces sûres. Les commandes dangereuses comme FLUSHALL, FLUSHDB, CONFIG, KEYS et DEBUG doivent être désactivées en production avec rename-command ou restreintes avec les ACL.","severity":"caution"},{"id":"replication-status","group":"Réplication","title":"État de la réplication (role, master link)","command":"redis-cli INFO replication","expect":"Vérifiez role (master/slave), connected_slaves et l'état de chaque réplica. Sur un réplica, master_link_status doit valoir 'up' ; 'down' signifie que le lien est rompu. Si master_last_io_seconds_ago est élevé (retard), le risque de perte de données et d'incohérence augmente.","severity":"danger"}]},"health/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"checks":[{"id":"cluster-health","group":"Général","title":"Santé du cluster (green / yellow / red)","command":"GET _cluster/health","expect":"status doit valoir 'green' : tous les shards primaires et réplicas sont alloués. 'yellow' signifie que les primaires sont intacts mais que certains réplicas ne sont pas affectés (risque de redondance) ; 'red' signifie qu'au moins un shard primaire est perdu/non affecté (une partie des données est indisponible, action immédiate requise). unassigned_shards doit valoir 0.","severity":"danger"},{"id":"cluster-version","group":"Général","title":"Version et informations sur le cluster","command":"GET /","expect":"version.number doit être une version prise en charge avec les correctifs de sécurité appliqués. Les versions anciennes/EOL (fin de vie) présentent des risques de sécurité et de stabilité. Vérifiez aussi que tous les nœuds exécutent la même version avec GET _cat/nodes?v&h=name,version (un mélange temporaire pendant un rolling upgrade est normal).","severity":"safe"},{"id":"pending-tasks","group":"Général","title":"Tâches de cluster en attente","command":"GET _cluster/pending_tasks","expect":"La liste doit être vide ou très courte. Des tâches qui s'accumulent et attendent longtemps (time_in_queue élevé) indiquent que le master node est surchargé ou que les mises à jour du cluster state sont bloquées ; cela dégrade le temps de réponse de tout le cluster.","severity":"caution"},{"id":"disk-allocation-watermark","group":"Capacité","title":"Allocation disque et état des watermarks","command":"GET _cat/allocation?v","expect":"disk.percent de chaque nœud doit rester sous le high watermark (90 % par défaut). Au-delà de 85 %, c'est un avertissement, 90 % (high) déclenche la relocalisation des shards, et 95 % (flood_stage) passe les indices en lecture seule. Vérifiez que la colonne shards est équilibrée entre les nœuds ; une répartition inégale fait qu'un nœud se remplit prématurément.","severity":"danger"},{"id":"largest-indices","group":"Capacité","title":"Plus grands indices (store size)","command":"GET _cat/indices?v&s=store.size:desc","expect":"Suivez les indices qui consomment le plus d'espace et leurs valeurs doc.count. La croissance incontrôlée des indices de logs/métriques est la principale cause de disques pleins ; définissez pour eux des politiques de rollover et de suppression avec ILM (Index Lifecycle Management). Soyez particulièrement attentif aux indices à l'état 'close' ou en santé yellow/red.","severity":"caution"},{"id":"jvm-heap-usage","group":"Capacité","title":"Utilisation du heap JVM et de la RAM (par nœud)","command":"GET _cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m","expect":"heap.percent doit rester durablement sous 75 % ; des valeurs soutenues de 85 %+ signifient une pression mémoire, des pauses GC fréquentes et un risque de circuit breaker. Le heap JVM doit représenter ~50 % de la RAM du serveur et au plus ~31GB (limite des compressed oops). Pour un heap élevé qui ne redescend pas, optimisez les requêtes ou ajoutez des nœuds.","severity":"caution"},{"id":"shard-count-limits","group":"Capacité","title":"Nombre de shards et limite de shards par nœud","command":"GET _cluster/health?level=shards","expect":"active_shards et le nombre de shards par nœud doivent rester raisonnables. La limite supérieure par défaut est cluster.max_shards_per_node = 1000 (par data node) ; approcher cette limite et avoir des milliers de petits shards au total (oversharding) crée une pression sur le master et gaspille de la mémoire. L'ancienne règle \"~20 shards par GB de heap\" ne s'applique pas à 8.3+ ; la recommandation actuelle est de 10-50GB par shard et une planification du heap basée sur le nombre de champs (mappings) par nœud. initializing_shards / relocating_shards ne doivent pas rester élevés longtemps. S'il y a trop de shards, utilisez des shards plus grands/moins nombreux ou le rollover.","severity":"caution"},{"id":"http-connections","group":"Capacité","title":"Nombre de connexions HTTP","command":"GET _nodes/stats/http","expect":"http.current_open doit rester dans des limites raisonnables. Si http.total_opened augmente très vite, les clients n'utilisent pas de pool de connexions et ouvrent une nouvelle connexion par requête (inefficace). Un current_open anormalement élevé signale une fuite de connexions ou un possible épuisement des ressources.","severity":"safe"},{"id":"thread-pool-rejections","group":"Performances","title":"Rejets des thread pools et saturation des files","command":"GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected","expect":"La colonne rejected doit valoir 0. Une valeur rejected croissante dans les pools write/bulk et search montre que le cluster est surchargé et renvoie des HTTP 429 (es_rejected_execution_exception). Une file constamment pleine signale une congestion. Appliquez un backoff côté client, réduisez la taille des lots ou ajoutez de la capacité.","severity":"danger"},{"id":"query-cache-hit","group":"Performances","title":"Taux de succès du query cache","command":"GET _nodes/stats/indices/query_cache","expect":"Le rapport hit_count / (hit_count + miss_count) doit être élevé pour les requêtes de filtre répétées. Un taux de succès faible avec beaucoup d'évictions indique que la taille du cache est insuffisante ou que les requêtes sont trop variables pour être mises en cache. Utiliser des filtres stables (filter context) augmente le taux de succès.","severity":"safe"},{"id":"slowlog-config","group":"Performances","title":"Réglages du journal des requêtes lentes (slowlog)","command":"GET /_all/_settings?filter_path=**.search.slowlog,**.indexing.slowlog","expect":"Des seuils de slowlog de recherche et d'indexation doivent être définis sur les indices importants (par ex. index.search.slowlog.threshold.query.warn: 10s). Avec le slowlog désactivé, le diagnostic des requêtes lentes devient plus difficile. Activez des seuils et surveillez les journaux pour identifier les requêtes coûteuses (agrégations larges, wildcards, pagination profonde).","severity":"safe"},{"id":"hot-threads","group":"Performances","title":"Hot threads","command":"GET _nodes/hot_threads","expect":"Affiche les threads les plus consommateurs de CPU. Utilisé en cas de CPU élevé/lenteur pour déterminer quelle opération (agrégation lourde, merge, GC, script) est le goulot d'étranglement. Si la même requête/le même merge coûteux revient sans cesse, optimisez cette requête ou la stratégie d'indexation.","severity":"safe"},{"id":"fielddata-memory","group":"Performances","title":"Utilisation mémoire de fielddata","command":"GET _cat/fielddata?v","expect":"L'utilisation mémoire de fielddata doit rester faible. Agréger/trier sur des champs text charge fielddata en mémoire et augmente le risque d'erreurs circuit breaker (Data too large) et d'OOM. Pour les champs à fielddata élevé, passez à un sous-champ keyword (doc_values).","severity":"caution"},{"id":"snapshot-status","group":"Sauvegarde et restauration","title":"Snapshot repositories et état du dernier snapshot","command":"GET _snapshot/_all","expect":"Au moins un dépôt de snapshots doit être défini. Après avoir confirmé l'existence des repositories, vérifiez l'état des derniers snapshots (avec le contrôle verify ci-dessous). S'il n'y a pas de repository, le cluster n'a aucune sauvegarde et des données peuvent être définitivement perdues en état RED. Mettez en place SLM (Snapshot Lifecycle Management) pour des snapshots automatiques réguliers.","severity":"danger"},{"id":"snapshot-latest-state","group":"Sauvegarde et restauration","title":"État de réussite des derniers snapshots","command":"GET _snapshot/<repo>/_all?verbose=false","expect":"L'état des derniers snapshots doit être 'SUCCESS'. 'PARTIAL' signifie que certains shards n'ont pas pu être sauvegardés, 'FAILED' que la sauvegarde a échoué ; dans les deux cas, cela signifie une perte de données à la restauration. La date du snapshot le plus récent (end_time) ne doit pas être trop ancienne (effectuez des sauvegardes à une fréquence qui respecte votre objectif de RPO).","severity":"danger"},{"id":"snapshot-repo-verify","group":"Sauvegarde et restauration","title":"Vérification du dépôt de snapshots","command":"POST _snapshot/<repo>/_verify","expect":"La vérification confirme que tous les nœuds peuvent accéder au repository (système de fichiers partagé, S3, HDFS, etc.) et liste dans la réponse les nœuds qui y ont accès. Si elle renvoie une erreur, certains nœuds ne peuvent pas écrire dans le repository de sauvegarde ; dans ce cas, les snapshots seront incomplets/en échec. Corrigez l'accès au repository et les identifiants.","severity":"caution"},{"id":"security-enabled","group":"Sécurité","title":"La sécurité (X-Pack security) est-elle activée","command":"GET _xpack/usage","expect":"security.enabled et security.available doivent valoir true. Avec la sécurité désactivée, le cluster est accessible sans authentification ; un tel cluster exposé à Internet ou à un réseau non fiable constitue une grave vulnérabilité (fuite/suppression de données). L'authentification, l'accès basé sur les rôles et TLS doivent être activés.","severity":"danger"},{"id":"users-roles","group":"Sécurité","title":"Utilisateurs et rôles","command":"GET _security/user","expect":"Examinez les utilisateurs définis et les affectations de rôles. Assurez-vous que les comptes intégrés (elastic, kibana_system, etc.) sont protégés par des mots de passe robustes. Selon le principe du moindre privilège, définissez pour chaque application des utilisateurs distincts aux rôles limités ; évitez l'usage inutilement répandu du rôle superuser. Utilisez GET _security/role pour les rôles.","severity":"caution"},{"id":"tls-transport-http","group":"Sécurité","title":"État TLS / HTTPS (transport et HTTP)","command":"GET _nodes/settings?filter_path=**.xpack.security.transport.ssl.enabled,**.xpack.security.http.ssl.enabled","expect":"xpack.security.transport.ssl.enabled doit valoir true (chiffrement entre nœuds, obligatoire). Idéalement, http.ssl.enabled doit aussi valoir true (HTTPS client vers cluster). Avec TLS désactivé, les identifiants et les données circulent en clair sur le réseau. Vérifiez aussi l'expiration des certificats et le mode de vérification (verification_mode: certificate/full).","severity":"danger"},{"id":"unassigned-shards","group":"Réplication","title":"Shards non affectés et leurs raisons","command":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason","expect":"Tous les shards doivent avoir l'état 'STARTED'. Les shards à l'état UNASSIGNED et leur unassigned.reason (NODE_LEFT, ALLOCATION_FAILED, DISK_THRESHOLD, etc.) doivent être investigués. Un primary non affecté signifie une santé RED, un réplica non affecté une santé YELLOW. Utilisez GET _cluster/allocation/explain pour la cause racine.","severity":"danger"},{"id":"recovery-active","group":"Réplication","title":"Récupérations de shards en cours","command":"GET _cat/recovery?v&active_only=true","expect":"Normalement, il ne doit y avoir aucune récupération active (liste vide). Une récupération temporaire après l'ajout/le retrait d'un nœud ou un failover est normale, mais des récupérations bloquées longtemps sans progression (%progress qui n'augmente pas) signalent un goulot réseau/disque. Si le rééquilibrage (relocation) affecte la charge, revoyez les réglages de limitation de la récupération (indices.recovery.max_bytes_per_sec).","severity":"caution"},{"id":"replica-count","group":"Réplication","title":"Nombre de réplicas par index","command":"GET _cat/indices?v&h=index,health,pri,rep,docs.count","expect":"Sur les indices de production, rep (number_of_replicas) doit valoir au moins 1 ; sur un index à 0 réplica, la perte d'un nœud signifie une perte de données définitive. Toutefois, dans un cluster multi-nœuds, le nombre de réplicas ne doit pas dépasser (nombre de data nodes - 1) ; sinon les réplicas supplémentaires ne peuvent pas être alloués et la santé reste YELLOW. Investiguez les indices dont la colonne health n'est pas green.","severity":"danger"}]},"recipes/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"recipes":[{"id":"redo-switch","task":"Basculement de redo log (changer de fichier journal courant)","category":"Redo et archives","command":"ALTER SYSTEM SWITCH LOGFILE;","what":"Ferme le groupe de redo log courant et passe au suivant ; en mode archivelog, déclenche l'archivage.","when":"Pour produire une archive manuellement, forcer un log switch, ou à des fins de test.","caution":"Des basculements fréquents dégradent les performances ; si les fichiers journaux sont trop petits, revoyez leur dimensionnement.","tags":["redo","switch","logfile","archive","archive","alter system"]},{"id":"redo-add-group","task":"Ajouter un nouveau groupe de redo log","category":"Redo et archives","command":"ALTER DATABASE ADD LOGFILE GROUP 4\n  ('/u01/oradata/ORCL/redo04a.log',\n   '/u02/oradata/ORCL/redo04b.log') SIZE 512M;","what":"Crée un nouveau groupe de redo log avec la taille et les membres indiqués (plusieurs membres = miroir).","when":"Lorsque le débit de redo est insuffisant ou que vous voulez augmenter le nombre de groupes.","caution":"Assurez-vous que le numéro de groupe n'entre pas en conflit avec les groupes existants ; vérifiez avec V$LOG.","tags":["redo","logfile","group","add","add","alter database"]},{"id":"redo-resize","task":"Redimensionner un groupe de redo log (drop + add)","category":"Redo et archives","command":"-- 1) The group to be resized must be INACTIVE; if not, switch:\nALTER SYSTEM SWITCH LOGFILE;\nALTER SYSTEM CHECKPOINT;\n-- 2) Drop the old group:\nALTER DATABASE DROP LOGFILE GROUP 1;\n-- 3) Add it again with the new size:\nALTER DATABASE ADD LOGFILE GROUP 1\n  ('/u01/oradata/ORCL/redo01a.log',\n   '/u02/oradata/ORCL/redo01b.log') SIZE 1G REUSE;","what":"Un redo log ne peut pas être redimensionné directement ; on supprime le groupe puis on le rajoute avec la nouvelle taille.","when":"Lorsque les fichiers redo sont trop petits/grands, pour ajuster la fréquence des checkpoints.","caution":"Seul un groupe INACTIVE ou UNUSED peut être supprimé ; ne supprimez pas le groupe CURRENT/ACTIVE. Au moins deux groupes doivent toujours subsister. REUSE réutilise le fichier existant.","tags":["redo","resize","size","drop","add","logfile","reuse"]},{"id":"enable-archivelog","task":"Passer la base en mode ARCHIVELOG","category":"Redo et archives","command":"SHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;\n-- Verify:\nARCHIVE LOG LIST;","what":"Passe la base en mode archive log ; les redo logs remplis sont archivés, ce qui rend possibles les sauvegardes à chaud et la restauration à un instant donné.","when":"Pour la stratégie de sauvegarde et de restauration d'une base de production (obligatoire avant RMAN, Data Guard).","caution":"Nécessite un redémarrage de l'instance ; planifiez l'interruption. Assurez-vous que la destination d'archivage (FRA ou LOG_ARCHIVE_DEST) dispose d'assez d'espace ; si elle se remplit, la DB s'arrête. Sur RAC : arrêtez toutes les instances avec srvctl stop database -d DB, ouvrez une seule instance avec STARTUP MOUNT, passez en ARCHIVELOG, arrêtez-la puis démarrez avec srvctl start database -d DB (11gR2+ ne nécessite pas cluster_database=false).","tags":["archivelog","archive","mode","mount","recovery","recovery"]},{"id":"fra-full-fix","task":"Résoudre une FRA / destination d'archivage pleine","category":"Redo et archives","command":"-- Check usage:\nSELECT name, space_limit/1024/1024/1024 AS limit_gb,\n       space_used/1024/1024/1024  AS used_gb,\n       ROUND(space_used/space_limit*100,1) AS pct_used\nFROM   v$recovery_file_dest;\n\n-- Fix A) Increase the FRA size:\nALTER SYSTEM SET db_recovery_file_dest_size=100G SCOPE=BOTH;\n\n-- Fix B) Clean up already backed-up archives with RMAN:\n-- RMAN> DELETE ARCHIVELOG ALL BACKED UP 1 TIMES TO DISK COMPLETED BEFORE 'SYSDATE-3';\n-- If there is no backup (CAREFUL): RMAN> DELETE ARCHIVELOG ALL COMPLETED BEFORE 'SYSDATE-1';","what":"Lorsque la Fast Recovery Area ou la destination d'archivage se remplit, la base se fige (ORA-00257). On résout le problème en agrandissant l'espace ou en nettoyant les archives.","when":"Erreur archiver ORA-00257, lorsque la DB n'accepte plus de nouvelles transactions.","caution":"Supprimer des archives non sauvegardées détruit la capacité de restauration. Faites d'abord une sauvegarde RMAN, puis supprimez. Ne supprimez pas avec rm depuis l'OS ; cela impose un CROSSCHECK RMAN. Tant que la FRA est pleine, sauvegardez les archives vers une destination hors FRA (FORMAT '/backup/arc_%U'). Avec Data Guard, ne supprimez pas les archives pas encore transférées/appliquées sur le standby ; sinon un gap se forme sur le standby. Utilisez CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY.","tags":["fra","recovery area","ora-00257","archiver","archive","full","cleanup"]},{"id":"redo-status","task":"Consulter l'état des redo logs","category":"Redo et archives","command":"SELECT l.group#, l.thread#, l.sequence#,\n       ROUND(l.bytes/1024/1024) AS mb,\n       l.members, l.archived, l.status,\n       lf.member AS file_path\nFROM   v$log l\n  JOIN v$logfile lf ON lf.group# = l.group#\nORDER BY l.group#, lf.member;","what":"Affiche l'état des groupes de redo log (CURRENT/ACTIVE/INACTIVE), leur taille, les fichiers membres et l'état d'archivage.","when":"Lors de l'examen de la configuration redo, ou pour voir quel groupe est actif avant un redimensionnement.","caution":"Requête uniquement ; aucun risque.","tags":["redo","status","status","v$log","v$logfile","logfile"]},{"id":"asm-add-disk","task":"Ajouter des disques à un diskgroup ASM","category":"ASM","command":"-- Connect to the ASM instance (sqlplus / as sysasm)\nALTER DISKGROUP DATA ADD DISK\n  '/dev/oracleasm/disks/DATA05' NAME DATA_0005,\n  '/dev/oracleasm/disks/DATA06' NAME DATA_0006\n  REBALANCE POWER 4;","what":"Ajoute de nouveaux disques au diskgroup DATA et lance un rebalance automatique ; la capacité augmente.","when":"Lorsqu'un diskgroup ASM commence à se remplir, ou lors d'une extension du stockage.","caution":"Nécessite le privilège SYSASM. Les tailles de disque doivent être similaires. Le rebalance génère une charge d'E/S ; utilisez un POWER faible aux heures chargées.","tags":["asm","diskgroup","add disk","add disk","rebalance","alter diskgroup"]},{"id":"asm-drop-disk","task":"Retirer un disque d'un diskgroup ASM","category":"ASM","command":"ALTER DISKGROUP DATA DROP DISK DATA_0005 REBALANCE POWER 4;\n\n-- WAIT until the drop completes (the disk can be physically removed once V$ASM_OPERATION is empty):\nSELECT group_number, operation, state, power,\n       sofar, est_work, est_minutes\nFROM   v$asm_operation;","what":"Retire le disque du diskgroup ; ses extents sont déplacés vers les disques restants (rebalance). Une fois terminé, le HDR_STATUS du disque passe à FORMER.","when":"Lors du remplacement d'un disque défaillant, d'une réduction de capacité ou d'une migration du stockage.","caution":"Ne retirez PAS physiquement le disque AVANT la fin du rebalance, sinon des données sont perdues. Assurez-vous que les disques restants ont assez d'espace libre.","tags":["asm","drop disk","remove disk","rebalance","v$asm_operation","alter diskgroup"]},{"id":"asm-rebalance-power","task":"Régler le POWER du rebalance ASM","category":"ASM","command":"-- Change the power of a running rebalance (1-1024, 0 stops it):\nALTER DISKGROUP DATA REBALANCE POWER 8;\n\n-- Stop the rebalance completely:\nALTER DISKGROUP DATA REBALANCE POWER 0;","what":"Règle le parallélisme/la vitesse d'un rebalance en cours. Un POWER plus élevé signifie plus rapide mais plus d'E/S.","when":"Pour accélérer un rebalance trop lent, ou ralentir/arrêter un rebalance qui affecte la production.","caution":"POWER 0 suspend le rebalance (un drop de disque ne se terminera pas). Une valeur élevée affecte les E/S de production.","tags":["asm","rebalance","power","power","diskgroup","tuning"]},{"id":"asm-diskgroup-space","task":"Consulter l'espace libre d'un diskgroup ASM","category":"ASM","command":"SELECT name,\n       state,\n       type,\n       total_mb/1024                 AS total_gb,\n       free_mb/1024                  AS free_gb,\n       ROUND((1-free_mb/total_mb)*100,1) AS pct_used,\n       usable_file_mb/1024           AS usable_gb\nFROM   v$asm_diskgroup\nORDER BY name;","what":"Affiche l'espace total, libre et utilisable (après redondance) de chaque diskgroup.","when":"Pour la planification de capacité, lorsque l'espace se raréfie, avant de décider d'ajouter des disques.","caution":"usable_file_mb tient compte de la redondance ; avec une redondance NORMAL/HIGH, cette colonne correspond à l'espace réellement utilisable. Requête uniquement ; aucun risque.","tags":["asm","diskgroup","space","space","free","v$asm_diskgroup","capacity"]},{"id":"asm-monitor-rebalance","task":"Surveiller un rebalance ASM","category":"ASM","command":"SELECT group_number, operation, pass, state, power,\n       sofar, est_work,\n       ROUND(sofar/NULLIF(est_work,0)*100,1) AS pct_done,\n       est_minutes\nFROM   v$asm_operation\nORDER BY group_number;","what":"Affiche la progression, le power et le temps restant estimé des opérations de rebalance/resync en cours.","when":"Pour vérifier si le rebalance est terminé après l'ajout/le retrait de disques.","caution":"Résultat vide = aucune opération active (terminée). Requête uniquement ; aucun risque.","tags":["asm","rebalance","monitor","monitor","v$asm_operation","progress"]},{"id":"add-tablespace","task":"Ajouter un nouveau tablespace","category":"Stockage","command":"-- File system:\nCREATE TABLESPACE customer_data\n  DATAFILE '/u01/oradata/ORCL/customer_data01.dbf'\n  SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 20G\n  EXTENT MANAGEMENT LOCAL\n  SEGMENT SPACE MANAGEMENT AUTO;\n\n-- If you use ASM:\nCREATE TABLESPACE customer_data\n  DATAFILE '+DATA' SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 20G;","what":"Crée un nouveau tablespace permanent dans lequel placer des segments de tables/index.","when":"Lors de l'allocation d'un stockage distinct pour une nouvelle application/un nouveau schéma.","caution":"Indiquez MAXSIZE, sinon le disque peut se remplir. Dans ASM, le nom de fichier est généré automatiquement.","tags":["tablespace","create","create","datafile","storage","storage","autoextend"]},{"id":"add-datafile","task":"Ajouter un datafile à un tablespace existant","category":"Stockage","command":"ALTER TABLESPACE customer_data\n  ADD DATAFILE '/u01/oradata/ORCL/customer_data02.dbf'\n  SIZE 2G AUTOEXTEND ON NEXT 256M MAXSIZE 30G;\n\n-- ASM:\nALTER TABLESPACE customer_data\n  ADD DATAFILE '+DATA' SIZE 2G AUTOEXTEND ON NEXT 256M MAXSIZE 30G;","what":"Augmente la capacité d'un tablespace existant en ajoutant un nouveau datafile.","when":"Lorsque le tablespace est plein et qu'un fichier unique approche la limite smallfile (32G/64G).","caution":"Un tablespace smallfile a une limite de blocs par fichier ; ajoutez un nouveau fichier si nécessaire. Indiquez MAXSIZE.","tags":["datafile","add","add","tablespace","alter tablespace","storage"]},{"id":"resize-datafile","task":"Redimensionner un datafile / régler l'autoextend","category":"Stockage","command":"-- Grow/shrink to a fixed size:\nALTER DATABASE DATAFILE '/u01/oradata/ORCL/customer_data01.dbf' RESIZE 5G;\n\n-- Enable autoextend:\nALTER DATABASE DATAFILE '/u01/oradata/ORCL/customer_data01.dbf'\n  AUTOEXTEND ON NEXT 256M MAXSIZE 30G;\n\n-- Disable autoextend:\nALTER DATABASE DATAFILE '/u01/oradata/ORCL/customer_data01.dbf' AUTOEXTEND OFF;","what":"Fixe la taille d'un datafile ou configure son comportement de croissance automatique.","when":"Pour récupérer de l'espace (réduction), préallouer de l'espace ou stopper une croissance incontrôlée.","caution":"La réduction avec RESIZE ne peut pas descendre sous les blocs utilisés en fin de fichier (ORA-03297). Déplacez d'abord les segments.","tags":["datafile","resize","size","autoextend","maxsize","alter database"]},{"id":"move-datafile-online","task":"Déplacer un datafile en ligne","category":"Stockage","command":"ALTER DATABASE MOVE DATAFILE\n  '/u01/oradata/ORCL/customer_data01.dbf'\n  TO '/u03/oradata/ORCL/customer_data01.dbf';\n\n-- Move into ASM:\nALTER DATABASE MOVE DATAFILE\n  '/u01/oradata/ORCL/customer_data01.dbf' TO '+DATA';\n\n-- Use KEEP to retain the old file instead of deleting it:\n-- ALTER DATABASE MOVE DATAFILE '...' TO '...' KEEP;","what":"Déplace un datafile vers un nouvel emplacement pendant que la base est ouverte (12c+) ; change le stockage sans interruption.","when":"Lors d'un changement de disque/point de montage, d'une migration vers ASM ou d'un déplacement vers un stockage rapide.","caution":"Nécessite 12c ou ultérieur et l'Enterprise Edition. Le déplacement génère des E/S supplémentaires. Si vous n'utilisez pas KEEP, l'ancien fichier est supprimé.","since":"Oracle 12c","note":"Le déplacement de datafile en ligne (ALTER DATABASE MOVE DATAFILE) est arrivé avec Oracle 12c (12.1) ; auparavant, déplacer un datafile impliquait de passer le tablespace/datafile OFFLINE, de le copier au niveau de l'OS et de le remapper avec ALTER DATABASE RENAME FILE.","tags":["datafile","move","move","online","alter database move datafile","asm"]},{"id":"temp-tablespace","task":"Ajouter un tablespace temporaire / changer celui par défaut","category":"Stockage","command":"-- Create a new temp tablespace:\nCREATE TEMPORARY TABLESPACE temp2\n  TEMPFILE '/u01/oradata/ORCL/temp2_01.dbf'\n  SIZE 2G AUTOEXTEND ON NEXT 256M MAXSIZE 20G;\n\n-- Change the database default temp:\nALTER DATABASE DEFAULT TEMPORARY TABLESPACE temp2;\n\n-- Add a tempfile to the old temp:\nALTER TABLESPACE temp ADD TEMPFILE '/u01/oradata/ORCL/temp_02.dbf' SIZE 2G;","what":"Fournit l'espace temporaire pour les opérations de tri/hash/global temporary table ; vous pouvez faire du nouveau tablespace celui par défaut.","when":"Pour ORA-01652 (unable to extend temp segment), un goulot sur temp ou un redimensionnement de temp.","caution":"Après le changement de défaut, ne faites pas immédiatement un DROP de l'ancien temp s'il est utilisé ; supprimez-le une fois les sessions écoulées.","tags":["temp","tablespace","tempfile","ora-01652","default temporary","storage"]},{"id":"tablespace-space","task":"Requête d'espace libre des tablespaces","category":"Stockage","command":"SELECT df.tablespace_name,\n       ROUND(df.total_mb/1024,2)                 AS total_gb,\n       ROUND(NVL(fs.free_mb,0)/1024,2)           AS free_gb,\n       ROUND((df.total_mb-NVL(fs.free_mb,0))/df.total_mb*100,1) AS pct_used\nFROM  (SELECT tablespace_name, SUM(bytes)/1024/1024 total_mb\n         FROM dba_data_files GROUP BY tablespace_name) df\n  LEFT JOIN\n       (SELECT tablespace_name, SUM(bytes)/1024/1024 free_mb\n          FROM dba_free_space GROUP BY tablespace_name) fs\n    ON df.tablespace_name = fs.tablespace_name\nORDER BY pct_used DESC;","what":"Affiche l'espace total et libre ainsi que le pourcentage d'utilisation de chaque tablespace (basé sur l'espace actuellement alloué, hors potentiel d'autoextend).","when":"Surveillance de l'espace, identification du tablespace à étendre.","caution":"Cette requête se base sur la taille actuelle des datafiles ; ils peuvent encore croître avec l'autoextend. Pour temp, utilisez DBA_TEMP_FREE_SPACE. Requête uniquement ; aucun risque.","tags":["tablespace","space","space","free","dba_free_space","usage","storage"]},{"id":"alter-parameter","task":"Modifier un paramètre (avec SCOPE)","category":"Mémoire et paramètres","command":"-- Both the running instance and the spfile (default, dynamic parameters):\nALTER SYSTEM SET open_cursors=500 SCOPE=BOTH;\n\n-- Only for the next restart (static parameters):\nALTER SYSTEM SET processes=1000 SCOPE=SPFILE;\n\n-- Immediate only, do not write to the spfile:\nALTER SYSTEM SET optimizer_mode='ALL_ROWS' SCOPE=MEMORY;\n\n-- View the current value:\nSHOW PARAMETER open_cursors;","what":"Modifie un paramètre d'initialisation dans l'instance en cours et/ou le spfile. SCOPE détermine le comportement.","when":"Pour l'optimisation, les ajustements de capacité et les corrections d'erreurs nécessitant un changement de paramètre.","caution":"Les paramètres statiques (processes, sga_max_size...) n'acceptent que SCOPE=SPFILE et nécessitent un redémarrage. Une valeur erronée peut empêcher l'instance de démarrer ; pour les paramètres critiques, sauvegardez d'abord un pfile.","tags":["parameter","parameter","alter system","scope","spfile","tuning"]},{"id":"flush-pool","task":"Flush du shared pool / buffer cache","category":"Mémoire et paramètres","command":"-- Clear the SQL plan/cursor cache:\nALTER SYSTEM FLUSH SHARED_POOL;\n\n-- Clear the buffer cache (usually only for test/benchmark):\nALTER SYSTEM FLUSH BUFFER_CACHE;","what":"Le flush du shared pool évince de la mémoire tout le SQL/PL-SQL et les informations du dictionnaire en cache ; le flush du buffer cache évince tous les blocs de données.","when":"Pour tester des problèmes de plan, après une grosse opération ponctuelle, ou pour un point de départ de benchmark cohérent.","caution":"Prudence EN PRODUCTION ! Après un flush, tout le SQL est réanalysé (tempête de hard parses) et les blocs sont lus depuis le disque ; une forte baisse temporaire des performances s'ensuit.","tags":["flush","shared pool","buffer cache","memory","alter system","cache"]},{"id":"spfile-pfile","task":"Créer un pfile à partir du spfile et inversement","category":"Mémoire et paramètres","command":"-- spfile -> pfile (for backup/editing):\nCREATE PFILE='/tmp/init_orcl_backup.ora' FROM SPFILE;\n\n-- pfile -> spfile (make the edited pfile permanent; effective after restart):\nCREATE SPFILE FROM PFILE='/tmp/init_orcl_backup.ora';\n\n-- Create a pfile from the running instance values (11g+):\nCREATE PFILE='/tmp/init_current.ora' FROM MEMORY;","what":"Convertit entre le spfile binaire et le pfile texte ; le pfile peut être modifié à la main.","when":"Sauvegarde avant un changement de paramètre, réparation d'un spfile corrompu, correction manuelle d'un paramètre statique.","caution":"Après CREATE SPFILE FROM PFILE, les modifications ne prennent effet qu'après un redémarrage. Vérifiez le chemin avant d'écraser un spfile existant.","tags":["spfile","pfile","create pfile","create spfile","parameter","backup"]},{"id":"sga-pga-target","task":"Modifier les cibles SGA / PGA","category":"Mémoire et paramètres","command":"-- Automatic memory management (ASMM) targets:\nALTER SYSTEM SET sga_target=8G   SCOPE=BOTH;\nALTER SYSTEM SET pga_aggregate_target=4G SCOPE=BOTH;\n\n-- Upper limit (static, requires restart):\nALTER SYSTEM SET sga_max_size=12G SCOPE=SPFILE;\n\n-- View the current memory distribution:\nSELECT component, current_size/1024/1024 AS mb\nFROM   v$sga_dynamic_components ORDER BY component;","what":"Fixe les cibles mémoire SGA et PGA ; Oracle gère les composants automatiquement (ASMM/AMM).","when":"Lors d'un ajout de RAM au serveur, d'un goulot mémoire ou d'une pression mémoire.","caution":"sga_target ne peut pas dépasser sga_max_size. sga_max_size est statique et nécessite un redémarrage. Laissez de la RAM au système d'exploitation et aux autres instances ; une sur-allocation provoque du swap/des OOM.","tags":["sga","pga","memory","memory","target","sga_target","pga_aggregate_target"]},{"id":"kill-session","task":"Terminer une session (kill session)","category":"Sessions et processus","command":"-- First find the session to kill:\nSELECT sid, serial#, username, status, machine, program\nFROM   v$session\nWHERE  username = 'CUSTOMER_APP' AND status = 'ACTIVE';\n\n-- Kill it (starts rollback immediately):\nALTER SYSTEM KILL SESSION '145,12345' IMMEDIATE;\n\n-- If it hangs, the OS process can be terminated:\n-- ALTER SYSTEM DISCONNECT SESSION '145,12345' IMMEDIATE;","what":"Termine la session ayant le SID,SERIAL# indiqué ; sa transaction ouverte est annulée (rollback).","when":"Pour arrêter une session qui détient des verrous, exécute une requête incontrôlée ou fait fuir des connexions.","caution":"S'il y a une transaction ouverte, le rollback peut être long (il se termine vite si peu de travail a été fait). Vérifiez d'abord par une requête pour ne pas terminer le mauvais SID. Le rollback d'un DML volumineux génère une charge d'E/S.","tags":["kill session","kill session","v$session","alter system","sid","serial"]},{"id":"long-running-queries","task":"Consulter les requêtes / sessions de longue durée","category":"Sessions et processus","command":"-- Active, long-running SQL:\nSELECT s.sid, s.serial#, s.username, s.status,\n       s.last_call_et AS seconds_active,\n       s.sql_id, q.sql_text,\n       s.event, s.machine, s.program\nFROM   v$session s\n  LEFT JOIN v$sql q ON q.sql_id = s.sql_id\n                   AND q.child_number = s.sql_child_number\nWHERE  s.type = 'USER'\n  AND  s.status = 'ACTIVE'\n  AND  s.last_call_et > 60\nORDER BY s.last_call_et DESC;\n\n-- Long operations with percentage progress:\nSELECT sid, serial#, opname, sofar, totalwork,\n       ROUND(sofar/NULLIF(totalwork,0)*100,1) AS pct, time_remaining\nFROM   v$session_longops\nWHERE  sofar < totalwork ORDER BY start_time DESC;","what":"Liste les sessions actives depuis plus de 60 secondes et la progression des opérations batch longues.","when":"En cas de plaintes de lenteur, pour trouver la requête qui occupe le système.","caution":"last_call_et est le temps écoulé depuis le dernier appel ; il peut aussi être élevé pour une session inactive, d'où l'importance du filtre status='ACTIVE'. Requête uniquement ; aucun risque.","tags":["long running","long query","v$session","v$sql","v$session_longops","active"]},{"id":"find-blocking","task":"Trouver la session bloquante","category":"Sessions et processus","command":"SELECT bl.sid       AS blocker_sid,\n       bl.serial#   AS blocker_serial,\n       bl.username  AS blocker_user,\n       w.sid        AS waiter_sid,\n       w.serial#    AS waiter_serial,\n       w.username   AS waiter_user,\n       w.seconds_in_wait AS wait_sec,\n       w.event\nFROM   v$session w\n  JOIN v$session bl ON bl.sid = w.blocking_session\nWHERE  w.blocking_session IS NOT NULL\nORDER BY w.seconds_in_wait DESC;","what":"Liste les paires de sessions bloquante/en attente avec le temps d'attente et l'événement.","when":"Pour trouver le coupable dans les problèmes de verrous du type \"application figée / attente enq: TX\".","caution":"Confirmez avec le responsable métier avant de terminer la session bloquante ; il peut s'agir d'une opération critique. Requête uniquement ; aucun risque.","tags":["blocking","lock","lock","blocker","waiter","v$session","blocking_session"]},{"id":"gather-stats","task":"Collecter les statistiques (DBMS_STATS)","category":"Maintenance","command":"-- Single table (including cascaded indexes):\nEXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'CUSTOMER', cascade => TRUE, estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, degree => 4);\n\n-- Whole schema:\nEXEC DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CUSTOMER_APP', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, cascade => TRUE, degree => 4);\n\n-- Whole database (run only in a maintenance window / off-peak hours):\nEXEC DBMS_STATS.GATHER_DATABASE_STATS(estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, degree => 4);","what":"Collecte les statistiques de tables/index pour l'optimiseur ; garantit la production de plans d'exécution corrects.","when":"Après des DML/chargements volumineux, en cas de régression de plan, après la création d'un index/d'une table.","caution":"Les changements de statistiques peuvent modifier les plans d'exécution ; sur les systèmes critiques, testez/comparez d'abord avec AWR. Le parallélisme DEGREE consomme du CPU. GATHER_DATABASE_STATS est lourd aux heures de forte activité.","tags":["dbms_stats","statistics","statistics","gather","optimizer","maintenance"]},{"id":"rebuild-index-online","task":"Reconstruire un index en ligne","category":"Maintenance","command":"-- Regular index:\nALTER INDEX customer_app.idx_customer_email REBUILD ONLINE PARALLEL 4;\n\n-- Moving it to a different tablespace:\nALTER INDEX customer_app.idx_customer_email\n  REBUILD ONLINE TABLESPACE customer_idx PARALLEL 4;\n\n-- Reset parallelism after the rebuild (so plans are not affected):\nALTER INDEX customer_app.idx_customer_email NOPARALLEL;","what":"Reconstruit l'index sans bloquer les DML (ONLINE) ; supprime la fragmentation et permet de changer de tablespace.","when":"Gonflement/fragmentation d'index, déplacement d'un index vers un autre tablespace, réparation d'un index UNUSABLE.","caution":"ONLINE utilise de l'espace temporaire et des E/S supplémentaires. PARALLEL est persistant ; réinitialisez-le avec NOPARALLEL après la reconstruction, sinon les requêtes s'exécutent en parallèle involontairement. ONLINE n'est pas pris en charge pour les index bitmap dans toutes les versions. La reconstruction ONLINE n'est disponible qu'en Enterprise Edition.","tags":["index","rebuild","online","rebuild","alter index","fragmentation","maintenance"]},{"id":"rman-full-backup","task":"Sauvegarde complète rapide avec RMAN","category":"Maintenance","command":"# From the shell: rman target /\nRUN {\n  ALLOCATE CHANNEL c1 DEVICE TYPE DISK;\n  ALLOCATE CHANNEL c2 DEVICE TYPE DISK;\n  BACKUP AS COMPRESSED BACKUPSET\n    DATABASE\n    PLUS ARCHIVELOG\n    TAG 'FULL_DAILY';\n  BACKUP CURRENT CONTROLFILE;\n  DELETE NOPROMPT OBSOLETE;\n  RELEASE CHANNEL c1;\n  RELEASE CHANNEL c2;\n}","what":"Effectue une sauvegarde complète compressée de toute la base et des archive logs en parallèle sur deux canaux, sauvegarde le controlfile et purge les sauvegardes obsolètes.","when":"Sauvegarde complète quotidienne/hebdomadaire, pour créer un point de restauration.","caution":"COMPRESSED consomme du CPU. DELETE OBSOLETE supprime les anciennes sauvegardes selon la politique de rétention ; définissez correctement la politique (CONFIGURE RETENTION POLICY). ARCHIVELOG exige que la base soit en mode ARCHIVELOG. Assurez-vous que la destination de sauvegarde dispose d'assez d'espace.","tags":["rman","backup","backup","full","compressed","archivelog","maintenance"]},{"id":"controlfile-trace","task":"Sauvegarder le controlfile en trace","category":"Maintenance","command":"-- Generate a trace (CREATE CONTROLFILE script):\nALTER DATABASE BACKUP CONTROLFILE TO TRACE\n  AS '/tmp/create_controlfile.sql' REUSE;\n\n-- Binary controlfile copy:\nALTER DATABASE BACKUP CONTROLFILE TO '/backup/controlfile_orcl.ctl' REUSE;","what":"Génère le script SQL (trace) qui recrée le controlfile et/ou une copie binaire de celui-ci.","when":"Après des changements de structure (ajout de datafiles/redo), lors de la préparation d'un plan de reprise après sinistre, ou pour recréer le controlfile si toutes les copies sont perdues.","caution":"Régénérez le fichier trace à chaque changement de structure ; un ancien script crée une structure erronée. REUSE écrase un fichier existant. Aucun risque pour le système, mais stockez le fichier généré en lieu sûr.","tags":["controlfile","trace","backup","backup","alter database","recovery","maintenance"]}]},"recipes/oracle-ha.json":{"engineId":"oracle","engine":"Oracle","order":1,"recipes":[{"id":"rac-srvctl-db-status","task":"Vérifier l'état de la base, des instances et des services RAC avec srvctl","category":"RAC et Grid","command":"# Which node each instance runs on, and in what state:\nsrvctl status database -db <db_unique_name> -verbose\n# Configuration registered in Clusterware (Oracle home, spfile, disk group, policy, role):\nsrvctl config database -db <db_unique_name>\n# A single instance:\nsrvctl status instance -db <db_unique_name> -instance <instance>\n# Which instances the services are running on:\nsrvctl status service -db <db_unique_name>","what":"Affiche l'état d'exécution de la base, de chaque instance et de ses services tel que vu par Clusterware (CRS), ainsi que la configuration enregistrée ; -verbose ajoute le mode d'ouverture de l'instance.","when":"Comme première vérification avant et après toute intervention RAC ; pendant un incident, pour voir quel nœud/quelle instance est touché.","caution":"Lecture seule. Exécutez-le depuis l'Oracle home de la base (utilisateur oracle) ; un srvctl issu d'un Grid home d'une autre version peut renvoyer des résultats trompeurs ou des erreurs.","since":"12c","note":"Les options longues -db/-instance/-service sont 12c+ ; les options courtes 11g (-d, -i, -s) fonctionnent encore en 19c mais sont dépréciées.","tags":["rac","srvctl","status","instance","service","cluster","config"],"untested":true},{"id":"rac-srvctl-start-stop","task":"Arrêter / démarrer une base RAC ou une seule instance avec srvctl (rolling)","category":"RAC et Grid","command":"# Whole database (all instances):\nsrvctl stop database -db <db_unique_name> -stopoption immediate\nsrvctl start database -db <db_unique_name>\n# Rolling maintenance: stop one instance, moving its services to the other instances\nsrvctl stop instance -db <db_unique_name> -instance <instance> \\\n  -stopoption immediate -drain_timeout 300 -failover\nsrvctl start instance -db <db_unique_name> -instance <instance>\n# By node:\nsrvctl stop instance -db <db_unique_name> -node <node> -stopoption immediate\n# Start in a specific open mode (e.g. a physical standby in mount):\nsrvctl start database -db <db_unique_name> -startoption mount","what":"Arrête/démarre la base ou l'instance choisie de façon contrôlée via Clusterware ; avec -failover, les services de l'instance arrêtée basculent vers les instances available, et -drain_timeout attend la vidange des sessions.","when":"Pour les redémarrages rolling (nœud par nœud) lors de patchs, de maintenance OS ou de changements de paramètres ; pour un arrêt complet planifié.","caution":"En RAC, utilisez srvctl au lieu de SHUTDOWN SQL*Plus ; sinon Clusterware ne connaît pas le TARGET de la ressource et peut redémarrer l'instance. stop database arrête toutes les instances = interruption totale. Si aucune instance available n'est définie pour les services, -failover ne peut pas déplacer les connexions.","since":"12c","note":"-drain_timeout et -failover sont 12.2+. N'utilisez -stopoption abort qu'en urgence ; le démarrage suivant exige une instance recovery.","tags":["rac","srvctl","stop","start","rolling","drain","shutdown"],"untested":true},{"id":"rac-service-add","task":"Ajouter un service RAC avec instances preferred/available (y compris Application Continuity)","category":"RAC et Grid","command":"srvctl add service -db <db_unique_name> -service <service> \\\n  -preferred <inst1>,<inst2> -available <inst3> \\\n  -pdb <pdb> -role PRIMARY -policy AUTOMATIC \\\n  -failovertype TRANSACTION -commit_outcome TRUE \\\n  -failoverretry 30 -failoverdelay 10 \\\n  -clbgoal SHORT -rlbgoal SERVICE_TIME -notification TRUE \\\n  -drain_timeout 300 -stopoption IMMEDIATE\nsrvctl start service -db <db_unique_name> -service <service>\n# Verify:\nsrvctl config service -db <db_unique_name> -service <service>\nsrvctl status service -db <db_unique_name> -service <service>","what":"Définit pour les applications un service qui s'exécute normalement sur les instances preferred et bascule vers une instance available en cas de défaillance ; avec -role PRIMARY, il ne démarre que dans le rôle primaire sous Data Guard.","when":"Pour connecter les applications à un service plutôt qu'à une instance, répartir les charges entre les nœuds et permettre un basculement transparent (drain/failover) pendant la maintenance planifiée.","caution":"Les applications doivent se connecter avec ce nom de service (via SCAN), pas avec un SID ou un nom d'instance. -failovertype TRANSACTION (Application Continuity) exige la prise en charge par l'application/le pilote ; en cas de doute, commencez par -failovertype SELECT (TAF) ou NONE. N'utilisez pas les services SYS$ ni le service par défaut nommé d'après db_unique_name pour les applications.","since":"12c","note":"-pdb est 12c+, -drain_timeout et -stopoption sont 12.2+. Modifiez-le avec srvctl modify service ; un changement permanent de preferred exige -modifyconfig.","tags":["rac","service","srvctl","preferred","available","taf","application continuity"],"untested":true},{"id":"rac-service-relocate","task":"Déplacer un service RAC vers une autre instance","category":"RAC et Grid","command":"# First check the current placement:\nsrvctl status service -db <db_unique_name> -service <service>\n# Relocate by instance (sessions are drained):\nsrvctl relocate service -db <db_unique_name> -service <service> \\\n  -oldinst <inst1> -newinst <inst3> -drain_timeout 120 -wait YES\n# By node (policy-managed / singleton service):\nsrvctl relocate service -db <db_unique_name> -service <service> \\\n  -currentnode <node1> -targetnode <node2>\n# Session distribution per instance (SQL):\nSELECT inst_id, service_name, COUNT(*) AS sessions\nFROM   gv$session\nWHERE  type = 'USER'\nGROUP  BY inst_id, service_name\nORDER  BY service_name, inst_id;","what":"Déplace un service en cours d'exécution d'une instance/d'un nœud vers un autre ; les nouvelles connexions vont vers l'instance cible et les sessions existantes se ferment d'elles-mêmes pendant drain_timeout.","when":"Pour vider un nœud avant maintenance, ramener un service sur son instance preferred après un failover, ou rééquilibrer la charge.","caution":"Le déplacement est temporaire : il ne modifie pas la configuration du service (preferred/available) ; au redémarrage de l'instance, le service revient sur son instance preferred. -force tue les sessions existantes ; si l'application ne peut pas se reconnecter, les utilisateurs subissent une interruption.","since":"12c","note":"-drain_timeout et -wait sont 12.2+. La requête SQL fonctionne aussi sur une instance unique ; la partie srvctl exige RAC.","tags":["rac","service","relocate","move","srvctl","drain","gv$session"],"untested":true},{"id":"rac-crsctl-stat-res","task":"Vérifier l'état des ressources du cluster (crsctl stat res -t)","category":"RAC et Grid","command":"# From the Grid home, as the grid user:\ncrsctl stat res -t\n# OHASD (init) layer: ora.cssd, ora.crsd, ora.asm, ora.ctssd...\ncrsctl stat res -t -init\n# Only problem resources (TARGET is ONLINE but STATE is not):\ncrsctl stat res -t -w \"((TARGET = ONLINE) and (STATE != ONLINE))\"\n# Profile and runtime details of a single resource:\ncrsctl stat res ora.<db_unique_name>.db -p\ncrsctl stat res ora.<db_unique_name>.db -v","what":"Liste sous forme de tableau, par nœud, chaque ressource gérée par Clusterware (base, instance, listener, SCAN, VIP, ASM, disk group, réseau) avec TARGET/STATE.","when":"Après le démarrage d'un nœud, après un patch et dans toute investigation d'incident ; pour trouver les ressources OFFLINE, INTERMEDIATE ou UNKNOWN.","caution":"Lecture seule. Ne gérez pas directement les ressources ora.* avec crsctl start/stop/modify ; utilisez srvctl pour les ressources de base de données (méthode prise en charge).","note":"Le nom de la ressource contient le db_unique_name en minuscules (ora.orcl.db). La sortie -init n'est entièrement visible qu'en root/grid.","tags":["rac","grid","crsctl","resource","resource","clusterware","status"],"untested":true},{"id":"rac-crsctl-check-cluster","task":"Vérifier la santé de la pile cluster, la liste des nœuds et la version de Clusterware","category":"RAC et Grid","command":"# CRS, CSS, EVM health check on all nodes:\ncrsctl check cluster -all\n# Full stack on this node, including OHASD:\ncrsctl check crs\n# Node list: number, status (Active/Inactive), pinned:\nolsnodes -n -s -t\n# Active version, patch level and upgrade state (after a rolling patch):\ncrsctl query crs activeversion -f\ncrsctl query crs softwareversion -all","what":"Montre que les démons Cluster Ready Services, Cluster Synchronization Services et Event Manager tournent sur chaque nœud, ainsi que les membres du cluster et la version active/logicielle de Clusterware.","when":"Lors du contrôle de santé matinal, après une éviction ou un redémarrage de nœud, et après un patch/une mise à niveau rolling pour confirmer que tous les nœuds sont au même niveau.","caution":"Lecture seule. Si activeversion -f affiche \"ROLLING PATCH\" ou un niveau de patch différent, une opération rolling est inachevée ; terminez-la (crsctl stop rollingpatch / rootcrs -postpatch) avant tout nouveau changement.","since":"12c","note":"activeversion -f et softwareversion -all sont 12c+.","tags":["rac","grid","crsctl","olsnodes","health","cluster","version"],"untested":true},{"id":"rac-ocr-backup","task":"Vérifier l'intégrité de l'OCR et sauvegarder l'OCR (ocrcheck / ocrconfig)","category":"RAC et Grid","command":"# OCR integrity and capacity check (run as root for the logical check):\nocrcheck\nocrcheck -config\n# Automatic backups (every 4 hours, daily, weekly):\nocrconfig -showbackup\n# Manual backup before a risky change (root):\nocrconfig -manualbackup\nocrconfig -showbackup manual\n# Move the backup location to a disk group (root):\nocrconfig -backuploc +<diskgroup>","what":"Affiche l'intégrité, les emplacements et les sauvegardes de l'Oracle Cluster Registry (référentiel de configuration du cluster) ; -manualbackup effectue immédiatement une sauvegarde physique de l'OCR.","when":"Juste avant les changements de configuration du cluster tels que l'ajout/la suppression de nœuds, le patch/la mise à niveau Grid, ou les changements de voting disk ou de réseau (oifcfg).","caution":"ocrconfig -restore doit s'exécuter avec CRS arrêté sur tous les nœuds et en mode exclusif ; restaurer la mauvaise sauvegarde peut empêcher le cluster de démarrer, faites-le donc avec le support Oracle. Par défaut, les sauvegardes automatiques peuvent rester sur le disque local d'un seul nœud ; déplacez l'emplacement vers un disk group partagé.","note":"Exécuté en tant qu'utilisateur grid, ocrcheck ignore la vérification de corruption logique (\"Logical corruption check bypassed\"). Les sauvegardes vers un disk group sont 12c+.","tags":["rac","ocr","ocrcheck","ocrconfig","backup","backup","grid"],"untested":true},{"id":"rac-votedisk-query","task":"Vérifier les voting disks et les timeouts CSS","category":"RAC et Grid","command":"# Voting disks, their state and disk group:\ncrsctl query css votedisk\n# Node eviction thresholds (seconds):\ncrsctl get css misscount\ncrsctl get css disktimeout\ncrsctl get css reboottime\n# Move the voting disks to another ASM disk group (root/grid):\ncrsctl replace votedisk +<diskgroup>","what":"Affiche l'état des voting disks (voting files) utilisés pour l'appartenance au cluster et les timeouts de heartbeat réseau/disque ; replace votedisk déplace tous les voting files vers le disk group cible.","when":"Lors de changements de stockage, d'une refonte de disk group ou d'une investigation d'éviction de nœud ; pour confirmer qu'une majorité (quorum) des voting disks est accessible.","caution":"Un nœud qui ne peut pas accéder à plus de la moitié des voting disks redémarre de lui-même. Le disk group cible doit avoir assez de failure groups (redondance normal : 3, high : 5 ; utilisez un quorum failgroup si nécessaire). Ne modifiez pas misscount/disktimeout sans l'accord du support Oracle.","note":"Valeurs par défaut : misscount 30 s, disktimeout 200 s (Linux).","tags":["rac","voting disk","votedisk","css","misscount","quorum","eviction"],"untested":true},{"id":"rac-cluvfy-checks","task":"Effectuer des vérifications pré/post avec Cluster Verification Utility (cluvfy)","category":"RAC et Grid","command":"# Full check after a Grid installation/change:\ncluvfy stage -post crsinst -n all -verbose\n# Before adding a node:\ncluvfy stage -pre nodeadd -n <new_node> -verbose\n# Component checks:\ncluvfy comp nodecon -n all -verbose\ncluvfy comp clocksync -n all -verbose\ncluvfy comp ocr -n all -verbose\ncluvfy comp asm -n all -verbose\n# Report only failed checks:\ncluvfy stage -post crsinst -n all -verbose | grep -iE \"failed|warning\"","what":"Valide la connectivité réseau, la synchronisation d'horloge, l'OCR, ASM, l'équivalence utilisateur et les prérequis OS sur tous les nœuds ; signale les problèmes avec des corrections suggérées.","when":"Avant et après l'installation de Grid, l'ajout de nœuds et les mises à niveau ; comme validation de référence pour des problèmes de cluster inexpliqués.","caution":"Lecture seule, mais il se connecte à tous les nœuds via ssh ; l'utilisateur grid a besoin de l'équivalence ssh. N'exécutez pas les scripts -fixup (root) sans les relire ; ils modifient des paramètres OS.","note":"Pour une analyse best practice plus large, utilisez orachk/exachk d'AHF. Télécharger le dernier cluvfy depuis My Oracle Support apporte davantage de vérifications.","tags":["rac","cluvfy","check","verification","precheck","grid","orachk"],"untested":true},{"id":"rac-node-eviction-diag","task":"Diagnostiquer la cause d'une éviction de nœud / d'un redémarrage inattendu de nœud","category":"RAC et Grid","command":"# 1) When, which node, evicted by whom: CRS alert log (on every node)\ngrep -E \"CRS-1601|CRS-1607|CRS-1608|CRS-1609|CRS-1610|CRS-1611|CRS-1612|CRS-8011\" \\\n  $ORACLE_BASE/diag/crs/$(hostname -s)/crs/trace/alert.log\n# 2) CSS details: was the network heartbeat or the disk heartbeat lost\ngrep -iE \"evict|misscount|heartbeat|disk HB|network HB\" \\\n  $ORACLE_BASE/diag/crs/$(hostname -s)/crs/trace/ocssd.trc | tail -100\n# 3) Who triggered the reboot (cssdagent/cssdmonitor):\nls -lt $ORACLE_BASE/diag/crs/$(hostname -s)/crs/trace/ohasd_cssd*.trc\n# 4) OS resources at the time of the event (Cluster Health Monitor):\noclumon dumpnodeview -allnodes -s \"2026-01-15 03:10:00\" -e \"2026-01-15 03:20:00\"\n# 5) Diagnostic collection from all nodes (AHF/TFA):\ntfactl diagcollect -crs -os -last 4h\n# 6) OS side: /var/log/messages, journalctl -k (OOM, NIC, multipath, I/O errors)","what":"Suit dans l'ordre la chaîne d'éviction de nœud : messages CRS-16xx dans l'alert log CRS, pertes de heartbeat dans ocssd.trc, métriques OS de CHM et journaux de tous les nœuds collectés avec TFA.","when":"Lorsqu'un nœud redémarre de façon inattendue ou est évincé du cluster, pour l'analyse de cause racine (RCA) et un SR auprès du support Oracle.","caution":"Collectez les journaux de chaque nœud couvrant l'heure de l'événement ; les fichiers trace tournent et des preuves peuvent être perdues. Vérifiez que les fuseaux horaires sont identiques sur les nœuds (CTSS/NTP). N'appliquez pas de \"corrections\" comme l'augmentation de misscount avant d'avoir collecté les preuves.","note":"Causes typiques : panne/perte de paquets de l'interconnect privé, latence d'E/S des voting disks, épuisement CPU/mémoire (OOM, swap), sauts d'horloge. Le chemin correspond à l'arborescence ADR 12c+ ; en 11g il se trouve sous $GRID_HOME/log/<host>/.","tags":["rac","eviction","reboot","ocssd","tfactl","oclumon","rca"],"untested":true},{"id":"rac-gv-cluster-waits","task":"Analyser les événements d'attente à l'échelle du cluster (gc waits) par instance avec GV$","category":"RAC et Grid","command":"-- Cluster-class waits per instance (since startup):\nSELECT inst_id, event, total_waits,\n       ROUND(time_waited_micro/1e6) AS time_waited_s,\n       ROUND(time_waited_micro/NULLIF(total_waits,0)/1000, 2) AS avg_ms\nFROM   gv$system_event\nWHERE  wait_class = 'Cluster'\nORDER  BY time_waited_micro DESC\nFETCH FIRST 15 ROWS ONLY;\n\n-- Sessions waiting right now, by instance and event:\nSELECT inst_id, event, COUNT(*) AS sessions\nFROM   gv$session\nWHERE  wait_class <> 'Idle' AND state = 'WAITING'\nGROUP  BY inst_id, event\nORDER  BY sessions DESC;\n\n-- ASH for the last 15 minutes: which instance is waiting on gc\nSELECT inst_id, event, COUNT(*) AS samples\nFROM   gv$active_session_history\nWHERE  sample_time > SYSTIMESTAMP - INTERVAL '15' MINUTE\n  AND  wait_class = 'Cluster'\nGROUP  BY inst_id, event\nORDER  BY samples DESC\nFETCH FIRST 10 ROWS ONLY;","what":"Utilise les vues GV$ pour afficher en une seule requête les attentes de classe Cluster (gc cr/current block 2-way/3-way, gc buffer busy, gc cr request) et leur latence moyenne sur toutes les instances.","when":"Lorsque des utilisateurs signalent une lenteur sur RAC, pour déterminer si le problème vient de l'interconnect/du global cache et sur quelle instance il se concentre.","caution":"gv$active_session_history exige la licence Diagnostics Pack. Si avg_ms dépasse quelques ms, vérifiez d'abord l'interconnect (MTU, perte de paquets, netstat -s) et le CPU de LMS ; gc buffer busy indique généralement un hot block applicatif (séquence, index croissant à droite).","note":"Fonctionne aussi sur une instance unique (renvoie inst_id = 1).","tags":["rac","gv$","gc","wait","wait event","interconnect","global cache"]},{"id":"asm-asmcmd-lsdg-du","task":"Afficher l'utilisation des disk groups, disques et répertoires avec asmcmd (lsdg / lsdsk / du)","category":"ASM","command":"# As the grid user, with ORACLE_SID=+ASM1 (or +ASM):\nasmcmd lsdg\n# Disks in the disk group: -k sizes, -p state (header/mount/mode):\nasmcmd lsdsk -k -G <diskgroup>\nasmcmd lsdsk -p -G <diskgroup>\n# I/O error and timing statistics:\nasmcmd lsdsk --statistics -G <diskgroup>\n# Disks that can be added (CANDIDATE/PROVISIONED/FORMER):\nasmcmd lsdsk --candidate -p\n# Space used by a database directory (MB):\nasmcmd du +<diskgroup>/<db_unique_name>\nasmcmd ls -l -s +<diskgroup>/<db_unique_name>/DATAFILE","what":"Affiche en ligne de commande la redondance, l'espace total/libre et Usable_file_MB des disk groups, l'état des disques et l'espace occupé par une base dans ASM.","when":"Pour les contrôles de capacité, pour identifier les disques candidats avant un ajout de disques, et pour trouver quelle base consomme l'espace lorsqu'une alerte \"disk group full\" se déclenche.","caution":"Lecture seule. Si Usable_file_MB est négatif dans lsdg, le disk group ne peut pas rétablir la redondance après la perte d'un disque/failgroup ; libérez de l'espace ou ajoutez des disques immédiatement. du renvoie deux valeurs : Used_MB et Mirror_used_MB (utilisation en miroir).","note":"Pour l'équivalent SQL, voir les procédures asm-diskgroup-space et asm-disk-health.","tags":["asm","asmcmd","lsdg","lsdsk","du","space","disk group"],"untested":true},{"id":"asm-disk-health","task":"Vérifier l'état des disques ASM, les compteurs d'erreurs et les disques candidats en SQL","category":"ASM","command":"-- On the ASM instance (or from a DB instance; there only the disk groups in use are visible):\nSELECT dg.name AS diskgroup, d.disk_number, d.name AS disk_name, d.path,\n       d.header_status, d.mount_status, d.mode_status, d.state,\n       d.failgroup, d.total_mb, d.free_mb, d.read_errs, d.write_errs\nFROM   v$asm_disk d\nLEFT   JOIN v$asm_diskgroup dg ON dg.group_number = d.group_number\nORDER  BY dg.name, d.disk_number;\n\n-- Disk group summary: AU size, offline disks, voting files:\nSELECT name, type, allocation_unit_size/1048576 AS au_mb,\n       compatibility, database_compatibility, offline_disks, voting_files\nFROM   v$asm_diskgroup;\n\n-- Databases using this ASM:\nSELECT instance_name, db_name, status, software_version, compatible_version\nFROM   v$asm_client;","what":"Liste le header_status de chaque disque (MEMBER, CANDIDATE, PROVISIONED, FORMER), l'état mount/mode, le failgroup et les compteurs d'erreurs de lecture/écriture ; le nombre de disques offline par disk group et les clients ASM.","when":"Avant d'ajouter des disques, pour vérifier qu'un nouveau LUN apparaît en CANDIDATE/PROVISIONED ; en cas de panne de disque, pour voir quel disque est OFFLINE et les éventuelles erreurs d'E/S.","caution":"Lecture seule. v$asm_disk lance une découverte des disques à chaque appel et est lent sur les systèmes comportant de nombreux disques ; pour une surveillance fréquente, utilisez v$asm_disk_stat (pas de découverte, n'affiche pas les nouveaux disques). N'ajoutez jamais avec FORCE à un autre disk group un disque dont le header_status est MEMBER ; cela détruit les données existantes.","note":"Ne renvoie aucune ligne sans ASM ; une sortie significative exige une instance ASM.","tags":["asm","v$asm_disk","disk","header_status","error","candidate","failgroup"]},{"id":"asm-diskgroup-attributes","task":"Afficher et modifier les attributs d'un disk group ASM (compatible, disk_repair_time)","category":"ASM","command":"-- Current attributes:\nSELECT dg.name AS diskgroup, a.name, a.value\nFROM   v$asm_attribute a\nJOIN   v$asm_diskgroup dg ON dg.group_number = a.group_number\nWHERE  a.name IN ('compatible.asm','compatible.rdbms','au_size','disk_repair_time',\n                  'failgroup_repair_time','content.type','sector_size')\nORDER  BY dg.name, a.name;\n\n-- Change (ASM instance, as SYSASM):\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'disk_repair_time' = '8h';\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'compatible.asm'   = '19.0.0.0.0';\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'compatible.rdbms' = '19.0.0.0.0';\n\n-- asmcmd equivalent:\n-- asmcmd lsattr -l -G <diskgroup>\n-- asmcmd setattr -G <diskgroup> disk_repair_time '8h'","what":"Affiche et modifie les niveaux de compatibilité du disk group (compatible.asm/rdbms), la taille d'AU et la durée pendant laquelle un disque temporairement défaillant est conservé avant d'être supprimé (disk_repair_time).","when":"Après une mise à niveau Grid, pour activer de nouvelles fonctionnalités ASM (flex disk groups, limites de fichiers plus élevées), ou pour prolonger disk_repair_time pendant une maintenance de stockage planifiée.","caution":"compatible.asm et compatible.rdbms NE PEUVENT PLUS ÊTRE ABAISSÉS UNE FOIS RELEVÉS. Si compatible.rdbms est supérieur au paramètre COMPATIBLE de la plus ancienne base utilisant ce disk group, cette base ne peut pas le monter. Relevez d'abord compatible.asm, puis compatible.rdbms. au_size ne peut être défini qu'à la création du disk group.","note":"Sans ASM, la requête ne renvoie aucune ligne. La valeur par défaut de disk_repair_time est 12h.","tags":["asm","attribute","compatible","disk_repair_time","disk group","au_size","v$asm_attribute"],"untested":true},{"id":"dg-show-configuration","task":"Afficher la configuration du broker Data Guard et l'état des bases (DGMGRL)","category":"Data Guard","command":"# From the shell (connect with a password; also required for switchover/failover):\ndgmgrl sys@<primary_db_unique_name>\nSHOW CONFIGURATION VERBOSE;\nSHOW DATABASE VERBOSE '<standby_db_unique_name>';\n# Monitorable properties:\nSHOW DATABASE '<primary_db_unique_name>' 'LogXptStatus';\nSHOW DATABASE '<standby_db_unique_name>' 'RecvQEntries';\nSHOW DATABASE '<primary_db_unique_name>' 'InconsistentLogXptProps';\nSHOW DATABASE '<standby_db_unique_name>' 'StatusReport';","what":"Affiche l'état global de la configuration du broker (SUCCESS/WARNING/ERROR), le mode de protection, le rôle de chaque base, le transport/apply lag et les incohérences entre le broker et le spfile.","when":"Avant et après chaque opération Data Guard (switchover, failover, changement de paramètre) ; lors du contrôle de santé quotidien.","caution":"Lecture seule. Tant que le broker est activé, ne modifiez pas en SQL les paramètres Data Guard comme log_archive_dest_n ou log_archive_config ; le broker les écrase avec ses propres valeurs. Effectuez les changements avec EDIT DATABASE ... SET PROPERTY, et en 19c+ utilisez EDIT DATABASE ... SET PARAMETER pour les paramètres d'initialisation.","note":"StatusReport liste les erreurs ORA- par base ; c'est le moyen le plus rapide de trouver la cause d'un état WARNING. InconsistentProperties est déprécié en 19c et ne renvoie jamais de valeur ; utilisez InconsistentLogXptProps pour les incohérences de transport redo.","tags":["data guard","dgmgrl","broker","configuration","standby","status","lag"],"untested":true},{"id":"dg-readiness-sql","task":"Vérifier le rôle primaire/standby et la préparation au switchover en SQL","category":"Data Guard","command":"SELECT name, db_unique_name, database_role, open_mode, protection_mode,\n       switchover_status, force_logging, flashback_on, dataguard_broker\nFROM   v$database;\n\n-- State of redo destinations and gaps (on the primary):\nSELECT dest_id, dest_name, status, type, database_mode,\n       recovery_mode, gap_status, error\nFROM   v$archive_dest_status\nWHERE  status <> 'INACTIVE';\n\n-- Missing archivelog range on the standby:\nSELECT thread#, low_sequence#, high_sequence# FROM v$archive_gap;","what":"Affiche le rôle de la base, le mode d'ouverture, le mode de protection, switchover_status (TO STANDBY / TO PRIMARY / SESSIONS ACTIVE / NOT ALLOWED), l'état FORCE LOGGING et flashback, ainsi que les erreurs et gaps des destinations redo.","when":"Comme pré-vérification avant un switchover/failover, après la création d'une nouvelle standby, et pour une surveillance de base là où le broker n'est pas utilisé.","caution":"Lecture seule. Si FORCE_LOGGING=NO, les opérations NOLOGGING laissent des blocs corrompus sur la standby ; ALTER DATABASE FORCE LOGGING doit être activé sur une primaire Data Guard. Si FLASHBACK_ON=NO, l'ancienne primaire ne peut pas être réintégrée après un failover et doit être reconstruite.","note":"Fonctionne aussi sur la primaire ; v$archive_gap n'a de sens que sur une physical standby.","tags":["data guard","v$database","switchover_status","force logging","gap","role","standby"]},{"id":"dg-lag-apply-rate","task":"Mesurer le transport/apply lag, le débit d'apply et les processus Data Guard sur la standby","category":"Data Guard","command":"-- Run on the standby:\nSELECT name, value, unit, time_computed, datum_time\nFROM   v$dataguard_stats\nWHERE  name IN ('transport lag','apply lag','apply finish time','estimated startup time');\n\n-- Redo apply rate (KB/s):\nSELECT item, units, sofar, total, TO_CHAR(timestamp,'YYYY-MM-DD HH24:MI:SS') AS ts\nFROM   v$recovery_progress\nWHERE  item IN ('Active Apply Rate','Average Apply Rate','Maximum Apply Rate','Last Applied Redo')\nORDER  BY start_time DESC, item;\n\n-- Transport and apply processes (12.2+; replaces v$managed_standby):\nSELECT name, role, action, thread#, sequence#, block#, delay_mins\nFROM   v$dataguard_process\nORDER  BY role;","what":"Indique le retard de la standby sur la primaire (transport lag = redo pas encore reçu, apply lag = redo pas encore appliqué), quand l'arriéré sera appliqué, le débit d'apply et quel processus travaille sur quelle séquence.","when":"Pour la surveillance RPO/RTO et, sur une alerte de lag, pour déterminer si le problème est dans le réseau (transport) ou dans les E/S/CPU de la standby (apply).","caution":"Lecture seule. Si datum_time est ancien (très en retard sur time_computed), la standby ne reçoit pas de données de la primaire et la valeur de lag n'est pas fiable. Vérifiez si DelayMins est défini ; un délai intentionnel ressemble à un apply lag.","since":"12c","note":"v$dataguard_process est 12.2+. Sans standby, v$dataguard_stats ne renvoie aucune ligne.","tags":["data guard","lag","lag","apply","transport","v$dataguard_stats","rpo"]},{"id":"dg-validate-database","task":"Valider la standby avec le broker avant un switchover/failover (VALIDATE DATABASE)","category":"Data Guard","command":"VALIDATE DATABASE '<standby_db_unique_name>';\nVALIDATE DATABASE VERBOSE '<standby_db_unique_name>';\n# spfile parameter differences between the two sides:\nVALIDATE DATABASE '<standby_db_unique_name>' SPFILE;\n# Network and static listener registrations (18c+):\nVALIDATE NETWORK CONFIGURATION FOR ALL;\nVALIDATE STATIC CONNECT IDENTIFIER FOR ALL;","what":"Vérifie si la standby est prête pour un changement de rôle (\"Ready for Switchover / Failover: Yes\"), le nombre/la taille des standby redo logs, l'état flashback et apply, les différences de spfile et les services statiques dont le broker a besoin pour redémarrer les instances.","when":"Comme étape obligatoire avant chaque switchover planifié ; après la création d'une nouvelle standby et après des changements réseau/listener.","caution":"Lecture seule. Si vous voyez \"Ready for Switchover: No\" ou un avertissement sur les standby redo logs, ne poursuivez pas le switchover. Une erreur de static connect identifier signifie que le broker ne peut pas démarrer l'instance pendant un switchover, ce qui impose une intervention manuelle.","since":"12c","note":"VALIDATE DATABASE est 12.1+, l'option SPFILE est 12.2+, NETWORK CONFIGURATION et STATIC CONNECT IDENTIFIER sont 18c+.","tags":["data guard","dgmgrl","validate","validate","switchover","standby redo log","broker"],"untested":true},{"id":"dg-switchover","task":"Effectuer un switchover planifié (DGMGRL, changement de rôle sans perte de données)","category":"Data Guard","command":"# Connect with a password (with OS authentication \"/\" the broker cannot restart the remote instance):\ndgmgrl sys@<primary_db_unique_name>\n# 1) Precheck:\nSHOW CONFIGURATION;\nVALIDATE DATABASE '<standby_db_unique_name>';\n# 2) Role change:\nSWITCHOVER TO '<standby_db_unique_name>';\n# 3) Verify: new primary and new standby\nSHOW CONFIGURATION;\nSHOW DATABASE '<primary_db_unique_name>';","what":"Inverse de façon contrôlée les rôles de la primaire et de la standby : tout le redo de la primaire est transmis et appliqué sur la standby, l'ancienne primaire redémarre en standby et le redo apply démarre automatiquement.","when":"Pour une maintenance planifiée sur le site primaire (OS, matériel, rolling upgrade), des exercices de DR ou des déménagements de data center.","caution":"La primaire subit une courte interruption ; les connexions applicatives doivent être redirigées vers la nouvelle primaire via des services définis avec -role PRIMARY et FAN/TAF/AC. Les transactions longues et un apply lag élevé allongent le switchover ; ramenez d'abord le lag près de zéro. En RAC, le broker gère lui-même les instances ; n'intervenez pas avec srvctl.","since":"12c","note":"En 12.1+, le broker effectue toutes les étapes en une commande ; sans broker, 11g utilisait ALTER DATABASE COMMIT TO SWITCHOVER en SQL (12c+ : ALTER DATABASE SWITCHOVER TO <db> VERIFY).","tags":["data guard","switchover","dgmgrl","role change","planned","standby","primary"],"untested":true},{"id":"dg-failover","task":"Effectuer un failover après la perte de la primaire (DGMGRL)","category":"Data Guard","command":"# Connect to the standby (the primary is unreachable):\ndgmgrl sys@<standby_db_unique_name>\n# 1) Assess the data that will be lost (Transport Lag / Apply Lag):\nSHOW DATABASE '<standby_db_unique_name>';\n# 2) Complete failover: all received redo is applied, then the role changes (recommended)\nFAILOVER TO '<standby_db_unique_name>';\n# 3) Emergency: immediately, without applying redo (more data loss)\n# FAILOVER TO '<standby_db_unique_name>' IMMEDIATE;\nSHOW CONFIGURATION;","what":"Promeut la standby au rôle de primaire lorsque la primaire est indisponible ; un failover complet applique d'abord tout le redo reçu, IMMEDIATE saute l'apply.","when":"Lorsque le site primaire, le stockage ou la base est perdu sans possibilité de récupération et que le RTO ne permet pas d'attendre un switchover.","caution":"Ce changement de rôle est irréversible. Assurez-vous d'abord que la primaire est VRAIMENT arrêtée ; si l'ancienne primaire reste ouverte et que l'application continue d'y écrire, vous obtenez un split-brain et des données divergentes (arrêtez/isolez l'ancienne primaire). En mode MaxPerformance, vous perdez autant de données que le transport lag. Ensuite, revoyez toutes les standbys et la stratégie de sauvegarde pour la nouvelle primaire.","note":"Pour récupérer l'ancienne primaire, voir la procédure dg-reinstate-database (exige flashback).","tags":["data guard","failover","dgmgrl","disaster","disaster recovery","data loss","split-brain"],"untested":true},{"id":"dg-reinstate-database","task":"Récupérer l'ancienne primaire en standby après un failover (REINSTATE)","category":"Data Guard","command":"# 1) Bring the old primary to MOUNT (sqlplus / as sysdba):\n#    SQL> STARTUP MOUNT;\n#    (RAC: srvctl start database -db <old_primary_db_unique_name> -startoption mount)\n# 2) From DGMGRL connected to the new primary:\ndgmgrl sys@<new_primary_db_unique_name>\nREINSTATE DATABASE '<old_primary_db_unique_name>';\n# 3) Verify:\nSHOW CONFIGURATION;\nSHOW DATABASE '<old_primary_db_unique_name>';","what":"Ramène l'ancienne primaire avant le SCN du failover avec flashback database, la convertit en physical standby et commence à recevoir le redo de la nouvelle primaire ; inutile de reconstruire la standby de zéro.","when":"Après un failover, une fois le serveur/stockage de l'ancienne primaire de nouveau accessible ; après un failover automatique FSFO (l'observer la réintègre généralement de lui-même).","caution":"Flashback doit avoir été activé sur l'ancienne primaire AVANT le failover et les flashback logs doivent couvrir le SCN du failover (db_flashback_retention_target, taille de la FRA) ; sinon le reinstate échoue et la standby doit être reconstruite avec RMAN duplicate. Les données écrites sur l'ancienne primaire après le failover sont définitivement perdues.","note":"Vérification préalable : la colonne flashback_on dans la procédure dg-readiness-sql.","tags":["data guard","reinstate","flashback","failover","old primary","standby","dgmgrl"],"untested":true},{"id":"dg-apply-transport-state","task":"Arrêter / démarrer le redo apply ou le redo transport avec le broker ; configurer un apply différé","category":"Data Guard","command":"# Redo apply on the standby:\nEDIT DATABASE '<standby_db_unique_name>' SET STATE='APPLY-OFF';\nEDIT DATABASE '<standby_db_unique_name>' SET STATE='APPLY-ON';\n# Redo transport on the primary (to all standbys):\nEDIT DATABASE '<primary_db_unique_name>' SET STATE='TRANSPORT-OFF';\nEDIT DATABASE '<primary_db_unique_name>' SET STATE='TRANSPORT-ON';\n# Delayed apply (minutes; protection against logical errors):\nEDIT DATABASE '<standby_db_unique_name>' SET PROPERTY DelayMins=240;\nSHOW DATABASE '<standby_db_unique_name>' DelayMins;","what":"Arrête/démarre de façon persistante, via le broker, le redo apply (MRP) sur la standby ou l'envoi du redo sur la primaire ; DelayMins retient le redo pendant la durée indiquée avant de l'appliquer.","when":"Pour une maintenance ou des sauvegardes sur la standby, des coupures réseau planifiées, le contrôle du transport lors de gros chargements en masse sur la primaire, ou la création d'une standby différée contre les suppressions accidentelles.","caution":"Tant que le broker est activé, n'utilisez pas ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL en SQL ; le broker rétablit l'état. Pendant APPLY-OFF, l'apply lag et le temps de failover (RTO) augmentent ; pendant TRANSPORT-OFF, les données générées sur la primaire ne sont pas protégées (RPO). Si FSFO est activé, ces opérations peuvent empêcher un failover.","note":"Avec DelayMins > 0, le redo est appliqué depuis les journaux archivés après le délai plutôt qu'en real-time apply ; la standby continue de recevoir le redo pendant le délai.","tags":["data guard","apply","transport","mrp","delaymins","stop","dgmgrl"],"untested":true},{"id":"dg-fsfo-observer","task":"Activer Fast-Start Failover (FSFO) et démarrer l'observer","category":"Data Guard","command":"# 1) Targets and threshold (seconds):\nEDIT DATABASE '<primary_db_unique_name>' SET PROPERTY FastStartFailoverTarget='<standby_db_unique_name>';\nEDIT DATABASE '<standby_db_unique_name>' SET PROPERTY FastStartFailoverTarget='<primary_db_unique_name>';\nEDIT CONFIGURATION SET PROPERTY FastStartFailoverThreshold=30;\n# 2) MaxAvailability for zero data loss (SYNC/FASTSYNC transport):\nEDIT DATABASE '<primary_db_unique_name>' SET PROPERTY LogXptMode='FASTSYNC';\nEDIT DATABASE '<standby_db_unique_name>' SET PROPERTY LogXptMode='FASTSYNC';\nEDIT CONFIGURATION SET PROTECTION MODE AS MAXAVAILABILITY;\n# Acceptable loss in MaxPerformance: EDIT CONFIGURATION SET PROPERTY FastStartFailoverLagLimit=30;\nENABLE FAST_START FAILOVER;\n# 3) Observer: on a third server, separate from primary and standby (passwordless connection via wallet)\ndgmgrl /@<primary_tns> \"START OBSERVER obs1 IN BACKGROUND FILE IS '/u01/app/oracle/dg/fsfo_obs1.dat' LOGFILE IS '/u01/app/oracle/dg/obs1.log' CONNECT IDENTIFIER IS <primary_tns>\"\n# 4) Verify:\nSHOW FAST_START FAILOVER;\nSHOW OBSERVER;","what":"Permet à l'observer de basculer automatiquement vers la standby après le seuil lorsque la primaire est injoignable, et de réintégrer automatiquement l'ancienne primaire à son retour.","when":"Sur les systèmes critiques dont l'objectif de RTO se compte en minutes, sans attendre d'intervention humaine.","caution":"L'observer ne doit pas tourner sur le même serveur/data center que la primaire ou la standby ; sinon FSFO échoue en cas de perte de site ou peut prendre la mauvaise décision lors d'une partition réseau. Flashback doit être activé sur les deux bases. Un seuil trop bas transforme de brèves coupures réseau en failovers inutiles. Pour une maintenance planifiée, exécutez DISABLE FAST_START FAILOVER ou tenez compte de l'observer avant d'arrêter la primaire.","since":"12c","note":"FASTSYNC est 12.2+, IN BACKGROUND et les observers multiples (SHOW OBSERVER) sont 12.2+. IN BACKGROUND exige des identifiants dans un Oracle wallet (mkstore).","tags":["data guard","fsfo","fast-start failover","observer","automatic failover","maxavailability","dgmgrl"],"untested":true},{"id":"dg-snapshot-standby","task":"Convertir une physical standby en snapshot standby et inversement (environnement de test)","category":"Data Guard","command":"# Make the standby read-write (redo is still received, apply stops):\nCONVERT DATABASE '<standby_db_unique_name>' TO SNAPSHOT STANDBY;\nSHOW DATABASE '<standby_db_unique_name>';\n# ... testing / application validation ...\n# Convert back: all test changes are discarded, accumulated redo is applied\nCONVERT DATABASE '<standby_db_unique_name>' TO PHYSICAL STANDBY;\nSHOW CONFIGURATION;","what":"Crée un guaranteed restore point automatique sur la standby et l'ouvre en lecture-écriture ; lors de la reconversion, il revient à ce point par flashback et applique le redo accumulé depuis la primaire.","when":"Pour répéter une mise à niveau, tester une version applicative ou exécuter Real Application Testing (DB Replay) sur des données de production sans créer de copie séparée.","caution":"Tant qu'elle est en snapshot standby, aucun redo n'est appliqué : le RPO est préservé mais le temps de failover (RTO) augmente avec le redo accumulé, et elle ne peut pas être une cible FSFO. Les flashback logs peuvent remplir la FRA ; surveillez l'espace de la FRA. Les changements effectués dans la snapshot sont définitivement abandonnés lors de la reconversion.","note":"Équivalent SQL : ALTER DATABASE CONVERT TO SNAPSHOT STANDBY / CONVERT TO PHYSICAL STANDBY (sans broker).","tags":["data guard","snapshot standby","test","restore point","flashback","convert","dgmgrl"],"untested":true},{"id":"rman-catalog-create","task":"Créer un catalogue de restauration RMAN et y enregistrer la base","category":"RMAN","command":"-- 1) In the catalog database (separate DB/PDB), with sqlplus:\nCREATE TABLESPACE rcat_ts DATAFILE '/u02/oradata/RCAT/rcat01.dbf' SIZE 1G AUTOEXTEND ON NEXT 256M;\nCREATE USER rcat IDENTIFIED BY \"<password>\"\n  DEFAULT TABLESPACE rcat_ts QUOTA UNLIMITED ON rcat_ts;\nGRANT RECOVERY_CATALOG_OWNER TO rcat;\n# 2) From the shell: create the catalog schema\nrman catalog rcat@<rcat_tns>\nCREATE CATALOG;\n# 3) Register the target database and verify:\nrman target / catalog rcat@<rcat_tns>\nREGISTER DATABASE;\nREPORT SCHEMA;\n# Resync controlfile records into the catalog (done automatically after backups):\nRESYNC CATALOG;","what":"Crée un catalogue de restauration (recovery catalog) qui stocke les métadonnées de sauvegarde dans un schéma central, indépendamment de la rétention du controlfile (control_file_record_keep_time), et y enregistre la base cible.","when":"Pour conserver en un seul endroit l'historique des sauvegardes de nombreuses bases, utiliser indifféremment les sauvegardes primaire/standby sous Data Guard, et pour les sauvegardes à long terme (KEEP) et les scripts stockés.","caution":"La base du catalogue doit se trouver sur un serveur différent de celui des bases qu'elle protège et doit elle-même être sauvegardée. La version du schéma du catalogue ne peut pas être inférieure à celle du client RMAN le plus récent utilisé ; un client plus récent exige UPGRADE CATALOG. Sous Data Guard, n'enregistrez que la primaire ; les standbys sont définies par DB_UNIQUE_NAME avec CONFIGURE DB_UNIQUE_NAME ... CONNECT IDENTIFIER.","note":"Gardez le schéma du catalogue dans une base séparée ; s'il est hébergé avec la cible, un sinistre emporte aussi le catalogue.","tags":["rman","catalog","catalog","recovery catalog","register","backup","backup"],"untested":true},{"id":"rman-duplicate-standby","task":"Créer une physical standby depuis la base active avec RMAN DUPLICATE","category":"RMAN","command":"# Prerequisites: standby instance in NOMOUNT with a pfile containing only db_name,\n# primary password file copied, static listener entries and tnsnames on both sides.\nrman target sys@<primary_tns> auxiliary sys@<standby_tns>\nRUN {\n  ALLOCATE CHANNEL p1 DEVICE TYPE DISK;\n  ALLOCATE CHANNEL p2 DEVICE TYPE DISK;\n  ALLOCATE AUXILIARY CHANNEL s1 DEVICE TYPE DISK;\n  ALLOCATE AUXILIARY CHANNEL s2 DEVICE TYPE DISK;\n  DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE\n    USING COMPRESSED BACKUPSET SECTION SIZE 32G\n    DORECOVER\n    SPFILE\n      SET db_unique_name='<standby_db_unique_name>'\n      SET control_files='+DATA','+FRA'\n      SET db_create_file_dest='+DATA'\n      SET db_recovery_file_dest='+FRA'\n      SET fal_server='<primary_tns>'\n      SET log_archive_dest_2=''\n      SET dg_broker_start='FALSE'\n    NOFILENAMECHECK;\n}","what":"Copie les datafiles de la primaire par le réseau vers le serveur standby (sans fichiers de sauvegarde intermédiaires), crée le spfile adapté à la standby et applique les archivelogs disponibles avec DORECOVER.","when":"Lors de la création d'une nouvelle standby Data Guard ou de la reconstruction d'une standby qui ne peut pas être réintégrée.","caution":"Génère de la charge réseau et d'E/S sur la primaire ; ne l'exécutez pas aux heures de pointe, ou limitez le nombre de canaux. NOFILENAMECHECK n'est sûr que lorsque la standby est sur un serveur SÉPARÉ ; sur le même serveur, il peut écraser les fichiers de la primaire. Ajoutez les standby redo logs (même taille que les online redo de la primaire, un groupe de plus par thread) avant ou après, puis configurez le broker.","since":"12c","note":"Le duplicate actif avec USING [COMPRESSED] BACKUPSET et SECTION SIZE est 12c+ ; 11g utilise des image copies. Si vous utilisez un système de fichiers au lieu d'ASM, définissez db_file_name_convert/log_file_name_convert.","tags":["rman","duplicate","standby","active database","data guard","setup","clone"],"untested":true},{"id":"rman-crosscheck-cleanup","task":"Valider et nettoyer les enregistrements de sauvegarde RMAN (CROSSCHECK, EXPIRED, OBSOLETE)","category":"RMAN","command":"SHOW RETENTION POLICY;\n# 1) Compare records with the physical files (missing ones become EXPIRED):\nCROSSCHECK BACKUP;\nCROSSCHECK COPY;\nCROSSCHECK ARCHIVELOG ALL;\n# 2) Review before deleting:\nLIST EXPIRED BACKUP;\nLIST EXPIRED ARCHIVELOG ALL;\nREPORT OBSOLETE;\n# 3) Clean up:\nDELETE NOPROMPT EXPIRED BACKUP;\nDELETE NOPROMPT EXPIRED COPY;\nDELETE NOPROMPT EXPIRED ARCHIVELOG ALL;\nDELETE NOPROMPT OBSOLETE;","what":"CROSSCHECK vérifie si les sauvegardes figurant dans les enregistrements RMAN existent encore sur disque/bande ; DELETE EXPIRED supprime les enregistrements des fichiers manquants, et DELETE OBSOLETE supprime, des enregistrements comme du disque, les sauvegardes dont la politique de rétention n'a plus besoin.","when":"Lorsque des fichiers de sauvegarde ont été supprimés/déplacés au niveau OS, lorsque la FRA se remplit, et dans le cadre de la tâche régulière de maintenance des sauvegardes.","caution":"DELETE OBSOLETE supprime physiquement les fichiers ; vérifiez d'abord la liste avec REPORT OBSOLETE et assurez-vous que la politique de rétention (RECOVERY WINDOW / REDUNDANCY) est celle attendue. Si vous lancez CROSSCHECK alors que le gestionnaire de bandes/médias est temporairement injoignable, des sauvegardes valides apparaissent EXPIRED ; n'exécutez pas DELETE EXPIRED à ce moment-là.","note":"Sous Data Guard, utilisez CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY pour la suppression des archivelogs.","tags":["rman","crosscheck","expired","obsolete","cleanup","retention","backup"],"untested":true},{"id":"rman-validate","task":"Valider la base et les sauvegardes contre la corruption (VALIDATE)","category":"RMAN","command":"# Physical + logical block check (without producing a backup):\nVALIDATE CHECK LOGICAL DATABASE;\n# Scan large datafiles in parallel (11g+, needs multiple channels):\nVALIDATE CHECK LOGICAL DATABASE SECTION SIZE 16G;\n# A single datafile / tablespace:\nVALIDATE DATAFILE 7;\nVALIDATE TABLESPACE users;\n# Read as if taking a backup and check (including archivelogs):\nBACKUP VALIDATE CHECK LOGICAL DATABASE ARCHIVELOG ALL;\n# Readability of a specific backup set:\nLIST BACKUP SUMMARY;\nVALIDATE BACKUPSET <bs_key>;\n-- Corrupt blocks found and the affected segment (SQL):\nSELECT file#, block#, blocks, corruption_change#, corruption_type\nFROM   v$database_block_corruption ORDER BY file#, block#;\nSELECT DISTINCT e.owner, e.segment_name, e.segment_type\nFROM   dba_extents e\nJOIN   v$database_block_corruption c\n  ON   e.file_id = c.file#\n AND   c.block# BETWEEN e.block_id AND e.block_id + e.blocks - 1;","what":"Lit les datafiles et les backup sets pour détecter la corruption de blocs physique et (avec CHECK LOGICAL) logique, en consignant les résultats dans v$database_block_corruption. La requête SQL montre à quelle table/quel index appartient le bloc corrompu.","when":"Après des erreurs de corruption comme ORA-01578/ORA-08103, après des problèmes de stockage, et comme contrôle d'intégrité régulier (hebdomadaire) ; pour prouver que les sauvegardes sont réellement restaurables.","caution":"Il effectue une lecture complète ; sur les grosses bases il génère beaucoup d'E/S, exécutez-le donc hors des heures de pointe. La requête sur dba_extents peut être lente sur les grosses bases. Si le bloc corrompu se trouve dans l'espace temporaire (TEMP) ou libre, aucun objet correspondant n'est renvoyé.","since":"11g","note":"Les requêtes ne renvoient aucune ligne en l'absence de corruption. VALIDATE avec SECTION SIZE est 11g+.","tags":["rman","validate","corruption","corruption","check logical","block","ora-01578"],"untested":true},{"id":"rman-restore-preview","task":"Voir quelles sauvegardes une restauration utiliserait et les valider (PREVIEW / VALIDATE)","category":"RMAN","command":"# Backups and archivelogs a restore needs (without reading the files):\nRESTORE DATABASE PREVIEW SUMMARY;\n# For a return to a point in time:\nRESTORE DATABASE UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\" PREVIEW;\n# Validate the backups by actually reading them (without restoring):\nRESTORE DATABASE VALIDATE;\nRESTORE ARCHIVELOG FROM TIME 'SYSDATE-1' VALIDATE;\nRESTORE CONTROLFILE VALIDATE;\nRESTORE SPFILE VALIDATE;","what":"PREVIEW indique, d'après le catalogue/le controlfile, quels backup sets/copies et quelle plage d'archivelogs une restauration utiliserait ; VALIDATE lit réellement ces sauvegardes et vérifie l'absence de corruption ou de fichier manquant.","when":"Pour planifier avant une vraie restauration, prouver que la stratégie de sauvegarde atteint l'objectif de RPO, et comme test de restauration périodique.","caution":"PREVIEW ne consulte que les enregistrements ; il est trompeur si des fichiers ont été supprimés, exécutez donc d'abord CROSSCHECK ou utilisez RESTORE ... VALIDATE. VALIDATE lit toute la sauvegarde ; cela peut être long via bande/réseau. Faites dépendre la date du masque TO_DATE, et non des paramètres NLS de la session.","note":"PREVIEW RECALL peut aussi demander le rappel des sauvegardes devant être récupérées depuis bande (exige la prise en charge par le media manager).","tags":["rman","restore","preview","validate","restore test","backup","rpo"],"untested":true},{"id":"rman-block-media-recovery","task":"Réparer en ligne des blocs corrompus (block media recovery, RECOVER CORRUPTION LIST)","category":"RMAN","command":"# 1) Detect corrupt blocks (populates v$database_block_corruption):\nVALIDATE CHECK LOGICAL DATABASE;\n# 2) Repair all blocks in the list (the datafile stays online):\nRECOVER CORRUPTION LIST;\n# or the file/block from the error message (ORA-01578: file# 7, block# 233):\nRECOVER DATAFILE 7 BLOCK 233;\nRECOVER DATAFILE 7 BLOCK 233 DATAFILE 9 BLOCK 1001;\n# 3) Verify:\nVALIDATE DATAFILE 7;\nSELECT * FROM v$database_block_corruption;","what":"Restaure uniquement les blocs corrompus depuis une sauvegarde (ou depuis les flashback logs / une standby Active Data Guard) et les fait avancer avec le redo ; le datafile et le tablespace restent en ligne, et l'application ne reçoit d'erreurs que lorsqu'elle accède à ces blocs.","when":"Lorsqu'une corruption de blocs isolée apparaît avec ORA-01578 / ORA-01110 ; lorsque restaurer tout le datafile provoquerait une interruption inutile.","caution":"Exige une sauvegarde saine contenant le bloc et tous les archivelogs de ce moment à maintenant ; une corruption causée par une opération NOLOGGING (ORA-26040) ne peut pas être réparée ainsi, l'objet doit être reconstruit. Il s'agit d'une fonctionnalité Enterprise Edition. Si vous ne trouvez pas la source de la corruption (stockage, HBA, firmware), elle se reproduira.","note":"Avec Active Data Guard, la block media recovery automatique 11.2+ répare automatiquement les blocs corrompus depuis la standby.","tags":["rman","block recovery","corrupt block","corruption","ora-01578","recover","repair"],"untested":true},{"id":"rman-tspitr","task":"Ramener un tablespace à un point dans le passé (TSPITR)","category":"RMAN","command":"-- 1) Is the tablespace self-contained (SQL, on the target DB):\nEXEC DBMS_TTS.TRANSPORT_SET_CHECK(ts_list => 'USERS', incl_constraints => TRUE, full_check => TRUE);\nSELECT * FROM transport_set_violations;\n-- 2) Objects created AFTER the target time that TSPITR will lose:\nSELECT owner, name, tablespace_name,\n       TO_CHAR(creation_time,'YYYY-MM-DD HH24:MI:SS') AS created\nFROM   ts_pitr_objects_to_be_dropped\nWHERE  tablespace_name = 'USERS'\n  AND  creation_time > TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS');\n# 3) RMAN: with an automatic auxiliary instance\nRECOVER TABLESPACE users\n  UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\"\n  AUXILIARY DESTINATION '/u03/aux';\n# 4) The tablespace is left OFFLINE: take a backup first, then bring it online\nBACKUP TABLESPACE users;\nALTER TABLESPACE users ONLINE;","what":"RMAN récupère SYSTEM/UNDO/SYSAUX et le tablespace cible jusqu'à l'instant demandé dans une instance auxiliaire temporaire, puis le rebranche dans la base cible par transportable tablespace ; le reste de la base reste à jour.","when":"Lorsqu'un batch défaillant ou un DROP/TRUNCATE erroné a endommagé les données d'un seul tablespace (ou d'un ensemble cohérent de tablespaces) et que les options flashback ne suffisent pas.","caution":"TOUS les changements effectués dans ce tablespace et tous les objets créés après l'instant cible sont perdus. L'ensemble de tablespaces doit être autonome (aucun index/table, partition, LOB ou FK dans un autre tablespace). Il ne peut pas être utilisé pour les tablespaces contenant des objets SYSTEM, UNDO ou SYS. Après un TSPITR, les sauvegardes antérieures de ce tablespace sont inutilisables ; faites immédiatement une sauvegarde. AUXILIARY DESTINATION nécessite de l'espace libre pour SYSTEM+SYSAUX+UNDO+le tablespace cible.","note":"Pour une seule table, rman-recover-table est moins destructif.","tags":["rman","tspitr","tablespace","pitr","point-in-time","auxiliary","rollback"],"untested":true},{"id":"rman-recover-table","task":"Récupérer une seule table à un instant passé depuis une sauvegarde RMAN (RECOVER TABLE)","category":"RMAN","command":"# Restore under a new name (the original table is not touched):\nRECOVER TABLE hr.employees\n  UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\"\n  AUXILIARY DESTINATION '/u03/aux'\n  REMAP TABLE hr.employees:employees_rec;\n# Multitenant: a table inside a PDB\nRECOVER TABLE hr.employees OF PLUGGABLE DATABASE <pdb>\n  UNTIL SCN 12345678\n  AUXILIARY DESTINATION '/u03/aux'\n  REMAP TABLE hr.employees:employees_rec;\n# Only produce a Data Pump dump, do not import:\nRECOVER TABLE hr.employees\n  UNTIL SCN 12345678\n  AUXILIARY DESTINATION '/u03/aux'\n  DATAPUMP DESTINATION '/u03/dump'\n  DUMP FILE 'employees_rec.dmp'\n  NOTABLEIMPORT;","what":"RMAN récupère une instance auxiliaire automatique jusqu'à l'instant demandé, exporte la table (ou la partition) avec Data Pump et l'importe dans la base cible sous un autre nom avec REMAP ; l'instance auxiliaire est ensuite supprimée.","when":"Pour récupérer les données d'une seule table supprimées/mises à jour par erreur ou supprimée avec PURGE, au-delà de ce que permet la rétention undo de Flashback Table/Query.","caution":"AUXILIARY DESTINATION nécessite de l'espace libre pour SYSTEM, SYSAUX, UNDO et le tablespace de la table ; l'opération peut durer des heures. Sans REMAP TABLE, l'opération échoue si une table de même nom existe ; utilisez toujours REMAP ou NOTABLEIMPORT pour éviter d'écraser l'original. Les tables des schémas SYS/SYSTEM et des tablespaces SYSTEM/SYSAUX ne peuvent pas être récupérées ainsi. Exige Enterprise Edition.","since":"12c","note":"Nécessaire : une sauvegarde complète plus les archivelogs couvrant l'instant cible. Pour une partition : RECOVER TABLE hr.sales:p2025 ... REMAP TABLE hr.sales:p2025:sales_p2025_rec.","tags":["rman","recover table","table recovery","pitr","auxiliary","remap","accidental delete"],"untested":true},{"id":"pdb-create-from-seed","task":"Créer une nouvelle PDB depuis PDB$SEED","category":"Multitenant","command":"-- In CDB$ROOT, as SYSDBA:\nCREATE PLUGGABLE DATABASE <pdb>\n  ADMIN USER pdbadmin IDENTIFIED BY \"<password>\" ROLES = (DBA)\n  DEFAULT TABLESPACE users\n    DATAFILE '/u02/oradata/<cdb>/<pdb>/users01.dbf' SIZE 512M AUTOEXTEND ON NEXT 64M\n  FILE_NAME_CONVERT = ('/u02/oradata/<cdb>/pdbseed/', '/u02/oradata/<cdb>/<pdb>/')\n  STORAGE (MAXSIZE 50G);\n-- With OMF/ASM, instead of FILE_NAME_CONVERT: CREATE_FILE_DEST = '+DATA'\nALTER PLUGGABLE DATABASE <pdb> OPEN;\nALTER PLUGGABLE DATABASE <pdb> SAVE STATE;\nSELECT con_id, name, open_mode, restricted FROM v$pdbs ORDER BY con_id;","what":"Crée une nouvelle pluggable database vide à partir du modèle seed ; définit dans la même commande l'utilisateur admin local, le tablespace par défaut et la limite de taille totale.","when":"Lors de l'ouverture d'une base isolée pour une nouvelle application/un nouveau schéma ; lors de la préparation d'environnements de test et de développement.","caution":"La nouvelle PDB démarre en MOUNTED ; elle nécessite OPEN et SAVE STATE (pour rester ouverte après un redémarrage de la CDB). Des chemins FILE_NAME_CONVERT erronés écrivent les fichiers dans le mauvais répertoire ; sans OMF, un chemin de fichier complet est obligatoire pour DATAFILE (ORA-02236). Licence : en 19c EE, 3 PDB utilisateur par CDB peuvent être utilisées sans l'option Multitenant.","since":"12c","note":"En RAC, ajoutez un service applicatif à la PDB avec srvctl add service -pdb.","tags":["multitenant","pdb","create","create pluggable database","seed","cdb","pdb$seed"]},{"id":"pdb-clone-local","task":"Cloner une PDB dans la même CDB (clone local / à chaud)","category":"Multitenant","command":"-- Hot clone prerequisites: local undo + ARCHIVELOG\nSELECT property_value AS local_undo\nFROM   database_properties WHERE property_name = 'LOCAL_UNDO_ENABLED';\nSELECT log_mode FROM v$database;\n\nCREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb>\n  FILE_NAME_CONVERT = ('/<pdb>/', '/<new_pdb>/');\n-- OMF/ASM: CREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb> CREATE_FILE_DEST = '+DATA';\nALTER PLUGGABLE DATABASE <new_pdb> OPEN;\nSELECT name, open_mode FROM v$pdbs WHERE name = UPPER('<new_pdb>');","what":"Crée une nouvelle PDB dans la même CDB en copiant les datafiles de la PDB source ; avec local undo et ARCHIVELOG, cela peut s'exécuter pendant que la source est ouverte en lecture-écriture (hot clone).","when":"Pour créer une copie de test/développement d'une PDB de production, répéter une mise à niveau ou un patch, ou faire une copie rapide avant un changement risqué.","caution":"La copie génère des E/S supplémentaires sur la source et nécessite autant d'espace que la source sur la cible. En mode shared undo ou en NOARCHIVELOG, la source doit être READ ONLY (documentation 19c). Le clone contient des données de production : vérifiez le masquage, les DB links, les jobs (DBMS_SCHEDULER) et les connexions aux systèmes externes avant de l'ouvrir.","since":"12c","note":"Un hot clone avec la source en lecture-écriture exige local undo + ARCHIVELOG en 19c. Pour une copie fine rapide, utilisez SNAPSHOT COPY (exige un stockage adapté).","tags":["multitenant","pdb","clone","clone","hot clone","copy","local undo"]},{"id":"pdb-clone-remote","task":"Cloner une PDB depuis une autre CDB via un DB link (remote clone)","category":"Multitenant","command":"-- 1) In the SOURCE CDB, a common user:\nCREATE USER c##clone_user IDENTIFIED BY \"<password>\" CONTAINER = ALL;\nGRANT CREATE SESSION, CREATE PLUGGABLE DATABASE TO c##clone_user CONTAINER = ALL;\n\n-- 2) In the TARGET CDB root, DB link and test:\nCREATE DATABASE LINK clone_link\n  CONNECT TO c##clone_user IDENTIFIED BY \"<password>\"\n  USING '//<source_host>:1521/<source_cdb_service>';\nSELECT * FROM dual@clone_link;\n\n-- 3) Clone and open:\nCREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb>@clone_link\n  FILE_NAME_CONVERT = ('/<pdb>/', '/<new_pdb>/');\nALTER PLUGGABLE DATABASE <new_pdb> OPEN;\n\n-- 4) Violations (version, options, parameters, patches):\nSELECT name, cause, type, message\nFROM   pdb_plug_in_violations\nWHERE  name = UPPER('<new_pdb>') AND status <> 'RESOLVED';","what":"Copie une PDB de la CDB source vers la CDB cible par le réseau via un DB link ; aucun fichier intermédiaire ni unplug nécessaire.","when":"Pour déplacer une PDB vers un autre serveur/une autre CDB, créer une copie de test de la production sur un serveur séparé, ou passer à une CDB de version plus récente.","caution":"Les CDB source et cible doivent être compatibles en endianness, jeu de caractères (ou cible AL32UTF8) et options ; une PDB clonée vers une version plus récente s'ouvre d'abord en RESTRICTED et nécessite datapatch/upgrade. Supprimez le DB link et le common user une fois le clone terminé. Le hot clone exige local undo + ARCHIVELOG sur la source.","since":"12c","note":"Pour la déplacer (la source est supprimée), utilisez l'option RELOCATE 12.2+ : CREATE PLUGGABLE DATABASE <pdb> FROM <pdb>@clone_link RELOCATE.","tags":["multitenant","pdb","remote clone","db link","clone","migration","relocate"]},{"id":"pdb-unplug-plug","task":"Débrancher une PDB et la brancher dans une autre CDB (ou la même)","category":"Multitenant","command":"-- SOURCE CDB: close and unplug to an XML manifest\nALTER PLUGGABLE DATABASE <pdb> CLOSE IMMEDIATE;\nALTER PLUGGABLE DATABASE <pdb> UNPLUG INTO '/u01/stage/<pdb>.xml';\n-- (Single-file archive: UNPLUG INTO '/u01/stage/<pdb>.pdb' - includes datafiles, 12.2+)\nDROP PLUGGABLE DATABASE <pdb> KEEP DATAFILES;\n\n-- TARGET CDB: check compatibility first\nSET SERVEROUTPUT ON\nDECLARE\n  l_ok BOOLEAN;\nBEGIN\n  l_ok := DBMS_PDB.CHECK_PLUG_COMPATIBILITY(\n            pdb_descr_file => '/u01/stage/<pdb>.xml',\n            pdb_name       => '<pdb>');\n  DBMS_OUTPUT.PUT_LINE(CASE WHEN l_ok THEN 'COMPATIBLE' ELSE 'NOT COMPATIBLE - see PDB_PLUG_IN_VIOLATIONS' END);\nEND;\n/\nSELECT name, cause, type, status, message\nFROM   pdb_plug_in_violations\nWHERE  name = UPPER('<pdb>') AND status <> 'RESOLVED';\n\n-- NOCOPY if the files stay in place, COPY FILE_NAME_CONVERT if they move:\nCREATE PLUGGABLE DATABASE <pdb> USING '/u01/stage/<pdb>.xml' NOCOPY TEMPFILE REUSE;\n-- From a .pdb archive: CREATE PLUGGABLE DATABASE <pdb> USING '/u01/stage/<pdb>.pdb'\n--                    FILE_NAME_CONVERT = ('/u01/stage/', '/u02/oradata/<cdb>/<pdb>/');\nALTER PLUGGABLE DATABASE <pdb> OPEN;","what":"Détache la PDB de la CDB et écrit ses métadonnées dans un manifeste XML (ou dans une archive .pdb unique avec les datafiles) ; après une vérification de compatibilité, la rebranche dans la CDB cible avec les mêmes fichiers.","when":"Pour déplacer une PDB vers une CDB de version plus récente sur le même serveur (mise à niveau unplug/plug), pour la consolidation de CDB, ou pour des déplacements où le clonage par le réseau n'est pas possible.","caution":"Après l'unplug, la PDB est indisponible dans la CDB source (interruption). Les fichiers branchés avec NOCOPY appartiennent désormais à la CDB cible ; faites d'abord une copie au niveau fichier ou une sauvegarde RMAN pour pouvoir revenir en arrière en cas d'échec. Tant que les violations de type ERROR ne sont pas résolues, la PDB s'ouvre en RESTRICTED. En la rebranchant dans la même CDB, omettre AS CLONE peut provoquer un conflit de GUID.","since":"12c","note":"Une archive .pdb (12.2+) inclut les datafiles, elle se déplace donc en un seul fichier ; avec la méthode XML, les datafiles sont copiés séparément.","tags":["multitenant","pdb","unplug","plug","migration","xml manifest","check_plug_compatibility"]},{"id":"pdb-open-save-state","task":"Ouvrir les PDB et les garder ouvertes après un redémarrage de la CDB (SAVE STATE)","category":"Multitenant","command":"-- Open all PDBs (RAC: INSTANCES = ALL):\nALTER PLUGGABLE DATABASE ALL OPEN;\n-- ALTER PLUGGABLE DATABASE <pdb> OPEN INSTANCES = ALL;\n-- Save the current open state (opens automatically after a CDB restart):\nALTER PLUGGABLE DATABASE <pdb> SAVE STATE;\n-- Saved states:\nSELECT con_name, instance_name, state, restricted FROM dba_pdb_saved_states;\n-- Remove the saved state:\nALTER PLUGGABLE DATABASE <pdb> DISCARD STATE;\n-- If it opens RESTRICTED, find the reason:\nSELECT name, cause, type, message\nFROM   pdb_plug_in_violations\nWHERE  status <> 'RESOLVED';","what":"Ouvre les PDB en READ WRITE et enregistre leur état d'ouverture actuel pour que la PDB s'ouvre automatiquement dans le même mode au redémarrage de la CDB.","when":"Après la création, le branchement ou le clonage d'une PDB ; lorsque vous constatez que des PDB restent MOUNTED après un redémarrage de la CDB.","caution":"SAVE STATE enregistre l'état au moment de la commande : si vous l'exécutez alors que la PDB est fermée, l'état \"fermé\" est enregistré (ou l'enregistrement est supprimé). En RAC, l'état est par instance ; avec une gestion par services (services PDB via srvctl), le démarrage par Clusterware est préférable à SAVE STATE. Sur une standby Data Guard, le mode d'ouverture dépend du rôle.","since":"12c","note":"12.1.0.2+.","tags":["multitenant","pdb","open","save state","auto open","restricted","dba_pdb_saved_states"]},{"id":"pdb-resource-limits","task":"Définir des limites de CPU, mémoire, E/S et stockage par PDB","category":"Multitenant","command":"ALTER SESSION SET CONTAINER = <pdb>;\n-- CPU (instance caging with Resource Manager; at PDB level in 19c):\nALTER SYSTEM SET cpu_count = 2 SCOPE = BOTH;\n-- Memory (sga_min_size guarantee, sga_target upper limit):\nALTER SYSTEM SET sga_target = 512M SCOPE = BOTH;\nALTER SYSTEM SET sga_min_size = 96M SCOPE = BOTH;\nALTER SYSTEM SET pga_aggregate_limit = 2G SCOPE = BOTH;\n-- I/O (non-Exadata):\nALTER SYSTEM SET max_iops = 5000 SCOPE = BOTH;\nALTER SYSTEM SET max_mbps = 200 SCOPE = BOTH;\n-- Number of sessions (after the PDB is reopened):\nALTER SYSTEM SET sessions = 200 SCOPE = SPFILE;\n-- Storage:\nALTER PLUGGABLE DATABASE STORAGE (MAXSIZE 10G MAX_SHARED_TEMP_SIZE 2G);\n\n-- Settings across all PDBs, from the root:\nALTER SESSION SET CONTAINER = CDB$ROOT;\nSELECT p.name AS pdb, sp.name, sp.value\nFROM   v$system_parameter sp JOIN v$pdbs p ON p.con_id = sp.con_id\nWHERE  sp.name IN ('cpu_count','sga_target','sga_min_size','max_iops','max_mbps','pga_aggregate_limit')\nORDER  BY p.name, sp.name;","what":"Limite, via des paramètres au niveau PDB, la part de CPU, SGA/PGA, IOPS/MBPS et de stockage total de la CDB qu'une PDB peut utiliser ; empêche les PDB \"voisines bruyantes\" d'affecter les autres.","when":"Dans les CDB consolidées, lorsque des applications ayant des SLA différents tournent dans la même CDB, ou lorsqu'on constate qu'une PDB épuise tout le serveur.","caution":"Le sga_target de la PDB doit être inférieur au sga_target de la CDB ; des valeurs trop basses provoquent des ORA-04031 et des problèmes de performances. La somme des sga_min_size ne doit pas réserver toute la SGA de la CDB. Pour que cpu_count soit effectif dans une PDB, un plan resource manager doit être actif dans la CDB. Lorsque MAXSIZE est atteint, la PDB reçoit ORA-65114.","since":"12c","note":"23ai exige que sga_min_size soit inférieur à 20 % de sga_target (ORA-56746), la limite en 19c est de 50 %. MAX_IOPS/MAX_MBPS sont 12.2+, les paramètres mémoire des PDB sont 12.2+.","tags":["multitenant","pdb","resource","resource","cpu_count","sga_target","maxsize"]},{"id":"pdb-container-context","task":"Vérifier dans quel conteneur vous êtes et interroger toutes les PDB depuis la racine (CDB_ / CONTAINERS)","category":"Multitenant","command":"-- Current container:\nSHOW CON_NAME\nSHOW CON_ID\nSHOW PDBS\nSELECT SYS_CONTEXT('USERENV','CON_NAME') AS con_name,\n       SYS_CONTEXT('USERENV','CON_ID')   AS con_id\nFROM   dual;\n\n-- All containers, from the root:\nSELECT con_id, name, open_mode FROM v$containers ORDER BY con_id;\n\n-- CDB_ views combine all open PDBs with CON_ID:\nSELECT c.name AS container, t.tablespace_name, t.status\nFROM   cdb_tablespaces t JOIN v$containers c ON c.con_id = t.con_id\nORDER  BY c.name, t.tablespace_name;\n\n-- Query any table/view across all PDBs with CONTAINERS():\nSELECT con_id, COUNT(*) AS users\nFROM   CONTAINERS(dba_users)\nGROUP  BY con_id ORDER BY con_id;","what":"Affiche le conteneur auquel la session est connectée ; depuis la racine, les vues CDB_ et la clause CONTAINERS() collectent en une seule requête les informations du dictionnaire de toutes les PDB ouvertes.","when":"Pour vous assurer d'être dans le bon conteneur avant un DDL/changement de paramètre, et pour établir un inventaire de l'espace, des utilisateurs et des objets sur toutes les PDB.","caution":"Lecture seule. Ne créez pas par erreur d'objets applicatifs ou d'utilisateurs locaux dans la racine (CDB$ROOT) ; ALTER SYSTEM dans la racine affecte toute la CDB. Les vues CDB_ et CONTAINERS() ne renvoient que les PDB ouvertes ; les PDB MOUNTED n'apparaissent pas dans le résultat.","since":"12c","note":"Pour changer de conteneur : ALTER SESSION SET CONTAINER = <pdb>; (voir rosetta \"Change the active database\").","tags":["multitenant","container","con_name","cdb_","containers","pdb","root"]},{"id":"pdb-refreshable-clone","task":"Créer une refreshable PDB, la rafraîchir et la convertir en PDB indépendante","category":"Multitenant","command":"-- In the target CDB root (DB link to the source ready: see pdb-clone-remote):\nCREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb>@clone_link\n  FILE_NAME_CONVERT = ('/<pdb>/', '/<new_pdb>/')\n  REFRESH MODE EVERY 60 MINUTES;\nSELECT pdb_name, status, refresh_mode, refresh_interval, last_refresh_scn\nFROM   dba_pdbs WHERE pdb_name = UPPER('<new_pdb>');\n\n-- Can only be opened READ ONLY; close it first to refresh manually:\nALTER PLUGGABLE DATABASE <new_pdb> OPEN READ ONLY;\nALTER PLUGGABLE DATABASE <new_pdb> CLOSE IMMEDIATE;\nALTER PLUGGABLE DATABASE <new_pdb> REFRESH;\n\n-- Change the mode (MANUAL) or convert to an independent, writable PDB (irreversible):\nALTER PLUGGABLE DATABASE <new_pdb> REFRESH MODE MANUAL;\nALTER PLUGGABLE DATABASE <new_pdb> REFRESH MODE NONE;\nALTER PLUGGABLE DATABASE <new_pdb> OPEN;","what":"Crée une copie maintenue à jour en appliquant le redo de la PDB source périodiquement ou à la demande ; elle s'ouvre en READ ONLY pour le reporting et devient une PDB indépendante en lecture-écriture avec REFRESH MODE NONE.","when":"Pour une copie de reporting/test de la production rafraîchie régulièrement, une migration de PDB à faible interruption (refresh final + REFRESH MODE NONE), ou une \"standby bon marché au niveau PDB\".","caution":"La PDB doit être fermée pendant un refresh ; tant qu'elle est ouverte en READ ONLY, le refresh automatique attend. Les archivelogs nécessaires au refresh doivent être accessibles sur la source. REFRESH MODE NONE est irréversible ; la copie ne peut plus être rafraîchie. Exige local undo + ARCHIVELOG sur la source. Cela ne remplace pas Data Guard pour un vrai DR.","since":"12c","note":"12.2+. Après REFRESH MODE NONE, le clone devient une PDB ordinaire et peut être ouvert en READ WRITE ; c'est irréversible.","tags":["multitenant","pdb","refreshable","refresh","refresh","reporting","clone"]}]},"recipes/oracle-ops.json":{"engineId":"oracle","engine":"Oracle","order":2,"recipes":[{"id":"awr-snapshot-report","task":"Prendre un snapshot AWR et générer un rapport AWR entre deux snapshots","category":"Performances","command":"-- 1) Manual snapshot before and after the event (run in CDB root):\nEXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT;\n\n-- 2) List snapshots:\nSELECT snap_id,\n       TO_CHAR(begin_interval_time,'YYYY-MM-DD HH24:MI') AS begin_time,\n       TO_CHAR(end_interval_time,'YYYY-MM-DD HH24:MI')   AS end_time\nFROM   dba_hist_snapshot\nWHERE  dbid = (SELECT dbid FROM v$database)\nORDER  BY snap_id DESC\nFETCH FIRST 24 ROWS ONLY;\n\n-- 3a) Interactive report (prompts for HTML/TEXT and snapshot range):\n-- @?/rdbms/admin/awrrpt.sql      (RAC: awrgrpt.sql, specific instance: awrrpti.sql)\n\n-- 3b) TEXT report from a script:\nSET LINESIZE 8000 PAGESIZE 0 TRIMSPOOL ON HEADING OFF FEEDBACK OFF\nSPOOL /tmp/awr_<begin_snap>_<end_snap>.txt\nSELECT output\nFROM   TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_TEXT(\n         l_dbid     => (SELECT dbid FROM v$database),\n         l_inst_num => (SELECT instance_number FROM v$instance),\n         l_bid      => <begin_snap>,\n         l_eid      => <end_snap>));\nSPOOL OFF\n-- For HTML: DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_HTML (same parameters)","what":"Produit un rapport entre deux snapshots AWR couvrant la fenêtre du problème : profil de charge, principaux événements d'attente, principaux SQL et statistiques d'instance. La fonction table permet d'extraire le rapport depuis un script ou un job du scheduler.","when":"Pour analyser un ralentissement sur une fenêtre de temps précise, comparer avant/après autour d'un test de charge ou d'un batch, et pour le reporting de capacité.","caution":"Exige la licence Diagnostics Pack (CONTROL_MANAGEMENT_PACK_ACCESS='DIAGNOSTIC' ou 'DIAGNOSTIC+TUNING') ; sans elle, mettez le paramètre à 'NONE' et utilisez Statspack. Un rapport ne peut pas couvrir un redémarrage d'instance. Les plages de plus d'une heure lissent les moyennes et masquent les pics courts ; gardez une fenêtre serrée.","note":"Pour l'AWR au niveau PDB (12.2+), vérifiez AWR_PDB_AUTOFLUSH_ENABLED=TRUE et AWR_SNAPSHOT_TIME_OFFSET/AWR_PDB_MAX_PARALLEL_SLAVES ; un rapport pris dans une PDB ne montre que les données de cette PDB. Intervalle/rétention : DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(interval => 30, retention => 43200).","tags":["awr","awr report","snapshot","awrrpt","dbms_workload_repository","performance report","diagnostics pack"]},{"id":"awr-diff-report","task":"Rapport AWR diff (compare period) entre deux périodes","category":"Performances","command":"-- Interactive: @?/rdbms/admin/awrddrpt.sql   (RAC: awrgdrpt.sql)\n\n-- From a script: period 1 (good) = <bid1>-<eid1>, period 2 (bad) = <bid2>-<eid2>\nSET LINESIZE 8000 PAGESIZE 0 TRIMSPOOL ON HEADING OFF FEEDBACK OFF\nSPOOL /tmp/awr_diff.txt\nSELECT output\nFROM   TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_DIFF_REPORT_TEXT(\n         dbid1 => (SELECT dbid FROM v$database), inst_num1 => 1,\n         bid1  => <bid1>, eid1 => <eid1>,\n         dbid2 => (SELECT dbid FROM v$database), inst_num2 => 1,\n         bid2  => <bid2>, eid2 => <eid2>));\nSPOOL OFF\n-- HTML: DBMS_WORKLOAD_REPOSITORY.AWR_DIFF_REPORT_HTML","what":"Place côte à côte deux plages de snapshots et affiche les écarts en pourcentage du DB time, des événements d'attente, du profil de charge et des statistiques par SQL. Répond directement à \"hier tout allait bien, aujourd'hui c'est lent\".","when":"Analyse de régression après une mise à niveau/un patch, un changement de paramètre, un déploiement ou une collecte de statistiques ; comparaison du même créneau horaire sur deux jours.","caution":"Exige la licence Diagnostics Pack. Les périodes comparées doivent avoir la même durée et une charge similaire, sinon les écarts sont trompeurs. Pour comparer des bases différentes (par ex. prod vs test), transférez les données AWR avec awrextr.sql/awrload.sql et passez le dbid2 correspondant.","tags":["awr diff","comparison","compare period","awrddrpt","regression","performance","diagnostics pack"]},{"id":"ash-top-activity","task":"Principaux événements d'attente et principaux SQL depuis ASH pour les dernières minutes","category":"Performances","command":"-- Last 30 min: wait event breakdown (each sample ~1 s of active session time)\nSELECT NVL(event, 'ON CPU') AS event, wait_class,\n       COUNT(*) AS samples,\n       ROUND(RATIO_TO_REPORT(COUNT(*)) OVER () * 100, 1) AS pct\nFROM   v$active_session_history\nWHERE  sample_time > SYSTIMESTAMP - INTERVAL '30' MINUTE\nGROUP  BY NVL(event, 'ON CPU'), wait_class\nORDER  BY samples DESC\nFETCH FIRST 10 ROWS ONLY;\n\n-- Last 30 min: top SQL (with CPU / User I/O breakdown)\nSELECT sql_id, sql_opname,\n       COUNT(*) AS samples,\n       SUM(CASE WHEN session_state = 'ON CPU' THEN 1 ELSE 0 END) AS cpu,\n       SUM(CASE WHEN wait_class = 'User I/O' THEN 1 ELSE 0 END)  AS user_io,\n       ROUND(RATIO_TO_REPORT(COUNT(*)) OVER () * 100, 1) AS pct\nFROM   v$active_session_history\nWHERE  sample_time > SYSTIMESTAMP - INTERVAL '30' MINUTE\n  AND  sql_id IS NOT NULL\nGROUP  BY sql_id, sql_opname\nORDER  BY samples DESC\nFETCH FIRST 10 ROWS ONLY;\n\n-- Older periods: DBA_HIST_ACTIVE_SESS_HISTORY (one sample every 10 s; COUNT(*)*10 = seconds)\n\n-- Ready-made ASH report (last 30 min):\nSET LINESIZE 8000 PAGESIZE 0 TRIMSPOOL ON HEADING OFF FEEDBACK OFF\nSELECT output\nFROM   TABLE(DBMS_WORKLOAD_REPOSITORY.ASH_REPORT_TEXT(\n         l_dbid     => (SELECT dbid FROM v$database),\n         l_inst_num => (SELECT instance_number FROM v$instance),\n         l_btime    => SYSDATE - 30/1440,\n         l_etime    => SYSDATE));\n-- Interactive: @?/rdbms/admin/ashrpt.sql","what":"Utilise les échantillons Active Session History pour montrer, en pourcentage, quels événements d'attente et quelles instructions SQL ont consommé du DB time au cours des dernières minutes ; diagnostique l'incident sans attendre un snapshot AWR.","when":"Pendant un ralentissement en cours, pour les pics courts qui disparaissent dans les moyennes AWR, et pour voir ce qu'une session ou un SQL donné attendait.","caution":"Interroger V$ACTIVE_SESSION_HISTORY et DBA_HIST_ACTIVE_SESS_HISTORY exige la licence Diagnostics Pack. ASH est échantillonné : le nombre d'échantillons estime le temps, ce n'est pas une mesure exacte, et un SQL très court peut ne pas apparaître. Le buffer ASH en mémoire ne conserve que quelques heures sur les systèmes chargés.","tags":["ash","active session history","top wait","top sql","wait","ashrpt","real-time analysis","diagnostics pack"]},{"id":"addm-task-report","task":"Lancer une analyse ADDM et obtenir son rapport","category":"Performances","command":"-- Analyze the range between two snapshots (in CDB root):\nVAR tname VARCHAR2(60)\nBEGIN\n  :tname := 'ADDM_<begin_snap>_<end_snap>';\n  DBMS_ADDM.ANALYZE_DB(\n    task_name      => :tname,\n    begin_snapshot => <begin_snap>,\n    end_snapshot   => <end_snap>);\nEND;\n/\n\nSET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 200 PAGESIZE 0\nSELECT DBMS_ADDM.GET_REPORT(task_name => :tname) FROM dual;\n\n-- Findings summary (by impact):\nSET PAGESIZE 100\nSELECT finding_name, type, ROUND(impact/1e6) AS impact_sec, message\nFROM   dba_advisor_findings\nWHERE  task_name = :tname\nORDER  BY impact DESC;\n\n-- Interactive alternative: @?/rdbms/admin/addmrpt.sql\n-- Clean up the task when done:\nEXEC DBMS_ADDM.DELETE(task_name => :tname);","what":"Automatic Database Diagnostic Monitor analyse le DB time entre deux snapshots AWR et signale les goulets d'étranglement avec leur impact (findings) et des recommandations concrètes.","when":"Pour obtenir un résumé rapide \"où dois-je regarder\" avant de lire le rapport AWR, ou pour analyser une plage personnalisée au-delà de l'exécution ADDM automatique après chaque snapshot.","caution":"Exige la licence Diagnostics Pack. N'appliquez pas aveuglément les recommandations ADDM (augmentation de paramètres, exécution de SQL Tuning Advisor, etc.) ; validez les recommandations mémoire avec les sections advisory du rapport AWR. La plage ne peut pas couvrir un redémarrage.","tags":["addm","dbms_addm","advisor","finding","finding","recommendation","diagnostics pack","performance"]},{"id":"sql-monitor-report","task":"Obtenir un rapport SQL Monitor (SQL long en cours ou terminé)","category":"Performances","command":"-- List monitored SQL (longer than 5 s, parallel, or /*+ MONITOR */):\nSELECT sql_id, status, username, sql_exec_id,\n       TO_CHAR(sql_exec_start,'YYYY-MM-DD HH24:MI:SS') AS exec_start,\n       ROUND(elapsed_time/1e6,1) AS elapsed_s,\n       ROUND(cpu_time/1e6,1)     AS cpu_s,\n       buffer_gets, px_servers_allocated\nFROM   v$sql_monitor\nORDER  BY sql_exec_start DESC\nFETCH FIRST 20 ROWS ONLY;\n\n-- TEXT report (actual rows, time and activity per plan line):\nSET LONG 10000000 LONGCHUNKSIZE 10000000 LINESIZE 300 PAGESIZE 0 TRIMSPOOL ON\nSELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(\n         sql_id       => '<sql_id>',\n         type         => 'TEXT',\n         report_level => 'ALL') AS report\nFROM   dual;\n\n-- Shareable HTML (interactive in a browser):\nSPOOL /tmp/sqlmon_<sql_id>.html\nSELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(\n         sql_id => '<sql_id>', type => 'ACTIVE', report_level => 'ALL')\nFROM   dual;\nSPOOL OFF","what":"Real-Time SQL Monitoring affiche, pour chaque étape du plan d'exécution d'un SQL long ou parallèle, les lignes réelles, le temps écoulé, l'utilisation mémoire/temp et l'activité ASH ; vous pouvez suivre la progression pendant que le SQL s'exécute encore.","when":"Pour savoir si une requête de batch/rapport est bloquée et ce qu'il reste à faire, pour analyser la répartition d'une requête parallèle (PX skew), et pour trouver les écarts entre lignes estimées (E-Rows) et réelles.","caution":"Exige la licence Tuning Pack (Diagnostics Pack est un prérequis). Par défaut, seuls les SQL consommant 5 s de CPU/E-S ou s'exécutant en parallèle sont surveillés ; utilisez le hint /*+ MONITOR */ pour les autres. Les plans très volumineux (_sqlmon_max_planlines, 300 par défaut) ne sont pas surveillés. Les entrées restent en mémoire pendant une durée limitée ; en 12c+, les rapports plus anciens sont aussi disponibles dans DBA_HIST_REPORTS.","tags":["sql monitor","report_sql_monitor","v$sql_monitor","real-time","long running query","parallel","tuning pack"]},{"id":"sql-trace-10046-tkprof","task":"Activer/désactiver la trace 10046 pour une session et la résumer avec TKPROF","category":"Performances","command":"-- 1) Find the target session and its trace file:\nSELECT s.sid, s.serial#, s.username, s.module, p.tracefile\nFROM   v$session s\n  JOIN v$process p ON p.addr = s.paddr\nWHERE  s.username = 'CUSTOMER_APP';\n\n-- 2) Enable trace (waits + binds = equivalent of level 12):\nBEGIN\n  DBMS_MONITOR.SESSION_TRACE_ENABLE(\n    session_id => <sid>,\n    serial_num => <serial#>,\n    waits      => TRUE,\n    binds      => TRUE,\n    plan_stat  => 'ALL_EXECUTIONS');\nEND;\n/\n-- Verify: SELECT sql_trace, sql_trace_waits, sql_trace_binds FROM v$session WHERE sid = <sid>;\n\n-- 3) Disable once the problem operation completes:\nEXEC DBMS_MONITOR.SESSION_TRACE_DISABLE(session_id => <sid>, serial_num => <serial#>);\n\n-- Alternatives:\n-- Your own session: ALTER SESSION SET EVENTS '10046 trace name context forever, level 12';\n-- Single SQL_ID, all sessions: ALTER SYSTEM SET EVENTS 'sql_trace[SQL: <sql_id>] wait=true, bind=true';\n--   disable: ALTER SYSTEM SET EVENTS 'sql_trace[SQL: <sql_id>] off';\n-- For a connection pool: DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE / SERV_MOD_ACT_TRACE_ENABLE\n\n-- 4) Summarize with TKPROF on the OS (most expensive SQL first):\n-- $ tkprof /u01/app/oracle/diag/rdbms/orcl/ORCL/trace/ORCL_ora_12345.trc /tmp/ora_12345.prf \\\n--     sys=no sort=exeela,fchela,prsela waits=yes aggregate=yes","what":"La trace SQL étendue (10046) écrit dans un fichier trace les temps de parse/execute/fetch, les événements d'attente et les valeurs de bind de chaque SQL exécuté par la session ; TKPROF en fait un rapport lisible agrégé par SQL.","when":"Lorsque AWR/ASH ne suffisent pas et que vous devez prouver de bout en bout où une opération utilisateur passe son temps ; pour les problèmes d'aller-retour application-base (SQL*Net).","caution":"Aucune licence requise. Les fichiers trace grossissent vite et ajoutent une surcharge à la session cible ; gardez-la courte et désactivez-la toujours (vérifiez DBA_ENABLED_TRACES pour les restes). binds=TRUE écrit des données sensibles (numéros d'identité, numéros de carte, etc.) dans le fichier trace. ALTER SESSION SET EVENTS exige le privilège ALTER SESSION (sinon ORA-01031). Par défaut, TKPROF n'affiche pas les valeurs de bind ; conservez le fichier .trc brut.","note":"Le traçage au niveau d'un pool de connexions ou d'un service produit plusieurs fichiers ; fusionnez-les avec trcsess (trcsess output=combined.trc clientid=<id> *.trc) puis exécutez TKPROF.","tags":["10046","trace","tkprof","dbms_monitor","sql trace","wait","bind","session tracing"]},{"id":"xplan-allstats-last","task":"Afficher le plan d'exécution réel avec les statistiques row source (ALLSTATS LAST)","category":"Performances","command":"-- 1) Collect row source statistics (for this query only):\nSET SERVEROUTPUT OFF\nSELECT /*+ gather_plan_statistics */ customer_id, SUM(amount)\nFROM   customer_app.sales\nWHERE  created_at >= DATE '2026-06-01'\nGROUP  BY customer_id;\n-- or for the whole session: ALTER SESSION SET statistics_level = ALL;\n\n-- 2) Plan of the last cursor run in the same session (E-Rows vs A-Rows):\nSELECT *\nFROM   TABLE(DBMS_XPLAN.DISPLAY_CURSOR(\n         sql_id          => NULL,\n         cursor_child_no => NULL,\n         format          => 'ALLSTATS LAST +PEEKED_BINDS +OUTLINE'));\n\n-- 3) A cursor from another session / from earlier:\nSELECT *\nFROM   TABLE(DBMS_XPLAN.DISPLAY_CURSOR('<sql_id>', NULL, 'ALLSTATS LAST +PEEKED_BINDS'));","what":"EXPLAIN PLAN est une estimation ; ceci montre le plan réellement exécuté, avec Starts, E-Rows (estimation), A-Rows (réel), A-Time, Buffers et l'utilisation mémoire par étape. C'est le moyen le plus direct de trouver l'étape où commence l'erreur d'estimation de cardinalité.","when":"Lors du tuning d'un SQL lent isolé, pour prouver le besoin de statistiques/histogrammes, et pour mesurer l'effet d'un hint ou d'un changement d'index.","caution":"Aucune licence requise. Avec SET SERVEROUTPUT ON, sql_id=>NULL affiche l'appel DBMS_OUTPUT comme dernier curseur ; mettez-le d'abord à OFF. N'activez pas statistics_level=ALL à l'échelle du système ; cela ajoute une surcharge CPU notable. Le hint modifie le texte SQL, qui reçoit donc un nouveau sql_id. L'utilisateur a besoin d'un accès en lecture à V$SQL_PLAN_STATISTICS_ALL (SELECT_CATALOG_ROLE).","tags":["dbms_xplan","display_cursor","allstats last","gather_plan_statistics","actual plan","execution plan","a-rows","cardinality"]},{"id":"sql-tuning-advisor-task","task":"Créer et exécuter une tâche SQL Tuning Advisor et lire ses recommandations","category":"Performances","command":"-- 1) Create and run a task for a SQL in the cursor cache:\nVAR task VARCHAR2(64)\nBEGIN\n  :task := DBMS_SQLTUNE.CREATE_TUNING_TASK(\n             sql_id      => '<sql_id>',\n             scope       => DBMS_SQLTUNE.SCOPE_COMPREHENSIVE,\n             time_limit  => 300,\n             task_name   => 'STA_<sql_id>',\n             description => 'Slow report query');\n  DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name => :task);\nEND;\n/\n-- For a historical SQL in AWR: begin_snap => <b>, end_snap => <e>, sql_id => '<sql_id>'\n\n-- 2) Status and report:\nSELECT task_name, status FROM dba_advisor_log WHERE task_name = :task;\n\nSET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 200 PAGESIZE 0\nSELECT DBMS_SQLTUNE.REPORT_TUNING_TASK(:task) FROM dual;\n\n-- 3) Get the recommendations as an executable script:\nSELECT DBMS_SQLTUNE.SCRIPT_TUNING_TASK(:task) FROM dual;\n\n-- 4) When done:\nEXEC DBMS_SQLTUNE.DROP_TUNING_TASK(task_name => :task);","what":"Exécute l'optimiseur dans un \"mode tuning\" étendu pour le SQL et produit des recommandations sur les statistiques manquantes, de nouveaux index, un SQL profile, des plans alternatifs et la restructuration du SQL ; valide la recommandation de profile par des exécutions de test.","when":"Lorsqu'un SQL problématique isolé nécessite un second avis rapide, ou pour trouver une correction par SQL profile à une régression de plan sans modifier le code.","caution":"Exige la licence Tuning Pack. La portée COMPREHENSIVE peut exécuter le SQL pour le tester (seulement la partie requête pour les DML) ; définissez time_limit en production. Les recommandations d'index ignorent le reste de la charge ; évaluez séparément le coût des DML. L'utilisateur a besoin du privilège ADVISOR.","tags":["sql tuning advisor","dbms_sqltune","tuning task","recommendation","sql profile","index recommendation","tuning pack"]},{"id":"sql-profile-accept","task":"Accepter, désactiver et supprimer un SQL profile","category":"Performances","command":"-- 1) Accept the profile recommended by the tuning task:\nDECLARE\n  l_name VARCHAR2(128);\nBEGIN\n  l_name := DBMS_SQLTUNE.ACCEPT_SQL_PROFILE(\n              task_name   => '<task_name>',\n              name        => 'PROF_<sql_id>',\n              description => 'STA recommendation, CHG-1234',\n              force_match => TRUE,     -- also matches statements differing only in literals\n              replace     => TRUE);\n  DBMS_OUTPUT.PUT_LINE('Profile: ' || l_name);\nEND;\n/\n\n-- 2) Profiles and the cursors using them:\nSELECT name, category, status, force_matching,\n       TO_CHAR(created,'YYYY-MM-DD HH24:MI') AS created\nFROM   dba_sql_profiles\nORDER  BY created DESC;\n\nSELECT sql_id, child_number, plan_hash_value, sql_profile\nFROM   v$sql\nWHERE  sql_profile IS NOT NULL;\n\n-- 3) Temporarily disable / drop:\nEXEC DBMS_SQLTUNE.ALTER_SQL_PROFILE(name => 'PROF_<sql_id>', attribute_name => 'STATUS', value => 'DISABLED');\nEXEC DBMS_SQLTUNE.DROP_SQL_PROFILE(name => 'PROF_<sql_id>', ignore => TRUE);","what":"Un SQL profile fournit à l'optimiseur des corrections supplémentaires de cardinalité/sélectivité (OPT_ESTIMATE et similaires) pour qu'il produise un meilleur plan sans modifier le texte SQL ; avec FORCE_MATCH, il s'applique aussi aux instructions qui ne diffèrent que par les littéraux.","when":"Lorsque le SQL ne peut pas être modifié (progiciel), lorsque STA recommande une amélioration validée, ou comme correction rapide d'une régression de plan.","caution":"Exige la licence Tuning Pack. Un profile ne \"verrouille\" pas le plan ; il corrige les estimations, donc le plan peut encore changer quand la distribution des données change. Si la stabilité du plan est impérative, utilisez une SQL plan baseline. Lorsque les deux existent, l'optimiseur choisit parmi les plans acceptés de la baseline. Lisez toujours les résultats de validation (section 'Validation results' du rapport) avant d'accepter.","tags":["sql profile","accept_sql_profile","dbms_sqltune","force_match","plan fix","dba_sql_profiles","tuning pack"]},{"id":"spm-load-from-cursor-cache","task":"Charger un bon plan depuis le cursor cache comme SQL plan baseline","category":"Performances","command":"-- 1) Compare the SQL's plans and their performance:\nSELECT sql_id, child_number, plan_hash_value, executions,\n       ROUND(elapsed_time/1e3/NULLIF(executions,0),1) AS ms_per_exec,\n       sql_plan_baseline\nFROM   v$sql\nWHERE  sql_id = '<sql_id>';\n\n-- 2) Load the chosen plan as a baseline:\nSET SERVEROUTPUT ON\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(\n         sql_id          => '<sql_id>',\n         plan_hash_value => <plan_hash_value>,\n         fixed           => 'NO',\n         enabled         => 'YES');\n  DBMS_OUTPUT.PUT_LINE(n || ' plan(s) loaded');\nEND;\n/\n\n-- 3) Verify the baseline:\nSELECT sql_handle, plan_name, origin, enabled, accepted, fixed, reproduced,\n       TO_CHAR(last_executed,'YYYY-MM-DD HH24:MI') AS last_exec\nFROM   dba_sql_plan_baselines\nWHERE  signature = (SELECT exact_matching_signature FROM v$sql\n                    WHERE sql_id = '<sql_id>' AND ROWNUM = 1);\n-- On subsequent executions v$sql.sql_plan_baseline should be populated.","what":"Utilise SQL Plan Management pour faire du plan choisi une baseline ACCEPTED ; l'optimiseur n'utilise alors que les plans de baseline acceptés et met de côté les nouveaux plans trouvés jusqu'à leur évolution.","when":"Pour revenir à un bon plan connu lors d'une régression de plan, et pour protéger les plans des SQL critiques avant une mise à niveau ou un changement de statistiques.","caution":"Aucune licence supplémentaire en Enterprise Edition (en SE2, limité à un plan de baseline par SQL). Exige ADMINISTER SQL MANAGEMENT OBJECT. Une baseline s'applique exactement au même texte SQL (signature) ; elle n'aide pas les SQL utilisant des littéraux. Si un index utilisé par le plan est supprimé, le plan ne peut pas être reproduit (REPRODUCED=NO) et l'optimiseur revient à un choix basé sur le coût. OPTIMIZER_USE_SQL_PLAN_BASELINES doit valoir TRUE (défaut).","note":"Si le bon plan n'est plus dans le cursor cache, chargez-le depuis AWR : en 12.2+ DBMS_SPM.LOAD_PLANS_FROM_AWR(begin_snap, end_snap, basic_filter => q'[sql_id='<sql_id>']') ; dans les versions antérieures, passez par un SQL Tuning Set avec LOAD_PLANS_FROM_SQLSET.","tags":["spm","sql plan baseline","dbms_spm","load_plans_from_cursor_cache","plan pinning","plan regression","plan_hash_value"]},{"id":"spm-evolve-baseline","task":"Faire évoluer les plans non acceptés d'une SQL plan baseline","category":"Performances","command":"-- 1) Plans waiting for acceptance:\nSELECT sql_handle, plan_name, origin, enabled, accepted, fixed,\n       TO_CHAR(created,'YYYY-MM-DD HH24:MI') AS created\nFROM   dba_sql_plan_baselines\nWHERE  accepted = 'NO'\nORDER  BY created DESC;\n\n-- 2) Evolve task: test-executes the candidate plan against the accepted plan\nVAR tname VARCHAR2(128)\nVAR ename VARCHAR2(128)\nBEGIN\n  :tname := DBMS_SPM.CREATE_EVOLVE_TASK(\n              sql_handle => '<sql_handle>',\n              time_limit => 600,\n              task_name  => 'EVOLVE_<sql_handle>');\n  :ename := DBMS_SPM.EXECUTE_EVOLVE_TASK(task_name => :tname);\nEND;\n/\n\nSET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 200 PAGESIZE 0\nSELECT DBMS_SPM.REPORT_EVOLVE_TASK(\n         task_name => :tname, execution_name => :ename,\n         type => 'TEXT', level => 'TYPICAL', section => 'ALL')\nFROM   dual;\n\n-- 3) If the report has recommendations (plans proven better), accept them:\nSET SERVEROUTPUT ON\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  n := DBMS_SPM.IMPLEMENT_EVOLVE_TASK(task_name => :tname);\n  DBMS_OUTPUT.PUT_LINE(n || ' plan(s) accepted');\nEND;\n/\nEXEC DBMS_SPM.DROP_EVOLVE_TASK(task_name => :tname);","what":"Lorsqu'une baseline existe, les nouveaux plans trouvés par l'optimiseur sont stockés avec ACCEPTED=NO. La tâche d'évolution exécute ces candidats à titre de test face au plan accepté et n'accepte que ceux qui sont nettement meilleurs.","when":"Pour les SQL avec baseline lorsqu'un nouvel index ou de nouvelles statistiques ont pu produire un meilleur plan ; pour décider manuellement sans attendre la tâche automatique SYS_AUTO_SPM_EVOLVE_TASK (12c+).","caution":"Exige Enterprise Edition. L'évolution exécute réellement le SQL (seulement la partie requête pour les DML) ; définissez time_limit pour les SQL lourds et évitez les heures de pointe. Exige ADMINISTER SQL MANAGEMENT OBJECT et ADVISOR. Les SQL parsés en tant que SYS ne peuvent pas évoluer (ORA-16963) ; exécutez-la en tant qu'utilisateur applicatif.","since":"Oracle 12c","note":"La fonction DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE antérieure à 12c est dépréciée ; 12c+ utilise l'API basée sur les tâches (CREATE/EXECUTE/IMPLEMENT_EVOLVE_TASK). Comportement de la tâche automatique : DBMS_SPM.SET_EVOLVE_TASK_PARAMETER('SYS_AUTO_SPM_EVOLVE_TASK', 'ACCEPT_PLANS', 'FALSE').","tags":["spm","evolve","create_evolve_task","baseline","plan acceptance","dbms_spm","accepted"]},{"id":"spm-alter-drop-baseline","task":"Fixer, désactiver ou supprimer un plan de SQL plan baseline","category":"Performances","command":"-- Show the plan content:\nSELECT *\nFROM   TABLE(DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(\n         sql_handle => '<sql_handle>',\n         plan_name  => '<plan_name>',\n         format     => 'TYPICAL'));\n\nSET SERVEROUTPUT ON\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  -- FIXED: this plan is preferred, new plans are not evolved automatically\n  n := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(\n         sql_handle => '<sql_handle>', plan_name => '<plan_name>',\n         attribute_name => 'fixed', attribute_value => 'YES');\n\n  -- Take the bad plan out of use (without dropping it):\n  n := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(\n         sql_handle => '<sql_handle>', plan_name => '<bad_plan_name>',\n         attribute_name => 'enabled', attribute_value => 'NO');\nEND;\n/\n\n-- Drop one plan or the SQL's entire baseline:\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  n := DBMS_SPM.DROP_SQL_PLAN_BASELINE(sql_handle => '<sql_handle>', plan_name => '<plan_name>');\n  -- without plan_name, all plans of the SQL are dropped:\n  -- n := DBMS_SPM.DROP_SQL_PLAN_BASELINE(sql_handle => '<sql_handle>');\n  DBMS_OUTPUT.PUT_LINE(n || ' plan(s) dropped');\nEND;\n/","what":"Gère les attributs des plans de baseline : un plan FIXED est préféré aux autres plans acceptés et arrête l'évolution automatique des nouveaux plans ; ENABLED=NO désactive un plan sans le supprimer ; DROP le supprime définitivement.","when":"Pour figer fermement un plan pour un SQL critique, désactiver un mauvais plan accepté par erreur, ou nettoyer les baselines devenues inutiles.","caution":"Exige Enterprise Edition. Les plans FIXED peuvent se dégrader avec la croissance des données et empêcher l'optimiseur d'utiliser un meilleur plan ; revoyez-les périodiquement. Un SQL dont tous les plans sont ENABLED=NO est optimisé librement. DROP est irréversible ; sauvegardez d'abord dans une table de staging avec DBMS_SPM.PACK_STGTAB_BASELINE.","tags":["spm","baseline","fixed","alter_sql_plan_baseline","drop_sql_plan_baseline","disable plan","display_sql_plan_baseline"]},{"id":"bind-sensitivity-acs-check","task":"Vérifier l'état du bind peeking et de l'adaptive cursor sharing","category":"Performances","command":"-- 1) Child cursors: bind-sensitive / bind-aware / shareable?\nSELECT sql_id, child_number, executions, plan_hash_value,\n       is_bind_sensitive, is_bind_aware, is_shareable,\n       ROUND(buffer_gets/NULLIF(executions,0)) AS gets_per_exec\nFROM   v$sql\nWHERE  sql_id = '<sql_id>'\nORDER  BY child_number;\n\n-- 2) ACS histogram and selectivity ranges:\nSELECT child_number, bucket_id, count\nFROM   v$sql_cs_histogram\nWHERE  sql_id = '<sql_id>'\nORDER  BY child_number, bucket_id;\n\nSELECT child_number, predicate, range_id, low, high\nFROM   v$sql_cs_selectivity\nWHERE  sql_id = '<sql_id>';\n\n-- 3) Peeked / captured bind values:\nSELECT child_number, name, position, datatype_string, value_string,\n       TO_CHAR(last_captured,'YYYY-MM-DD HH24:MI:SS') AS captured\nFROM   v$sql_bind_capture\nWHERE  sql_id = '<sql_id>'\nORDER  BY child_number, position;\n\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('<sql_id>', NULL, 'TYPICAL +PEEKED_BINDS'));\n\n-- 4) Why was a new child created?\nSELECT child_number, bind_mismatch, bind_equiv_failure,\n       optimizer_mismatch, load_optimizer_stats\nFROM   v$sql_shared_cursor\nWHERE  sql_id = '<sql_id>';","what":"L'optimiseur choisit un plan en examinant la valeur de bind lors du premier hard parse. L'adaptive cursor sharing peut créer des child cursors/plans différents par valeur de bind sur les colonnes avec histogrammes ; ces requêtes montrent si le curseur est bind-sensitive/aware, quelle valeur a été examinée et pourquoi des enfants ont été créés.","when":"Pour les plaintes du type \"le même SQL est parfois rapide, parfois lent\", avec une distribution de données asymétrique, et lorsque le nombre de child cursors explose (high version count).","caution":"Aucune licence requise ; lecture seule. V$SQL_BIND_CAPTURE est échantillonné (environ toutes les 15 minutes par défaut) et ne contient que les binds WHERE/HAVING ; ce n'est pas la valeur de chaque exécution. ACS ne rend un curseur bind-aware qu'après quelques mauvaises exécutions. Pour les SQL critiques, charger plusieurs plans dans une SQL plan baseline est plus déterministe que de compter sur ACS.","since":"Oracle 11g","note":"L'adaptive cursor sharing a été introduit en 11g. Pour l'analyse de high version count, la colonne REASON (XML) de V$SQL_SHARED_CURSOR donne la cause détaillée.","tags":["bind peeking","adaptive cursor sharing","acs","bind variable","v$sql_bind_capture","child cursor","version count"]},{"id":"top-sql-awr-history","task":"Trouver les SQL les plus coûteux en temps écoulé/CPU sur une période passée (AWR + V$SQLAREA)","category":"Performances","command":"-- AWR: top 20 by elapsed time within a snapshot range (per plan)\nSELECT *\nFROM  (SELECT st.sql_id,\n              st.plan_hash_value,\n              SUM(st.executions_delta)                       AS execs,\n              ROUND(SUM(st.elapsed_time_delta)/1e6, 1)       AS elapsed_s,\n              ROUND(SUM(st.cpu_time_delta)/1e6, 1)           AS cpu_s,\n              ROUND(SUM(st.iowait_delta)/1e6, 1)             AS io_wait_s,\n              SUM(st.buffer_gets_delta)                      AS buffer_gets,\n              ROUND(SUM(st.elapsed_time_delta)/1e3\n                    / NULLIF(SUM(st.executions_delta),0), 2) AS ms_per_exec,\n              MAX(st.parsing_schema_name)                    AS parsing_schema\n       FROM   dba_hist_sqlstat st\n       WHERE  st.dbid = (SELECT dbid FROM v$database)\n         AND  st.snap_id BETWEEN <begin_snap> AND <end_snap>\n       GROUP  BY st.sql_id, st.plan_hash_value\n       ORDER  BY elapsed_s DESC)\nWHERE ROWNUM <= 20;\n-- For CPU ranking: ORDER BY cpu_s DESC\n-- SQL text: SELECT DBMS_LOB.SUBSTR(sql_text, 4000, 1) FROM dba_hist_sqltext WHERE sql_id = '<sql_id>';\n-- Plan history of a single SQL: @?/rdbms/admin/awrsqrpt.sql\n\n-- Current (since instance startup, still in cache) by CPU:\nSELECT sql_id,\n       ROUND(cpu_time/1e6,1)     AS cpu_s,\n       ROUND(elapsed_time/1e6,1) AS elapsed_s,\n       executions, buffer_gets, disk_reads,\n       ROUND(cpu_time/1e3/NULLIF(executions,0),2) AS cpu_ms_per_exec,\n       parsing_schema_name,\n       SUBSTR(sql_text,1,80) AS sql_text\nFROM   v$sqlarea\nWHERE  parsing_schema_name NOT IN ('SYS','SYSTEM')\nORDER  BY cpu_time DESC\nFETCH FIRST 20 ROWS ONLY;","what":"Additionne les colonnes *_DELTA de DBA_HIST_SQLSTAT pour lister les SQL les plus coûteux d'une plage de snapshots, ventilés par plan_hash_value, ce qui révèle aussi les changements de plan d'un même SQL. La requête V$SQLAREA donne les valeurs cumulées de ce qui est encore en cache.","when":"Pour \"qu'est-ce qui a chargé le système cette nuit entre 02:00 et 04:00 ?\", pour détecter les changements de plan (même sql_id, plan_hash_value différent), et pour établir une liste de candidats au tuning.","caution":"Interroger DBA_HIST_* exige la licence Diagnostics Pack ; V$SQLAREA non. AWR ne conserve que les principaux SQL (top-N par défaut), donc des instructions petites mais très fréquentes peuvent manquer. Les valeurs de V$SQLAREA sont perdues lorsque le curseur sort du cache. Les deltas restent corrects après un redémarrage, mais la requête basée sur le cache repart de zéro.","tags":["top sql","dba_hist_sqlstat","v$sqlarea","elapsed","cpu","most expensive query","plan change","diagnostics pack"]},{"id":"stats-stale-prefs","task":"Trouver les statistiques obsolètes et définir des préférences de statistiques par table (prefs)","category":"Statistiques","command":"-- 1) Flush DML counters to the dictionary and list stale objects:\nEXEC DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO;\n\nSELECT owner, table_name, partition_name, num_rows,\n       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI') AS last_analyzed,\n       stale_stats, stattype_locked\nFROM   dba_tab_statistics\nWHERE  owner = 'CUSTOMER_APP'\n  AND  (stale_stats = 'YES' OR last_analyzed IS NULL)\nORDER  BY table_name, partition_name;\n\nSELECT table_name, partition_name, inserts, updates, deletes, truncated\nFROM   dba_tab_modifications\nWHERE  table_owner = 'CUSTOMER_APP';\n\n-- 2) Per-table preferences (used by the auto stats job and manual GATHER calls):\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'STALE_PERCENT', '5');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'METHOD_OPT', 'FOR ALL COLUMNS SIZE AUTO FOR COLUMNS SIZE 254 CUSTOMER_ID');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'DEGREE', '4');\n\nSELECT DBMS_STATS.GET_PREFS('STALE_PERCENT', 'CUSTOMER_APP', 'SALES') AS stale_pct FROM dual;\nSELECT table_name, preference_name, preference_value\nFROM   dba_tab_stat_prefs WHERE owner = 'CUSTOMER_APP';\n\n-- 3) Gather only stale objects:\nEXEC DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CUSTOMER_APP', options => 'GATHER STALE');\n-- Revert a preference: EXEC DBMS_STATS.DELETE_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'STALE_PERCENT');","what":"Les statistiques deviennent obsolètes lorsque le taux de modification dépasse STALE_PERCENT (10 % par défaut). Les préférences conservent par table l'échantillonnage, les histogrammes, le parallélisme et les seuils d'obsolescence, afin que la tâche automatique de statistiques nocturne applique les mêmes règles.","when":"Lorsque le seuil de 10 % se déclenche trop tard sur les grandes tables, lorsque certaines colonnes doivent toujours avoir un histogramme, et pour définir la stratégie de collecte dans le dictionnaire plutôt que dans des scripts.","caution":"Aucune licence requise. Les préférences sont remplacées par les paramètres explicites d'un appel GATHER manuel. Modifier METHOD_OPT ajoute ou supprime des histogrammes et peut changer les plans. DBA_TAB_MODIFICATIONS n'est à jour qu'après un flush (flush automatique environ toutes les 15 minutes).","tags":["stale stats","stale_percent","set_table_prefs","dba_tab_statistics","dba_tab_modifications","statistics preference","gather stale"]},{"id":"stats-lock-unlock","task":"Verrouiller et déverrouiller les statistiques d'une table/d'un schéma","category":"Statistiques","command":"-- Lock (the auto stats job and normal GATHER calls no longer touch them):\nEXEC DBMS_STATS.LOCK_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'ORDERS');\nEXEC DBMS_STATS.LOCK_SCHEMA_STATS(ownname => 'CUSTOMER_APP');\n-- A single partition only: DBMS_STATS.LOCK_PARTITION_STATS('CUSTOMER_APP', 'SALE_PART', 'P2026_H1');\n\n-- Locked objects:\nSELECT owner, table_name, partition_name, stattype_locked\nFROM   dba_tab_statistics\nWHERE  owner = 'CUSTOMER_APP' AND stattype_locked IS NOT NULL;\n\n-- Deliberate gathering while locked (instead of ORA-20005):\nEXEC DBMS_STATS.GATHER_TABLE_STATS('CUSTOMER_APP', 'ORDERS', force => TRUE);\n\n-- Unlock:\nEXEC DBMS_STATS.UNLOCK_SCHEMA_STATS(ownname => 'CUSTOMER_APP');\nEXEC DBMS_STATS.UNLOCK_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'ORDERS');","what":"Verrouille les statistiques contre toute modification ; la tâche automatique ignore les objets verrouillés et un appel GATHER au niveau table lève ORA-20005. force=>TRUE collecte sans supprimer le verrou.","when":"Pour conserver des statistiques représentatives sur les tables de staging/volatiles vidées et remplies dans la journée, protéger les plans d'une table critique contre les changements de statistiques, ou préserver les valeurs définies avec DBMS_STATS.SET_TABLE_STATS.","caution":"Aucune licence requise. Les statistiques verrouillées s'écartent de la réalité avec le temps et causent des erreurs de cardinalité ; listez régulièrement les objets verrouillés. Un verrou de schéma ne couvre pas les tables ajoutées ultérieurement au schéma. Sur une table verrouillée, CREATE/REBUILD INDEX ne collecte pas non plus automatiquement les statistiques d'index.","tags":["lock stats","lock statistics","unlock_table_stats","lock_schema_stats","ora-20005","stattype_locked","dbms_stats"]},{"id":"stats-restore-history","task":"Restaurer les statistiques à un point dans le passé","category":"Statistiques","command":"-- 1) Retention and the oldest point you can return to:\nSELECT DBMS_STATS.GET_STATS_HISTORY_RETENTION    AS retention_days,\n       DBMS_STATS.GET_STATS_HISTORY_AVAILABILITY AS oldest_available\nFROM   dual;\n\n-- 2) Statistics history of the table:\nSELECT table_name, partition_name,\n       TO_CHAR(stats_update_time,'YYYY-MM-DD HH24:MI:SS TZH:TZM') AS stats_update_time\nFROM   dba_tab_stats_history\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'SALES'\nORDER  BY stats_update_time DESC;\n\n-- 3) Return to the point before the plan regressed:\nBEGIN\n  DBMS_STATS.RESTORE_TABLE_STATS(\n    ownname         => 'CUSTOMER_APP',\n    tabname         => 'SALES',\n    as_of_timestamp => TIMESTAMP '2026-10-07 22:00:00 +03:00',\n    no_invalidate   => FALSE);\nEND;\n/\n-- Whole schema: DBMS_STATS.RESTORE_SCHEMA_STATS('CUSTOMER_APP', <timestamp>)\n\n-- Change the retention (days):\n-- EXEC DBMS_STATS.ALTER_STATS_HISTORY_RETENTION(31);","what":"DBMS_STATS sauvegarde les statistiques précédentes dans SYSAUX avant chaque collecte (31 jours par défaut). Les procédures RESTORE_* ramènent les statistiques d'une table/d'un schéma à leur état à l'instant donné ; avec no_invalidate=>FALSE, les curseurs sont reparsés immédiatement.","when":"Comme retour arrière le plus rapide lorsqu'une régression de plan suit la collecte de statistiques nocturne, et pour tester si un changement a été causé par les statistiques.","caution":"Aucune licence requise. Les statistiques verrouillées ne peuvent pas être restaurées (déverrouillez d'abord). La collecte automatique suivante ramène de nouvelles statistiques après la restauration ; pour une correction permanente, définissez des préférences de table, verrouillez les statistiques ou utilisez une SQL plan baseline. Une longue rétention fait grossir SYSAUX (tables WRI$_OPTSTAT_*).","tags":["restore stats","restore statistics","restore_table_stats","dba_tab_stats_history","stats history","plan regression","dbms_stats"]},{"id":"stats-pending-publish","task":"Collecter les statistiques en attente (pending), les tester et les publier","category":"Statistiques","command":"-- 1) Disable publishing for the table (new statistics go to pending):\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'ORDERS', 'PUBLISH', 'FALSE');\nEXEC DBMS_STATS.GATHER_TABLE_STATS('CUSTOMER_APP', 'ORDERS');\n\n-- 2) Compare pending and published values:\nSELECT owner, table_name, partition_name, num_rows,\n       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI') AS last_analyzed\nFROM   dba_tab_pending_stats\nWHERE  owner = 'CUSTOMER_APP';\nSELECT num_rows, last_analyzed FROM dba_tables\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'ORDERS';\n\n-- 3) Try plans with pending statistics in this session only:\nALTER SESSION SET optimizer_use_pending_statistics = TRUE;\nEXPLAIN PLAN FOR SELECT COUNT(*) FROM customer_app.orders WHERE status = 7;\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +ROWS'));\nALTER SESSION SET optimizer_use_pending_statistics = FALSE;\n\n-- 4a) If good, publish:\nEXEC DBMS_STATS.PUBLISH_PENDING_STATS('CUSTOMER_APP', 'ORDERS', no_invalidate => FALSE);\n-- 4b) If bad, discard:\n-- EXEC DBMS_STATS.DELETE_PENDING_STATS('CUSTOMER_APP', 'ORDERS');\n\n-- 5) Return to normal behavior:\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'ORDERS', 'PUBLISH', 'TRUE');","what":"Avec la préférence PUBLISH=FALSE, les statistiques collectées sont écrites dans une zone d'attente au lieu du dictionnaire, donc les autres sessions ne sont pas affectées. Les plans sont validés dans une session de test avec optimizer_use_pending_statistics, puis publiés ou abandonnés en une commande.","when":"Pour tester sans risque en production l'impact sur les plans de statistiques rafraîchies sur des tables OLTP critiques, et pour une bascule contrôlée après un gros chargement de données.","caution":"Aucune licence requise. Tant que PUBLISH=FALSE reste en place, les statistiques collectées par la tâche automatique s'accumulent aussi en attente et ne sont jamais publiées ; pensez à remettre la préférence à TRUE. EXPLAIN PLAN est une estimation ; si possible, exécutez la requête et vérifiez DISPLAY_CURSOR.","since":"Oracle 11g","tags":["pending stats","publish","publish_pending_stats","optimizer_use_pending_statistics","statistics testing","dbms_stats","dba_tab_pending_stats"]},{"id":"stats-incremental-partitioned","task":"Configurer les statistiques incrémentales pour une table partitionnée","category":"Statistiques","command":"-- 1) Enable incremental mode (global statistics are derived from partition synopses):\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_PART', 'INCREMENTAL', 'TRUE');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_PART', 'INCREMENTAL_STALENESS', 'USE_STALE_PERCENT');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_PART', 'GRANULARITY', 'AUTO');\n\n-- 2) The first gather scans everything and builds synopses; later ones read only changed partitions:\nEXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'SALE_PART');\n\n-- 3) Verify:\nSELECT DBMS_STATS.GET_PREFS('INCREMENTAL', 'CUSTOMER_APP', 'SALE_PART') AS incremental FROM dual;\nSELECT partition_name, num_rows,\n       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI:SS') AS last_analyzed, stale_stats\nFROM   dba_tab_statistics\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'SALE_PART'\nORDER  BY partition_position NULLS FIRST;\n\n-- 4) Pre-build synopses on a staging table to be loaded via exchange (12c+):\n-- EXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_STAGE', 'INCREMENTAL', 'TRUE');\n-- EXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_STAGE', 'INCREMENTAL_LEVEL', 'TABLE');\n-- EXEC DBMS_STATS.GATHER_TABLE_STATS('CUSTOMER_APP', 'SALE_STAGE');","what":"En mode incrémental, un synopsis est stocké par partition ; les statistiques globales (niveau table), en particulier le NDV, sont calculées en fusionnant les synopsis sans relire toute la table. Seules les partitions modifiées sont relues.","when":"Sur les grandes tables (échelle To) partitionnées par range/interval où les nouvelles données n'arrivent que dans les partitions récentes, lorsque la collecte des statistiques globales dépasse la fenêtre de maintenance.","caution":"Pas de licence propre, mais l'option Partitioning (EE payante) est déjà requise. Les synopsis consomment de l'espace dans SYSAUX (le format HyperLogLog 12.2+ est bien plus compact). La première collecte est un parcours complet ; lancez-la dans une fenêtre de maintenance. Donner à ESTIMATE_PERCENT une autre valeur que AUTO_SAMPLE_SIZE désactive le mode incrémental.","since":"Oracle 11g","note":"Les préférences INCREMENTAL_STALENESS et INCREMENTAL_LEVEL ont été ajoutées en 12c ; en 12.2+, le format des synopsis est contrôlé par APPROXIMATE_NDV_ALGORITHM (HYPERLOGLOG).","tags":["incremental stats","synopsis","partition statistics","global stats","incremental_staleness","dbms_stats","large table"]},{"id":"partition-split-merge-online","task":"Scinder et fusionner des partitions en ligne","category":"Partitionnement","command":"-- SPLIT: split a range partition in two, keep indexes usable\nALTER TABLE customer_app.sale_part\n  SPLIT PARTITION p2026_h2 AT (DATE '2026-10-01')\n  INTO (PARTITION p2026_q3, PARTITION p2026_q4)\n  UPDATE INDEXES ONLINE;\n\n-- MERGE: merge two adjacent partitions\nALTER TABLE customer_app.sale_part\n  MERGE PARTITIONS p2026_q3, p2026_q4 INTO PARTITION p2026_h2\n  UPDATE INDEXES ONLINE;\n\n-- ADD: new partition at the end (when there is no MAXVALUE)\nALTER TABLE customer_app.sale_part\n  ADD PARTITION p2027 VALUES LESS THAN (DATE '2028-01-01');\n\n-- Check: partition bounds and UNUSABLE index partitions\nSELECT partition_name, partition_position, high_value, num_rows\nFROM   dba_tab_partitions\nWHERE  table_owner = 'CUSTOMER_APP' AND table_name = 'SALE_PART'\nORDER  BY partition_position;\n\nSELECT index_name, partition_name, status\nFROM   dba_ind_partitions\nWHERE  index_owner = 'CUSTOMER_APP' AND status <> 'USABLE';","what":"SPLIT divise une partition en deux à une valeur limite (12c+ : en plusieurs) ; MERGE combine des partitions adjacentes. Avec ONLINE, les DML continuent pendant l'opération ; avec UPDATE INDEXES, les index locaux et globaux ne deviennent pas UNUSABLE.","when":"Pour scinder une partition MAXVALUE/\"autre\" qui a grossi, fusionner d'anciennes petites partitions avant archivage, ou changer le schéma de partitionnement (mensuel vers trimestriel).","caution":"Exige l'option Partitioning (EE payante) ; ONLINE exige Enterprise Edition. SPLIT/MERGE réécrit physiquement les données : lent sur les grosses partitions, génère du redo/undo et de l'espace temporaire. Sans ONLINE, la table est verrouillée pour les DML pendant l'opération ; sans UPDATE INDEXES, les index globaux deviennent UNUSABLE. Scinder une partition vide est rapide (fast split).","since":"Oracle 12.2","note":"ONLINE SPLIT est arrivé en 12.2 et ONLINE MERGE en 18c ; les deux sont disponibles en 19c. Dans les versions plus anciennes, retirez le mot-clé ONLINE et exécutez l'opération dans une fenêtre de maintenance.","tags":["split partition","merge partition","add partition","split a partition","merge partitions","update indexes","online","partitioning"]},{"id":"partition-exchange","task":"Charger en masse ou archiver des données par échange de partition","category":"Partitionnement","command":"-- 1) Staging table that exactly matches the target (12.2+):\nCREATE TABLE customer_app.sale_stage\n  FOR EXCHANGE WITH TABLE customer_app.sale_part;\n\n-- 2) Load the staging table and build indexes matching the local indexes:\nINSERT /*+ APPEND */ INTO customer_app.sale_stage\nSELECT * FROM customer_app.sale_load WHERE created_at >= DATE '2027-01-01';\nCOMMIT;\nCREATE INDEX customer_app.sale_stage_customer_ix ON customer_app.sale_stage (customer_id);\n\n-- 3) Swap (dictionary-only operation, no data movement):\nALTER TABLE customer_app.sale_part\n  EXCHANGE PARTITION p2027 WITH TABLE customer_app.sale_stage\n  INCLUDING INDEXES WITHOUT VALIDATION\n  UPDATE GLOBAL INDEXES;\n\n-- 4) Verify:\nSELECT COUNT(*) FROM customer_app.sale_part PARTITION (p2027);\nSELECT index_name, partition_name, status\nFROM   dba_ind_partitions\nWHERE  index_owner = 'CUSTOMER_APP' AND status <> 'USABLE';","what":"Échange dans le dictionnaire le segment d'une partition avec celui d'une table ordinaire ; des millions de lignes \"entrent\" dans la table ou en \"sortent\" pour archivage en quelques secondes. FOR EXCHANGE WITH TABLE crée une table de staging qui garantit la compatibilité des colonnes et des colonnes cachées.","when":"Pour les chargements en masse DWH/ETL, le déplacement d'une ancienne partition vers une table d'archive, et la préparation des index et statistiques sans affecter la table active pendant le chargement.","caution":"Exige l'option Partitioning (EE payante). WITHOUT VALIDATION ne vérifie pas que les lignes de staging respectent les bornes de la partition ; les données erronées restent dans la mauvaise partition et les requêtes peuvent renvoyer des résultats faux ; en cas de doute, utilisez WITH VALIDATION (parcours complet). Sans UPDATE GLOBAL INDEXES, les index globaux deviennent UNUSABLE. L'état des contraintes unique/PK doit être identique des deux côtés.","note":"CREATE TABLE ... FOR EXCHANGE WITH TABLE a été introduit en 12.2 ; auparavant, créez la table de staging avec CREATE TABLE ... AS SELECT * FROM <table> WHERE 1=0 et vérifiez manuellement les différences de colonnes cachées/virtuelles.","tags":["exchange partition","staging","for exchange with table","bulk load","archive","update global indexes","partitioning"]},{"id":"partition-drop-truncate-gidx","task":"Tronquer/supprimer une partition en gardant les index globaux utilisables","category":"Partitionnement","command":"-- Empty a partition / remove a partition (global indexes stay maintained):\nALTER TABLE customer_app.sale_part TRUNCATE PARTITION p2025 UPDATE INDEXES;\nALTER TABLE customer_app.sale_part DROP PARTITION p2025 UPDATE INDEXES;\n\n-- 12c+ asynchronous global index maintenance: index is VALID but carries orphaned entries\nSELECT index_name, status, orphaned_entries\nFROM   dba_indexes\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'SALE_PART';\n\n-- Clean orphaned entries now (normally SYS.PMO_DEFERRED_GIDX_MAINT_JOB does it at night):\nEXEC DBMS_PART.CLEANUP_GIDX(schema_name_in => 'CUSTOMER_APP', table_name_in => 'SALE_PART');\n-- or a single index: ALTER INDEX customer_app.sale_part_id_gidx COALESCE CLEANUP;\n\n-- Target the partition by value instead of name:\n-- ALTER TABLE customer_app.sale_part DROP PARTITION FOR (DATE '2025-06-15') UPDATE INDEXES;","what":"Supprime instantanément les anciennes données au niveau partition au lieu d'un DELETE ligne à ligne. En 12c+, UPDATE INDEXES ne fait que marquer les index globaux dans le dictionnaire (maintenance asynchrone) au lieu de mettre à jour chaque entrée ; l'index reste immédiatement utilisable et les entrées orphelines sont nettoyées plus tard.","when":"Lors de la purge d'anciennes périodes dans le cadre d'une politique de rétention des données, dans les jobs ILM/de purge, et pour éviter la charge undo/redo d'un DELETE sur les grandes tables.","caution":"Exige l'option Partitioning (EE payante). DROP/TRUNCATE est irréversible (flashback table ne restaure pas une partition) ; sauvegardez ou archivez d'abord par échange. Si UPDATE INDEXES est oublié, les index globaux deviennent UNUSABLE et les requêtes peuvent rencontrer ORA-01502. L'opération échoue si des tables enfants la référencent (FK). Sur les tables interval, la dernière partition range (de transition) ne peut pas être supprimée.","since":"Oracle 12c","tags":["drop partition","truncate partition","update indexes","orphaned_entries","cleanup_gidx","global index","data purge","partitioning"]},{"id":"interval-partitioning","task":"Créer un partitionnement interval et convertir une table range existante en interval","category":"Partitionnement","command":"-- New table: a partition is created automatically for each month\nCREATE TABLE customer_app.measurements (\n  ts        DATE   NOT NULL,\n  sensor_id NUMBER,\n  value     NUMBER)\nPARTITION BY RANGE (ts) INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))\n( PARTITION p_first VALUES LESS THAN (DATE '2026-01-01') );\n\n-- Convert an existing range table to interval (there must be no MAXVALUE partition):\nALTER TABLE customer_app.sale_part SET INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'));\n-- Back to range: ALTER TABLE customer_app.sale_part SET INTERVAL ();\n\n-- Rename a system-named partition to a meaningful name:\nALTER TABLE customer_app.measurements RENAME PARTITION FOR (DATE '2026-03-15') TO p2026_03;\n\n-- Check:\nSELECT partition_name, partition_position, interval, high_value\nFROM   dba_tab_partitions\nWHERE  table_owner = 'CUSTOMER_APP' AND table_name = 'MEASUREMENTS'\nORDER  BY partition_position;\n\nSELECT table_name, partitioning_type, interval\nFROM   dba_part_tables\nWHERE  owner = 'CUSTOMER_APP';","what":"Le partitionnement interval crée automatiquement une nouvelle partition à l'arrivée de la première ligne au-delà de la dernière borne, ce qui élimine les erreurs ORA-14400 dues à l'oubli de la partition du mois suivant. SET INTERVAL convertit une table range existante sans déplacer de données.","when":"Pour les tables qui croissent par date, comme les séries temporelles, les journaux et les commandes, et pour réduire les scripts de maintenance des partitions.","caution":"Exige l'option Partitioning (EE payante). La clé de partitionnement doit être une seule colonne NUMBER/DATE/TIMESTAMP ; une partition MAXVALUE doit d'abord être supprimée. Une date erronée ou extrême (par ex. l'an 9999) ouvre une partition lointaine et fausse la numérotation des partitions ; avec des intervalles étroits, vous pouvez approcher la limite de 1 048 575 partitions (ORA-14300), validez donc les dates dans l'application. Les partitions automatiques vont dans le tablespace par défaut de la table (orientez-les avec STORE IN).","since":"Oracle 11g","tags":["interval partitioning","interval","automatic partition","numtoyminterval","ora-14400","set interval","range partition","partitioning"]},{"id":"partition-wise-join-check","task":"Vérifier les clés de partitionnement et l'utilisation des partition-wise joins","category":"Partitionnement","command":"-- 1) Do both tables have the same partitioning type, key and count?\nSELECT p.owner, p.table_name, p.partitioning_type, k.column_name,\n       p.partition_count, p.subpartitioning_type\nFROM   dba_part_tables p\n  JOIN dba_part_key_columns k\n    ON k.owner = p.owner AND k.name = p.table_name AND k.object_type = 'TABLE'\nWHERE  p.owner = 'CUSTOMER_APP'\n  AND  p.table_name IN ('CUSTOMER_H', 'ORDERS_H');\n\n-- 2) Explain the join and inspect the plan shape:\nEXPLAIN PLAN FOR\nSELECT /*+ PARALLEL(4) */ m.city, SUM(s.amount)\nFROM   customer_app.customer_h m\n  JOIN customer_app.orders_h s ON s.customer_id = m.id\nGROUP  BY m.city;\n\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +PARALLEL +PARTITION'));\n-- Full PWJ: HASH JOIN sits BELOW the PARTITION HASH ALL line (PX PARTITION HASH ALL in parallel)\n-- Partial PWJ: PX SEND PARTITION (KEY) on one side only\n-- No PWJ: PX SEND HASH / BROADCAST on both sides","what":"Deux tables équi-partitionnées sur la clé de jointure peuvent être jointes paire de partitions par paire de partitions (full partition-wise join), ce qui réduit fortement le trafic de redistribution et la mémoire dans les requêtes parallèles. La forme du plan indique si le PWJ est utilisé.","when":"Lors de la conception de grandes jointures fact-to-fact ou fact-to-dimension dans un DWH, lorsque les requêtes parallèles montrent une forte redistribution PX (PX Deq Credit: send blkd), et lors de la validation de la stratégie de partitionnement.","caution":"Licence : option Partitioning (EE payante) ; les requêtes parallèles exigent Enterprise Edition. EXPLAIN PLAN est une estimation ; utilisez DISPLAY_CURSOR/SQL Monitor pour le plan réel. Un PWJ complet nécessite le même nombre de partitions (une puissance de deux est recommandée pour le hash) et des types de données de clé identiques. Le PWJ peut aussi apparaître dans des plans séquentiels, mais le vrai gain est en exécution parallèle.","tags":["partition-wise join","pwj","equi-partitioning","hash partition","parallel query","dba_part_key_columns","partitioning"]},{"id":"online-redefinition-flow","task":"Redéfinir une table en ligne avec DBMS_REDEFINITION (flux complet)","category":"Opérations en ligne","command":"-- 1) Is it eligible? (use CONS_USE_ROWID if there is no PK)\nEXEC DBMS_REDEFINITION.CAN_REDEF_TABLE('CUSTOMER_APP', 'CUSTOMER', DBMS_REDEFINITION.CONS_USE_PK);\n\n-- 2) Interim table with the target structure: hash partitioned in this example\nCREATE TABLE customer_app.customer_int (\n  id NUMBER, name VARCHAR2(100), city VARCHAR2(30))\nPARTITION BY HASH (id) PARTITIONS 8;\n\n-- 3) Start: initial copy + change tracking through an MV log\nBEGIN\n  DBMS_REDEFINITION.START_REDEF_TABLE(\n    uname        => 'CUSTOMER_APP',\n    orig_table   => 'CUSTOMER',\n    int_table    => 'CUSTOMER_INT',\n    col_mapping  => NULL,\n    options_flag => DBMS_REDEFINITION.CONS_USE_PK);\nEND;\n/\n\n-- 4) Copy indexes, constraints, triggers, grants and statistics:\nSET SERVEROUTPUT ON\nDECLARE\n  l_errors PLS_INTEGER;\nBEGIN\n  DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS(\n    uname            => 'CUSTOMER_APP',\n    orig_table       => 'CUSTOMER',\n    int_table        => 'CUSTOMER_INT',\n    copy_indexes     => DBMS_REDEFINITION.CONS_ORIG_PARAMS,\n    copy_triggers    => TRUE,\n    copy_constraints => TRUE,\n    copy_privileges  => TRUE,\n    ignore_errors    => FALSE,\n    num_errors       => l_errors,\n    copy_statistics  => TRUE);\n  DBMS_OUTPUT.PUT_LINE('Error count: ' || l_errors);\nEND;\n/\nSELECT object_name, base_table_name, ddl_txt FROM dba_redefinition_errors;\n\n-- 5) Close the gap before finishing (shortens finish; can be run several times):\nEXEC DBMS_REDEFINITION.SYNC_INTERIM_TABLE('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_INT');\n\n-- 6) Swap under a short lock:\nEXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_INT', dml_lock_timeout => 60);\n\n-- 7) Drop the interim table, which now holds the old structure:\nDROP TABLE customer_app.customer_int PURGE;\n\n-- On problems: EXEC DBMS_REDEFINITION.ABORT_REDEF_TABLE('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_INT');\n-- Status: SELECT * FROM dba_redefinition_status WHERE base_table_owner = 'CUSTOMER_APP';","what":"Modifie la structure d'une table (partitionnement, types/ordre des colonnes, tablespace, compression, paramètres LOB) pendant que l'application continue de tourner : les données sont copiées dans une table intermédiaire, les changements sont synchronisés via un MV log, et à la fin les noms sont échangés sous un verrou très bref.","when":"Pour les changements qu'ALTER TABLE ne peut pas faire en ligne, comme partitionner sans interruption une grande table non partitionnée, changer le type d'une colonne ou convertir des LOB BasicFile en SecureFiles.","caution":"Exige Enterprise Edition (plus l'option Partitioning si la cible est partitionnée). Le processus nécessite deux fois l'espace de la table et ajoute une surcharge de redo et de MV log. FINISH prend un bref verrou exclusif ; les transactions longues le font attendre (dml_lock_timeout). Vérifiez toujours les erreurs de COPY_TABLE_DEPENDENTS dans DBA_REDEFINITION_ERRORS ; les FK peuvent être copiées en NOVALIDATE. Répétez d'abord dans un environnement de test.","note":"Le paramètre dml_lock_timeout a été ajouté en 12c. En 12.2+, suivez la progression dans V$ONLINE_REDEF. Pour un simple déplacement/changement de compression, ALTER TABLE ... MOVE ONLINE suffit au lieu de DBMS_REDEFINITION.","tags":["dbms_redefinition","online redefinition","redefinition","zero downtime","convert to partitioned","can_redef_table","finish_redef_table","enterprise edition"]},{"id":"table-move-online","task":"Déplacer une table ou une partition en ligne (ALTER TABLE MOVE ONLINE)","category":"Opérations en ligne","command":"-- Move the table to another tablespace / reorganize it (DML continues):\nALTER TABLE customer_app.orders MOVE ONLINE TABLESPACE customer_data;\n\n-- Compress while moving (BASIC; for OLTP compression use ROW STORE COMPRESS ADVANCED):\nALTER TABLE customer_app.orders MOVE ONLINE COMPRESS;\n\n-- A single partition:\nALTER TABLE customer_app.sale_part\n  MOVE PARTITION p2026_h1 ONLINE TABLESPACE customer_archive UPDATE INDEXES;\n\n-- Verify: indexes must stay VALID\nSELECT index_name, status FROM dba_indexes\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'ORDERS';\nSELECT table_name, tablespace_name, compression, compress_for\nFROM   dba_tables\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'ORDERS';","what":"Réécrit le segment de la table : réduit la fragmentation et le high water mark, change de tablespace, applique une compression. Avec ONLINE, les DML ne sont pas bloqués pendant l'opération et les index sont maintenus automatiquement et restent VALID.","when":"Pour récupérer de l'espace après des suppressions massives, déplacer une table vers un nouveau tablespace/stockage, et compresser d'anciennes partitions dans un tablespace d'archive.","caution":"Exige Enterprise Edition ; ROW STORE COMPRESS ADVANCED exige l'option Advanced Compression (payante). L'opération nécessite un espace supplémentaire égal à la taille de la table et génère beaucoup de redo. De brefs verrous sont pris au début et à la fin ; les longues transactions ouvertes la font attendre. Un MOVE sans ONLINE ni UPDATE INDEXES laisse tous les index UNUSABLE.","since":"Oracle 12.2","note":"MOVE ONLINE au niveau table a été introduit en 12.2 ; MOVE PARTITION ONLINE au niveau partition existe depuis 12.1. En 12.1 et antérieur, utilisez DBMS_REDEFINITION pour les tables.","tags":["move online","alter table move","move table","reorganization","high water mark","compression","enterprise edition"]},{"id":"tde-keystore-setup","task":"Créer, ouvrir, définir la clé maître et sauvegarder un keystore TDE (19c+)","category":"Sécurité","command":"-- 1) Keystore location (static parameter, restart required), then keystore type:\nALTER SYSTEM SET WALLET_ROOT = '/u01/app/oracle/admin/ORCL/wallet' SCOPE = SPFILE;\n-- SHUTDOWN IMMEDIATE;  STARTUP;\nALTER SYSTEM SET TDE_CONFIGURATION = 'KEYSTORE_CONFIGURATION=FILE' SCOPE = BOTH;\n\n-- 2) In CDB root: create a software keystore (under WALLET_ROOT/tde), open it, set the master key\nADMINISTER KEY MANAGEMENT CREATE KEYSTORE IDENTIFIED BY \"<keystore_pwd>\";\nADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN\n  IDENTIFIED BY \"<keystore_pwd>\" CONTAINER = ALL;\nADMINISTER KEY MANAGEMENT SET KEY USING TAG 'mk_2026_10'\n  IDENTIFIED BY \"<keystore_pwd>\" WITH BACKUP USING 'mk_before' CONTAINER = ALL;\n\n-- 3) Open automatically after restart (auto-login, cwallet.sso):\nADMINISTER KEY MANAGEMENT CREATE AUTO_LOGIN KEYSTORE\n  FROM KEYSTORE IDENTIFIED BY \"<keystore_pwd>\";\n\n-- 4) Backup (repeat after every SET KEY / rekey, store SEPARATELY from database backups):\nADMINISTER KEY MANAGEMENT BACKUP KEYSTORE USING 'daily'\n  FORCE KEYSTORE IDENTIFIED BY \"<keystore_pwd>\" TO '/backup/tde/';\n\n-- 5) Status:\nSELECT con_id, wrl_type, wrl_parameter, status, wallet_type, keystore_mode\nFROM   v$encryption_wallet;\nSELECT con_id, key_id, tag, creation_time, activation_time\nFROM   v$encryption_keys ORDER BY creation_time;","what":"Met en place un keystore logiciel pour Transparent Data Encryption en mode united (toutes les PDB partagent le keystore de la CDB), crée la clé de chiffrement maître et active l'auto-login pour qu'il s'ouvre après un redémarrage. En 19c+, la méthode recommandée est WALLET_ROOT/TDE_CONFIGURATION plutôt que ENCRYPTION_WALLET_LOCATION dans sqlnet.ora.","when":"Comme première étape avant le chiffrement de tablespaces/colonnes dans les projets de conformité exigeant le chiffrement des données au repos (RGPD, PCI-DSS, ISO 27001).","caution":"En on-premise, exige l'option Advanced Security (payante) ; elle est incluse dans les licences Oracle Cloud (OCI/Autonomous). WALLET_ROOT est statique et nécessite un redémarrage de l'instance (interruption). Si le keystore et son mot de passe sont perdus, les données chiffrées sont DÉFINITIVEMENT illisibles ; stockez les sauvegardes du keystore en lieu sûr et séparément des sauvegardes de la base. Toute personne détenant le fichier auto-login (cwallet.sso) peut ouvrir la base ; restreignez les permissions du fichier et utilisez LOCAL AUTO_LOGIN si approprié. Avec Data Guard, copiez aussi le keystore sur la standby.","since":"Oracle 18c","note":"WALLET_ROOT et TDE_CONFIGURATION ont été introduits en 18c et sont standard en 19c ; la méthode ENCRYPTION_WALLET_LOCATION de sqlnet.ora (12c-18c) est dépréciée en 19c et n'est plus prise en charge en 23ai. En 23ai, l'algorithme par défaut est AES256 ; le chiffrement avec GOST et SEED n'est plus pris en charge et le déchiffrement avec ceux-ci est déprécié. WALLET_ROOT est un paramètre statique et exige un redémarrage de l'instance.","tags":["tde","keystore","wallet","master key","encryption","advanced security","wallet_root","administer key management"],"untested":true},{"id":"tde-tablespace-encryption","task":"Chiffrer des tablespaces avec TDE (nouveaux et existants, en ligne/hors ligne)","category":"Sécurité","command":"-- Prerequisite: keystore OPEN and master key set (v$encryption_wallet.status = 'OPEN')\n\n-- 1) New encrypted tablespace:\nCREATE TABLESPACE customer_enc\n  DATAFILE '+DATA' SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 20G\n  ENCRYPTION USING 'AES256' ENCRYPT;\n\n-- 2a) Encrypt an existing tablespace ONLINE (12.2+, needs extra space temporarily):\nALTER TABLESPACE customer_data ENCRYPTION ONLINE USING 'AES256' ENCRYPT;\n-- If not OMF: ... ENCRYPT FILE_NAME_CONVERT = ('customer_data01.dbf', 'customer_data01_enc.dbf');\n\n-- 2b) OFFLINE conversion (no extra space, tablespace unavailable meanwhile):\nALTER TABLESPACE customer_data OFFLINE NORMAL;\nALTER TABLESPACE customer_data ENCRYPTION OFFLINE ENCRYPT;\nALTER TABLESPACE customer_data ONLINE;\n\n-- 3) Progress and status:\nSELECT t.name AS tablespace_name, e.encryptionalg, e.status, e.con_id\nFROM   v$encrypted_tablespaces e\n  JOIN v$tablespace t ON t.ts# = e.ts# AND t.con_id = e.con_id;\nSELECT tablespace_name, encrypted FROM dba_tablespaces ORDER BY tablespace_name;","what":"Le chiffrement de tablespace chiffre chaque bloc écrit dans les datafiles ; les données sont en clair dans le buffer cache, donc aucun changement d'application ni de SQL n'est nécessaire. La conversion en ligne construit une copie chiffrée du datafile et bascule dessus pendant que les DML continuent ; la conversion hors ligne chiffre les blocs sur place.","when":"Pour chiffrer au repos les tablespaces applicatifs contenant des données sensibles, déplacer des données vers un nouveau tablespace chiffré, ou clore une constatation d'audit.","caution":"En on-premise, exige l'option Advanced Security (payante). La conversion en ligne nécessite un espace supplémentaire égal aux datafiles chiffrés et génère beaucoup d'E/S et de redo ; une standby Data Guard effectue le même travail. La conversion hors ligne doit être exécutée séparément sur une standby Data Guard. SYSTEM/SYSAUX/UNDO peuvent être chiffrés en ligne en 12.2+ mais demandent une attention particulière. Les sauvegardes de tablespaces chiffrés ne peuvent pas être restaurées sans le keystore.","since":"Oracle 12.2","note":"La conversion de tablespace en ligne/hors ligne a été introduite en 12.2. Pour chiffrer automatiquement les nouveaux tablespaces, 19c utilise TABLESPACE_ENCRYPTION=AUTO_ENABLE (RU 19.16+) ou ENCRYPT_NEW_TABLESPACES=ALWAYS. Le keystore doit d'abord être ouvert avec une clé maître définie (voir tde-keystore-setup).","tags":["tde","tablespace encryption","encryption online","aes256","v$encrypted_tablespaces","advanced security","at-rest"],"untested":true},{"id":"unified-audit-policy-lifecycle","task":"Créer une politique Unified Auditing, l'activer sous condition, consulter les enregistrements, la supprimer","category":"Sécurité","command":"-- 1) Object + condition based policy (excluding the batch module):\nCREATE AUDIT POLICY pol_customer_dml\n  ACTIONS SELECT ON customer_app.customer,\n          UPDATE ON customer_app.customer,\n          DELETE ON customer_app.customer\n  WHEN 'SYS_CONTEXT(''USERENV'',''MODULE'') NOT LIKE ''batch%'''\n  EVALUATE PER SESSION;\n\n-- 2) Privileged operations policy:\nCREATE AUDIT POLICY pol_ddl_priv\n  PRIVILEGES DROP ANY TABLE, ALTER SYSTEM\n  ACTIONS DROP USER, ALTER USER, CREATE USER;\n\n-- 3) Enable:\nAUDIT POLICY pol_customer_dml EXCEPT customer_app;   -- everyone except the schema owner\nAUDIT POLICY pol_ddl_priv WHENEVER SUCCESSFUL;\nAUDIT POLICY ORA_LOGON_FAILURES WHENEVER NOT SUCCESSFUL;\n\n-- 4) Which policy is enabled for whom?\nSELECT policy_name, enabled_option, entity_name, success, failure\nFROM   audit_unified_enabled_policies ORDER BY policy_name;\nSELECT policy_name, audit_option, object_schema, object_name, audit_condition\nFROM   audit_unified_policies WHERE policy_name LIKE 'POL_%';\n\n-- 5) Records:\nSELECT event_timestamp, dbusername, action_name, object_schema, object_name,\n       return_code, unified_audit_policies, os_username, userhost\nFROM   unified_audit_trail\nWHERE  event_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY\n  AND  unified_audit_policies LIKE '%POL_%'\nORDER  BY event_timestamp DESC;\n-- 19c (queued-write): EXEC DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL;  (not in 23ai)\n\n-- 6) Disable and remove:\nNOAUDIT POLICY pol_customer_dml;\nNOAUDIT POLICY pol_ddl_priv;\nDROP AUDIT POLICY pol_customer_dml;\nDROP AUDIT POLICY pol_ddl_priv;","what":"Unified Auditing collecte tous les enregistrements d'audit dans une piste unique (schéma AUDSYS, UNIFIED_AUDIT_TRAIL). Les politiques sont définies par action sur objet, privilège système ou action ; la condition WHEN et les listes d'utilisateurs BY/EXCEPT réduisent le bruit.","when":"Pour enregistrer les accès aux tables sensibles, surveiller les DDL privilégiés et les changements de privilèges, voir les tentatives de connexion échouées et satisfaire les exigences d'audit.","caution":"Aucune licence supplémentaire. Auditer les SELECT sur des tables très sollicitées produit de nombreux enregistrements et fait grossir SYSAUX ; restreignez avec des conditions et EXCEPT, et planifiez l'archivage/le nettoyage avec DBMS_AUDIT_MGMT (CLEAN_AUDIT_TRAIL, SET_LAST_ARCHIVE_TIMESTAMP). Une politique activée ne peut pas être supprimée (ORA-46361) ; exécutez d'abord NOAUDIT. Les changements de politique prennent effet dans les nouvelles sessions. 19c fonctionne en mode mixte par défaut, donc des enregistrements AUDIT_TRAIL traditionnels peuvent aussi être écrits.","since":"Oracle 12c","note":"En 23ai, l'audit traditionnel n'est plus pris en charge ; seul l'unified auditing existe, et FLUSH_UNIFIED_AUDIT_TRAIL a été supprimé car les enregistrements sont écrits directement dans la table. En 19c, passer en mode unified pur exige de relinker le binaire oracle avec uniaud_on.","tags":["unified auditing","audit policy","auditing","unified_audit_trail","create audit policy","noaudit","ora_logon_failures","security"]},{"id":"vpd-row-level-policy","task":"Ajouter une politique d'accès au niveau ligne avec VPD (DBMS_RLS)","category":"Sécurité","command":"-- 1) Policy function returning the predicate:\nCREATE OR REPLACE FUNCTION customer_app.customer_vpd_fn (\n  p_schema IN VARCHAR2,\n  p_object IN VARCHAR2\n) RETURN VARCHAR2\nAS\nBEGIN\n  -- The schema owner sees all rows (NULL = no restriction)\n  IF SYS_CONTEXT('USERENV', 'SESSION_USER') = 'CUSTOMER_APP' THEN\n    RETURN NULL;\n  END IF;\n  -- Other users see only their own city\n  RETURN 'city = SYS_CONTEXT(''USERENV'', ''CLIENT_IDENTIFIER'')';\nEND;\n/\n\n-- 2) Attach the policy to the table:\nBEGIN\n  DBMS_RLS.ADD_POLICY(\n    object_schema   => 'CUSTOMER_APP',\n    object_name     => 'CUSTOMER',\n    policy_name     => 'CUSTOMER_CITY_VPD',\n    function_schema => 'CUSTOMER_APP',\n    policy_function => 'CUSTOMER_VPD_FN',\n    statement_types => 'SELECT, UPDATE, DELETE',\n    update_check    => TRUE,\n    policy_type     => DBMS_RLS.CONTEXT_SENSITIVE);\nEND;\n/\n\n-- 3) Check:\nSELECT object_owner, object_name, policy_name, function,\n       sel, upd, del, chk_option, enable, policy_type\nFROM   dba_policies WHERE object_owner = 'CUSTOMER_APP';\n-- Test (as the application user): EXEC DBMS_SESSION.SET_IDENTIFIER('Ankara');\n\n-- 4) Temporarily disable / drop:\nEXEC DBMS_RLS.ENABLE_POLICY('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_CITY_VPD', FALSE);\nEXEC DBMS_RLS.DROP_POLICY('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_CITY_VPD');","what":"Virtual Private Database ajoute automatiquement le prédicat WHERE renvoyé par la fonction de politique à chaque SQL sur la table, de sorte que les utilisateurs d'une même table ne voient/modifient que les lignes auxquelles ils ont droit. update_check=>TRUE empêche un utilisateur de mettre à jour une ligne vers une valeur qu'il ne pourrait pas voir.","when":"Dans les applications multi-tenant, pour l'isolation des données par région/agence, et pour ajouter une sécurité au niveau ligne sans toucher au code applicatif.","caution":"Inclus dans Enterprise Edition, aucune licence supplémentaire. CLIENT_IDENTIFIER peut être défini librement par le client ; en production, utilisez un application context défini par un package de confiance (CREATE CONTEXT ... USING). SYS et les utilisateurs ayant EXEMPT ACCESS POLICY contournent la politique ; les exports Data Pump sans ce privilège exportent silencieusement des données partielles. Une erreur dans la fonction bloque tout accès avec ORA-28112. Un mauvais policy_type (par ex. STATIC) met le prédicat en cache et renvoie des résultats faux.","tags":["vpd","virtual private database","dbms_rls","row security","row level security","add_policy","policy function","security"]},{"id":"password-profile-hardening","task":"Créer un profil de politique de mots de passe et l'attribuer aux utilisateurs","category":"Sécurité","command":"-- Hardened profile for application/human users:\nCREATE PROFILE app_user_prf LIMIT\n  FAILED_LOGIN_ATTEMPTS    5\n  PASSWORD_LOCK_TIME       1/24          -- 1 hour lock\n  PASSWORD_LIFE_TIME       90\n  PASSWORD_GRACE_TIME      7\n  PASSWORD_REUSE_TIME      365\n  PASSWORD_REUSE_MAX       10\n  INACTIVE_ACCOUNT_TIME    60            -- accounts unused for 60 days are locked\n  PASSWORD_VERIFY_FUNCTION ora12c_strong_verify_function;\n\n-- Service accounts: password never expires, gradual password rollover enabled:\nCREATE PROFILE service_account_prf LIMIT\n  FAILED_LOGIN_ATTEMPTS    UNLIMITED\n  PASSWORD_LIFE_TIME       UNLIMITED\n  PASSWORD_ROLLOVER_TIME   1;            -- old and new password both valid for 1 day\n\nALTER USER customer_app PROFILE app_user_prf;\n\n-- Check:\nSELECT username, profile, account_status,\n       TO_CHAR(expiry_date,'YYYY-MM-DD') AS expiry_date, lock_date\nFROM   dba_users\nWHERE  profile IN ('APP_USER_PRF', 'SERVICE_ACCOUNT_PRF');\n\nSELECT profile, resource_name, limit\nFROM   dba_profiles\nWHERE  profile = 'APP_USER_PRF' AND resource_type = 'PASSWORD'\nORDER  BY resource_name;","what":"Un profil applique à un groupe d'utilisateurs des règles de verrouillage après échecs de connexion, de durée de vie, de réutilisation et de complexité (verify function) des mots de passe, et de verrouillage des comptes inactifs. PASSWORD_ROLLOVER_TIME permet aux applications de basculer sans interruption pendant le changement du mot de passe d'un compte de service.","when":"Pour les exigences d'audit de sécurité/CIS benchmark, lors de la création de nouveaux utilisateurs applicatifs, et pour séparer par groupe d'utilisateurs les valeurs laxistes du profil DEFAULT.","caution":"Aucune licence supplémentaire. Une limite FAILED_LOGIN_ATTEMPTS sur un compte de service permet à un seul serveur d'application avec un mauvais mot de passe de verrouiller le compte et de provoquer une interruption de production. PASSWORD_LIFE_TIME sur les comptes applicatifs peut provoquer un état EXPIRED inattendu. La verify function doit exister sous SYS (sinon @?/rdbms/admin/utlpwdmg.sql ; ce script modifie aussi le profil DEFAULT). Changer de profil ne revalide pas les mots de passe existants.","note":"INACTIVE_ACCOUNT_TIME a été ajouté en 12.2 ; PASSWORD_ROLLOVER_TIME est arrivé en 21c et a été rétroporté en 19c avec la RU 19.12. Les sessions se connectant avec l'ancien mot de passe pendant la période de rollover peuvent être suivies via les enregistrements d'audit.","tags":["profile","password policy","password_verify_function","failed_login_attempts","inactive_account_time","password_rollover_time","account lock","security"]},{"id":"privilege-analysis-capture","task":"Trouver les privilèges inutilisés avec privilege analysis","category":"Sécurité","command":"-- 1) Capture privilege usage for a specific user:\nBEGIN\n  DBMS_PRIVILEGE_CAPTURE.CREATE_CAPTURE(\n    name        => 'CAP_APP_USER',\n    description => 'Actual privilege usage of the application user',\n    type        => DBMS_PRIVILEGE_CAPTURE.G_CONTEXT,\n    condition   => 'SYS_CONTEXT(''USERENV'',''SESSION_USER'') = ''CUSTOMER_APP''');\n  DBMS_PRIVILEGE_CAPTURE.ENABLE_CAPTURE(name => 'CAP_APP_USER', run_name => 'RUN_2026_10');\nEND;\n/\n-- Other types: G_DATABASE (whole DB), G_ROLE (roles => role_name_list('DBA')), G_ROLE_AND_CONTEXT\n\n-- 2) After a representative workload (including month end), stop and generate results:\nBEGIN\n  DBMS_PRIVILEGE_CAPTURE.DISABLE_CAPTURE(name => 'CAP_APP_USER');\n  DBMS_PRIVILEGE_CAPTURE.GENERATE_RESULT(name => 'CAP_APP_USER', run_name => 'RUN_2026_10');\nEND;\n/\n\n-- 3) Used and never-used privileges:\nSELECT username, sys_priv, obj_priv, object_owner, object_name, used_role\nFROM   dba_used_privs   WHERE capture = 'CAP_APP_USER';\nSELECT username, sys_priv, obj_priv, object_owner, object_name\nFROM   dba_unused_privs WHERE capture = 'CAP_APP_USER';\n\n-- 4) Through which role chain was the privilege used?\nSELECT username, sys_priv, used_role, path\nFROM   dba_used_sysprivs_path WHERE capture = 'CAP_APP_USER';\n\n-- 5) Cleanup:\nEXEC DBMS_PRIVILEGE_CAPTURE.DROP_CAPTURE(name => 'CAP_APP_USER');","what":"Privilege analysis enregistre à l'exécution quels privilèges système/objet sont réellement utilisés dans la portée choisie (base entière, rôle, condition) ; le résultat liste les privilèges inutilisés afin de planifier des REVOKE selon le principe du moindre privilège.","when":"Lors du nettoyage de comptes applicatifs ayant des privilèges DBA/ANY, pour les revues de privilèges avant un audit, et pour savoir ce dont un progiciel a réellement besoin.","caution":"Inclus dans Enterprise Edition depuis 2018 (exigeait auparavant une licence Database Vault) ; aucune licence supplémentaire. La capture entraîne une légère surcharge. Le résultat ne reflète que la charge de la fenêtre de capture : si les traitements de fin de mois, de fin d'année ou d'urgence n'ont pas tourné, un privilège indiqué comme \"inutilisé\" peut encore être nécessaire, révoquez donc progressivement. Une seule capture peut être active à la fois ; seule exception, une capture G_DATABASE peut être activée en même temps qu'une capture de rôle ou de contexte.","since":"Oracle 12c","tags":["privilege analysis","dbms_privilege_capture","unused privileges","least privilege","dba_unused_privs","privilege cleanup","security"]},{"id":"opatch-inventory-prereq","task":"Inspecter l'inventaire avec OPatch et effectuer les vérifications avant patch","category":"Patchs et cycle de vie","command":"# OPatch version (must meet the minimum in the patch README; update with patch 6880880 if needed)\n$ORACLE_HOME/OPatch/opatch version\n\n# Installed patches: short list / detailed inventory\n$ORACLE_HOME/OPatch/opatch lspatches\n$ORACLE_HOME/OPatch/opatch lsinventory -detail -oh $ORACLE_HOME\n\n# Pre-apply checks (in the unzipped patch directory):\ncd /u01/stage/<patch_id>\n$ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./\n$ORACLE_HOME/OPatch/opatch prereq CheckSystemSpace -ph ./\n\n# For Grid Infrastructure / RAC homes (as root), analyze mode:\n# opatchauto apply /u01/stage/<patch_id> -analyze","what":"Liste les patchs binaires (RU, one-off) appliqués à l'Oracle Home et vérifie, avant d'appliquer un nouveau patch, s'il entre en conflit avec les patchs existants et s'il y a assez d'espace disque.","when":"Avant une RU trimestrielle/un patch de sécurité, lorsque le support demande la sortie de l'inventaire sur un SR, et pour comparer le niveau de patch de deux environnements.","caution":"Exécutez les commandes en tant qu'utilisateur OS propriétaire de l'Oracle Home (oracle) ; opatchauto exige root. Les vérifications prereq sont en lecture seule et n'affectent pas la base, mais si un conflit est signalé, n'appliquez rien avant d'avoir obtenu un patch merge/overlay du support Oracle. Sauvegardez l'Oracle Home (tar) et l'inventaire avant d'appliquer.","note":"Pour consulter l'inventaire en SQL, DBMS_QOPATCH.GET_OPATCH_LSINVENTORY peut être utilisé (sur les installations disposant d'un inventaire OPatch).","tags":["opatch","lsinventory","lspatches","prereq","patch","patch conflict","opatchauto","release update"],"untested":true},{"id":"datapatch-apply-verify","task":"Mettre à jour la couche SQL avec datapatch -verbose après un patch binaire","category":"Patchs et cycle de vie","command":"# 1) After the binary patch (opatch apply), start the DB; all PDBs must be open:\nsqlplus / as sysdba <<EOF\nSTARTUP;\nALTER PLUGGABLE DATABASE ALL OPEN;\nEXIT;\nEOF\n\n# 2) Check only first, then apply:\ncd $ORACLE_HOME/OPatch\n./datapatch -prereq\n./datapatch -verbose\n# For specific PDBs: ./datapatch -verbose -pdbs PDB1,PDB2\n\n# 3) Recompile invalid objects:\nsqlplus / as sysdba @?/rdbms/admin/utlrp.sql\n\n# Logs: $ORACLE_BASE/cfgtoollogs/sqlpatch/","what":"datapatch applique les composants SQL/PL-SQL (dictionnaire, packages) des patchs binaires de l'Oracle Home à la CDB root, à PDB$SEED et à toutes les PDB ouvertes, et consigne le résultat dans DBA_REGISTRY_SQLPATCH. Il annule aussi la partie SQL des patchs retirés.","when":"Après chaque patch binaire RU/RUR/one-off ou son retrait ; pour les PDB déplacées ou débranchées/branchées dans un home nouvellement patché ; et pour le relancer lorsque DBA_REGISTRY_SQLPATCH affiche WITH ERRORS.","caution":"Les PDB fermées ou MOUNTED ne sont pas patchées et peuvent s'ouvrir en mode RESTRICTED ; ouvrez-les toutes d'abord. Ne l'exécutez pas sous forte charge (verrous du dictionnaire, invalidation de packages). En RAC, exécutez-le une seule fois depuis un seul nœud. Avec Data Guard, exécutez datapatch uniquement sur la primaire ; les changements atteignent la standby via le redo (patchez d'abord les binaires de la standby).","note":"Le datapatch livré avec les RU 19c récentes propose -sanity_checks pour vérifier la santé du dictionnaire et de l'environnement (objets invalides, violations de PDB, état des tablespaces/TEMP) avant le patch.","tags":["datapatch","sql patch","release update","dba_registry_sqlpatch","post-patch","utlrp","pdb","patching"],"untested":true},{"id":"sqlpatch-registry-check","task":"Vérifier l'historique des patchs SQL, l'état des composants et les objets invalides","category":"Patchs et cycle de vie","command":"-- 1) datapatch history (most recent at the bottom):\nSELECT patch_id, patch_type, action, status, source_version, target_version,\n       TO_CHAR(action_time,'YYYY-MM-DD HH24:MI') AS action_time, description\nFROM   dba_registry_sqlpatch\nORDER  BY action_time;\n\n-- Failed entries and their log files:\nSELECT install_id, patch_id, action, status, logfile\nFROM   dba_registry_sqlpatch\nWHERE  status <> 'SUCCESS';\n\n-- In a CDB, for all PDBs (from root):\nSELECT con_id, patch_id, action, status\nFROM   cdb_registry_sqlpatch ORDER BY con_id, action_time;\n\n-- 2) Components must be VALID:\nSELECT comp_id, comp_name, version_full, status\nFROM   dba_registry ORDER BY comp_id;\n\n-- 3) Invalid object summary (compare before/after utlrp):\nSELECT owner, object_type, COUNT(*) AS invalid_cnt\nFROM   dba_objects\nWHERE  status = 'INVALID'\nGROUP  BY owner, object_type\nORDER  BY invalid_cnt DESC;","what":"Montre d'un coup d'œil si la couche SQL a vraiment été mise à jour après le patch (dictionnaire en phase avec les binaires), quel patch a échoué dans quelle PDB, si les composants du dictionnaire sont VALID, et le nombre d'objets invalides.","when":"Comme étape de vérification après chaque datapatch, pour capturer une référence avant un patch ou une mise à niveau, et lorsqu'une incohérence est suspectée après le branchement d'une PDB.","caution":"Lecture seule ; aucune licence requise. SOURCE_VERSION/TARGET_VERSION sont renseignés dans le modèle RU 18c+. Si STATUS='WITH ERRORS', corrigez l'erreur indiquée dans LOGFILE et relancez datapatch. Certains composants (par ex. XDB/JAVA inutilisés) peuvent apparaître INVALID/REMOVED ; comparez avec l'état avant patch.","note":"Pour voir l'inventaire binaire en SQL, utilisez SELECT XMLTRANSFORM(DBMS_QOPATCH.GET_OPATCH_LIST, DBMS_QOPATCH.GET_OPATCH_XSLT) FROM dual; il lève ORA-20013 sur les installations sans inventaire OPatch.","tags":["dba_registry_sqlpatch","patch history","datapatch","dba_registry","invalid objects","invalid object","patch verification"]},{"id":"scheduler-job-lifecycle","task":"Créer, exécuter manuellement, désactiver et supprimer un job du scheduler","category":"Scheduler","command":"-- 1) Create disabled, set attributes, then enable:\nBEGIN\n  DBMS_SCHEDULER.CREATE_JOB(\n    job_name        => 'CUSTOMER_APP.NIGHTLY_STATS',\n    job_type        => 'PLSQL_BLOCK',\n    job_action      => q'[BEGIN DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CUSTOMER_APP', options => 'GATHER STALE'); END;]',\n    start_date      => SYSTIMESTAMP,\n    repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=30; BYSECOND=0',\n    enabled         => FALSE,\n    auto_drop       => FALSE,\n    comments        => 'Nightly stale statistics gathering');\n  DBMS_SCHEDULER.SET_ATTRIBUTE('CUSTOMER_APP.NIGHTLY_STATS', 'max_run_duration', INTERVAL '2' HOUR);\n  DBMS_SCHEDULER.SET_ATTRIBUTE('CUSTOMER_APP.NIGHTLY_STATS', 'logging_level', DBMS_SCHEDULER.LOGGING_FULL);\n  DBMS_SCHEDULER.ENABLE('CUSTOMER_APP.NIGHTLY_STATS');\nEND;\n/\n\n-- 2) Validate the calendar expression (next run time):\nSET SERVEROUTPUT ON\nDECLARE\n  l_next TIMESTAMP WITH TIME ZONE;\nBEGIN\n  DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING(\n    calendar_string   => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=30; BYSECOND=0',\n    start_date        => SYSTIMESTAMP,\n    return_date_after => SYSTIMESTAMP,\n    next_run_date     => l_next);\n  DBMS_OUTPUT.PUT_LINE('Next run: ' || l_next);\nEND;\n/\n\n-- 3) Run now in the background (does not affect the schedule):\nEXEC DBMS_SCHEDULER.RUN_JOB('CUSTOMER_APP.NIGHTLY_STATS', use_current_session => FALSE);\n\n-- 4) Status:\nSELECT owner, job_name, enabled, state, run_count, failure_count,\n       last_start_date, next_run_date\nFROM   dba_scheduler_jobs WHERE owner = 'CUSTOMER_APP';\n\n-- 5) Stop (if running) / disable / drop:\n-- EXEC DBMS_SCHEDULER.STOP_JOB('CUSTOMER_APP.NIGHTLY_STATS', force => FALSE);\nEXEC DBMS_SCHEDULER.DISABLE('CUSTOMER_APP.NIGHTLY_STATS', force => TRUE);\nEXEC DBMS_SCHEDULER.DROP_JOB('CUSTOMER_APP.NIGHTLY_STATS', force => TRUE);","what":"Définit un job PL/SQL planifié avec DBMS_SCHEDULER ; fixe max_run_duration (lève un événement en cas de dépassement) et un niveau de journalisation détaillé, valide à l'avance l'expression de calendrier, et permet une exécution manuelle sans perturber la planification.","when":"Pour exécuter dans la base plutôt que par cron des travaux récurrents tels que maintenance, ETL et nettoyage, en tenant compte de RAC/Data Guard.","caution":"Aucune licence supplémentaire. Le fuseau horaire de start_date compte : SYSTIMESTAMP utilise le TZ du serveur de base ; utilisez une région explicite (par ex. 'Europe/Istanbul') pour éviter les décalages aux changements d'heure. Si JOB_QUEUE_PROCESSES=0, aucun job ne s'exécute. DISABLE avec force=>TRUE n'arrête pas une exécution en cours, il empêche seulement les exécutions futures ; utilisez STOP_JOB pour l'interrompre. Créer un job dans un autre schéma exige CREATE ANY JOB.","tags":["dbms_scheduler","job","scheduled job","create_job","run_job","repeat_interval","disable job","scheduler"]},{"id":"scheduler-job-history","task":"Surveiller l'historique d'exécution et les échecs des jobs du scheduler","category":"Scheduler","command":"-- 1) Recent runs: duration, CPU, error code and message\nSELECT owner, job_name, status, error#,\n       TO_CHAR(actual_start_date,'YYYY-MM-DD HH24:MI:SS') AS started,\n       run_duration, cpu_used,\n       SUBSTR(additional_info, 1, 200) AS info\nFROM   dba_scheduler_job_run_details\nWHERE  owner = 'CUSTOMER_APP'\nORDER  BY actual_start_date DESC\nFETCH FIRST 20 ROWS ONLY;\n\n-- 2) Failures in the last 24 hours (whole DB):\nSELECT owner, job_name, error#, actual_start_date, SUBSTR(errors, 1, 300) AS errors\nFROM   dba_scheduler_job_run_details\nWHERE  status <> 'SUCCEEDED'\n  AND  log_date > SYSTIMESTAMP - INTERVAL '1' DAY\nORDER  BY log_date DESC;\n\n-- 3) Currently running:\nSELECT owner, job_name, session_id, running_instance, elapsed_time, cpu_used\nFROM   dba_scheduler_running_jobs;\n\n-- 4) Lifecycle events (ENABLE/DISABLE/RUN/STOP...):\nSELECT log_date, owner, job_name, operation, status\nFROM   dba_scheduler_job_log\nWHERE  owner = 'CUSTOMER_APP'\nORDER  BY log_date DESC\nFETCH FIRST 20 ROWS ONLY;\n\n-- Log retention (days): EXEC DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE('log_history', '30');","what":"Liste, depuis les vues de journal du scheduler, l'heure de début, la durée, l'utilisation CPU, l'état et le message d'erreur de chaque exécution, ainsi que les jobs en cours et les événements du cycle de vie des jobs.","when":"Lorsqu'un job nocturne ne s'est pas exécuté ou a duré trop longtemps, pour le suivi des SLA des jobs, et lors de la définition de règles d'échec dans un système de supervision (OEM/Zabbix/Prometheus).","caution":"Lecture seule ; aucune licence requise. La rétention par défaut des journaux est de 30 jours, et les jobs avec logging_level OFF ne produisent aucun enregistrement. La colonne ERRORS existe en 12c+ ; dans les versions plus anciennes, consultez ADDITIONAL_INFO. En RAC, RUNNING_INSTANCE indique quel nœud a exécuté le job.","tags":["scheduler","job history","dba_scheduler_job_run_details","failed job","dba_scheduler_running_jobs","job log","monitoring"]},{"id":"resource-plan-basic","task":"Créer et activer un plan Resource Manager simple","category":"Scheduler","command":"-- 1) Define groups, plan, directives and mapping inside a pending area (PDB or non-CDB):\nBEGIN\n  DBMS_RESOURCE_MANAGER.CLEAR_PENDING_AREA;\n  DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA;\n\n  DBMS_RESOURCE_MANAGER.CREATE_CONSUMER_GROUP('OLTP_GRP',  'Online transactions');\n  DBMS_RESOURCE_MANAGER.CREATE_CONSUMER_GROUP('REPORT_GRP', 'Reporting / batch');\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN('DAYTIME_PLAN', 'Business hours plan');\n\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(\n    plan => 'DAYTIME_PLAN', group_or_subplan => 'OLTP_GRP',\n    comment => 'OLTP first', mgmt_p1 => 70);\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(\n    plan => 'DAYTIME_PLAN', group_or_subplan => 'REPORT_GRP',\n    comment => 'Reports limited', mgmt_p1 => 20,\n    parallel_degree_limit_p1 => 4,\n    switch_group => 'CANCEL_SQL', switch_time => 1800, switch_for_call => TRUE);\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(\n    plan => 'DAYTIME_PLAN', group_or_subplan => 'OTHER_GROUPS',\n    comment => 'Everyone else (mandatory)', mgmt_p1 => 10);\n\n  DBMS_RESOURCE_MANAGER.SET_CONSUMER_GROUP_MAPPING(\n    attribute      => DBMS_RESOURCE_MANAGER.ORACLE_USER,\n    value          => 'REPORT_USER',\n    consumer_group => 'REPORT_GRP');\n\n  DBMS_RESOURCE_MANAGER.VALIDATE_PENDING_AREA;\n  DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA;\nEND;\n/\n-- Switch privilege required for the mapping to take effect:\nEXEC DBMS_RESOURCE_MANAGER_PRIVS.GRANT_SWITCH_CONSUMER_GROUP('REPORT_USER', 'REPORT_GRP', FALSE);\n\n-- 2) Activate (FORCE: prevents maintenance windows from switching the plan):\nALTER SYSTEM SET resource_manager_plan = 'FORCE:DAYTIME_PLAN' SCOPE = BOTH;\n\n-- 3) Monitor:\nSELECT sid, username, resource_consumer_group FROM v$session WHERE username IS NOT NULL;\nSELECT name, active_sessions, execution_waiters, consumed_cpu_time, cpu_wait_time\nFROM   v$rsrc_consumer_group;\n\n-- 4) Roll back: deactivate the plan, then delete it\n-- ALTER SYSTEM SET resource_manager_plan = '' SCOPE = BOTH;\n-- BEGIN DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA;\n--       DBMS_RESOURCE_MANAGER.DELETE_PLAN_CASCADE('DAYTIME_PLAN');\n--       DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA; END;\n-- /","what":"Décide quelle charge l'emporte en cas de contention CPU : donne 70 % du CPU au groupe OLTP et 20 % au groupe rapports, limite le parallélisme à 4 dans le groupe rapports et annule les appels dépassant 30 minutes (CANCEL_SQL). Les utilisateurs sont associés automatiquement aux consumer groups.","when":"Pour les charges mixtes où les rapports de journée ralentissent l'OLTP, pour plafonner les requêtes parallèles incontrôlées, et pour annuler automatiquement les requêtes qui s'emballent.","caution":"Resource Manager exige Enterprise Edition (très limité en SE2) ; aucune licence supplémentaire. Les pourcentages mgmt_p1 ne s'appliquent qu'en cas de contention CPU et ne peuvent pas dépasser 100 % au total à un même niveau. CANCEL_SQL tue l'instruction en cours avec ORA-00040 ; un mauvais mapping peut tuer un travail critique, testez donc soigneusement les seuils. Tant qu'un plan est actif, le bridage CPU apparaît sous l'événement d'attente 'resmgr:cpu quantum'. Pour le partage entre PDB au niveau CDB, utilisez CREATE_CDB_PLAN.","tags":["resource manager","dbms_resource_manager","resource plan","consumer group","cpu priority","cancel_sql","resmgr:cpu quantum","resource management"]}]},"recipes/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"recipes":[{"id":"wal-switch","task":"Changer de segment WAL","category":"WAL et checkpoint","command":"SELECT pg_switch_wal();","what":"Ferme le segment WAL courant et passe à un nouveau ; déclenche l'archivage.","when":"Pour produire un fichier d'archive ou forcer l'envoi du WAL après une sauvegarde.","caution":"Avant PostgreSQL 10, c'était pg_switch_xlog().","since":"PostgreSQL 10","note":"Cette fonction a été renommée pg_switch_wal() dans PostgreSQL 10 ; auparavant c'était pg_switch_xlog() (le WAL s'appelait alors 'xlog').","tags":["wal","switch","checkpoint","archive"]},{"id":"manual-checkpoint","task":"Exécuter un CHECKPOINT manuel","category":"WAL et checkpoint","command":"CHECKPOINT;","what":"Écrit les buffers modifiés sur disque et crée un enregistrement de checkpoint ; fait avancer le point de départ de la récupération.","when":"Avant une sauvegarde, dans une fenêtre de maintenance, ou pour synchroniser sur disque avant d'arrêter le serveur.","caution":"Peut générer beaucoup d'E/S ; ne l'exécutez pas en production aux heures de pointe. Seul un superutilisateur ou (PG15+) le rôle pg_checkpoint peut l'exécuter.","tags":["checkpoint","wal","io","flush"]},{"id":"wal-status-size","task":"Afficher la taille du WAL et le LSN courant","category":"WAL et checkpoint","command":"SELECT pg_current_wal_lsn() AS current_lsn, pg_walfile_name(pg_current_wal_lsn()) AS current_wal_file, pg_size_pretty(sum(size)) AS wal_dir_size FROM pg_ls_waldir();","what":"Renvoie le LSN WAL courant, le nom du fichier WAL en cours d'écriture et la taille totale du répertoire pg_wal.","when":"Pour surveiller l'accumulation du WAL, l'utilisation du disque et l'espacement des checkpoints.","caution":"pg_ls_waldir et pg_walfile_name sont disponibles dans PostgreSQL 10+ ; l'accès peut exiger le rôle pg_monitor.","tags":["wal","lsn","size","monitor"]},{"id":"archive-lag","task":"Afficher le retard d'archivage (WAL pas encore archivé)","category":"WAL et checkpoint","command":"SELECT last_archived_wal, last_failed_wal, archived_count, failed_count, pg_walfile_name(pg_current_wal_lsn()) AS current_wal FROM pg_stat_archiver;","what":"Montre le retard d'archivage en comparant le dernier fichier WAL archivé, le WAL en cours d'écriture et les compteurs d'échecs d'archivage.","when":"Pour vérifier si archive_command prend du retard et détecter les échecs d'archivage.","caution":"Si failed_count ne cesse d'augmenter, archive_command échoue et pg_wal peut se remplir ; agissez immédiatement pour éviter la saturation du disque.","tags":["archive","wal","lag","monitor"]},{"id":"create-tablespace","task":"Ajouter un nouveau tablespace","category":"Stockage","command":"CREATE TABLESPACE ssd_ts OWNER postgres LOCATION '/mnt/ssd/pgdata';","what":"Définit un nouveau tablespace dans le répertoire donné ; tables et index peuvent être placés sur ce disque.","when":"Pour répartir les données sur un autre disque (par ex. SSD rapide) et séparer les E/S.","caution":"Le répertoire doit déjà exister, être vide et appartenir à l'utilisateur postgres. Ne répartissez pas les données sur d'autres disques avec des liens symboliques ; utilisez le mécanisme de tablespace propre à PostgreSQL.","tags":["tablespace","storage","disk","create"]},{"id":"move-table-tablespace","task":"Déplacer une table vers un autre tablespace","category":"Stockage","command":"ALTER TABLE customer SET TABLESPACE ssd_ts;","what":"Déplace physiquement toutes les données de la table vers le nouveau tablespace (disque).","when":"Pour déplacer une table vers un disque plus rapide/séparé.","caution":"Un verrou ACCESS EXCLUSIVE est pris sur la table pendant le déplacement ; la table est inaccessible jusqu'à la fin du déplacement. Faites-le dans une fenêtre de maintenance pour les grandes tables.","tags":["tablespace","table","move","storage"]},{"id":"move-index-tablespace","task":"Déplacer un index vers un autre tablespace","category":"Stockage","command":"ALTER INDEX customer_email_idx SET TABLESPACE ssd_ts;","what":"Déplace l'index vers le tablespace donné.","when":"Pour placer les index sur un disque distinct des données et répartir les E/S.","caution":"Un verrou ACCESS EXCLUSIVE est pris pendant le déplacement ; l'index ne peut pas être utilisé. ALTER TABLE ... SET TABLESPACE ne couvre pas les index de la table et ne peut donc pas tous les déplacer d'un coup ; déplacez chaque index séparément.","tags":["tablespace","index","move","storage"]},{"id":"db-table-size","task":"Interroger la taille de la base et des tables","category":"Stockage","command":"SELECT relname AS table_name, pg_size_pretty(pg_total_relation_size(relid)) AS total_size, pg_size_pretty(pg_relation_size(relid)) AS data_only FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;","what":"Liste les 20 plus grandes tables avec leur taille totale, index et TOAST compris.","when":"Pour analyser l'utilisation du disque et trouver quelles tables occupent l'espace.","caution":"Pour la taille de toute la base : SELECT pg_size_pretty(pg_database_size(current_database()));","tags":["size","table","database","storage"]},{"id":"table-bloat","task":"Estimer le bloat d'une table","category":"Stockage","command":"SELECT schemaname, relname AS table_name, n_dead_tup AS dead_rows, n_live_tup AS live_rows, round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_pct, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 0 ORDER BY n_dead_tup DESC LIMIT 20;","what":"Affiche le taux de bloat à partir du nombre de dead tuples et de l'heure du dernier autovacuum.","when":"Pour déterminer les besoins de VACUUM et si autovacuum suit le rythme.","caution":"n_dead_tup est une estimation mise à jour en continu à partir des compteurs DML (elle baisse après VACUUM ; ANALYZE ne fait que corriger l'estimation) ; pour le bloat physique exact, l'extension pgstattuple (pgstattuple('customer')) est plus précise.","tags":["bloat","dead-tuple","vacuum","storage"]},{"id":"vacuum-analyze-table","task":"VACUUM ANALYZE d'une table donnée","category":"Maintenance","command":"VACUUM (ANALYZE, VERBOSE) customer;","what":"Nettoie les dead tuples de la table, rend l'espace réutilisable et met à jour les statistiques du planificateur.","when":"Après un DELETE/UPDATE en masse, ou lorsque les plans de requête se dégradent.","caution":"VACUUM ne rend pas l'espace disque à l'OS ; récupérer l'espace exige VACUUM FULL, qui prend un verrou ACCESS EXCLUSIVE. Un VACUUM simple ne bloque pas la table.","tags":["vacuum","analyze","maintenance","cleanup"]},{"id":"vacuum-freeze","task":"Exécuter un VACUUM FREEZE agressif","category":"Maintenance","command":"VACUUM (FREEZE, VERBOSE, ANALYZE) customer;","what":"Marque immédiatement comme 'frozen' tous les tuples éligibles de la table ; réduit le risque de wraparound des identifiants de transaction.","when":"Lorsqu'un avertissement de wraparound apparaît, ou à titre préventif sur de grandes tables qui seront archivées/passeront en lecture seule.","caution":"Génère beaucoup d'E/S car il parcourt toute la table. En situation d'urgence de wraparound, la base peut de fait passer en lecture seule ; surveillez datfrozenxid au préalable.","tags":["vacuum","freeze","wraparound","maintenance"]},{"id":"reindex-concurrently","task":"Reconstruire un index sans verrouiller (REINDEX CONCURRENTLY)","category":"Maintenance","command":"REINDEX INDEX CONCURRENTLY customer_email_idx;","what":"Reconstruit l'index sans bloquer les écritures ; supprime le bloat de l'index.","when":"Lorsqu'un index a grossi/est corrompu, sur un système en production sans fenêtre de maintenance.","caution":"Exige PostgreSQL 12+. En cas d'échec, il peut laisser un index INVALID avec le suffixe _ccnew ; vérifiez pg_index.indisvalid et nettoyez avec DROP INDEX si nécessaire. Pour reconstruire toute la table : REINDEX TABLE CONCURRENTLY customer;","since":"PostgreSQL 12","note":"REINDEX ... CONCURRENTLY est arrivé dans PostgreSQL 12 ; auparavant, REINDEX verrouillait l'index avec un verrou ACCESS EXCLUSIVE (une reconstruction sans verrou exigeait CREATE INDEX CONCURRENTLY + DROP).","tags":["reindex","concurrently","index","maintenance"]},{"id":"autovacuum-per-table","task":"Régler autovacuum pour une table donnée","category":"Maintenance","command":"ALTER TABLE customer SET (autovacuum_vacuum_scale_factor = 0.02, autovacuum_vacuum_threshold = 5000, autovacuum_analyze_scale_factor = 0.01);","what":"Abaisse les seuils de déclenchement d'autovacuum pour cette seule table, afin que les tables grandes/actives soient nettoyées plus souvent.","when":"Lorsque autovacuum prend du retard sur de grandes tables avec une forte activité UPDATE/DELETE.","caution":"Des réglages trop agressifs peuvent entraîner des exécutions d'autovacuum permanentes et une charge d'E/S. Pour annuler le réglage : ALTER TABLE customer RESET (autovacuum_vacuum_scale_factor);","tags":["autovacuum","tuning","table","maintenance"]},{"id":"statistics-target","task":"Augmenter la cible de statistiques d'une colonne","category":"Maintenance","command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500; ANALYZE customer;","what":"Augmente le niveau de détail de l'histogramme/de l'échantillon collecté pour une colonne donnée ; améliore les estimations du planificateur.","when":"Lorsque de mauvais plans de requête apparaissent sur des colonnes à distribution asymétrique.","caution":"Une valeur élevée (100 par défaut, 10000 max) augmente la durée d'ANALYZE et le temps de planification. Le changement ne prend effet qu'après ANALYZE.","tags":["statistics","planner","analyze","tuning"]},{"id":"alter-system-reload","task":"Modifier un paramètre et recharger (ALTER SYSTEM)","category":"Paramètres","command":"ALTER SYSTEM SET work_mem = '64MB'; SELECT pg_reload_conf();","what":"Écrit le paramètre dans postgresql.auto.conf et recharge la configuration (effet immédiat pour les paramètres ne nécessitant pas de redémarrage).","when":"Pour modifier de façon persistante les paramètres applicables par reload, comme work_mem et les réglages de journalisation.","caution":"Certains paramètres comme shared_buffers et max_connections ne prennent effet qu'au redémarrage, pas au reload ; ceux dont pg_settings.context = 'postmaster' exigent un redémarrage.","tags":["alter-system","reload","parameter","config"]},{"id":"session-set","task":"Modifier un paramètre pour la session (SET)","category":"Paramètres","command":"SET work_mem = '256MB';","what":"Modifie le paramètre uniquement pour la connexion/session courante ; il est réinitialisé à la fin de la session.","when":"Lorsqu'un réglage doit être relevé temporairement pour une seule requête lourde ou un batch.","caution":"Utilisez SET LOCAL pour le limiter à une seule transaction. Un changement global exige ALTER SYSTEM.","tags":["set","session","parameter","work-mem"]},{"id":"show-setting","task":"Afficher la valeur courante d'un paramètre","category":"Paramètres","command":"SELECT name, setting, unit, context, source FROM pg_settings WHERE name = 'shared_buffers';","what":"Affiche la valeur effective du paramètre, son unité, son mode d'application (context) et sa source.","when":"Pour connaître la valeur en vigueur d'un paramètre et savoir s'il exige un redémarrage ou un reload.","caution":"Raccourci : SHOW shared_buffers; ou SELECT current_setting('shared_buffers'); Si la colonne context vaut 'postmaster', un redémarrage est nécessaire.","tags":["show","pg_settings","parameter","current-setting"]},{"id":"physical-replica-basebackup","task":"Créer une réplique physique (pg_basebackup)","category":"Réplication","command":"pg_basebackup -h primary_host -U replicator -D /var/lib/postgresql/16/main -Fp -Xs -P -R --slot=replica_slot_1 -C","what":"Effectue une sauvegarde physique complète depuis la primaire, transmet le WAL en flux (-Xs), écrit standby.signal et primary_conninfo (-R) et crée le replication slot (-C).","when":"Pour créer une nouvelle standby en streaming (réplique en lecture ou cible de failover).","caution":"Le répertoire cible (-D) doit être vide. Sur la primaire, pg_hba.conf doit accorder l'accès 'replication' à l'utilisateur replicator, et il faut un wal_keep_size / un slot suffisant. Avec PostgreSQL 12+, -R produit standby.signal au lieu de recovery.conf.","tags":["replication","basebackup","standby","physical"]},{"id":"promote-replica","task":"Promouvoir une réplique en primaire","category":"Réplication","command":"SELECT pg_promote(wait := true, wait_seconds := 60);","what":"Sort le serveur standby du mode recovery et en fait une primaire accessible en écriture.","when":"Pour faire de la standby la nouvelle primaire lors d'un failover planifié ou d'urgence.","caution":"La promotion est irréversible ; le serveur ne peut plus suivre l'ancienne primaire. Assurez-vous que l'ancienne primaire est vraiment arrêtée pour éviter un split-brain. Alternative : pg_ctl promote.","since":"PostgreSQL 12","note":"La fonction SQL pg_promote() est arrivée dans PostgreSQL 12 ; auparavant, un fichier déclencheur (promote_trigger_file) ou pg_ctl promote servait à promouvoir une standby.","tags":["replication","promote","failover","standby"]},{"id":"replication-lag","task":"Afficher le retard de réplication","category":"Réplication","command":"SELECT client_addr, state, sent_lsn, replay_lsn, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes, write_lag, flush_lag, replay_lag FROM pg_stat_replication;","what":"Sur la primaire, affiche pour chaque standby l'écart de replay en octets par rapport à la position WAL courante de la primaire, ainsi que les lags write/flush/replay.","when":"Pour surveiller le retard de la réplication en streaming (à exécuter sur la primaire).","caution":"Pour le retard en secondes sur la standby : SELECT now() - pg_last_xact_replay_timestamp(); cette valeur augmente aussi en l'absence d'écritures sur la primaire, interprétez-la donc avec l'écart de LSN. Un résultat vide signifie qu'aucune standby n'est connectée.","tags":["replication","lag","lsn","monitor"]},{"id":"replication-slot","task":"Créer et supprimer un replication slot","category":"Réplication","command":"SELECT pg_create_physical_replication_slot('replica_slot_1'); -- To drop: SELECT pg_drop_replication_slot('replica_slot_1');","what":"Crée un slot physique qui garantit que la primaire ne supprime pas le WAL nécessaire tant que la standby ne l'a pas confirmé ; la seconde commande supprime le slot.","when":"Pour s'assurer que la standby ne perd pas de WAL lors de courtes interruptions.","caution":"Un slot inutilisé/abandonné entraîne une accumulation de WAL et un disque plein. Surveillez l'état des slots : SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained FROM pg_replication_slots; Un slot actif ne peut pas être supprimé.","tags":["replication","slot","wal","cleanup"]},{"id":"logical-replication","task":"Mettre en place une publication et une souscription de réplication logique","category":"Réplication","command":"-- Source (publisher): CREATE PUBLICATION pub_customer FOR TABLE customer; -- Target (subscriber): CREATE SUBSCRIPTION sub_customer CONNECTION 'host=primary_host dbname=appdb user=replicator password=secret' PUBLICATION pub_customer;","what":"Démarre une réplication logique au niveau table en créant une publication pour des tables précises sur la source et une souscription qui s'y connecte sur la cible.","when":"Pour les montées de version, la réplication de tables choisies vers un autre serveur, ou la distribution de données unidirectionnelle.","caution":"Exige PostgreSQL 10+. Le schéma des tables cibles doit déjà exister ; la souscription effectue automatiquement le COPY initial. wal_level doit valoir logical (exige un redémarrage). Les changements DDL et les valeurs de séquences ne sont pas répliqués ; appliquez les changements de schéma séparément des deux côtés et synchronisez les séquences avec setval lors de la bascule.","since":"PostgreSQL 10","note":"La réplication logique native (CREATE PUBLICATION / CREATE SUBSCRIPTION) est arrivée dans PostgreSQL 10 ; auparavant, des extensions externes comme pglogical étaient nécessaires pour cela.","tags":["logical","replication","publication","subscription"]},{"id":"terminate-backend","task":"Terminer un backend (pg_terminate_backend)","category":"Sessions","command":"SELECT pg_terminate_backend(12345);","what":"Termine de force le processus backend du PID donné ; coupe la connexion et la transaction en cours.","when":"Pour fermer une connexion bloquée/figée ou une session détenant un verrou.","caution":"La transaction en cours est annulée ; le travail non validé est perdu. Essayez d'abord le plus doux pg_cancel_backend. Vérifiez le PID dans pg_stat_activity.","tags":["terminate","backend","session","kill"]},{"id":"cancel-backend","task":"Annuler une requête en cours (pg_cancel_backend)","category":"Sessions","command":"SELECT pg_cancel_backend(12345);","what":"Annule la requête en cours d'exécution sur le PID donné ; la connexion reste ouverte.","when":"Pour arrêter une requête longue sans couper la connexion.","caution":"Il n'annule qu'une requête active ; il ne ferme pas une session à l'état idle in transaction, ce qui exige pg_terminate_backend.","tags":["cancel","backend","query","session"]},{"id":"active-blocking-queries","task":"Afficher les requêtes actives et bloquantes","category":"Sessions","command":"SELECT pid, usename, state, wait_event_type, wait_event, pg_blocking_pids(pid) AS blocking_pids, now() - query_start AS duration, left(query, 100) AS query FROM pg_stat_activity WHERE state <> 'idle' AND pid <> pg_backend_pid() ORDER BY duration DESC;","what":"Liste tous les backends non inactifs, quels PID les bloquent et depuis combien de temps ils s'exécutent.","when":"Pour diagnostiquer les problèmes de verrouillage/blocage et les requêtes longues.","caution":"pg_blocking_pids est disponible dans PostgreSQL 9.6+. Si le tableau blocking_pids n'est pas vide, ces PID détiennent le verrou ; annulez/terminez ce PID si nécessaire.","since":"PostgreSQL 9.6","note":"La fonction pg_blocking_pids() est arrivée dans PostgreSQL 9.6 ; auparavant, trouver les processus bloquants exigeait une jointure manuelle sur pg_locks.","tags":["activity","blocking","lock","session"]},{"id":"create-extension","task":"Ajouter une extension","category":"Extensions","command":"CREATE EXTENSION IF NOT EXISTS pg_stat_statements;","what":"Installe l'extension donnée dans la base courante (ajoute des fonctions, vues, types, etc.).","when":"Lorsque des fonctionnalités supplémentaires comme pg_stat_statements, pgcrypto ou postgis sont nécessaires.","caution":"Pour les extensions qui exigent shared_preload_libraries, comme pg_stat_statements, ajoutez-les d'abord à postgresql.conf et redémarrez le serveur. Les fichiers de l'extension (paquet contrib) doivent être installés sur le serveur.","tags":["extension","create","pg_stat_statements","plugin"]},{"id":"list-extensions","task":"Lister les extensions installées","category":"Extensions","command":"SELECT extname AS extension, extversion AS version FROM pg_extension ORDER BY extname;","what":"Liste toutes les extensions installées dans la base courante et leurs versions.","when":"Pour voir quelles extensions sont installées et si une mise à jour est nécessaire.","caution":"Pour voir les extensions installables (mais pas encore installées) : SELECT * FROM pg_available_extensions; Mise à jour : ALTER EXTENSION pg_stat_statements UPDATE;","tags":["extension","list","pg_extension","plugin"]}]},"recipes/postgres-ha.json":{"engineId":"postgres","engine":"PostgreSQL","order":1,"recipes":[{"id":"patroni-cluster-status","task":"Afficher l'état, la topologie et l'historique du cluster Patroni","category":"Patroni","command":"# Members, roles, state, timeline and lag (MB):\npatronictl -c /etc/patroni/patroni.yml list <cluster>\n# Extended info (pending restart, tags) + timestamp, refresh every 5 s:\npatronictl -c /etc/patroni/patroni.yml list <cluster> -e -t -w 5\n# Leader -> replica tree (including cascading replicas):\npatronictl -c /etc/patroni/patroni.yml topology <cluster>\n# Past failovers/switchovers (timeline, LSN, reason, time):\npatronictl -c /etc/patroni/patroni.yml history <cluster>","what":"Affiche la vue du cluster par Patroni depuis le DCS : rôles leader/réplique, état des membres (running/streaming), timeline, retard de réplication et redémarrages en attente ; history liste les changements de timeline avec le LSN auquel ils ont eu lieu.","when":"Avant et après chaque switchover/failover/maintenance, et comme première étape de diagnostic lorsqu'une alerte se déclenche.","caution":"La sortie reflète ce qui est stocké dans le DCS ; si le DCS est injoignable, la commande échoue ou peut afficher des données périmées. Ne lancez pas de switchover avant d'avoir confirmé que tous les membres sont sur la même timeline et que Lag in MB est inférieur à maximum_lag_on_failover.","untested":true,"tags":["patroni","cluster status","topology","history","lag"]},{"id":"patroni-switchover","task":"Effectuer un switchover planifié avec Patroni","category":"Patroni","command":"# Immediate switchover without confirmation:\npatronictl -c /etc/patroni/patroni.yml switchover <cluster> --leader <leader_member> --candidate <member> --force\n# Scheduled switchover (ISO 8601 timestamp with time zone):\npatronictl -c /etc/patroni/patroni.yml switchover <cluster> --leader <leader_member> --candidate <member> --scheduled '2026-10-10T02:00:00+03:00' --force\n# Cancel a pending scheduled switchover:\npatronictl -c /etc/patroni/patroni.yml flush <cluster> switchover","what":"Rétrograde de façon contrôlée le leader sain et promeut la réplique choisie (--candidate) ; l'ancien leader rejoint automatiquement le cluster comme réplique du nouveau leader. --scheduled reporte l'opération à l'heure indiquée.","when":"Pour déplacer la primaire vers un autre nœud avant une maintenance (patch OS, remplacement matériel, mise à niveau mineure) ou pour rétablir les rôles d'origine après un failover.","caution":"Toutes les connexions sont coupées lors du changement de leader ; l'application doit pouvoir se reconnecter. Patroni refuse le switchover si le retard du candidat dépasse maximum_lag_on_failover. Avec synchronous_mode activé, le candidat doit être une standby synchrone.","note":"Dans les anciennes versions de Patroni, l'option --leader s'appelait --master ; Patroni 4 a supprimé --master. --primary équivaut à --leader.","untested":true,"tags":["patroni","switchover","planned switchover","maintenance","leader"]},{"id":"patroni-failover","task":"Effectuer un failover manuel avec Patroni","category":"Patroni","command":"# Manual failover while the leader is unhealthy (candidate is mandatory):\npatronictl -c /etc/patroni/patroni.yml failover <cluster> --candidate <member> --force\n# Verify the result and the new timeline:\npatronictl -c /etc/patroni/patroni.yml list <cluster>\npatronictl -c /etc/patroni/patroni.yml history <cluster>","what":"Promeut la réplique indiquée quel que soit l'état du leader ; destiné aux cas où le failover automatique n'a pas eu lieu ou a choisi le mauvais nœud.","when":"Lorsque le leader est injoignable/défaillant et que le failover automatique ne s'est pas déclenché (par ex. en mode pause), ou lorsque vous devez forcer le passage vers un nœud précis.","caution":"Avec une réplication asynchrone, les transactions pas encore reçues par le candidat sont perdues (RPO > 0). Si le leader est sain, utilisez switchover au lieu de failover. Au retour de l'ancien leader, il peut nécessiter pg_rewind (si use_pg_rewind est activé) ou un reinit.","note":"Dans Patroni 3.2+, --candidate est obligatoire pour failover ; l'option --leader/--primary de failover a été dépréciée en 3.2 et supprimée dans Patroni 4.0. Utilisez switchover si vous devez nommer le leader.","untested":true,"tags":["patroni","failover","manual failover","promote","rpo"]},{"id":"patroni-reinit-replica","task":"Reconstruire une réplique défaillante avec Patroni (reinit)","category":"Patroni","command":"# Wipe the replica's PGDATA and rebuild it (create_replica_methods: pgbackrest/basebackup):\npatronictl -c /etc/patroni/patroni.yml reinit <cluster> <member> --wait --force\n# Take the basebackup directly from the leader instead of the backup repository:\npatronictl -c /etc/patroni/patroni.yml reinit <cluster> <member> --from-leader --wait --force","what":"Supprime le répertoire de données de la réplique indiquée et la reconstruit de zéro selon les create_replica_methods de Patroni (restauration pgBackRest ou pg_basebackup).","when":"Lorsqu'une réplique ne peut pas rejoindre la réplication à cause d'une incohérence de timeline, d'un échec de pg_rewind, de WAL manquant ou d'une corruption de données.","caution":"Toutes les données locales du membre sont supprimées ; vérifiez bien que la commande cible le bon membre (Patroni refuse reinit sur le leader). Sur les grosses bases, cela peut prendre des heures et ajoute une charge d'E/S/réseau sur le leader ; le cluster fonctionne pendant ce temps avec une réplique en moins. --from-leader n'existe que dans les versions récentes de Patroni 4.x (vérifiez avec patronictl reinit --help) ; il ignore create_replica_methods et effectue un pg_basebackup depuis le leader.","untested":true,"tags":["patroni","reinit","rebuild replica","rebuild","basebackup"]},{"id":"patroni-edit-config","task":"Modifier les paramètres du cluster dans Patroni (edit-config) et appliquer les redémarrages en attente","category":"Patroni","command":"# Show the current dynamic configuration stored in the DCS:\npatronictl -c /etc/patroni/patroni.yml show-config <cluster>\n# Cluster-wide PostgreSQL parameter (distributed to all members):\npatronictl -c /etc/patroni/patroni.yml edit-config <cluster> -p 'work_mem=64MB' -p 'max_connections=500' --force\n# Patroni behavior parameter:\npatronictl -c /etc/patroni/patroni.yml edit-config <cluster> -s 'maximum_lag_on_failover=1048576' --force\n# Parameters requiring a restart are flagged as 'Pending restart':\npatronictl -c /etc/patroni/patroni.yml list <cluster> -e\n# Restart replicas first, then the leader:\npatronictl -c /etc/patroni/patroni.yml restart <cluster> --role replica --pending --force\npatronictl -c /etc/patroni/patroni.yml restart <cluster> --role leader --pending --force","what":"Écrit les paramètres PostgreSQL et Patroni dans la configuration dynamique du DCS ; Patroni applique le changement à tous les membres, active immédiatement ceux qui sont rechargeables et signale ceux qui exigent un redémarrage comme 'Pending restart'.","when":"Pour modifier de façon cohérente sur chaque nœud d'un cluster géré par Patroni des paramètres comme max_connections, shared_buffers ou work_mem.","caution":"Sous Patroni, ne modifiez pas postgresql.conf à la main et n'utilisez pas ALTER SYSTEM : Patroni réécrit postgresql.conf, et postgresql.auto.conf prime sur les réglages de Patroni, ce qui crée des divergences entre nœuds. Sur une standby, max_connections, max_worker_processes, max_wal_senders, max_prepared_transactions et max_locks_per_transaction ne peuvent pas être inférieurs à ceux de la primaire ; planifiez l'ordre des redémarrages en conséquence.","untested":true,"tags":["patroni","edit-config","parameter","pending restart","dcs"]},{"id":"patroni-pause-resume","task":"Mettre Patroni en mode maintenance et en sortir (pause/resume)","category":"Patroni","command":"# Maintenance mode: automatic failover and Patroni's control over PostgreSQL stop\npatronictl -c /etc/patroni/patroni.yml pause <cluster> --wait\npatronictl -c /etc/patroni/patroni.yml list <cluster>          # should show 'Maintenance mode: on'\n# ... maintenance work (e.g. manual PostgreSQL restart, DCS maintenance) ...\npatronictl -c /etc/patroni/patroni.yml resume <cluster> --wait","what":"Met le cluster en mode maintenance : Patroni n'effectue aucun failover automatique et ne redémarre pas un PostgreSQL arrêté ; resume rétablit la gestion normale.","when":"Pendant une maintenance du DCS (etcd/Consul), une mise à niveau de Patroni, ou lorsque PostgreSQL doit être géré manuellement sans interférence de Patroni.","caution":"Si la primaire tombe pendant la pause, il n'y a pas de failover automatique et l'interruption se prolonge. N'oubliez pas resume après la maintenance ; ajoutez une vérification 'Maintenance mode' à vos alertes. Démarrer manuellement un PostgreSQL en primaire pendant la pause peut provoquer un split-brain.","untested":true,"tags":["patroni","pause","resume","maintenance mode","maintenance"]},{"id":"patroni-dcs-health","task":"Vérifier la santé du DCS Patroni (etcd/Consul) et de l'API REST","category":"Patroni","command":"# etcd v3 (default API in etcd 3.4+): member health, leader, raft term and DB size\netcdctl --endpoints=https://<etcd1>:2379,https://<etcd2>:2379,https://<etcd3>:2379 --cacert=/etc/etcd/ca.crt endpoint health --cluster -w table\netcdctl --endpoints=https://<etcd1>:2379 --cacert=/etc/etcd/ca.crt endpoint status --cluster -w table\netcdctl --endpoints=https://<etcd1>:2379 --cacert=/etc/etcd/ca.crt alarm list\n# Consul:\nconsul members\nconsul operator raft list-peers\n# Patroni REST API (on each member):\ncurl -s http://<member>:8008/cluster | python3 -m json.tool\ncurl -s -o /dev/null -w '%{http_code}\\n' http://<member>:8008/primary   # 200 = leader, 503 = not leader","what":"Vérifie le quorum, le leader et la capacité du DCS où Patroni détient le verrou de leader, ainsi que la vue que chaque membre a de son propre rôle via l'API REST de Patroni. Le point de terminaison /primary sert de base aux health checks du load balancer (HAProxy).","when":"Lorsque Patroni journalise 'DCS is not accessible', après un demote/failover inattendu, et comme contrôle de santé de routine.","caution":"Si le DCS perd le quorum, Patroni ne peut pas renouveler le verrou de leader et rétrograde la primaire en lecture seule (sauf si failsafe_mode, Patroni 3.0+, est activé) ; une panne d'etcd est donc une interruption des écritures en base. Lorsque la taille de la base etcd atteint son quota (2 GB par défaut), une alarme NOSPACE stoppe les écritures. Faites tourner le DCS sur au moins 3 nœuds avec des disques séparés.","untested":true,"tags":["patroni","etcd","consul","dcs health","rest api"]},{"id":"pgbackrest-archive-config","task":"Configurer l'archivage WAL pour pgBackRest (archive-push/archive-get)","category":"Sauvegarde et PITR","command":"# postgresql.conf (changing archive_mode requires a restart):\nwal_level = replica\narchive_mode = on\narchive_command = 'pgbackrest --stanza=<stanza> archive-push %p'\n# On the restore/standby side (in postgresql.conf on PG12+):\nrestore_command = 'pgbackrest --stanza=<stanza> archive-get %f \"%p\"'\n\n# /etc/pgbackrest/pgbackrest.conf (database host, local repo example):\n[global]\nrepo1-path=/var/lib/pgbackrest\nrepo1-retention-full=2\narchive-async=y\nspool-path=/var/spool/pgbackrest\nprocess-max=4\ncompress-type=zst\nstart-fast=y\n\n[<stanza>]\npg1-path=/var/lib/pgsql/16/data","what":"Fait envoyer par PostgreSQL chaque segment WAL terminé vers le dépôt pgBackRest (archive-push) et l'y récupérer pendant la restauration (archive-get) ; archive-async rend l'envoi parallèle et asynchrone via un répertoire spool.","when":"Comme première étape de la mise en place des sauvegardes physiques et du PITR avec pgBackRest, avant stanza-create.","caution":"Tant qu'archive_command échoue, le WAL s'accumule dans pg_wal et la primaire s'arrête quand le disque est plein ; surveillez pg_stat_archiver.failed_count. Avec Patroni, définissez ces paramètres via patroni.yml / edit-config plutôt que dans postgresql.conf. Archiver deux clusters différents dans la même stanza corrompt le dépôt.","untested":true,"tags":["pgbackrest","archiving","archive_command","wal archive","configuration"]},{"id":"pgbackrest-stanza-create-check","task":"Créer la stanza pgBackRest et vérifier l'archivage (stanza-create/check)","category":"Sauvegarde et PITR","command":"# Create the stanza in the repository (once):\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info stanza-create\n# Verify configuration and archiving end to end (switches WAL and waits for the segment to reach the repo):\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info check","what":"stanza-create crée dans le dépôt les métadonnées du cluster (system identifier, version) ; check teste la connexion PostgreSQL, archive_command et l'arrivée du WAL dans le dépôt avec un vrai changement de WAL.","when":"À l'installation initiale, après un changement d'archive_command ou de dépôt, et comme contrôle de santé en supervision/cron.","caution":"Exécutez les commandes en tant que postgres (ou propriétaire du dépôt) ; les exécuter en root casse la propriété des fichiers. Après une mise à niveau majeure, stanza-upgrade est requis, pas stanza-create. Si check échoue, le PITR peut être impossible même si les sauvegardes se terminent.","untested":true,"tags":["pgbackrest","stanza","stanza-create","check","archive verification"]},{"id":"pgbackrest-backup-full-diff-incr","task":"Effectuer des sauvegardes full, différentielles et incrémentales avec pgBackRest","category":"Sauvegarde et PITR","command":"sudo -u postgres pgbackrest --stanza=<stanza> --type=full backup\nsudo -u postgres pgbackrest --stanza=<stanza> --type=diff backup   # changes since the last full\nsudo -u postgres pgbackrest --stanza=<stanza> --type=incr backup   # changes since the last backup of any type\n# cron example (postgres user): full on Sunday, diff on other days\n# 30 1 * * 0   pgbackrest --stanza=<stanza> --type=full backup\n# 30 1 * * 1-6 pgbackrest --stanza=<stanza> --type=diff backup","what":"Une sauvegarde full copie tout le cluster, diff copie les fichiers modifiés depuis la dernière full, incr copie les fichiers modifiés depuis la dernière sauvegarde de tout type ; à la fin, l'archivage du WAL nécessaire est vérifié et expire s'exécute automatiquement selon la rétention.","when":"Dans un planning de sauvegarde régulier : typiquement full hebdomadaire + diff/incr quotidienne, incr plus fréquentes sur les gros clusters.","caution":"Sans full préalable, les demandes diff/incr sont automatiquement promues en full. Une chaîne incr plus longue augmente le temps de restauration et la dépendance ; perdre une sauvegarde de la chaîne rend les suivantes inutilisables. Pour soulager la primaire, sauvegardez depuis une standby avec backup-standby=y (la primaire reste nécessaire pour la coordination).","untested":true,"tags":["pgbackrest","backup","full","differential","incremental"]},{"id":"pgbackrest-info","task":"Afficher le catalogue de sauvegardes pgBackRest et la plage d'archives WAL (info)","category":"Sauvegarde et PITR","command":"sudo -u postgres pgbackrest --stanza=<stanza> info\n# JSON for monitoring/automation:\nsudo -u postgres pgbackrest --stanza=<stanza> --output=json info\n# Details of a single backup (databases, LSN range):\nsudo -u postgres pgbackrest --stanza=<stanza> --set=<backup_label> info","what":"Liste l'état de la stanza, le label, le type, l'heure de début/fin et la taille de chaque sauvegarde, la sauvegarde dont elle dépend, ainsi que la plage min/max des archives WAL du dépôt.","when":"Avant une restauration/un PITR pour confirmer que l'instant cible est couvert, pour vérifier que les sauvegardes s'exécutent régulièrement, et pour contrôler la rétention.","caution":"info montre qu'une sauvegarde existe, pas qu'elle est restaurable ; validez par des restaurations de test régulières et pgbackrest verify. S'il y a un trou dans la plage min/max du WAL, le PITR ne peut pas le franchir.","untested":true,"tags":["pgbackrest","info","backup list","catalog","monitoring"]},{"id":"pgbackrest-restore-latest","task":"Restaurer la dernière sauvegarde avec pgBackRest","category":"Sauvegarde et PITR","command":"# PostgreSQL must be stopped and PGDATA empty (otherwise use --delta)\nsudo systemctl stop postgresql-16\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info restore\n# From a specific backup: --set=<backup_label>\n# When restoring to another server for testing, disable archiving:\n# sudo -u postgres pgbackrest --stanza=<stanza> --archive-mode=off restore\nsudo systemctl start postgresql-16","what":"Restaure la dernière sauvegarde et écrit recovery.signal et restore_command ; au démarrage, PostgreSQL rejoue tout le WAL archivé jusqu'au point cohérent le plus récent et se promeut sur une nouvelle timeline.","when":"Lorsque le répertoire de données de la primaire est perdu/corrompu, ou pour tester régulièrement que les sauvegardes sont restaurables.","caution":"Si le cluster restauré et l'ancien cluster archivent en même temps dans la même stanza, le dépôt est corrompu ; utilisez --archive-mode=off pour les restaurations de test. Sous Patroni, mettez d'abord le cluster en pause ou arrêtez Patroni, sinon Patroni interfère. S'il existe des tablespaces ou des chemins hors de PGDATA, vérifiez les options --tablespace-map et link.","untested":true,"tags":["pgbackrest","restore","restore","recovery","disaster recovery"]},{"id":"pgbackrest-pitr-restore","task":"Restaurer à un instant donné avec pgBackRest (PITR, --type=time)","category":"Sauvegarde et PITR","command":"sudo systemctl stop postgresql-16\nsudo -u postgres pgbackrest --stanza=<stanza> --delta --type=time \"--target=2026-10-08 14:30:00+03\" --target-action=pause restore\nsudo systemctl start postgresql-16\n# Recovery stops at the target (read-only); validate the data:\npsql -X -c \"SELECT pg_is_in_recovery(), pg_last_xact_replay_timestamp();\"\n# If correct, end recovery and open read-write on a new timeline:\npsql -X -c \"SELECT pg_wal_replay_resume();\"","what":"Restaure une sauvegarde terminée avant l'instant cible et ne rejoue le WAL que jusqu'à la cible ; target-action=pause s'arrête à la cible pour que vous puissiez valider les données, et pg_wal_replay_resume() effectue la promotion.","when":"Pour revenir au moment juste avant une erreur logique comme un DELETE/DROP/UPDATE accidentel.","caution":"Tous les changements postérieurs à la cible sont perdus sur ce cluster ; si possible, effectuez le PITR sur un serveur séparé et ne rapatriez que les données concernées avec pg_dump. Indiquez explicitement le fuseau horaire dans la cible. pgBackRest 2.24+ sélectionne automatiquement une sauvegarde adaptée antérieure à la cible ; dans les versions plus anciennes, indiquez avec --set une sauvegarde terminée avant la cible. Si la cible est antérieure à l'heure de fin de la sauvegarde, la restauration ne peut pas atteindre la cohérence.","untested":true,"tags":["pgbackrest","pitr","point in time recovery","point in time","recovery"]},{"id":"pgbackrest-delta-restore-standby","task":"Reconstruire rapidement une standby avec la restauration delta de pgBackRest","category":"Sauvegarde et PITR","command":"sudo systemctl stop postgresql-16\n# Keeps the existing PGDATA and fetches only differing files; writes standby.signal:\nsudo -u postgres pgbackrest --stanza=<stanza> --delta --type=standby \"--recovery-option=primary_conninfo=host=<primary_host> port=5432 user=replicator application_name=<standby_name>\" restore\nsudo systemctl start postgresql-16\npsql -X -c \"SELECT status, sender_host FROM pg_stat_wal_receiver;\"","what":"--delta compare les fichiers du PGDATA existant avec la sauvegarde (taille/horodatage et checksum) et ne copie que ceux qui diffèrent ; --type=standby écrit standby.signal et recovery-option ajoute primary_conninfo. La standby rattrape d'abord son retard depuis l'archive, puis passe en streaming.","when":"Pour refaire une réplique, sans copie complète, d'une standby en retard ou dont la timeline a divergé, ou d'une ancienne primaire où pg_rewind a échoué.","caution":"Delta remplace/supprime les fichiers différents ou en trop du répertoire cible par ceux de la sauvegarde ; l'exécuter sur le mauvais serveur détruit les données de ce serveur. pgBackRest refuse de restaurer par-dessus un PostgreSQL en cours d'exécution, mais vérifiez vous-même que le service est arrêté. Gardez le mot de passe dans .pgpass plutôt que dans primary_conninfo.","untested":true,"tags":["pgbackrest","delta restore","rebuild standby","rebuild","replica"]},{"id":"pgbackrest-retention-expire","task":"Configurer la rétention pgBackRest et exécuter expire","category":"Sauvegarde et PITR","command":"# /etc/pgbackrest/pgbackrest.conf [global] - count based:\nrepo1-retention-full-type=count\nrepo1-retention-full=2\nrepo1-retention-diff=6\n# Time-based alternative (at least a 14-day PITR window):\n# repo1-retention-full-type=time\n# repo1-retention-full=14\n\n# Expire runs automatically after each backup; to trigger it manually:\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info expire","what":"Définit combien de sauvegardes full/diff (count) ou combien de jours de sauvegardes full (time) conserver ; expire supprime les sauvegardes hors politique, les sauvegardes diff/incr qui en dépendent, et les archives WAL devenues inutiles.","when":"Pour maîtriser la taille du dépôt et garantir l'exigence de fenêtre RPO/PITR (par ex. pouvoir revenir 14 jours en arrière).","caution":"La fenêtre de PITR est bornée par la plus ancienne sauvegarde full conservée ; avec des full hebdomadaires et retention-full=2, vous pouvez revenir au plus environ 2 semaines en arrière. repo1-retention-archive suit par défaut la rétention des full ; une valeur plus basse ne laisse les anciennes sauvegardes utilisables que jusqu'à leur point de cohérence. expire est irréversible.","untested":true,"tags":["pgbackrest","retention","retention period","expire","backup cleanup"]},{"id":"standby-manual-config","task":"Configurer manuellement une standby (standby.signal + primary_conninfo)","category":"Réplication","command":"# On the standby, with PostgreSQL stopped (PGDATA came from a backup/copy):\ntouch $PGDATA/standby.signal\ncat >> $PGDATA/postgresql.auto.conf <<'EOF'\nprimary_conninfo = 'host=<primary_host> port=5432 user=replicator application_name=<standby_name>'\nprimary_slot_name = '<slot>'\nEOF\npg_ctl -D $PGDATA start\n# PG13+: change the connection on a running standby without restart (e.g. point it to a new primary):\npsql -X -c \"ALTER SYSTEM SET primary_conninfo = 'host=<new_primary_host> port=5432 user=replicator application_name=<standby_name>'\" -c \"SELECT pg_reload_conf();\"","what":"Le fichier standby.signal fait démarrer le serveur en standby ; primary_conninfo définit la connexion de streaming et primary_slot_name le replication slot à utiliser. En PG13+, primary_conninfo et primary_slot_name peuvent être modifiés par reload.","when":"Pour transformer en standby un répertoire de données préparé sans pg_basebackup -R (pgBackRest, snapshot, rsync), ou pour pointer une standby existante vers une nouvelle primaire.","caution":"Le slot doit être créé au préalable sur la primaire (pg_create_physical_replication_slot), sinon le walreceiver échoue en boucle. Mettez le mot de passe dans ~/.pgpass plutôt que dans conninfo. application_name doit correspondre exactement au nom figurant dans synchronous_standby_names. Si standby.signal est oublié, le serveur démarre en primaire et passe sur une timeline distincte.","since":"PostgreSQL 12","note":"Avant PostgreSQL 12, ces réglages se trouvaient dans recovery.conf (standby_mode = on) ; PG12 les a déplacés vers standby.signal et postgresql.conf. Avant PG13, modifier primary_conninfo exigeait un redémarrage.","tags":["standby","standby.signal","primary_conninfo","replica setup","streaming"]},{"id":"standby-wal-receiver-lag","task":"Surveiller le retard de réception et de replay du WAL sur la standby (pg_stat_wal_receiver)","category":"Réplication","command":"-- Run on the standby:\nSELECT pg_is_in_recovery() AS in_recovery,\n       pg_last_wal_receive_lsn() AS receive_lsn,\n       pg_last_wal_replay_lsn() AS replay_lsn,\n       pg_size_pretty(pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())) AS replay_backlog,\n       now() - pg_last_xact_replay_timestamp() AS last_replay_age,\n       pg_get_wal_replay_pause_state() AS pause_state;\nSELECT pid, status, sender_host, sender_port, slot_name, received_tli, flushed_lsn, latest_end_lsn, now() - last_msg_receipt_time AS since_last_msg FROM pg_stat_wal_receiver;","what":"Indique quelle quantité de WAL la standby a reçue et appliquée (arriéré de replay), l'âge de la dernière transaction rejouée, si le replay est en pause, et l'état de la connexion walreceiver (quelle primaire, slot, timeline, heure du dernier message).","when":"Pour diagnostiquer un retard lorsque la primaire est injoignable ou sur des standbys en cascade ; sur une alerte 'standby en retard', pour distinguer un problème réseau (réception) d'un problème de replay (E/S, conflit).","caution":"Si pg_stat_wal_receiver ne renvoie aucune ligne, la standby n'est pas en streaming (alimentée depuis l'archive ou déconnectée). last_replay_age augmente même lorsque la primaire n'a pas d'écritures ; interprétez-le avec l'écart receive_lsn/replay_lsn. Si l'arriéré de replay augmente alors que la réception avance, la standby a des requêtes longues (max_standby_streaming_delay) ou un goulet d'E/S.","since":"PostgreSQL 14","note":"pg_get_wal_replay_pause_state() a été ajouté dans PostgreSQL 14 ; auparavant seul pg_is_wal_replay_paused() existait. Le reste de la requête fonctionne en PG10+.","tags":["standby","replication lag","wal receiver","replay","lag"]},{"id":"pg-rewind-old-primary","task":"Réintégrer l'ancienne primaire comme réplique après un failover avec pg_rewind","category":"Réplication","command":"# Prerequisite (must be set BEFORE the incident): wal_log_hints = on OR data checksums; full_page_writes = on\npg_controldata $PGDATA | grep -E 'wal_log_hints|checksum'\n# 1) The old primary must be stopped:\npg_ctl -D $PGDATA stop -m fast\n# 2) Dry run first:\npg_rewind --target-pgdata=$PGDATA --source-server='host=<new_primary_host> port=5432 user=postgres dbname=postgres' --progress --dry-run\n# 3) Actual rewind + standby configuration (standby.signal + primary_conninfo):\npg_rewind --target-pgdata=$PGDATA --source-server='host=<new_primary_host> port=5432 user=postgres dbname=postgres' --progress --write-recovery-conf\n# 4) Review postgresql.auto.conf copied from the source (port, primary_slot_name), then start:\npg_ctl -D $PGDATA start","what":"Rend le répertoire de données de l'ancienne primaire cohérent avec la nouvelle primaire en copiant depuis celle-ci les blocs modifiés après le point de divergence de timeline ; en fait une réplique en quelques minutes sans basebackup complet.","when":"Lorsque l'ancienne primaire revient après un failover/une promotion et se trouve en retard sur la nouvelle timeline avec des changements divergents.","caution":"Les transactions restées sur l'ancienne primaire après le failover et jamais parvenues à la nouvelle primaire sont définitivement supprimées. pg_rewind copie aussi les fichiers de configuration (postgresql.conf, postgresql.auto.conf, pg_hba.conf) depuis la source ; corrigez les réglages de port et de slot avant de démarrer. Si le WAL nécessaire depuis le point de divergence n'est plus sur la source, récupérez-le depuis l'archive avec -c (--restore-target-wal). La connexion source nécessite un superutilisateur ou un rôle disposant des privilèges sur les fonctions listés dans la documentation. Dans Patroni, cela se fait automatiquement avec use_pg_rewind: true.","since":"PostgreSQL 13","note":"-R/--write-recovery-conf et -c/--restore-target-wal ont été ajoutés dans PostgreSQL 13 ; en PG13+, pg_rewind exécute lui-même la crash recovery sur une cible arrêtée de façon non propre (à désactiver avec --no-ensure-shutdown). pg_rewind existe depuis 9.5.","tags":["pg_rewind","after failover","old primary","timeline","rejoin"]},{"id":"sync-standby-quorum","task":"Configurer la réplication synchrone par quorum (ANY) ou par priorité (FIRST)","category":"Réplication","command":"-- Quorum: commit completes once any 1 standby in the list confirms\nALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (stby_a, stby_b)';\n-- Priority based: the first connected standby in the list is sync, the others 'potential'\n-- ALTER SYSTEM SET synchronous_standby_names = 'FIRST 1 (stby_a, stby_b)';\nSELECT pg_reload_conf();\nSELECT application_name, state, sync_state, sync_priority FROM pg_stat_replication ORDER BY application_name;","what":"Définit quelles standbys doivent confirmer avant qu'un commit se termine : ANY N est basé sur un quorum (sync_state = quorum), FIRST N rend synchrones (sync) les N premières standbys connectées dans l'ordre de la liste et les autres en attente (potential). Le niveau de confirmation est défini par synchronous_commit (remote_write, on, remote_apply).","when":"Sur les systèmes qui n'admettent aucune perte de données lors d'un failover (RPO = 0) ; avec plusieurs standbys, ANY est préférable pour qu'une seule standby défaillante n'arrête pas les écritures.","caution":"Si le nombre requis de standbys synchrones n'est pas connecté, les commits restent bloqués sans erreur et l'application se fige. Les noms de la liste doivent correspondre à l'application_name des standbys. En urgence, une session peut le contourner avec SET synchronous_commit = local. Sous Patroni, gérez cela avec synchronous_mode plutôt qu'à la main.","since":"PostgreSQL 10","note":"La syntaxe FIRST a été ajoutée dans PostgreSQL 9.6 et ANY (quorum commit) dans PostgreSQL 10 ; la forme sans mot-clé 'N (a, b)' signifie FIRST.","tags":["synchronous replication","synchronous_standby_names","quorum","rpo","sync"]},{"id":"slot-retained-wal-monitor","task":"Surveiller le WAL retenu par les replication slots et supprimer sans risque un slot abandonné","category":"Réplication","command":"SELECT slot_name, slot_type, database, active, active_pid, wal_status,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal,\n       pg_size_pretty(safe_wal_size) AS safe_wal_size,\n       xmin, catalog_xmin\nFROM pg_replication_slots\nORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC NULLS LAST;\n-- Safe drop: drops only if not active (returns 0 rows if active)\nSELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots WHERE slot_name = '<slot>' AND NOT active;","what":"Montre sur la primaire combien de WAL chaque slot retient, wal_status (reserved/extended/unreserved/lost), la marge de sécurité restante sous max_slot_wal_keep_size (safe_wal_size), et les valeurs xmin/catalog_xmin qui retiennent VACUUM ; la seconde requête supprime le slot uniquement s'il n'est pas utilisé.","when":"Lorsque le répertoire pg_wal grossit, sur une alerte disque et en supervision de routine ; pour le nettoyage des slots après la suppression définitive d'une standby ou d'un abonné.","caution":"Un slot inactif retient le WAL sans limite et peut remplir le disque et arrêter la primaire ; un slot retenant xmin/catalog_xmin bloque aussi le nettoyage des dead tuples et crée un risque de bloat/wraparound. Supprimer un slot empêche sa standby/son abonné de reprendre là où il s'était arrêté (une reconstruction est nécessaire) ; vérifiez son propriétaire avant de le supprimer. À exécuter sur la primaire (pg_current_wal_lsn() échoue sur une standby).","since":"PostgreSQL 13","note":"wal_status et safe_wal_size ont été ajoutés dans PostgreSQL 13. PG16 a ajouté conflicting pour les slots logiques et PG17 inactive_since ; inactive_since est l'indicateur le plus direct d'un slot abandonné.","tags":["replication slot","wal accumulation","disk full","drop slot","retained wal"]},{"id":"slot-max-wal-keep-size","task":"Limiter le WAL que les slots peuvent retenir (max_slot_wal_keep_size)","category":"Réplication","command":"ALTER SYSTEM SET max_slot_wal_keep_size = '100GB';\nSELECT pg_reload_conf();\nSELECT slot_name, active, wal_status, pg_size_pretty(safe_wal_size) AS safe_wal_size FROM pg_replication_slots;","what":"Fixe une limite au WAL que les replication slots peuvent conserver dans pg_wal (-1 par défaut = illimité) ; en cas de dépassement, le checkpoint supprime l'ancien WAL et le slot passe à wal_status = 'lost'. Le paramètre prend effet par reload.","when":"Comme garde-fou sur tout cluster utilisant des slots, contre le risque qu'une standby ou un abonné déconnecté remplisse le disque de la primaire et arrête tout le système.","caution":"La limite privilégie la survie de la primaire au détriment du propriétaire du slot : une standby dont le slot est 'lost' doit être reconstruite et une souscription logique recréée. Choisissez une valeur inférieure à l'espace libre du disque de pg_wal mais supérieure au WAL généré pendant la plus longue interruption prévisible. Si wal_status vaut 'unreserved', le slot est sur le point d'être perdu ; déclenchez une alerte.","since":"PostgreSQL 13","note":"max_slot_wal_keep_size a été ajouté dans PostgreSQL 13 ; auparavant, il n'existait aucun moyen de plafonner le WAL retenu par les slots et la seule protection était la supervision plus la suppression manuelle des slots.","tags":["max_slot_wal_keep_size","slot limit","wal accumulation","disk protection","replication slot"]},{"id":"logical-add-table-refresh","task":"Ajouter une table à une publication et rafraîchir la souscription (REFRESH PUBLICATION)","category":"Réplication logique","command":"-- Publisher:\nALTER PUBLICATION pub_app ADD TABLE orders;\n-- PG15+: all tables in a schema: ALTER PUBLICATION pub_app ADD TABLES IN SCHEMA app_schema;\nSELECT pubname, schemaname, tablename FROM pg_publication_tables WHERE pubname = 'pub_app';\n-- Subscriber (the table structure must already exist):\nALTER SUBSCRIPTION sub_app REFRESH PUBLICATION WITH (copy_data = true);\nSELECT srrelid::regclass AS table_name, srsubstate FROM pg_subscription_rel ORDER BY 1;","what":"Ajoute une nouvelle table à la publication côté publisher ; côté subscriber, REFRESH PUBLICATION inclut les nouvelles tables dans la souscription et lance la copie initiale (tablesync) avec copy_data = true. srsubstate : i = init, d = copie des données, f = copie terminée, s = synchronisé, r = prêt.","when":"Lorsqu'une nouvelle table est ajoutée à une réplication logique en cours, ou qu'une table est retirée de la publication.","caution":"Même avec des publications FOR ALL TABLES ou TABLES IN SCHEMA, le subscriber ne prend pas en compte une nouvelle table tant que REFRESH n'est pas exécuté. REFRESH échoue si la table n'existe pas sur le subscriber ; le DDL n'est pas répliqué. Si une table publiée n'a ni PRIMARY KEY ni REPLICA IDENTITY, les UPDATE/DELETE échouent sur le publisher. La copie initiale d'une grande table retient longtemps un slot et un snapshot sur le publisher.","since":"PostgreSQL 10","note":"ADD TABLES IN SCHEMA a été ajouté dans PostgreSQL 15 (PG15 a aussi ajouté les filtres de lignes avec WHERE et les listes de colonnes).","tags":["logical replication","publication","add table","refresh publication","subscription"]},{"id":"logical-subscription-monitor","task":"Surveiller la réplication logique (pg_stat_subscription, erreurs d'apply, retard des slots)","category":"Réplication logique","command":"-- Subscriber:\nSELECT subname, pid, leader_pid, relid::regclass AS syncing_table, received_lsn, latest_end_lsn, now() - last_msg_receipt_time AS since_last_msg, latest_end_time FROM pg_stat_subscription;\nSELECT subname, apply_error_count, sync_error_count, stats_reset FROM pg_stat_subscription_stats;\n-- Publisher:\nSELECT slot_name, plugin, database, active, wal_status,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS confirmed_lag,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal\nFROM pg_replication_slots WHERE slot_type = 'logical';","what":"Côté subscriber, affiche les workers apply/tablesync, les LSN reçus et confirmés, l'heure du dernier message et le cumul des erreurs d'apply/sync ; côté publisher, affiche le WAL non confirmé (confirmed_flush) du slot logique et le WAL total qu'il retient.","when":"Pendant la mise en place de la réplication logique, pour confirmer que le retard tend vers zéro lors de la bascule d'une mise à niveau/migration, et pour les alertes d'erreur.","caution":"Si pid est NULL, le worker d'apply ne tourne pas (souscription désactivée ou échecs et redémarrages répétés) ; consultez le journal du serveur. Un apply_error_count croissant indique un conflit. Une souscription bloquée accumule WAL et catalog_xmin sur le publisher ; surveillez le disque et le bloat du catalogue du publisher.","since":"PostgreSQL 15","note":"pg_stat_subscription_stats a été ajouté dans PostgreSQL 15 et pg_stat_subscription.leader_pid dans PG16 (parallel apply) ; PG17 a ajouté la colonne worker_type. Les colonnes de base de pg_stat_subscription existent depuis PG10.","tags":["logical replication","subscription monitoring","pg_stat_subscription","apply error","lag"]},{"id":"logical-conflict-skip","task":"Résoudre un conflit de réplication logique (disable_on_error, SKIP)","category":"Réplication logique","command":"-- 1) Prevent a failing apply worker from looping forever:\nALTER SUBSCRIPTION sub_app SET (disable_on_error = true);\n-- 2) Find the finish LSN of the conflicting transaction in the subscriber log:\n--    CONTEXT: ... in transaction 749, finished at 0/5035378\n-- 3a) Preferred: fix/delete the conflicting row on the subscriber, then re-enable:\nALTER SUBSCRIPTION sub_app ENABLE;\n-- 3b) Or skip the whole remote transaction (NONE of its changes are applied on the subscriber):\nALTER SUBSCRIPTION sub_app SKIP (lsn = '0/5035378');\nALTER SUBSCRIPTION sub_app ENABLE;\nSELECT subname, subenabled, subskiplsn FROM pg_subscription;","what":"Lorsqu'une erreur comme une clé dupliquée, une ligne manquante ou un problème de permission arrête l'apply sur le subscriber : disable_on_error désactive automatiquement la souscription ; corrigez les données en conflit puis faites ENABLE, ou utilisez SKIP pour ignorer toute la transaction distante identifiée par le LSN 'finished at' du journal.","when":"Lorsque le worker d'apply échoue et redémarre sans cesse à cause d'écritures directes sur le subscriber, d'une copie initiale incomplète ou d'écritures bidirectionnelles.","caution":"SKIP abandonne aussi les changements non conflictuels de cette transaction et les deux côtés divergent définitivement ; préférez corriger les données, et comparez la table après un SKIP. Tant que le conflit n'est pas résolu, le slot du publisher continue d'accumuler du WAL. Empêchez par des privilèges les écritures applicatives sur les tables du subscriber.","since":"PostgreSQL 15","note":"SKIP et disable_on_error ont été ajoutés dans PostgreSQL 15 ; auparavant, on faisait la même chose en avançant l'origine sur le subscriber avec pg_replication_origin_advance() (plus risqué).","tags":["logical replication","conflict","conflict","skip","duplicate key"]},{"id":"logical-sync-sequences","task":"Synchroniser les valeurs des séquences vers le subscriber en réplication logique","category":"Réplication logique","command":"# Generate the setval script on the publisher and apply it on the subscriber (at cutover, after writes are stopped):\npsql -h <publisher_host> -d <db> -XAtc \"SELECT format('SELECT setval(%L, %s, %s);', quote_ident(schemaname) || '.' || quote_ident(sequencename), COALESCE(last_value, start_value), (last_value IS NOT NULL)::text) FROM pg_sequences;\" | psql -h <subscriber_host> -d <db> -X","what":"Comme la réplication logique ne transporte pas les valeurs de séquences (identity compris), ceci lit la dernière valeur de chaque séquence sur le publisher, puis génère et applique directement les commandes setval qui fixent la même valeur sur le subscriber.","when":"Lors de la bascule d'une mise à niveau/migration par réplication logique, juste avant de pointer l'application vers le subscriber.","caution":"Exécuté alors que le publisher reçoit encore des écritures, les valeurs sont immédiatement périmées et des erreurs de clé dupliquée suivent après la bascule ; exécutez-le après l'arrêt des écritures ou ajoutez une marge de sécurité. Le DDL n'est pas non plus répliqué : appliquez séparément les changements de schéma (ALTER TABLE, nouveaux index) des deux côtés, le subscriber en premier. pg_sequences renvoie un last_value NULL pour les séquences que vous ne pouvez pas lire ; exécutez la commande en tant que propriétaire ou superutilisateur.","since":"PostgreSQL 10","tags":["logical replication","sequence","setval","ddl not replicated","cutover"]},{"id":"logical-orphan-slot-cleanup","task":"Supprimer une souscription et nettoyer un slot logique orphelin sur le publisher","category":"Réplication logique","command":"-- Subscriber: while the publisher is unreachable DROP SUBSCRIPTION cannot drop the remote slot and fails; detach the slot first:\nALTER SUBSCRIPTION sub_app DISABLE;\nALTER SUBSCRIPTION sub_app SET (slot_name = NONE);\nDROP SUBSCRIPTION sub_app;\n-- Publisher: find and drop orphaned logical slots (including tablesync slots pg_<suboid>_sync_<relid>_<sysid>):\nSELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal FROM pg_replication_slots WHERE slot_type = 'logical' AND NOT active;\nSELECT pg_drop_replication_slot('sub_app');","what":"Supprime la souscription sans toucher au slot distant (slot_name = NONE) ; puis trouve et supprime sur le publisher les slots logiques qui n'ont plus de consommateur, ainsi que les slots tablesync résiduels.","when":"Lorsqu'une souscription doit être supprimée alors que le publisher est injoignable, après la fin d'une migration, ou lorsqu'un slot logique avec active = false accumule du WAL sur le publisher.","caution":"Un slot logique orphelin retient à la fois le WAL et catalog_xmin : le disque se remplit et les catalogues système gonflent. Cependant, supprimer le slot empêche cette souscription de reprendre ; avant de le supprimer, vérifiez qu'aucun subscriber (y compris sur un autre serveur) n'utilise ce slot. Un slot actif ne peut pas être supprimé ; le walsender connecté doit d'abord être terminé.","since":"PostgreSQL 10","tags":["logical replication","slot cleanup","drop subscription","orphaned slot","catalog_xmin"]},{"id":"pg-upgrade-link-runbook","task":"Mise à niveau majeure avec pg_upgrade --link (runbook de bout en bout)","category":"Mise à niveau","command":"# As the postgres user, in a writable directory; example 16 -> 17\nexport OLD_BIN=/usr/pgsql-16/bin NEW_BIN=/usr/pgsql-17/bin\nexport OLD_DATA=/var/lib/pgsql/16/data NEW_DATA=/var/lib/pgsql/17/data\n# 0) initdb the new cluster with the same encoding/locale and checksum setting as the old one (-k if the old cluster has checksums)\n# 1) Compatibility check (can run while the old cluster is up, changes no data):\n$NEW_BIN/pg_upgrade --old-bindir=$OLD_BIN --new-bindir=$NEW_BIN --old-datadir=$OLD_DATA --new-datadir=$NEW_DATA --check\n# 2) Stop the old cluster and upgrade with hard links:\n$OLD_BIN/pg_ctl -D $OLD_DATA stop -m fast\n$NEW_BIN/pg_upgrade --old-bindir=$OLD_BIN --new-bindir=$NEW_BIN --old-datadir=$OLD_DATA --new-datadir=$NEW_DATA --link --jobs=4\n# 3) Carry postgresql.conf/pg_hba.conf customizations over to the new cluster, start it:\n$NEW_BIN/pg_ctl -D $NEW_DATA -l /tmp/pg17_start.log start\n# 4) Gather statistics, run generated scripts if any:\n$NEW_BIN/vacuumdb --all --analyze-in-stages\n# psql -X -f update_extensions.sql   (if pg_upgrade generated it)\n# 5) After validation and a fresh backup, delete the old cluster:\n./delete_old_cluster.sh","what":"Valide d'abord la compatibilité avec --check, puis effectue la mise à niveau majeure en quelques minutes quelle que soit la taille des données, en créant des liens physiques (hard links) vers les fichiers de données de l'ancien cluster au lieu de les copier ; ensuite collecte les statistiques par étapes et supprime l'ancien cluster.","when":"Pour passer sur place un gros cluster à une nouvelle version majeure avec une interruption courte acceptable (quelques minutes).","caution":"Avec --link, une fois le nouveau cluster démarré, l'ancien ne peut plus être utilisé sans risque ; le seul retour arrière possible est une sauvegarde faite avant la mise à niveau. Les répertoires de données ancien et nouveau doivent être sur le même système de fichiers. Les plans peuvent être médiocres sur le nouveau cluster tant qu'analyze n'est pas terminé. Si vous utilisez pgBackRest, exécutez stanza-upgrade et faites immédiatement une nouvelle sauvegarde full ; les sauvegardes de l'ancienne version ne peuvent pas être restaurées dans le nouveau cluster.","note":"Les standbys physiques ne sont pas mises à niveau avec pg_upgrade : après la mise à niveau de la primaire, mettez-les à jour avec la méthode rsync --hard-links --size-only de la documentation ou reconstruisez-les. Les slots logiques ne sont pas migrés avant PG17 (ils le sont si l'ancien cluster est en PG17+) ; les statistiques du planificateur ne sont pas migrées avant PG18.","tags":["pg_upgrade","major upgrade","version upgrade","link","check"]},{"id":"logical-upgrade-near-zero-downtime","task":"Mise à niveau majeure quasi sans interruption par réplication logique","category":"Mise à niveau","command":"# 0) On the old version wal_level = logical (restart); every table needs a PRIMARY KEY or REPLICA IDENTITY\n# 1) Move roles and schema to the new version (a 'role postgres already exists' error is expected):\npg_dumpall -h <old_host> --globals-only | psql -h <new_host> -X -d postgres\npg_dump -h <old_host> --schema-only --create -d <db> | psql -h <new_host> -X -d postgres\n# 2) Publication on the old version, subscription on the new version (initial copy + continuous stream):\npsql -h <old_host> -d <db> -X -c \"CREATE PUBLICATION upg_pub FOR ALL TABLES;\"\npsql -h <new_host> -d <db> -X -c \"CREATE SUBSCRIPTION upg_sub CONNECTION 'host=<old_host> dbname=<db> user=<repl_user>' PUBLICATION upg_pub;\"\n# 3) Wait until all tables are 'r' (ready) and lag is ~0:\npsql -h <new_host> -d <db> -X -c \"SELECT srsubstate, count(*) FROM pg_subscription_rel GROUP BY 1;\"\npsql -h <old_host> -d <db> -X -c \"SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag FROM pg_replication_slots WHERE slot_name = 'upg_sub';\"\n# 4) Cutover: stop application writes, confirm lag = 0, synchronize sequences (logical-sync-sequences)\n# 5) Drop the subscription (the remote slot is dropped too), point the application to the new server:\npsql -h <new_host> -d <db> -X -c \"DROP SUBSCRIPTION upg_sub;\"","what":"Crée un cluster vide dans la nouvelle version, y transfère le schéma et diffuse en continu les données depuis l'ancienne version par réplication logique ; seule l'étape finale de bascule (arrêt des écritures, synchronisation des séquences, redirection) provoque une courte interruption. Une seconde réplication en sens inverse peut garder ouverte une voie de retour arrière.","when":"Lorsque l'interruption exigée par pg_upgrade est inacceptable, lors d'un passage simultané à un nouveau matériel/OS, ou pour sauter plusieurs versions majeures.","caution":"Le DDL, les séquences, les large objects et le contenu des vues matérialisées ne sont pas répliqués ; gelez le schéma pendant la migration. Sur les tables sans clé primaire/replica identity, les UPDATE/DELETE échouent sur le publisher. Sur les grosses bases, la copie initiale est longue et retient WAL et catalog_xmin sur l'ancien serveur. Chaque base nécessite sa propre publication/souscription. Comparez nombres de lignes/checksums avant la bascule.","since":"PostgreSQL 10","note":"pg_createsubscriber, ajouté dans PostgreSQL 17, convertit une standby physique en subscriber logique sans copie initiale, ce qui supprime le temps de copie initiale sur les grosses bases.","tags":["upgrade","logical replication","zero downtime migration","major upgrade","cutover"]},{"id":"pgbouncer-show-pools-stats","task":"Afficher les pools, statistiques et connexions PgBouncer (SHOW POOLS/STATS/CLIENTS/SERVERS)","category":"PgBouncer","command":"# Connect to the PgBouncer admin console (user must be in admin_users/stats_users):\npsql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer -X\nSHOW POOLS;    -- if cl_waiting > 0 and maxwait grows, the pool is saturated\nSHOW STATS;    -- per-database total/average query, transaction and wait times\nSHOW CLIENTS;  -- client connections (state, source address, linked server connection)\nSHOW SERVERS;  -- server connections opened to PostgreSQL\nSHOW CONFIG;","what":"Pour chaque pool base/utilisateur, affiche les clients actifs et en attente (cl_active, cl_waiting), les connexions serveur actives/inactives (sv_active, sv_idle), le temps d'attente le plus long (maxwait) et les compteurs de requêtes/transactions.","when":"Lorsque l'application signale des échecs/timeouts de connexion, pour dimensionner les pools, et pour voir les connexions actives avant une maintenance.","caution":"La console d'administration n'accepte pas le SQL ; elle n'exécute que des commandes PgBouncer. Si cl_waiting reste élevé, vérifiez d'abord côté serveur les transactions longues ou les verrous ; augmenter aveuglément default_pool_size pousse PostgreSQL vers sa limite max_connections et une pression mémoire. Les valeurs de SHOW STATS sont cumulées ; les moyennes portent sur l'intervalle stats_period.","untested":true,"tags":["pgbouncer","show pools","connection pooling","statistics","connection pool"]},{"id":"pgbouncer-pause-resume-reload","task":"Retenir les connexions pendant une maintenance avec PgBouncer (PAUSE/RESUME) et recharger la configuration (RELOAD)","category":"PgBouncer","command":"psql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer -X\nRELOAD;            -- apply pgbouncer.ini and auth_file changes without dropping connections\nPAUSE <db>;        -- closes server connections once running queries/transactions finish; new requests are queued\n-- ... maintenance: switchover, PostgreSQL restart, etc. ...\nRESUME <db>;\n-- If the host in [databases] was changed (after RELOAD), gradually refresh server connections:\nRECONNECT <db>;","what":"PAUSE vide les connexions côté PostgreSQL sans couper les connexions clientes et met les nouvelles requêtes en file d'attente ; RESUME relance la file. Un court redémarrage de la primaire ou un switchover apparaît ainsi à l'application comme un bref délai plutôt que comme des erreurs.","when":"Pendant de courtes fenêtres de maintenance (switchover, redémarrage de version mineure, redémarrage pour paramètre) et lors de l'application de modifications de pgbouncer.ini/userlist.","caution":"PAUSE ne rend la main qu'après la fin de toutes les transactions actives ; une requête longue retient PAUSE (terminez cette session dans PostgreSQL si nécessaire). Les clients en attente reçoivent une erreur à l'expiration de query_wait_timeout (120 s par défaut) ; terminez la maintenance dans ce délai. Oublier RESUME après PAUSE équivaut à une interruption totale. Certains réglages (listen_addr, listen_port, etc.) ne changent pas avec RELOAD et exigent un redémarrage.","untested":true,"tags":["pgbouncer","pause","resume","reload","maintenance"]},{"id":"pgbouncer-pool-mode","task":"Choix du pool_mode PgBouncer et dimensionnement de base","category":"PgBouncer","command":"; /etc/pgbouncer/pgbouncer.ini\n[databases]\nappdb = host=<primary_host> port=5432 dbname=appdb pool_mode=transaction\nreportdb = host=<primary_host> port=5432 dbname=appdb pool_mode=session\n\n[pgbouncer]\nlisten_addr = 0.0.0.0\nlisten_port = 6432\nauth_type = scram-sha-256\nauth_file = /etc/pgbouncer/userlist.txt\npool_mode = transaction\nmax_client_conn = 5000\ndefault_pool_size = 20\nreserve_pool_size = 5\nmax_prepared_statements = 200\nquery_wait_timeout = 120","what":"Le mode session lie un client à une connexion serveur pour toute la durée de sa connexion ; le mode transaction n'attribue une connexion serveur que pour la durée d'une transaction et sert des milliers de clients avec quelques dizaines de connexions PostgreSQL ; le mode statement rend la connexion après chaque requête et interdit les transactions multi-instructions. pool_mode peut aussi être redéfini par base.","when":"Mode transaction pour les applications OLTP/microservices qui ouvrent de nombreuses connexions de courte durée ; mode session pour les travaux qui dépendent de l'état de session (reporting, migrations, services utilisant LISTEN).","caution":"En mode transaction, l'état de session n'est pas préservé entre les transactions : SET (sauf SET LOCAL), les advisory locks de niveau session, LISTEN, les curseurs WITH HOLD, les tables temporaires entre transactions et PREPARE au niveau SQL ne sont pas fiables. Les prepared statements au niveau protocole ne sont pris en charge qu'avec PgBouncer 1.21+ et max_prepared_statements > 0 (200 par défaut depuis 1.24, 0 avant). default_pool_size multiplié par le nombre de paires (base, utilisateur) ne doit pas dépasser max_connections de PostgreSQL (moins la marge superuser_reserved_connections).","untested":true,"tags":["pgbouncer","pool_mode","transaction mode","connection pooling","sizing"]}]},"recipes/postgres-ops.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"recipes":[{"id":"pgss-top-queries","task":"Trouver les requêtes les plus coûteuses (pg_stat_statements : temps total et moyen)","category":"Performances","command":"-- By total time: queries consuming the most server time\nSELECT queryid,\n       calls,\n       round(total_exec_time::numeric, 1) AS total_ms,\n       round(mean_exec_time::numeric, 2) AS mean_ms,\n       round(stddev_exec_time::numeric, 2) AS stddev_ms,\n       round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 1) AS pct_total,\n       rows / NULLIF(calls, 0) AS rows_per_call,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 120) AS query\nFROM pg_stat_statements\nWHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())\nORDER BY total_exec_time DESC\nLIMIT 20;\n\n-- By mean time per call: individually slow queries\nSELECT queryid,\n       calls,\n       round(mean_exec_time::numeric, 2) AS mean_ms,\n       round(max_exec_time::numeric, 2) AS max_ms,\n       round(mean_plan_time::numeric, 2) AS mean_plan_ms,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 120) AS query\nFROM pg_stat_statements\nWHERE calls >= 50\nORDER BY mean_exec_time DESC\nLIMIT 20;","what":"Liste les requêtes normalisées qui consomment le plus de temps serveur dans la base courante, avec leur part du total ; la seconde requête liste les plus lentes par appel.","when":"Premier réflexe lorsque la charge CPU/E-S augmente ou que les utilisateurs signalent que 'la base est lente' ; pour décider par où commencer le tuning.","caution":"pg_stat_statements ne collecte rien tant qu'il n'est pas ajouté à shared_preload_libraries, que le serveur n'est pas redémarré et que CREATE EXTENSION n'est pas exécuté dans la base. Les valeurs sont cumulées depuis la dernière réinitialisation. Un stddev_ms élevé par rapport à mean_ms indique que le plan ou la distribution des données varie d'un appel à l'autre. mean_plan_time n'est renseigné qu'avec pg_stat_statements.track_planning = on (off par défaut ; l'activer ajoute une surcharge). Voir le texte des requêtes des autres utilisateurs exige un superutilisateur ou le rôle pg_read_all_stats.","since":"PostgreSQL 13","note":"Dans PostgreSQL 13, les colonnes total_time/mean_time ont été renommées total_exec_time/mean_exec_time et les temps de planification (total_plan_time) ont obtenu des colonnes distinctes ; utilisez les anciens noms en PG12 et antérieur.","tags":["pg_stat_statements","slow query","most expensive query","top sql","performance","query tuning"]},{"id":"pgss-io-temp-heavy","task":"Trouver les requêtes à l'origine des E/S disque, fichiers temporaires et WAL (pg_stat_statements)","category":"Performances","command":"SELECT queryid,\n       calls,\n       shared_blks_hit,\n       shared_blks_read,\n       round(100.0 * shared_blks_hit / NULLIF(shared_blks_hit + shared_blks_read, 0), 1) AS hit_pct,\n       pg_size_pretty(temp_blks_written * current_setting('block_size')::bigint) AS temp_written,\n       pg_size_pretty(wal_bytes) AS wal,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 120) AS query\nFROM pg_stat_statements\nORDER BY shared_blks_read + temp_blks_read + temp_blks_written DESC\nLIMIT 20;","what":"Affiche les requêtes qui lisent le plus de blocs hors des shared buffers, écrivent le plus dans des fichiers temporaires (tris/hachages ne tenant pas dans work_mem) et génèrent le plus de WAL, avec leur taux de succès cache.","when":"Pour trouver la requête responsable lorsque la latence de lecture disque augmente, que des alertes de fichiers temporaires se déclenchent (log_temp_files) ou que le volume de WAL augmente brusquement.","caution":"shared_blks_read compte aussi les lectures servies par le page cache de l'OS ; le temps disque réel exige track_io_timing = on (mesurez d'abord sa surcharge avec pg_test_timing). Si temp_written est élevé, la correction consiste généralement à relever work_mem par rôle/session ou à corriger la requête/l'index ; relever work_mem globalement augmente le risque mémoire.","since":"PostgreSQL 13","note":"Les colonnes wal_bytes/wal_records sont arrivées en PG13. En PG17, blk_read_time/blk_write_time ont été renommées shared_blk_read_time/shared_blk_write_time et local_blk_*_time a été ajouté.","tags":["pg_stat_statements","io","temp files","temp_blks_written","work_mem","wal"]},{"id":"pgss-reset","task":"Réinitialiser les statistiques pg_stat_statements (toutes ou une seule requête)","category":"Performances","command":"-- Last reset time and number of entries evicted because the table was full (dealloc)\nSELECT dealloc, stats_reset FROM pg_stat_statements_info;\n\n-- Reset a single query only (userid, dbid, queryid; 0 = all)\nSELECT pg_stat_statements_reset(0, 0, 5927525816675996975);\n\n-- Reset all statistics\nSELECT pg_stat_statements_reset();","what":"Réinitialise les compteurs pg_stat_statements entièrement ou par utilisateur/base/queryid ; pg_stat_statements_info affiche l'heure de la dernière réinitialisation et combien d'entrées ont été évincées faute de place.","when":"Pour repartir d'une mesure propre avant un changement de tuning (index, paramètre, déploiement) ou pour mesurer isolément le nouveau plan d'une requête.","caution":"Une réinitialisation est irréversible ; les outils de supervision/APM qui reposent sur des compteurs cumulés afficheront un trou, faites donc d'abord un snapshot (voir la procédure sur les régressions de plan). Si dealloc ne cesse d'augmenter, pg_stat_statements.max est trop petit (le modifier exige un redémarrage). Par défaut, seuls les superutilisateurs peuvent l'exécuter ; accordez-le avec GRANT EXECUTE ON FUNCTION.","since":"PostgreSQL 14","note":"La réinitialisation paramétrée (userid, dbid, queryid) est arrivée en PG12, la vue pg_stat_statements_info en PG14. PG17 a ajouté un quatrième paramètre, minmax_only : pg_stat_statements_reset(0, 0, 0, true) ne réinitialise que les temps min/max ; les colonnes stats_since et minmax_stats_since sont aussi PG17.","tags":["pg_stat_statements","reset","clear statistics","queryid","statistics","baseline"]},{"id":"auto-explain-setup","task":"Journaliser les plans d'exécution des requêtes lentes avec auto_explain","category":"Performances","command":"-- 1) Per session: effective immediately, no restart (superuser)\nLOAD 'auto_explain';\nSET auto_explain.log_min_duration = '500ms';\nSET auto_explain.log_analyze = on;\nSET auto_explain.log_buffers = on;\nSET auto_explain.log_timing = off;\nSET auto_explain.log_nested_statements = on;\n\n-- 2) Persistent: loaded by new sessions, a reload is enough.\n--    The module must be LOADed in this session before ALTER SYSTEM accepts auto_explain.* parameters.\nLOAD 'auto_explain';\nALTER SYSTEM SET session_preload_libraries = 'auto_explain';\nALTER SYSTEM SET auto_explain.log_min_duration = '2s';\nALTER SYSTEM SET auto_explain.log_analyze = on;\nALTER SYSTEM SET auto_explain.log_buffers = on;\nALTER SYSTEM SET auto_explain.log_timing = off;\nALTER SYSTEM SET auto_explain.sample_rate = 0.1;\nSELECT pg_reload_conf();","what":"Écrit dans le journal du serveur le plan d'exécution des requêtes dépassant le seuil (avec le nombre réel de lignes et les données de buffers lorsque log_analyze est actif) ; capture le plan produit avec les vrais paramètres de l'application.","when":"Lorsqu'une requête n'est lente que depuis l'application ou avec des paramètres précis et ne peut pas être reproduite par un EXPLAIN manuel ; pour attraper des changements de plan intermittents.","caution":"log_analyze = on ajoute une surcharge d'instrumentation à chaque requête échantillonnée, y compris celles sous le seuil ; log_timing = off et sample_rate < 1 la réduisent. log_min_duration = 0 inonde le journal en production. ALTER SYSTEM SET session_preload_libraries écrase la valeur existante ; vérifiez d'abord SHOW session_preload_libraries. session_preload_libraries n'affecte pas les sessions existantes ; le charger dans chaque processus exige shared_preload_libraries plus un redémarrage.","note":"auto_explain est un module contrib et ne nécessite pas CREATE EXTENSION. Les options log_wal (PG13+) et log_settings (PG12+) sont aussi disponibles.","tags":["auto_explain","execution plan","slow query","log","plan capture","explain"]},{"id":"explain-analyze-buffers-wal","task":"Lire un plan en profondeur avec EXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL)","category":"Performances","command":"-- ANALYZE really executes the statement: run DML inside a transaction and roll it back\nBEGIN;\nEXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL, VERBOSE)\nUPDATE customer SET balance = balance + 1 WHERE city = 'Ankara';\nROLLBACK;\n\n-- For SELECT\nEXPLAIN (ANALYZE, BUFFERS, SETTINGS)\nSELECT name FROM customer ORDER BY name DESC LIMIT 10 OFFSET 1000;","what":"Renvoie le plan avec les statistiques d'exécution réelles : BUFFERS affiche par nœud les blocs shared hit/read/dirtied et temp, SETTINGS affiche les paramètres non par défaut qui influent sur la planification, WAL affiche les enregistrements/octets de WAL générés.","when":"Pour analyser l'écart entre lignes estimées et réelles, les tris/hachages débordant sur disque (Sort Method: external merge, temp read/written) et le coût WAL des DML.","caution":"ANALYZE exécute réellement l'instruction ; exécutez toujours les DML dans BEGIN/ROLLBACK (les triggers et incréments de séquences ont quand même lieu). Conseils de lecture : un écart de 10x ou plus entre 'rows' estimé et 'actual rows' indique un problème de statistiques ; pour les nœuds dans des boucles, temps et lignes sont par itération, multipliez par loops pour le total ; un 'Heap Fetches' élevé sur un Index Only Scan signifie que la visibility map est périmée, et 'Workers Planned' > 'Workers Launched' signifie que le pool de workers est épuisé. Utilisez TIMING OFF lorsque la surcharge de chronométrage est élevée ; COSTS OFF masque aussi la section JIT.","since":"PostgreSQL 13","note":"L'option SETTINGS est arrivée en PG12, WAL en PG13. PG16 a ajouté GENERIC_PLAN, PG17 SERIALIZE et MEMORY ; en PG18, BUFFERS est actif par défaut avec ANALYZE.","tags":["explain analyze","buffers","execution plan","reading plans","wal","performance"]},{"id":"plan-regression-queryid","task":"Détecter les régressions de plan par queryid (comparaison de snapshots pg_stat_statements)","category":"Performances","command":"-- 1) Take a reference snapshot (before deploy/upgrade/ANALYZE)\nCREATE TABLE IF NOT EXISTS pgss_snapshot AS\n  SELECT now() AS snap_time, userid, dbid, queryid, calls, total_exec_time,\n         shared_blks_hit, shared_blks_read, left(query, 200) AS query\n  FROM pg_stat_statements WITH NO DATA;\nINSERT INTO pgss_snapshot\n  SELECT now(), userid, dbid, queryid, calls, total_exec_time,\n         shared_blks_hit, shared_blks_read, left(query, 200)\n  FROM pg_stat_statements;\n\n-- 2) Later: queries whose time per call grew the most since the snapshot\nWITH base AS (\n  SELECT DISTINCT ON (userid, dbid, queryid) *\n  FROM pgss_snapshot\n  ORDER BY userid, dbid, queryid, snap_time DESC\n)\nSELECT s.queryid,\n       s.calls - b.calls AS new_calls,\n       round((b.total_exec_time / b.calls)::numeric, 2) AS old_mean_ms,\n       round(((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))::numeric, 2) AS new_mean_ms,\n       round((b.shared_blks_hit + b.shared_blks_read)::numeric / b.calls, 0) AS old_blks_per_call,\n       round((s.shared_blks_hit + s.shared_blks_read - b.shared_blks_hit - b.shared_blks_read)::numeric\n             / (s.calls - b.calls), 0) AS new_blks_per_call,\n       left(regexp_replace(s.query, '\\s+', ' ', 'g'), 100) AS query\nFROM pg_stat_statements s\nJOIN base b USING (userid, dbid, queryid)\nWHERE s.calls - b.calls >= 10\n  AND b.calls > 0\nORDER BY ((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))\n         / NULLIF(b.total_exec_time / b.calls, 0) DESC NULLS LAST\nLIMIT 20;\n\n-- 3) Is the suspicious queryid running right now?\nSELECT pid, state, now() - query_start AS runtime, left(query, 100) AS query\nFROM pg_stat_activity\nWHERE query_id = -4094682686355101727;","what":"Enregistre dans une table les compteurs cumulés de pg_stat_statements et les compare plus tard à l'état courant par queryid ; affiche le temps moyen et les buffers par appel des appels effectués après le snapshot à côté des anciennes valeurs.","when":"Pour trouver la requête dont le plan a changé après un déploiement, une mise à niveau majeure, un rafraîchissement des statistiques ou un changement de paramètre ('hier c'était rapide, aujourd'hui c'est lent').","caution":"Si pg_stat_statements_reset() s'exécute entre le snapshot et la comparaison, ou si des entrées sont évincées faute de place (dealloc), les deltas n'ont aucun sens ; vérifiez pg_stat_statements_info.stats_reset. Une hausse des buffers par appel est un signe de changement de plan plus fiable qu'une hausse du temps, car elle ne dépend pas du cache. Pour confirmer le plan, faites un EXPLAIN de la requête avec les vrais paramètres ou utilisez auto_explain ; PostgreSQL n'a pas de figeage de plan natif (l'extension pg_hint_plan est nécessaire). Supprimez la table de snapshot une fois terminé.","since":"PostgreSQL 14","note":"pg_stat_activity.query_id et le paramètre compute_query_id sont arrivés en PG14 (auto par défaut : calculé lorsque pg_stat_statements est chargé). La ligne 'Query Identifier' de la sortie EXPLAIN VERBOSE affiche la même valeur. Les étapes 1 et 2 fonctionnent aussi en PG13.","tags":["plan regression","queryid","pg_stat_statements","snapshot","plan change","performance"]},{"id":"parallel-query-usage","task":"Vérifier la capacité de requêtes parallèles et l'utilisation actuelle des workers","category":"Performances","command":"-- Parallel worker pool settings\nSELECT name, current_setting(name) AS value\nFROM pg_settings\nWHERE name IN ('max_worker_processes', 'max_parallel_workers', 'max_parallel_workers_per_gather',\n               'max_parallel_maintenance_workers', 'parallel_setup_cost', 'parallel_tuple_cost',\n               'min_parallel_table_scan_size', 'min_parallel_index_scan_size')\nORDER BY name;\n\n-- Queries currently running in parallel and their worker counts\nSELECT a.pid AS leader_pid,\n       count(w.pid) AS workers_running,\n       current_setting('max_parallel_workers')::int AS max_parallel_workers,\n       now() - a.query_start AS runtime,\n       left(a.query, 80) AS query\nFROM pg_stat_activity a\nJOIN pg_stat_activity w ON w.leader_pid = a.pid AND w.pid <> a.pid\nGROUP BY a.pid, a.query_start, a.query\nORDER BY workers_running DESC;\n\n-- Pin / reset the planned worker count for a single table\nALTER TABLE orders SET (parallel_workers = 4);\nALTER TABLE orders RESET (parallel_workers);","what":"Affiche les limites des requêtes parallèles et combien de workers parallèles chaque processus leader utilise en ce moment ; illustre le réglage du nombre de workers par table.","when":"Lorsque les requêtes analytiques sont plus lentes que prévu, que le plan montre 'Workers Planned' > 'Workers Launched', ou que des cœurs CPU restent inactifs.","caution":"max_parallel_workers est plafonné par max_worker_processes (redémarrage requis), et ce pool est partagé avec les workers de réplication logique et les autres background workers. En OLTP, un max_parallel_workers_per_gather élevé sature le CPU en cas de concurrence ; chaque worker utilise aussi son propre work_mem. Les requêtes qui écrivent des données ou verrouillent des lignes (sauf CREATE TABLE AS / SELECT INTO), les curseurs et les requêtes appelant des fonctions PARALLEL UNSAFE n'obtiennent pas de plan parallèle. ALTER TABLE ... SET (parallel_workers) prend un bref ShareUpdateExclusiveLock.","since":"PostgreSQL 13","note":"pg_stat_activity.leader_pid est arrivé en PG13 ; auparavant, les workers parallèles n'apparaissent que comme backend_type = 'parallel worker' et ne peuvent pas être rattachés à leur leader.","tags":["parallel query","parallel worker","leader_pid","max_parallel_workers","gather","cpu"]},{"id":"jit-impact-check","task":"Mesurer l'impact de la compilation JIT sur le temps des requêtes","category":"Performances","command":"-- Share of JIT time in total execution time (PG15+)\nSELECT queryid,\n       calls,\n       jit_functions,\n       round((jit_generation_time + jit_inlining_time + jit_optimization_time + jit_emission_time)::numeric, 1) AS jit_ms,\n       round(total_exec_time::numeric, 1) AS total_ms,\n       round((100 * (jit_generation_time + jit_inlining_time + jit_optimization_time + jit_emission_time)\n             / NULLIF(total_exec_time, 0))::numeric, 1) AS jit_pct,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 100) AS query\nFROM pg_stat_statements\nWHERE jit_functions > 0\nORDER BY jit_pct DESC NULLS LAST\nLIMIT 20;\n\n-- Compare on a single query: check the \"JIT:\" section at the end of the output and Execution Time\nSET jit = on;\nEXPLAIN (ANALYZE) SELECT city, sum(balance) FROM customer GROUP BY city;\nSET jit = off;\nEXPLAIN (ANALYZE) SELECT city, sum(balance) FROM customer GROUP BY city;\nRESET jit;","what":"Affiche, par requête, la part du temps de compilation JIT (generation, inlining, optimization, emission) dans le temps d'exécution ; mesure l'effet net en exécutant la même requête avec JIT activé et désactivé.","when":"Lorsque de courtes requêtes OLTP ou de reporting présentent des délais inexpliqués de quelques dizaines à quelques centaines de ms ; en particulier sur les tables très partitionnées où des estimations de coût gonflées déclenchent le JIT.","caution":"Le JIT n'intervient que lorsque le coût du plan dépasse jit_above_cost (100000 par défaut) ; avec des estimations de coût gonflées, la compilation peut durer plus longtemps que l'exécution pour des requêtes courtes. Plutôt que de le désactiver globalement, envisagez d'abord ALTER ROLE ... SET jit = off pour le rôle/la base concerné ou un relèvement de jit_above_cost. EXPLAIN (ANALYZE, COSTS OFF) n'affiche pas la section JIT. Si PostgreSQL a été compilé sans prise en charge de LLVM, le JIT n'est jamais utilisé ; vérifiez avec SELECT pg_jit_available();","since":"PostgreSQL 15","note":"Les colonnes jit_* ont été ajoutées à pg_stat_statements en PG15 ; PG17 a ajouté jit_deform_count/jit_deform_time. Le JIT a été introduit en PG11 et est actif par défaut depuis PG12.","tags":["jit","llvm","jit_above_cost","pg_stat_statements","compilation time","performance"]},{"id":"blocking-tree","task":"Construire l'arbre de blocage (trouver le bloqueur racine)","category":"Sessions","command":"WITH RECURSIVE tree AS (\n  SELECT a.pid, a.pid AS root_pid, 0 AS level, ARRAY[a.pid] AS path\n  FROM pg_stat_activity a\n  WHERE cardinality(pg_blocking_pids(a.pid)) = 0\n    AND EXISTS (SELECT 1 FROM pg_stat_activity w WHERE a.pid = ANY (pg_blocking_pids(w.pid)))\n  UNION ALL\n  SELECT w.pid, t.root_pid, t.level + 1, t.path || w.pid\n  FROM tree t\n  JOIN pg_stat_activity w ON t.pid = ANY (pg_blocking_pids(w.pid))\n  WHERE w.pid <> ALL (t.path)\n), node AS (\n  SELECT DISTINCT ON (pid) pid, root_pid, level\n  FROM tree\n  ORDER BY pid, level DESC\n)\nSELECT repeat('  ', n.level) || n.pid AS pid_tree,\n       n.root_pid,\n       pg_blocking_pids(n.pid) AS blocked_by,\n       a.usename,\n       a.state,\n       a.wait_event_type || ':' || a.wait_event AS wait,\n       now() - a.xact_start AS xact_age,\n       left(regexp_replace(a.query, '\\s+', ' ', 'g'), 80) AS query\nFROM node n\nJOIN pg_stat_activity a USING (pid)\nORDER BY n.root_pid, n.level, n.pid;","what":"Suit récursivement la chaîne d'attente avec pg_blocking_pids() ; à partir du bloqueur racine (qui n'attend personne), affiche chaque session en attente sous forme d'arbre indenté avec les PID qui la bloquent directement et l'âge de sa transaction.","when":"Lorsque l'application subit des timeouts massifs ou des files qui s'accumulent ; pour trouver, parmi des dizaines de sessions en attente, l'unique session racine sur laquelle intervenir.","caution":"La racine est souvent une session 'idle in transaction' oubliée qui détient le verrou ; la colonne query affiche la DERNIÈRE instruction de cette session, pas celle qui a pris le verrou. Un DDL attendant ACCESS EXCLUSIVE bloque aussi les simples SELECT placés derrière lui et apparaît comme un nœud intermédiaire. pg_blocking_pids() verrouille brièvement le lock manager ; n'exécutez pas cette requête en boucle serrée. Une session atteignable par plusieurs branches n'est affichée qu'une fois, à son niveau le plus profond.","since":"PostgreSQL 9.6","tags":["blocking","lock tree","lock chain","pg_blocking_pids","blocking session","lock"]},{"id":"lock-wait-detail","task":"Examiner en détail les attentes de verrous par objet et mode de verrou (pg_locks)","category":"Sessions","command":"SELECT DISTINCT\n       w.pid AS waiting_pid,\n       w.locktype,\n       coalesce(w.relation::regclass::text, 'xid ' || w.transactionid, w.virtualxid, w.objid::text) AS target,\n       w.mode AS wanted_mode,\n       b.pid AS blocker_pid,\n       b.mode AS blocker_mode,\n       b.granted AS blocker_holds,\n       now() - wa.state_change AS waiting_for,\n       left(wa.query, 60) AS waiting_query,\n       left(ba.query, 60) AS blocker_query\nFROM pg_locks w\nJOIN pg_locks b\n  ON b.pid = ANY (pg_blocking_pids(w.pid))\n AND b.locktype = w.locktype\n AND b.database IS NOT DISTINCT FROM w.database\n AND b.relation IS NOT DISTINCT FROM w.relation\n AND b.page IS NOT DISTINCT FROM w.page\n AND b.tuple IS NOT DISTINCT FROM w.tuple\n AND b.virtualxid IS NOT DISTINCT FROM w.virtualxid\n AND b.transactionid IS NOT DISTINCT FROM w.transactionid\n AND b.classid IS NOT DISTINCT FROM w.classid\n AND b.objid IS NOT DISTINCT FROM w.objid\n AND b.objsubid IS NOT DISTINCT FROM w.objsubid\nJOIN pg_stat_activity wa ON wa.pid = w.pid\nJOIN pg_stat_activity ba ON ba.pid = b.pid\nWHERE NOT w.granted\nORDER BY waiting_for DESC, waiting_pid;","what":"Pour chaque demande de verrou non accordée (granted = false), affiche l'objet cible (table, identifiant de transaction, etc.), le mode demandé, le PID bloquant et son mode, si le bloqueur détient réellement le verrou, et le temps d'attente approximatif.","when":"Pour comprendre quel conflit de table et de mode de verrou provoque le blocage ; par ex. un DDL attendant AccessExclusiveLock, ou un verrou de ligne apparaissant comme une attente transactionid.","caution":"Comme la correspondance utilise pg_blocking_pids(), seules les demandes réellement en conflit sont listées. blocker_holds = false signifie que le bloqueur ne détient pas encore le verrou mais est placé avant dans la file avec un mode en conflit (lock queue). locktype = 'transactionid' est un conflit au niveau ligne : la session attend la fin de la transaction qui a modifié la ligne ; la ligne elle-même n'est pas visible dans pg_locks. pg_locks peut contenir des milliers de lignes sur les gros systèmes ; ne l'exécutez pas fréquemment. waiting_for est une approximation basée sur state_change.","since":"PostgreSQL 9.6","tags":["pg_locks","lock wait","lock waiting","accessexclusivelock","transactionid","lock"]},{"id":"long-idle-transactions","task":"Trouver les transactions longues et 'idle in transaction'","category":"Sessions","command":"SELECT pid,\n       usename,\n       datname,\n       application_name,\n       client_addr,\n       state,\n       now() - xact_start AS xact_age,\n       now() - state_change AS in_state_for,\n       backend_xid,\n       age(backend_xmin) AS xmin_age,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 100) AS last_query\nFROM pg_stat_activity\nWHERE xact_start IS NOT NULL\n  AND backend_type = 'client backend'\n  AND pid <> pg_backend_pid()\n  AND (now() - xact_start > interval '5 minutes' OR state LIKE 'idle in transaction%')\nORDER BY xact_start;","what":"Affiche les sessions dont la transaction ouverte a plus de 5 minutes ou qui restent inactives au sein d'une transaction ; avec l'âge de la transaction, le temps passé dans l'état courant, le XID attribué et l'âge du xmin qu'elles retiennent.","when":"Lorsque le bloat augmente, que VACUUM signale 'dead row versions cannot be removed yet', pendant des attentes de verrous, ou lorsqu'une fuite du pool de connexions est suspectée.","caution":"Les transactions longues (surtout REPEATABLE READ/SERIALIZABLE ou celles qui gardent des curseurs ouverts) retiennent l'horizon de nettoyage de VACUUM (xmin horizon) via backend_xmin, ce qui provoque du bloat et le vieillissement des XID dans toute la base. 'idle in transaction (aborted)' est une session qui a rencontré une erreur et attend un ROLLBACK. Correction permanente : idle_in_transaction_session_timeout par rôle et des limites de transaction correctes dans l'application.","tags":["long transaction","idle in transaction","xmin","bloat","session","long running"]},{"id":"terminate-idle-in-transaction","task":"Terminer en masse, de façon contrôlée, les longues sessions 'idle in transaction'","category":"Sessions","command":"-- 1) Dry run first: who will be affected?\nSELECT pid, usename, application_name, client_addr,\n       now() - state_change AS idle_for, left(query, 80) AS last_query\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)')\n  AND now() - state_change > interval '15 minutes'\n  AND backend_type = 'client backend'\n  AND pid <> pg_backend_pid();\n\n-- 2) Terminate; wait up to 5 s for each process to exit\nSELECT pid, usename, now() - state_change AS idle_for,\n       pg_terminate_backend(pid, 5000) AS terminated\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)')\n  AND now() - state_change > interval '15 minutes'\n  AND backend_type = 'client backend'\n  AND pid <> pg_backend_pid();","what":"Liste d'abord les sessions qui ont laissé une transaction ouverte et inactive pendant une durée donnée, puis les termine ; le paramètre timeout attend que le processus soit réellement terminé (ou que le délai expire).","when":"Pour une intervention d'urgence lorsque des sessions oubliées détenant des verrous provoquent des blocages, ou retiennent l'horizon xmin et bloquent VACUUM.","caution":"pg_cancel_backend n'a aucun effet sur une session 'idle in transaction' (il n'y a pas de requête en cours à annuler) ; pg_terminate_backend est nécessaire et tous les changements non validés sont annulés. Restreignez le filtre par usename/application_name ; excluez les sessions de réplication, de sauvegarde et de supervision. Les pools de connexions rouvriront les connexions terminées. Si le délai expire, la fonction émet un avertissement et renvoie false. Comme correction permanente, définissez idle_in_transaction_session_timeout par rôle.","since":"PostgreSQL 14","note":"pg_terminate_backend(pid, timeout) à deux arguments est arrivé en PG14 ; auparavant seul pg_terminate_backend(pid) existe et il rend la main dès l'envoi du signal.","tags":["pg_terminate_backend","idle in transaction","terminate session","kill session","bulk","lock"]},{"id":"role-timeouts","task":"Définir statement_timeout / lock_timeout par rôle et par base","category":"Sessions","command":"-- Application role: short queries, short lock waits, a limit on forgotten transactions\nALTER ROLE web_app SET statement_timeout = '30s';\nALTER ROLE web_app SET lock_timeout = '5s';\nALTER ROLE web_app SET idle_in_transaction_session_timeout = '10min';\n\n-- Let the reporting role run longer, only in this database\nALTER ROLE report_app IN DATABASE mydb SET statement_timeout = '15min';\n\n-- Show configured role/database settings\nSELECT coalesce(r.rolname, '(all)') AS role,\n       coalesce(d.datname, '(all)') AS database,\n       s.setconfig\nFROM pg_db_role_setting s\nLEFT JOIN pg_roles r ON r.oid = s.setrole\nLEFT JOIN pg_database d ON d.oid = s.setdatabase\nORDER BY 1, 2;\n\n-- Revert\nALTER ROLE web_app RESET lock_timeout;","what":"Rend les paramètres de timeout persistants au niveau du rôle (et éventuellement de la base), afin que chaque charge s'exécute avec ses propres limites sans toucher au réglage global.","when":"Pour éviter que des requêtes incontrôlées n'épuisent les ressources, que des files de DDL/verrous ne figent l'application, et que des transactions oubliées ne produisent du bloat.","caution":"Les réglages ne s'appliquent qu'aux NOUVELLES sessions ; recyclez les connexions existantes des pools. Les utilisateurs peuvent remplacer la valeur avec SET dans leur session ; ce n'est pas une frontière de sécurité. Ne définissez pas statement_timeout globalement dans postgresql.conf : les tâches de maintenance comme pg_dump, VACUUM et CREATE INDEX seraient aussi annulées. Priorité : rôle+base > rôle > base > postgresql.conf.","note":"idle_in_transaction_session_timeout est arrivé en PG9.6, idle_session_timeout en PG14, transaction_timeout en PG17.","tags":["statement_timeout","lock_timeout","idle_in_transaction_session_timeout","alter role","query timeout","timeout"]},{"id":"ddl-lock-timeout-retry","task":"Appliquer un DDL en toute sécurité avec lock_timeout et des tentatives répétées (éviter les files de verrous)","category":"Sessions","command":"-- Single attempt: give up if the lock is not acquired within 3 s instead of queueing and blocking traffic\nSET lock_timeout = '3s';\nALTER TABLE customer ADD COLUMN IF NOT EXISTS segment text;\nRESET lock_timeout;\n\n-- Automatic retry: up to 5 attempts, 3 s apart\nDO $$\nDECLARE\n  attempt int := 0;\nBEGIN\n  LOOP\n    attempt := attempt + 1;\n    BEGIN\n      SET LOCAL lock_timeout = '2s';\n      ALTER TABLE customer ADD COLUMN IF NOT EXISTS segment text;\n      RAISE NOTICE 'OK, attempt %', attempt;\n      EXIT;\n    EXCEPTION WHEN lock_not_available THEN\n      RAISE NOTICE 'lock not acquired, attempt %', attempt;\n      IF attempt >= 5 THEN RAISE; END IF;\n      PERFORM pg_sleep(3);\n    END;\n  END LOOP;\nEND $$;","what":"Empêche un DDL ayant besoin d'ACCESS EXCLUSIVE d'attendre indéfiniment lorsqu'il ne peut pas obtenir le verrou, et de bloquer toutes les requêtes qui s'accumulent derrière lui (lock queue) ; réessaie à intervalles courts.","when":"Lors de l'application en production, sur des tables très sollicitées, de changements rapides mais fortement verrouillants comme ALTER TABLE, CREATE/DROP TRIGGER, ATTACH PARTITION ou RENAME.","caution":"Sans lock_timeout, un ALTER TABLE placé derrière un seul long SELECT fait aussi attendre toutes les lectures/écritures suivantes et peut épuiser le pool de connexions en quelques minutes. lock_timeout ne limite que l'attente du verrou ; il ne limite pas la durée d'un DDL qui réécrit la table (par ex. changement de type de colonne). Le bloc DO est une transaction unique et conserve un snapshot entre les tentatives ; pour de longs scénarios de tentatives, préférez une boucle shell externe.","tags":["lock_timeout","ddl","alter table","lock queue","retry","lock"]},{"id":"connection-usage","task":"Mesurer l'utilisation des connexions par rapport à max_connections","category":"Sessions","command":"-- Capacity: used slots and slots left for regular users\nSELECT max_conn,\n       reserved,\n       used,\n       max_conn - reserved - used AS free_for_normal_users,\n       round(100.0 * used / max_conn, 1) AS used_pct\nFROM (SELECT current_setting('max_connections')::int AS max_conn,\n             current_setting('superuser_reserved_connections')::int\n             + coalesce(current_setting('reserved_connections', true)::int, 0) AS reserved,\n             (SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend') AS used) x;\n\n-- Breakdown by database, user, application and state\nSELECT datname, usename, application_name, state,\n       count(*) AS sessions,\n       max(now() - state_change) AS longest_in_state\nFROM pg_stat_activity\nWHERE backend_type = 'client backend'\nGROUP BY datname, usename, application_name, state\nORDER BY sessions DESC;\n\n-- Role and database connection limits\nSELECT rolname, rolconnlimit FROM pg_roles WHERE rolconnlimit <> -1;\nSELECT datname, datconnlimit FROM pg_database WHERE datconnlimit <> -1;","what":"Affiche les slots de connexion utilisés et ceux restant aux utilisateurs ordinaires par rapport à max_connections, la répartition par base/utilisateur/application/état, et les CONNECTION LIMIT configurés.","when":"Sur les erreurs 'too many clients already' / 'remaining connection slots are reserved', lors de la planification de capacité, ou pour évaluer le besoin d'un pooler.","caution":"max_connections ne limite que les backends clients ; les workers autovacuum, les background workers et les walsenders utilisent des slots séparés. Les derniers slots superuser_reserved_connections (plus reserved_connections en PG16+) sont fermés aux utilisateurs ordinaires. Au lieu de porter max_connections à des centaines ou des milliers, utilisez un pooler comme PgBouncer : chaque connexion est un processus distinct, et les coûts mémoire et de snapshot croissent avec le nombre de connexions. De nombreuses sessions 'idle' indiquent un pool applicatif surdimensionné. Empêchez une application incontrôlée de consommer tous les slots avec ALTER ROLE ... CONNECTION LIMIT ; modifier max_connections exige un redémarrage.","note":"Le paramètre reserved_connections et le rôle pg_use_reserved_connections sont arrivés en PG16 ; la requête fonctionne aussi sur les versions plus anciennes (le paramètre compte pour 0 lorsqu'il est absent).","tags":["max_connections","connection count","connection","pooler","superuser_reserved_connections","session"]},{"id":"autovacuum-progress","task":"Suivre la progression des opérations autovacuum/VACUUM en cours","category":"Maintenance","command":"SELECT p.pid,\n       a.backend_type,\n       p.datname,\n       p.relid::regclass AS table_name,\n       p.phase,\n       pg_size_pretty(p.heap_blks_total * current_setting('block_size')::bigint) AS table_size,\n       round(100.0 * p.heap_blks_scanned / NULLIF(p.heap_blks_total, 0), 1) AS scanned_pct,\n       round(100.0 * p.heap_blks_vacuumed / NULLIF(p.heap_blks_total, 0), 1) AS vacuumed_pct,\n       p.index_vacuum_count,\n       now() - a.xact_start AS running_for,\n       a.wait_event_type || ':' || a.wait_event AS wait,\n       left(a.query, 80) AS query\nFROM pg_stat_progress_vacuum p\nJOIN pg_stat_activity a USING (pid)\nORDER BY running_for DESC;\n\n-- Busy autovacuum workers / upper limit\nSELECT count(*) FILTER (WHERE backend_type = 'autovacuum worker') AS av_workers_busy,\n       current_setting('autovacuum_max_workers') AS av_max_workers\nFROM pg_stat_activity;","what":"Affiche, pour chaque VACUUM en cours (worker autovacuum ou manuel), la table, la phase, le pourcentage du heap parcouru/nettoyé, le nombre de passes de vacuum d'index et l'événement d'attente ; ainsi que le nombre de workers autovacuum occupés.","when":"Pour estimer quand un VACUUM sur une grande table se terminera, si tous les workers autovacuum sont occupés (tables en file d'attente), et si le vacuum attend à cause du bridage.","caution":"index_vacuum_count > 1 signifie que la liste des dead tuples ne tenait pas en mémoire et que les index ont été parcourus plusieurs fois ; augmentez maintenance_work_mem/autovacuum_work_mem (avant PG17, 1GB au maximum est utilisé pour cela). wait = Timeout:VacuumDelay correspond au bridage basé sur le coût (autovacuum_vacuum_cost_delay/cost_limit). Ne terminez pas un autovacuum dont le texte de requête indique 'to prevent wraparound' ; il redémarre immédiatement. VACUUM FULL et CLUSTER apparaissent dans pg_stat_progress_cluster, pas dans cette vue.","since":"PostgreSQL 9.6","note":"En PG17, les colonnes max_dead_tuples/num_dead_tuples sont devenues max_dead_tuple_bytes/num_dead_item_ids et indexes_total/indexes_processed ont été ajoutées ; la requête utilise donc des colonnes indépendantes de la version.","tags":["autovacuum","vacuum progress","pg_stat_progress_vacuum","vacuum","maintenance","progress"]},{"id":"xid-wraparound-risk","task":"Mesurer le risque de wraparound des identifiants de transaction (âge XID par base et par table)","category":"Maintenance","command":"-- Per database: distance to autovacuum_freeze_max_age and the ~2^31 limit\nSELECT datname,\n       age(datfrozenxid) AS xid_age,\n       mxid_age(datminmxid) AS mxid_age,\n       round(100.0 * age(datfrozenxid) / current_setting('autovacuum_freeze_max_age')::int, 1) AS pct_of_freeze_max_age,\n       round(100.0 * age(datfrozenxid) / 2147483647, 1) AS pct_of_wraparound\nFROM pg_database\nORDER BY age(datfrozenxid) DESC;\n\n-- Oldest tables in the current database (including TOAST)\nSELECT c.oid::regclass AS table_name,\n       greatest(age(c.relfrozenxid), age(t.relfrozenxid)) AS xid_age,\n       mxid_age(c.relminmxid) AS mxid_age,\n       pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,\n       s.last_autovacuum,\n       s.last_vacuum\nFROM pg_class c\nLEFT JOIN pg_class t ON t.oid = c.reltoastrelid\nLEFT JOIN pg_stat_all_tables s ON s.relid = c.oid\nWHERE c.relkind IN ('r', 'm')\nORDER BY 2 DESC\nLIMIT 20;","what":"Affiche pour chaque base l'âge du plus ancien XID non gelé et du multixact par rapport à autovacuum_freeze_max_age et à la limite de wraparound ; dans la base courante, les tables les plus anciennes (TOAST compris) avec leur taille et l'heure du dernier vacuum.","when":"En supervision de routine (seuils typiques : pct_of_wraparound 50 % avertissement / 75 % critique), lorsque le journal affiche 'must be vacuumed within N transactions', et pour prioriser le travail de freeze.","caution":"Pour les tables où pct_of_freeze_max_age dépasse 100, autovacuum lance un vacuum obligatoire (anti-wraparound) ; il est lourd en E/S et redémarre même s'il est terminé. Si l'âge ne baisse pas, la cause est presque toujours un élément qui retient l'horizon xmin (transaction longue, transaction préparée, replication slot, hot_standby_feedback) ; corrigez-le d'abord, sinon même VACUUM FREEZE ne peut pas faire baisser l'âge. Près de la limite, le serveur avertit d'abord, puis refuse d'attribuer de nouveaux XID (écritures) lorsqu'il n'en reste que quelques millions. Pour un freeze ciblé : VACUUM (FREEZE, VERBOSE) <table>;","note":"PG14 a ajouté vacuum_failsafe_age / vacuum_multixact_failsafe_age (1,6 milliard par défaut) : lorsqu'une table atteint cet âge, VACUUM passe en mode d'urgence qui ignore le cost delay et le nettoyage des index.","tags":["wraparound","xid age","datfrozenxid","relfrozenxid","freeze","multixact"]},{"id":"xmin-horizon-holders","task":"Trouver ce qui retient l'horizon xmin et empêche VACUUM de supprimer les dead tuples","category":"Maintenance","command":"SELECT 'session' AS source, pid::text AS id, usename::text AS owner, state AS detail,\n       age(backend_xmin) AS xmin_age\nFROM pg_stat_activity\nWHERE backend_xmin IS NOT NULL AND pid <> pg_backend_pid()\nUNION ALL\nSELECT 'prepared_xact', gid, owner::text, prepared::text, age(transaction)\nFROM pg_prepared_xacts\nUNION ALL\nSELECT 'replication_slot', slot_name::text, slot_type, active::text,\n       greatest(age(xmin), age(catalog_xmin))\nFROM pg_replication_slots\nWHERE xmin IS NOT NULL OR catalog_xmin IS NOT NULL\nUNION ALL\nSELECT 'standby_feedback', application_name, usename::text, state, age(backend_xmin)\nFROM pg_stat_replication\nWHERE backend_xmin IS NOT NULL\nORDER BY xmin_age DESC NULLS LAST;","what":"Liste, par âge de xmin, les quatre sources qui peuvent bloquer le nettoyage des dead tuples : sessions détenant des snapshots, transactions préparées, replication slots et standbys envoyant hot_standby_feedback.","when":"Lorsque VACUUM VERBOSE signale 'dead row versions cannot be removed yet', que le bloat ne cesse d'augmenter et que l'âge XID ne baisse pas.","caution":"Le plus ancien xmin définit l'horizon de nettoyage ; les replication slots et le feedback des standbys affectent toutes les bases. Les transactions préparées survivent aux redémarrages ; vérifiez le propriétaire et fermez-les avec COMMIT PREPARED / ROLLBACK PREPARED '<gid>'. Supprimer un slot inutilisé casse son aval (standby, CDC) ; vérifiez d'abord le propriétaire. Exécutée sur une standby, elle n'affiche que les sessions de cette standby.","tags":["xmin horizon","vacuum blocker","dead tuple","prepared transaction","replication slot","bloat"]},{"id":"pgstattuple-bloat","task":"Mesurer le bloat des tables et index avec pgstattuple","category":"Maintenance","command":"CREATE EXTENSION IF NOT EXISTS pgstattuple;\n\n-- Fast estimate: skips pages marked all-visible in the visibility map\nSELECT pg_size_pretty(table_len) AS table_size,\n       round(approx_tuple_percent::numeric, 1) AS live_pct,\n       round(dead_tuple_percent::numeric, 1) AS dead_pct,\n       round(approx_free_percent::numeric, 1) AS free_pct\nFROM pgstattuple_approx('orders');\n\n-- Exact measurement: reads the whole table\nSELECT pg_size_pretty(table_len) AS table_size,\n       round(dead_tuple_percent::numeric, 1) AS dead_pct,\n       round(free_percent::numeric, 1) AS free_pct,\n       pg_size_pretty(dead_tuple_len + free_space) AS reclaimable\nFROM pgstattuple('orders');\n\n-- B-tree indexes: leaf density (~90 when freshly built; below 50 is bloated)\nWITH idx AS MATERIALIZED (\n  SELECT i.indexrelid\n  FROM pg_index i\n  JOIN pg_class c ON c.oid = i.indexrelid\n  JOIN pg_am am ON am.oid = c.relam\n  WHERE am.amname = 'btree' AND i.indrelid = 'orders'::regclass\n)\nSELECT idx.indexrelid::regclass AS index_name,\n       pg_size_pretty(s.index_size) AS index_size,\n       s.avg_leaf_density,\n       s.leaf_fragmentation\nFROM idx\nCROSS JOIN LATERAL pgstatindex(idx.indexrelid) s;","what":"Affiche les pourcentages de tuples vivants/morts et d'espace libre d'une table (estimation rapide et valeur exacte) et l'espace récupérable ; pour les index B-tree, la densité des feuilles et la fragmentation.","when":"Lorsque l'estimation n_dead_tup de pg_stat_user_tables ne suffit pas ; pour étayer par des données une décision de pg_repack, VACUUM FULL ou REINDEX.","caution":"pgstattuple et pgstatindex lisent toute la relation et génèrent beaucoup d'E/S sur les grandes tables (ils ne prennent qu'un AccessShareLock). Utilisez d'abord pgstattuple_approx. Par défaut, un superutilisateur ou le rôle pg_stat_scan_tables est requis. Un peu d'espace libre est normal (fillfactor, place pour les HOT updates) ; VACUUM le rend réutilisable mais ne réduit pas le fichier ; le récupérer exige pg_repack ou VACUUM FULL (verrou ACCESS EXCLUSIVE !). Reconstruisez un index à faible avg_leaf_density avec REINDEX CONCURRENTLY.","since":"PostgreSQL 12","note":"pgstattuple est une extension contrib ; pgstattuple_approx est arrivé en PG9.5. MATERIALIZED dans la CTE (PG12+) empêche que pgstatindex soit appelé, et échoue, sur des index non B-tree.","tags":["bloat","pgstattuple","pgstatindex","index bloat","dead tuple","free space"]},{"id":"pg-repack-online","task":"Réorganiser une table gonflée sans verrous longs (pg_repack)","category":"Maintenance","command":"-- Once per target database (the pg_repack package must be installed on the server)\nCREATE EXTENSION pg_repack;\n\n# Dry run: show what would be done\npg_repack -h localhost -U postgres -d mydb --table=public.orders --dry-run\n\n# Rewrite the table and its indexes: 2 parallel index builds, wait at most 60 s for locks\npg_repack -h localhost -U postgres -d mydb --table=public.orders --jobs=2 --wait-timeout=60 --no-kill-backend\n\n# Rebuild indexes only\npg_repack -h localhost -U postgres -d mydb --table=public.orders --only-indexes","what":"Construit en arrière-plan une copie compactée de la table, capture les changements concurrents dans une table de journal via un trigger, et échange les tables à la fin sous un bref verrou ACCESS EXCLUSIVE ; contrairement à VACUUM FULL, il ne bloque pas les lectures/écritures pendant toute l'opération.","when":"Lorsqu'un bloat important que VACUUM ne peut pas rendre (vérifié avec pgstattuple) doit être récupéré sur un système en production.","caution":"La table doit avoir une PRIMARY KEY ou un index UNIQUE sur des colonnes NOT NULL. Elle nécessite temporairement un espace disque supplémentaire égal à la table plus ses index et génère beaucoup de WAL (retard des répliques, volume d'archives). Des verrous ACCESS EXCLUSIVE sont nécessaires au début et à la fin ; à l'expiration de --wait-timeout, le comportement par défaut est d'annuler les requêtes en conflit et, si nécessaire, de terminer leurs sessions, tandis que --no-kill-backend ignore la table. N'exécutez pas de DDL sur la table cible pendant l'opération. Les versions du client et de l'extension doivent correspondre. En cas d'interruption, nettoyez les restes avec DROP EXTENSION pg_repack CASCADE; CREATE EXTENSION pg_repack;","note":"pg_repack ne fait pas partie du cœur de PostgreSQL (paquet séparé, par ex. postgresql-17-repack).","tags":["pg_repack","bloat","online","vacuum full alternative","table reorganization","repack"]},{"id":"invalid-indexes","task":"Trouver et nettoyer les index INVALID","category":"Maintenance","command":"SELECT n.nspname AS schema_name,\n       c.relname AS index_name,\n       t.relname AS table_name,\n       pg_size_pretty(pg_relation_size(c.oid)) AS index_size,\n       i.indisvalid,\n       i.indisready,\n       format('DROP INDEX CONCURRENTLY %I.%I;', n.nspname, c.relname) AS fix\nFROM pg_index i\nJOIN pg_class c ON c.oid = i.indexrelid\nJOIN pg_class t ON t.oid = i.indrelid\nJOIN pg_namespace n ON n.oid = c.relnamespace\nWHERE NOT i.indisvalid OR NOT i.indisready;\n\n-- Before dropping: is an index build still in progress?\nSELECT pid, relid::regclass AS table_name, index_relid::regclass AS index_name, command, phase\nFROM pg_stat_progress_create_index;","what":"Liste les index invalides laissés par des CREATE INDEX CONCURRENTLY ou REINDEX CONCURRENTLY en échec, avec leur taille, et génère la commande de suppression.","when":"Après des opérations d'index en ligne échouées ou annulées, lors des contrôles de santé de routine et avant une mise à niveau majeure.","caution":"Un CREATE INDEX CONCURRENTLY / REINDEX CONCURRENTLY en cours apparaît aussi comme INVALID ; vérifiez pg_stat_progress_create_index avant de supprimer. Un index INVALID n'est pas utilisé par les requêtes, mais si indisready = true il est encore maintenu à chaque INSERT/UPDATE, c'est-à-dire qu'il n'est qu'une surcharge. Il peut être reconstruit avec REINDEX INDEX CONCURRENTLY, mais cela échoue de nouveau si la cause (par ex. une violation d'unicité) n'est pas corrigée. Les restes de REINDEX CONCURRENTLY portent le suffixe _ccnew/_ccold. DROP INDEX CONCURRENTLY ne peut pas s'exécuter dans un bloc de transaction.","tags":["invalid index","indisvalid","create index concurrently","broken index","_ccnew","index"]},{"id":"extended-statistics","task":"Créer des statistiques étendues pour des colonnes corrélées (CREATE STATISTICS)","category":"Maintenance","command":"-- city and district are correlated: the planner assumes independence and underestimates rows\nCREATE STATISTICS customer_city_district_stx (dependencies, ndistinct, mcv) ON city, district FROM customer;\nANALYZE customer;\n\n-- Collected statistics\nSELECT statistics_name, attnames, kinds, n_distinct, dependencies\nFROM pg_stats_ext\nWHERE tablename = 'customer';\n\n-- Verify: estimated rows should now be close to actual rows\nEXPLAIN (ANALYZE, TIMING OFF)\nSELECT * FROM customer WHERE city = 'Ankara' AND district = 'Cankaya';","what":"Informe le planificateur de la corrélation entre colonnes (dépendances fonctionnelles, nombre de valeurs distinctes combinées, valeurs les plus fréquentes conjointes) ; corrige les estimations de lignes pour les conditions WHERE et GROUP BY multi-colonnes.","when":"Lorsque EXPLAIN ANALYZE montre des lignes estimées très inférieures aux lignes réelles pour des filtres multi-colonnes (par ex. 12 contre 570) et qu'une mauvaise méthode de jointure/un mauvais index est choisi en conséquence.","caution":"Ne prend effet qu'après ANALYZE. Lorsque le nombre de combinaisons de colonnes dépasse largement la cible de statistiques (100 par défaut), la liste mcv ne peut pas en couvrir la plupart, et mcv peut alors ne pas corriger une estimation que dependencies corrigerait. Vérifiez avec EXPLAIN ; si nécessaire, retirez mcv de la définition ou relevez la cible avec ALTER STATISTICS customer_city_district_stx SET STATISTICS 1000;. dependencies n'est utilisé que pour les conditions d'égalité/IN, ndistinct pour les estimations de GROUP BY. Chaque définition allonge ANALYZE ; ne l'ajoutez qu'aux groupes de colonnes présentant une erreur d'estimation avérée.","since":"PostgreSQL 12","note":"CREATE STATISTICS est arrivé en PG10 (ndistinct, dependencies), le type mcv en PG12, ALTER STATISTICS ... SET STATISTICS en PG13, les statistiques sur expressions en PG14.","tags":["extended statistics","create statistics","correlation","row estimate","cardinality","analyze"]},{"id":"analyze-staleness","task":"Trouver les tables aux statistiques obsolètes (stratégie ANALYZE)","category":"Maintenance","command":"SELECT schemaname,\n       relname AS table_name,\n       n_live_tup,\n       n_mod_since_analyze,\n       round(100.0 * n_mod_since_analyze / NULLIF(n_live_tup, 0), 1) AS mod_pct,\n       greatest(last_analyze, last_autoanalyze) AS last_analyzed,\n       round(current_setting('autovacuum_analyze_threshold')::int\n             + current_setting('autovacuum_analyze_scale_factor')::numeric * n_live_tup) AS autoanalyze_at\nFROM pg_stat_user_tables\nWHERE n_mod_since_analyze > 0\nORDER BY mod_pct DESC NULLS LAST\nLIMIT 20;\n\n-- After a bulk load, without waiting for autovacuum\nANALYZE (VERBOSE) orders;","what":"Affiche le nombre de lignes modifiées depuis le dernier ANALYZE, son rapport aux lignes vivantes, et le seuil approximatif de modifications auquel autovacuum déclenchera ANALYZE.","when":"Après des chargements/suppressions en masse, lorsque les plans montrent des estimations de lignes erronées, ou lorsqu'on soupçonne autoanalyze de prendre du retard sur de grandes tables.","caution":"Le seuil calculé utilise les réglages globaux ; s'il existe des reloptions autovacuum_analyze_* propres à la table (pg_class.reloptions), le seuil réel diffère. Sur les grandes tables, le scale_factor par défaut (0.1) se déclenche bien trop tard ; abaissez-le par table. Autovacuum n'exécute pas ANALYZE sur le parent d'une table partitionnée ; les statistiques au niveau du parent nécessitent un ANALYZE manuel. Les tables temporaires ne sont jamais vues par autovacuum. ANALYZE lit un échantillon et prend un ShareUpdateExclusiveLock (ne bloque pas les DML).","tags":["analyze","stale statistics","n_mod_since_analyze","autoanalyze","statistics","planner"]},{"id":"visibility-map-index-only","task":"Vérifier la couverture de la visibility map et l'efficacité des Index Only Scan","category":"Maintenance","command":"-- Tables with a low all-visible page ratio: Index Only Scans must visit the heap\nSELECT c.oid::regclass AS table_name,\n       c.relpages,\n       c.relallvisible,\n       round(100.0 * c.relallvisible / NULLIF(c.relpages, 0), 1) AS all_visible_pct,\n       pg_size_pretty(pg_relation_size(c.oid)) AS heap_size,\n       s.n_ins_since_vacuum,\n       s.last_autovacuum\nFROM pg_class c\nJOIN pg_stat_user_tables s ON s.relid = c.oid\nWHERE c.relpages > 1000\nORDER BY all_visible_pct NULLS FIRST\nLIMIT 20;\n\n-- If \"Heap Fetches\" in the plan is close to the returned rows, the visibility map is stale\nEXPLAIN (ANALYZE, BUFFERS) SELECT id FROM customer WHERE id BETWEEN 1 AND 50000;\n\n-- Exact count (pg_visibility extension)\nCREATE EXTENSION IF NOT EXISTS pg_visibility;\nSELECT * FROM pg_visibility_map_summary('customer');","what":"Affiche le taux de pages all-visible des tables, les lignes insérées depuis le dernier vacuum, et la fréquence à laquelle un Index Only Scan a dû accéder au heap (Heap Fetches).","when":"Lorsqu'un Index Only Scan est sous-performant ; sur les tables à forte insertion/append-only et lors de la validation de la conception d'un index couvrant (INCLUDE).","caution":"relpages/relallvisible sont des estimations enregistrées par le dernier VACUUM/ANALYZE. Seul VACUUM positionne les bits de la visibility map ; chaque UPDATE/DELETE efface le bit de la page concernée. Pour un taux faible sur des tables fréquemment mises à jour, abaissez autovacuum_vacuum_scale_factor par table. pg_visibility_map_summary lit le petit fork VM, pas le heap.","since":"PostgreSQL 13","note":"n_ins_since_vacuum et l'autovacuum déclenché par les insertions (autovacuum_vacuum_insert_threshold/scale_factor) sont arrivés en PG13 ; auparavant, les tables append-only pouvaient ne voir aucun vacuum avant un vacuum anti-wraparound, donc la visibility map n'est pas positionnée.","tags":["visibility map","index only scan","heap fetches","relallvisible","pg_visibility","vacuum"]},{"id":"range-partition-monthly","task":"Créer une table partitionnée par range mensuel (générer les partitions dans une boucle)","category":"Partitionnement","command":"CREATE TABLE sales (\n  id         bigint GENERATED ALWAYS AS IDENTITY,\n  created_at      date   NOT NULL,\n  customer_id bigint NOT NULL,\n  amount      numeric(12,2),\n  PRIMARY KEY (id, created_at)\n) PARTITION BY RANGE (created_at);\n\n-- An index defined on the parent propagates to all partitions automatically\nCREATE INDEX sales_customer_id_idx ON sales (customer_id);\n\n-- 12 monthly partitions for 2026\nDO $$\nDECLARE\n  m date;\nBEGIN\n  FOR m IN SELECT generate_series(date '2026-01-01', date '2026-12-01', interval '1 month')::date LOOP\n    EXECUTE format(\n      'CREATE TABLE IF NOT EXISTS %I PARTITION OF sales FOR VALUES FROM (%L) TO (%L)',\n      'sales_' || to_char(m, 'YYYY_MM'), m, (m + interval '1 month')::date);\n  END LOOP;\nEND $$;\n\n-- Hierarchy, bounds and sizes\nSELECT t.relid::regclass AS partition_name,\n       t.parentrelid::regclass AS parent,\n       t.isleaf,\n       t.level,\n       pg_get_expr(c.relpartbound, c.oid) AS bounds,\n       pg_size_pretty(pg_total_relation_size(t.relid)) AS total_size\nFROM pg_partition_tree('sales') t\nJOIN pg_class c ON c.oid = t.relid\nORDER BY t.level, bounds;","what":"Crée un parent partitionné par RANGE sur une colonne de date, un index qui se propage à chaque partition, et 12 partitions mensuelles dans une boucle ; puis liste la hiérarchie avec les bornes et les tailles.","when":"Pour les grandes tables en croissance continue qui suppriment périodiquement les anciennes données (DETACH/DROP) ou dont les requêtes filtrent surtout par plage de dates (commandes, journaux, séries temporelles).","caution":"Sur une table partitionnée, les contraintes PRIMARY KEY/UNIQUE doivent inclure la clé de partitionnement (il n'existe pas d'index unique global). La borne inférieure est incluse, la borne supérieure exclue (FROM inclus, TO exclu). Sans job qui crée à l'avance les partitions futures (pg_cron, pg_partman), les INSERT hors plage échouent. Des milliers de partitions augmentent le temps de planification et la surcharge des verrous. CREATE INDEX CONCURRENTLY n'est pas pris en charge sur le parent : utilisez CREATE INDEX ... ON ONLY sur le parent, CREATE INDEX CONCURRENTLY sur chaque partition, puis ALTER INDEX ... ATTACH PARTITION.","since":"PostgreSQL 12","note":"Le partitionnement déclaratif est arrivé en PG10, les index/PRIMARY KEY sur le parent et les partitions par défaut en PG11, pg_partition_tree() en PG12.","tags":["partition","range partition","declarative partitioning","monthly partition","pg_partition_tree","partitioning"]},{"id":"attach-partition-safely","task":"Attacher une table préparée comme partition avec un verrou court (ATTACH PARTITION)","category":"Partitionnement","command":"-- 1) Prepare the data in a separate table without touching the live table\nCREATE TABLE sales_2027_01 (LIKE sales INCLUDING DEFAULTS INCLUDING CONSTRAINTS);\n-- ... bulk load (COPY / INSERT) ...\nALTER TABLE sales_2027_01 ADD PRIMARY KEY (id, created_at);\nCREATE INDEX CONCURRENTLY sales_2027_01_customer_id_idx ON sales_2027_01 (customer_id);\n\n-- 2) A CHECK that exactly matches the bounds: avoids a full scan during ATTACH\nALTER TABLE sales_2027_01 ADD CONSTRAINT sales_2027_01_bound_chk\n  CHECK (created_at IS NOT NULL AND created_at >= DATE '2027-01-01' AND created_at < DATE '2027-02-01') NOT VALID;\nALTER TABLE sales_2027_01 VALIDATE CONSTRAINT sales_2027_01_bound_chk;\n\n-- 3) Attach (SHARE UPDATE EXCLUSIVE on the parent; reads/writes continue)\nSET lock_timeout = '5s';\nALTER TABLE sales ATTACH PARTITION sales_2027_01\n  FOR VALUES FROM ('2027-01-01') TO ('2027-02-01');\nRESET lock_timeout;\n\n-- 4) Drop the now redundant CHECK\nALTER TABLE sales_2027_01 DROP CONSTRAINT sales_2027_01_bound_chk;","what":"Transforme en partition des données chargées dans une table séparée, sans parcours complet ni verrous longs, grâce à une contrainte CHECK prévalidée correspondant aux bornes et à des index construits à l'avance.","when":"Pour les chargements en masse (ETL) ou les restaurations depuis une archive : préparez les données sans toucher à la table active, puis mettez-les en ligne en une seule étape.","caution":"ATTACH prend SHARE UPDATE EXCLUSIVE sur le parent et ACCESS EXCLUSIVE sur la table attachée. Sans CHECK valide correspondant, la table est entièrement parcourue sous ce verrou pour valider les bornes ; si les index correspondant à ceux du parent manquent, ATTACH les construit sous le verrou, préparez donc les deux à l'avance. Si une partition par défaut existe, elle est aussi parcourue pour prouver qu'elle ne contient aucune ligne de la nouvelle plage (voir la procédure sur la partition par défaut). Les noms et types de colonnes doivent correspondre exactement au parent ; créer la table avec LIKE ... INCLUDING est le moyen le plus sûr.","since":"PostgreSQL 12","note":"ATTACH PARTITION ne prenant que SHARE UPDATE EXCLUSIVE sur le parent est arrivé en PG12 ; en PG11 et antérieur, le parent est verrouillé en ACCESS EXCLUSIVE.","tags":["attach partition","add partition","check constraint","not valid","etl","partitioning"]},{"id":"detach-partition-concurrently","task":"Détacher une ancienne partition sans bloquer le trafic (DETACH CONCURRENTLY)","category":"Partitionnement","command":"-- Must run outside a transaction block\nALTER TABLE sales DETACH PARTITION sales_2026_01 CONCURRENTLY;\n\n-- If interrupted (cancel, lost connection) the partition stays 'detach pending'; complete it:\nALTER TABLE sales DETACH PARTITION sales_2026_01 FINALIZE;\n\n-- Any pending detach?\nSELECT inhrelid::regclass AS partition_name, inhparent::regclass AS parent\nFROM pg_inherits\nWHERE inhdetachpending;\n\n-- The detached table is now standalone: archive or drop it\nDROP TABLE sales_2026_01;","what":"Détache la partition en deux transactions ; ne prend que SHARE UPDATE EXCLUSIVE sur le parent pour ne pas bloquer les requêtes en cours, et ajoute à la table détachée une contrainte CHECK équivalente à la borne de partition.","when":"Lors du détachement d'anciennes périodes sur un système en production pour rétention/archivage (suivi d'un DROP, d'un déplacement vers un autre tablespace ou d'une archive pg_dump).","caution":"Ne peut pas s'exécuter dans un bloc BEGIN ... COMMIT et ne peut pas être utilisé lorsque le parent a une partition par défaut. Il attend toutes les transactions qui utilisent le parent et ont démarré avant lui ; les transactions longues le prolongent. En cas d'interruption, la partition reste masquée aux requêtes mais non détachée ('detach pending') et doit être finalisée avec FINALIZE. Un DETACH simple sans CONCURRENTLY est plus rapide mais exige ACCESS EXCLUSIVE sur le parent ; essayez-le avec lock_timeout.","since":"PostgreSQL 14","tags":["detach partition","concurrently","finalize","retention","archiving","partitioning"]},{"id":"default-partition-pitfall","task":"Piège de la partition par défaut : une nouvelle partition échoue avec 'would be violated by some row'","category":"Partitionnement","command":"-- Which periods have rows in the default partition?\nSELECT date_trunc('month', created_at)::date AS month, count(*) AS row_count\nFROM sales_default\nGROUP BY 1\nORDER BY 1;\n\n-- Move the rows into a new partition and attach it in one transaction\nBEGIN;\nSET LOCAL lock_timeout = '5s';\nLOCK TABLE sales_default IN ACCESS EXCLUSIVE MODE;\nCREATE TABLE sales_2027_02 (LIKE sales INCLUDING DEFAULTS INCLUDING CONSTRAINTS);\nWITH moved AS (\n  DELETE FROM sales_default\n  WHERE created_at >= DATE '2027-02-01' AND created_at < DATE '2027-03-01'\n  RETURNING *\n)\nINSERT INTO sales_2027_02 SELECT * FROM moved;\nALTER TABLE sales ATTACH PARTITION sales_2027_02\n  FOR VALUES FROM ('2027-02-01') TO ('2027-03-01');\nCOMMIT;","what":"Compte par période les lignes tombées dans la partition par défaut ; déplace les lignes de la plage de la nouvelle partition dans une nouvelle table et l'attache, le tout en une seule transaction.","when":"Lorsque CREATE TABLE ... PARTITION OF ou ATTACH échoue avec 'updated partition constraint for default partition would be violated by some row', ou lorsqu'on constate que la partition par défaut grossit.","caution":"En présence d'une partition par défaut, chaque nouvelle partition parcourt entièrement la partition par défaut et y maintient ACCESS EXCLUSIVE pendant ce temps ; une grosse partition par défaut signifie une interruption à chaque maintenance. La partition par défaut est fermée aux écritures pendant le déplacement des lignes ; faites-le en période de faible trafic. Correction permanente : créez les partitions à l'avance et gardez la partition par défaut vide sous supervision (alerte si nombre de lignes > 0), ou n'en utilisez pas du tout, pour que les INSERT hors plage échouent et que le problème apparaisse tôt. Une partition par défaut empêche aussi DETACH ... CONCURRENTLY.","since":"PostgreSQL 11","tags":["default partition","partition error","violated by some row","partition constraint","pitfall","partitioning"]},{"id":"partition-pruning-check","task":"Vérifier que le partition pruning fonctionne (à la planification et à l'exécution)","category":"Partitionnement","command":"SHOW enable_partition_pruning;\n\n-- Plan-time pruning: only the relevant partition should appear in the plan\nEXPLAIN (COSTS OFF)\nSELECT count(*) FROM sales WHERE created_at >= DATE '2026-03-01' AND created_at < DATE '2026-04-01';\n\n-- Run-time pruning: generic plan / parameterized query, \"Subplans Removed: N\"\nPREPARE q_sales(date) AS SELECT count(*) FROM sales WHERE created_at = $1;\nSET plan_cache_mode = force_generic_plan;\nEXPLAIN (ANALYZE, COSTS OFF, TIMING OFF) EXECUTE q_sales('2026-03-15');\nRESET plan_cache_mode;\nDEALLOCATE q_sales;\n\n-- Pruning killer: a function on the key column scans every partition\nEXPLAIN (COSTS OFF)\nSELECT count(*) FROM sales WHERE date_trunc('month', created_at) = DATE '2026-03-01';","what":"Compare le pruning à la planification, le pruning à l'exécution avec un plan générique ('Subplans Removed: N'), et la perte du pruning lorsqu'une fonction est appliquée à la clé de partitionnement.","when":"Lorsque les requêtes sur une table partitionnée sont plus lentes que prévu, que le plan parcourt toutes les partitions, ou que l'application utilise un ORM/des prepared statements.","caution":"Appliquer une fonction ou un cast à la clé de partitionnement (date_trunc(created_at), created_at::text) désactive le pruning ; écrivez la condition sous forme de plage sur la clé. Avec un plan générique, le pruning a lieu au démarrage de l'exécuteur et n'apparaît que sous 'Subplans Removed' ; pour les expressions STABLE comme now(), il est aussi visible dans EXPLAIN sans ANALYZE. Avec enable_partition_pruning = off, aucun pruning n'a lieu. Avec de nombreuses partitions, mesurez aussi le Planning Time.","since":"PostgreSQL 12","note":"Le pruning à l'exécution est arrivé en PG11 ; plan_cache_mode est arrivé en PG12, qui a aussi fortement amélioré les performances de planification avec de nombreuses partitions.","tags":["partition pruning","subplans removed","generic plan","plan_cache_mode","explain","partitioning"]},{"id":"pg-partman-setup","task":"Automatiser la création et la rétention des partitions avec pg_partman","category":"Partitionnement","command":"CREATE SCHEMA partman;\nCREATE EXTENSION pg_partman SCHEMA partman;\n\nCREATE TABLE public.measurements (\n  id    bigint GENERATED ALWAYS AS IDENTITY,\n  ts    timestamptz NOT NULL,\n  value numeric,\n  PRIMARY KEY (id, ts)\n) PARTITION BY RANGE (ts);\n\n-- Daily partitions, pre-create 7 days ahead (pg_partman 5.x syntax)\nSELECT partman.create_parent(\n  p_parent_table := 'public.measurements',\n  p_control      := 'ts',\n  p_interval     := '1 day',\n  p_premake      := 7\n);\n\n-- Drop partitions older than 30 days\nUPDATE partman.part_config\n   SET retention = '30 days',\n       retention_keep_table = false\n WHERE parent_table = 'public.measurements';\n\n-- Maintenance: run periodically (schedule with pg_cron or pg_partman_bgw)\nCALL partman.run_maintenance_proc();\n\n-- Any rows in the default partition?\nSELECT * FROM partman.check_default(p_exact_count := true);","what":"Enregistre une table parent auprès de pg_partman ; crée à l'avance les partitions futures, supprime celles qui dépassent la période de rétention et signale les lignes tombées dans la partition par défaut.","when":"Lorsque vous avez besoin d'une automatisation standard et observable au lieu de gérer à la main ou par des scripts maison des partitions quotidiennes/mensuelles (tables de journaux, de métriques, d'événements).","caution":"L'API a changé entre pg_partman 4.x et 5.x : en 5.x, p_type n'accepte que 'range'/'list' ('range' par défaut), et le p_type := 'native' de 4.x est invalide ; vérifiez la version installée avec SELECT extversion FROM pg_extension WHERE extname = 'pg_partman';. create_parent crée aussi une partition par défaut par défaut. retention_keep_table = false supprime définitivement (DROP) les partitions ; si vous avez besoin d'une archive, laissez-le à true et utilisez retention_schema. Si run_maintenance_proc() n'est pas planifié, aucune nouvelle partition n'est créée et les données tombent dans la partition par défaut. pg_partman_bgw exige shared_preload_libraries plus un redémarrage.","note":"pg_partman ne fait pas partie du cœur de PostgreSQL (paquet séparé, par ex. postgresql-17-partman). Les commandes utilisent la syntaxe de pg_partman 5.x.","tags":["pg_partman","partition maintenance","retention","create_parent","automatic partitioning","partitioning"]},{"id":"least-privilege-roles","task":"Construire une hiérarchie de rôles à moindre privilège (GRANT + ALTER DEFAULT PRIVILEGES)","category":"Sécurité","command":"-- Group roles (NOLOGIN): object owner, read-only, read-write\nCREATE ROLE app_owner NOLOGIN;\nCREATE ROLE app_readonly NOLOGIN;\nCREATE ROLE app_readwrite NOLOGIN;\nGRANT app_readonly TO app_readwrite;\nCREATE SCHEMA app AUTHORIZATION app_owner;\n\n-- Restrict PUBLIC's default privileges\nREVOKE ALL ON DATABASE mydb FROM PUBLIC;\nREVOKE CREATE ON SCHEMA public FROM PUBLIC;\n\n-- Existing objects\nGRANT CONNECT ON DATABASE mydb TO app_readonly;\nGRANT USAGE ON SCHEMA app TO app_readonly;\nGRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;\nGRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_readwrite;\nGRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO app_readwrite;\n\n-- Objects app_owner will create IN THE FUTURE\nALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT SELECT ON TABLES TO app_readonly;\nALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT INSERT, UPDATE, DELETE ON TABLES TO app_readwrite;\nALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT USAGE, SELECT ON SEQUENCES TO app_readwrite;\n\n-- Login users get privileges only through group membership\nCREATE ROLE web_app LOGIN PASSWORD 'Change#2026' IN ROLE app_readwrite;\nCREATE ROLE report_app LOGIN PASSWORD 'Change#2026' IN ROLE app_readonly;\n\n-- Configured default privileges (psql: \\ddp)\nSELECT pg_get_userbyid(d.defaclrole) AS owner_role,\n       n.nspname AS schema_name,\n       d.defaclobjtype AS object_type,\n       d.defaclacl\nFROM pg_default_acl d\nLEFT JOIN pg_namespace n ON n.oid = d.defaclnamespace\nORDER BY 1, 2, 3;","what":"Construit une hiérarchie de rôles de groupe propriétaire, lecture seule et lecture-écriture ; accorde les bons privilèges sur les tables/séquences existantes et futures, et n'autorise les utilisateurs applicatifs que par appartenance à un groupe.","when":"Lors de la mise en place d'un nouveau schéma applicatif, pour corriger les applications qui se connectent en superutilisateur ou en propriétaire des tables, et pour simplifier les privilèges avant un audit.","caution":"ALTER DEFAULT PRIVILEGES ne s'applique qu'aux objets créés ULTÉRIEUREMENT par le rôle nommé dans FOR ROLE ; si l'outil de migration crée les tables sous un autre utilisateur, les privilèges ne sont pas appliqués, exécutez donc le DDL avec SET ROLE app_owner. GRANT ... ON ALL TABLES ne couvre que les objets existant à ce moment-là. Les bases créées en PG15+ n'accordent déjà plus CREATE sur le schéma public à PUBLIC ; les bases mises à niveau depuis des versions plus anciennes nécessitent le REVOKE. REVOKE ALL ON DATABASE ... FROM PUBLIC retire aussi CONNECT et TEMP ; accordez-les séparément aux autres rôles qui en ont besoin. Utilisez psql \\password au lieu de mettre les mots de passe en ligne de commande (ils restent hors des journaux et de l'historique).","note":"Les rôles pg_read_all_data / pg_write_all_data sont arrivés en PG14, les options INHERIT/SET sur l'appartenance aux rôles (GRANT ... WITH INHERIT FALSE) en PG16.","tags":["least privilege","role hierarchy","alter default privileges","grant","privilege","security"]},{"id":"row-level-security","task":"Mettre en place l'isolation des tenants avec la row-level security","category":"Sécurité","command":"ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;\n-- Make the table owner subject to the policy too\nALTER TABLE app.orders FORCE ROW LEVEL SECURITY;\n\nCREATE POLICY orders_tenant_isolation ON app.orders\n  FOR ALL\n  TO app_readwrite\n  USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::int)\n  WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::int);\n\n-- The application sets the tenant at the start of every transaction (pooler-friendly)\nBEGIN;\nSET LOCAL app.tenant_id = '42';\nSELECT * FROM app.orders;\nCOMMIT;\n\n-- Configured policies\nSELECT schemaname, tablename, policyname, roles, cmd, qual, with_check\nFROM pg_policies\nWHERE tablename = 'orders';","what":"Active RLS sur la table et définit une politique qui filtre les lignes selon l'identifiant de tenant d'une variable de session (USING) et bloque les écritures pour un autre tenant (WITH CHECK).","when":"Pour imposer l'isolation des données dans la base pour les applications multi-tenant au lieu de s'en remettre au code applicatif.","caution":"Les superutilisateurs et les rôles dotés de l'attribut BYPASSRLS contournent toujours les politiques ; le propriétaire de la table n'y est soumis qu'avec FORCE ROW LEVEL SECURITY. Avec RLS activé et aucune politique correspondante, le résultat est vide (sans erreur). current_setting(..., true) renvoie NULL lorsque la variable n'est pas définie ; NULLIF empêche la chaîne vide laissée après RESET d'échouer en ''::int. Les utilisateurs peuvent modifier app.tenant_id avec SET ; seule une couche applicative de confiance doit le définir et les utilisateurs finaux ne doivent pas avoir d'accès SQL. Avec un pooler en mode transaction, utilisez SET LOCAL au lieu de SET. Indexez la colonne tenant_id utilisée dans la politique. pg_dump échoue s'il est exécuté par un utilisateur soumis à RLS ; faites les sauvegardes avec un rôle BYPASSRLS.","since":"PostgreSQL 9.5","tags":["row level security","rls","create policy","multi-tenant","tenant isolation","security"]},{"id":"pg-hba-validate-reload","task":"Valider l'ordre des règles de pg_hba.conf et recharger sans risque","category":"Sécurité","command":"-- File location\nSHOW hba_file;\n\n# Rules are read top to bottom and the FIRST matching rule wins;\n# specific rules must come BEFORE general ones:\n#   hostssl  appdb  web_app  10.10.0.0/16  scram-sha-256\n#   host     all    all      10.10.0.0/16  reject\n#   host     all    all      0.0.0.0/0     scram-sha-256\n\n-- Validate BEFORE reloading: pg_hba_file_rules reads the file on disk\nSELECT line_number, type, database, user_name, address, netmask, auth_method, error\nFROM pg_hba_file_rules\nORDER BY line_number;\n\n-- Apply if the error column is empty\nSELECT pg_reload_conf();","what":"Indique où se trouve pg_hba.conf, comment PostgreSQL l'interprète (ligne, type, base, utilisateur, adresse, méthode) et les éventuelles erreurs de syntaxe avant le reload ; puis recharge la configuration.","when":"Lors de l'ajout d'un nouveau réseau client/d'une nouvelle application, de la migration de md5 vers scram-sha-256, ou de l'investigation d'erreurs 'no pg_hba.conf entry for host'.","caution":"pg_hba_file_rules montre le fichier tel qu'il est maintenant, pas les règles actives. Si une ligne a une valeur error non vide, un reload ne charge PAS du tout pg_hba.conf et les anciennes règles restent en vigueur (le journal indique 'pg_hba.conf was not reloaded'). Les changements n'affectent que les nouvelles connexions. Une règle large placée tôt (par ex. host all all 0.0.0.0/0 trust) neutralise toutes les restrictions qui la suivent. Avec SSL désactivé, les lignes hostssl ne peuvent pas correspondre et affichent un message dans la colonne error. Par défaut, seuls les superutilisateurs peuvent lire la vue. Assurez-vous que les outils qui modifient le fichier en root ne changent pas son propriétaire ; si l'utilisateur postgres ne peut pas le lire, le reload échoue.","since":"PostgreSQL 10","note":"pg_hba_file_rules est arrivé en PG10 ; les versions plus récentes ont aussi les colonnes rule_number et file_name. PG16 prend en charge les directives include dans pg_hba.conf et la correspondance par regex dans les champs utilisateur/base.","tags":["pg_hba.conf","authentication","rule order","reload","pg_hba_file_rules","security"]},{"id":"ssl-enable-verify","task":"Activer SSL/TLS et vérifier les connexions chiffrées (pg_stat_ssl)","category":"Sécurité","command":"# Server certificate (self-signed for testing; use a corporate CA in production)\nopenssl req -new -x509 -days 365 -nodes -subj \"/CN=db.example.com\" -keyout server.key -out server.crt\nchmod 600 server.key    # in the data directory, owned by postgres\n\n-- Enable SSL (a reload is enough)\nALTER SYSTEM SET ssl = on;\nALTER SYSTEM SET ssl_cert_file = 'server.crt';\nALTER SYSTEM SET ssl_key_file = 'server.key';\nALTER SYSTEM SET ssl_min_protocol_version = 'TLSv1.2';\nSELECT pg_reload_conf();\n\n-- Which connections are encrypted?\nSELECT a.pid, a.usename, a.client_addr, a.application_name,\n       s.ssl, s.version, s.cipher, s.bits\nFROM pg_stat_ssl s\nJOIN pg_stat_activity a USING (pid)\nWHERE a.backend_type = 'client backend'\nORDER BY s.ssl, a.usename;\n\n# On the client, verify the server identity as well\npsql \"host=db.example.com dbname=postgres user=postgres sslmode=verify-full sslrootcert=root.crt\"","what":"Active TLS avec un certificat serveur sans redémarrage, fixe la version minimale du protocole, et indique si les connexions actuelles sont chiffrées, avec la version TLS et le chiffrement utilisé.","when":"Pour les exigences d'audit de sécurité/de conformité (PCI, RGPD), afin de protéger les mots de passe et les données transitant sur le réseau.","caution":"ssl = on seul n'impose pas le chiffrement ; les clients peuvent encore se connecter sans chiffrement avec sslmode=prefer/disable. Pour l'imposer, utilisez hostssl au lieu de host dans pg_hba.conf (et ajoutez hostnossl ... reject). sslmode=require ne vérifie pas l'identité du serveur (exposé au MITM) ; utilisez verify-full pour la vérification du certificat. Le fichier de clé doit appartenir à l'utilisateur postgres avec le mode 0600 (ou à root avec 0640) ; sinon SSL n'est pas chargé, vérifiez donc le journal et SHOW ssl après le reload. Les renouvellements de certificat sont aussi pris en compte par un reload.","since":"PostgreSQL 10","note":"La modification des réglages SSL par reload au lieu d'un redémarrage est arrivée en PG10, ssl_min_protocol_version en PG12 ; sa valeur par défaut est TLSv1.2 depuis PG13.","tags":["ssl","tls","pg_stat_ssl","hostssl","certificate","encryption"]},{"id":"scram-migration","task":"Migrer des mots de passe md5 vers scram-sha-256","category":"Sécurité","command":"-- 1) Store new passwords as SCRAM\nALTER SYSTEM SET password_encryption = 'scram-sha-256';\nSELECT pg_reload_conf();\n\n-- 2) Find roles still carrying md5 password hashes (requires superuser)\nSELECT rolname,\n       CASE\n         WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'scram-sha-256'\n         WHEN rolpassword LIKE 'md5%' THEN 'md5'\n         WHEN rolpassword IS NULL THEN 'no password'\n         ELSE 'other'\n       END AS password_type,\n       rolvaliduntil\nFROM pg_authid\nWHERE rolcanlogin\nORDER BY password_type, rolname;\n\n-- 3) Reset the password of each md5 role (preferably in psql: \\password legacy_app)\nALTER ROLE legacy_app PASSWORD 'New#Password2';\n\n# 4) Once all roles use SCRAM, change md5 -> scram-sha-256 in pg_hba.conf and reload","what":"Fait stocker les nouveaux mots de passe en SCRAM-SHA-256, trouve les rôles de connexion qui portent encore des hachages md5, et donne les étapes pour passer pg_hba.conf à scram-sha-256 une fois les mots de passe renouvelés.","when":"Lorsqu'un audit de sécurité signale md5, après une mise à niveau vers PG14+, ou en préparation des versions où md5 est déprécié.","caution":"SCRAM ne peut pas être dérivé des hachages md5 existants ; chaque mot de passe doit être redéfini en clair (nécessite une coordination avec les équipes applicatives). La méthode 'md5' de pg_hba.conf effectue automatiquement une authentification SCRAM pour les rôles dont le mot de passe est en SCRAM, donc garder md5 pendant la migration est une étape intermédiaire sûre, mais une fois la méthode passée à 'scram-sha-256', les rôles aux mots de passe md5 ne peuvent plus se connecter. Les anciens pilotes clients (libpq < 10, anciens pilotes JDBC/.NET) peuvent ne pas prendre en charge SCRAM ; mettez d'abord les pilotes à jour. ALTER ROLE ... PASSWORD peut écrire le mot de passe dans le journal via log_statement ; psql \\password le hache côté client avant l'envoi.","since":"PostgreSQL 10","note":"SCRAM-SHA-256 est arrivé en PG10, et la valeur par défaut de password_encryption est devenue scram-sha-256 en PG14. PG18 déprécie les mots de passe md5 et émet un avertissement.","tags":["scram-sha-256","md5","password_encryption","password","authentication","security"]},{"id":"pgaudit-object-audit","task":"Conserver des journaux d'audit par rôle et par objet avec pgaudit","category":"Sécurité","command":"-- Prerequisite: shared_preload_libraries = 'pgaudit' + restart, then:\nCREATE EXTENSION pgaudit;\n\n-- Session auditing: server-wide, only DDL and role/privilege changes\nALTER SYSTEM SET pgaudit.log = 'ddl, role';\nALTER SYSTEM SET pgaudit.log_relation = on;\nALTER SYSTEM SET pgaudit.log_parameter = off;\nSELECT pg_reload_conf();\n\n-- All reads/writes of a specific application role\nALTER ROLE web_app SET pgaudit.log = 'read, write';\n\n-- Object auditing: privileges granted to the auditor role = operations to log\nCREATE ROLE auditor NOLOGIN;\nALTER SYSTEM SET pgaudit.role = 'auditor';\nSELECT pg_reload_conf();\nGRANT SELECT, DELETE ON customer TO auditor;\nGRANT UPDATE (balance) ON customer TO auditor;","what":"Enregistre, sous forme de lignes de journal structurées préfixées par 'AUDIT:', les DDL et changements de privilèges à l'échelle du serveur, toutes les lectures/écritures d'un rôle donné, et les accès aux tables/colonnes sensibles accordées au rôle auditeur.","when":"Pour prouver qui a accédé à des données sensibles lors d'audits de conformité tels que RGPD, PCI-DSS ou SOX.","caution":"Activer pgaudit.log = 'all' ou 'read' à l'échelle du serveur augmente fortement le volume de journaux et les E/S ; restreignez la portée par audit de rôle ou d'objet. Un superutilisateur peut désactiver l'audit (ALTER SYSTEM/ALTER ROLE) ; envoyez les enregistrements vers un système de journalisation central (syslog/SIEM) et stockez-les indépendamment du serveur de base. log_parameter = on journalise aussi les paramètres de bind (données personnelles). Les réglages de rôle prennent effet dans les nouvelles sessions. La version majeure de pgaudit doit correspondre à la version majeure de PostgreSQL (pgaudit 17.x = PG17).","note":"pgaudit ne fait pas partie du cœur de PostgreSQL (paquet séparé, par ex. postgresql-17-pgaudit).","tags":["pgaudit","audit","auditing","object audit","log","security"]},{"id":"memory-settings-check","task":"Revoir les réglages mémoire (shared_buffers, work_mem, maintenance_work_mem)","category":"Paramètres","command":"SELECT name,\n       current_setting(name) AS value,\n       source,\n       context\nFROM pg_settings\nWHERE name IN ('shared_buffers', 'effective_cache_size', 'work_mem', 'hash_mem_multiplier',\n               'maintenance_work_mem', 'autovacuum_work_mem', 'max_connections',\n               'max_parallel_workers_per_gather', 'huge_pages', 'temp_buffers', 'wal_buffers')\nORDER BY name;\n\n-- Rough ceilings: work_mem x max_connections and the autovacuum memory ceiling\nSELECT pg_size_pretty(pg_size_bytes(current_setting('work_mem'))\n                      * current_setting('max_connections')::int) AS work_mem_x_max_connections,\n       pg_size_pretty(pg_size_bytes(current_setting('maintenance_work_mem'))\n                      * current_setting('autovacuum_max_workers')::int) AS autovacuum_mem_ceiling;\n\n-- Trace of operations that did not fit in work_mem: temp files per database\nSELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_size, stats_reset\nFROM pg_stat_database\nWHERE datname IS NOT NULL\nORDER BY temp_bytes DESC;","what":"Affiche les paramètres liés à la mémoire avec leur valeur, leur source (défaut/fichier de configuration) et leur contexte de modification (postmaster = redémarrage) ; des plafonds mémoire approximatifs pour work_mem et autovacuum ; et le volume de fichiers temporaires par base.","when":"Lors de la mise en place d'un nouveau serveur, après un ajout de RAM, après un événement OOM killer, ou lorsqu'une forte utilisation de fichiers temporaires est constatée.","caution":"work_mem n'est pas alloué par connexion mais par NŒUD de tri/hachage (multiplié par hash_mem_multiplier pour les hachages) et séparément pour chaque worker parallèle ; une requête complexe peut utiliser plusieurs multiples de work_mem, le produit calculé est donc un avertissement approximatif, pas une borne supérieure. Points de départ typiques : shared_buffers ~25 % de la RAM (redémarrage requis), effective_cache_size ~50-75 % (simple indication pour le planificateur, n'alloue rien), maintenance_work_mem 512MB-2GB. Avec autovacuum_work_mem = -1, chaque worker autovacuum peut utiliser maintenance_work_mem. Si temp_bytes est élevé, relevez work_mem par rôle ou par requête, pas globalement. Avec un shared_buffers important sous Linux, envisagez huge_pages.","note":"hash_mem_multiplier est arrivé en PG13 (2.0 par défaut depuis PG15).","tags":["shared_buffers","work_mem","maintenance_work_mem","memory","memory tuning","temp_bytes"]},{"id":"pending-restart-config-errors","task":"Afficher les paramètres en attente de redémarrage et les erreurs des fichiers de configuration","category":"Paramètres","command":"-- Parameters changed but not effective until a restart\nSELECT name, setting, unit, context, source, sourcefile, pending_restart\nFROM pg_settings\nWHERE pending_restart;\n\n-- Lines in configuration files that were not applied or have errors\nSELECT sourcefile, sourceline, seqno, name, setting, applied, error\nFROM pg_file_settings\nWHERE NOT applied OR error IS NOT NULL\nORDER BY sourcefile, sourceline;\n\n-- Where does a parameter's effective value come from?\nSELECT name, setting, source, sourcefile, sourceline\nFROM pg_settings\nWHERE name = 'work_mem';","what":"Affiche les paramètres modifiés dans les fichiers mais en attente d'un redémarrage, les lignes des fichiers de configuration non appliquées ou en erreur, et de quel fichier/quelle ligne provient la valeur effective d'un paramètre.","when":"Pour vérifier après un ALTER SYSTEM ou une modification de postgresql.conf que le reload a eu l'effet attendu, et pour lister les changements en attente avant un redémarrage planifié.","caution":"pg_file_settings lit les fichiers tels qu'ils sont maintenant ; si un paramètre est défini à plusieurs endroits, seul le dernier a applied = true (postgresql.auto.conf est lu en dernier, donc ALTER SYSTEM l'emporte toujours). Une ligne avec applied = false et un error vide est masquée par une autre définition ; ce n'est pas une erreur. Un paramètre qui exige un redémarrage affiche l'erreur 'setting could not be applied'. Un fichier erroné peut être appliqué partiellement au reload ; après un changement, exécutez pg_reload_conf() puis vérifiez ces requêtes et le journal du serveur. Ne modifiez pas postgresql.auto.conf à la main ; utilisez ALTER SYSTEM RESET.","since":"PostgreSQL 9.5","tags":["pending_restart","pg_file_settings","restart","configuration error","alter system","config"]},{"id":"checkpoint-stats","task":"Revoir le comportement des checkpoints (PG17+ pg_stat_checkpointer, PG16 et antérieur pg_stat_bgwriter)","category":"Paramètres","command":"-- PG17+\nSELECT num_timed,\n       num_requested,\n       round(100.0 * num_requested / NULLIF(num_timed + num_requested, 0), 1) AS req_pct,\n       restartpoints_timed,\n       restartpoints_req,\n       round((write_time / 1000)::numeric / NULLIF(num_timed + num_requested, 0), 1) AS avg_write_s,\n       round((sync_time / 1000)::numeric / NULLIF(num_timed + num_requested, 0), 1) AS avg_sync_s,\n       pg_size_pretty(buffers_written * current_setting('block_size')::bigint) AS written_by_checkpoint,\n       stats_reset\nFROM pg_stat_checkpointer;\n\n-- PG16 and earlier\nSELECT checkpoints_timed,\n       checkpoints_req,\n       round(100.0 * checkpoints_req / NULLIF(checkpoints_timed + checkpoints_req, 0), 1) AS req_pct,\n       round((checkpoint_write_time / 1000)::numeric / NULLIF(checkpoints_timed + checkpoints_req, 0), 1) AS avg_write_s,\n       round((checkpoint_sync_time / 1000)::numeric / NULLIF(checkpoints_timed + checkpoints_req, 0), 1) AS avg_sync_s,\n       pg_size_pretty(buffers_checkpoint * current_setting('block_size')::bigint) AS written_by_checkpoint,\n       pg_size_pretty(buffers_clean * current_setting('block_size')::bigint) AS written_by_bgwriter,\n       pg_size_pretty(buffers_backend * current_setting('block_size')::bigint) AS written_by_backends,\n       maxwritten_clean,\n       stats_reset\nFROM pg_stat_bgwriter;\n\n-- Related settings\nSELECT name, current_setting(name) AS value\nFROM pg_settings\nWHERE name IN ('checkpoint_timeout', 'max_wal_size', 'min_wal_size',\n               'checkpoint_completion_target', 'log_checkpoints')\nORDER BY name;","what":"Affiche le rapport entre checkpoints planifiés et checkpoints forcés par le volume de WAL, le temps moyen d'écriture/synchronisation par checkpoint et le volume de buffers écrits (en PG16 et antérieur, avec les écritures du bgwriter et des backends).","when":"Lorsque les périodes à forte écriture montrent des pics d'E/S, que la génération de WAL est élevée, ou que le journal avertit 'checkpoints are occurring too frequently'.","caution":"Un req_pct élevé (typiquement au-delà de 10-20 %) signifie que les checkpoints sont forcés parce que max_wal_size se remplit ; augmentez max_wal_size et surveillez les lignes 'checkpoint starting: wal' avec log_checkpoints = on. Un checkpoint_timeout plus long étale les E/S mais allonge la crash recovery. En PG16 et antérieur, un written_by_backends élevé signifie que les backends doivent écrire eux-mêmes les pages modifiées (shared_buffers/bgwriter insuffisants) ; en PG17, cela figure dans pg_stat_io. Les moyennes comptent aussi les checkpoints planifiés sautés faute de travail, lisez-les donc comme des ordres de grandeur. Les compteurs sont cumulés depuis stats_reset.","since":"PostgreSQL 17","note":"En PG17, les statistiques du checkpointer sont passées de pg_stat_bgwriter à la nouvelle vue pg_stat_checkpointer (checkpoints_timed → num_timed, checkpoints_req → num_requested, buffers_checkpoint → buffers_written) ; buffers_backend et buffers_backend_fsync ont été supprimés. PG18 a ajouté les colonnes num_done et slru_written.","tags":["checkpoint","pg_stat_checkpointer","pg_stat_bgwriter","max_wal_size","checkpoint_timeout","wal"]},{"id":"relation-size-breakdown","task":"Lister la taille des tables ventilée en heap / index / TOAST, et les plus gros index","category":"Stockage","command":"SELECT n.nspname AS schema_name,\n       c.relname AS table_name,\n       pg_size_pretty(pg_total_relation_size(c.oid)) AS total,\n       pg_size_pretty(pg_relation_size(c.oid)) AS heap,\n       pg_size_pretty(pg_indexes_size(c.oid)) AS indexes,\n       pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid) - pg_indexes_size(c.oid)) AS toast_fsm_vm,\n       round(100.0 * pg_total_relation_size(c.oid) / NULLIF(pg_database_size(current_database()), 0), 1) AS pct_of_db\nFROM pg_class c\nJOIN pg_namespace n ON n.oid = c.relnamespace\nWHERE c.relkind IN ('r', 'm')\n  AND n.nspname NOT IN ('pg_catalog', 'information_schema')\nORDER BY pg_total_relation_size(c.oid) DESC\nLIMIT 20;\n\n-- Largest indexes and their usage counts\nSELECT s.schemaname,\n       s.relname AS table_name,\n       s.indexrelname AS index_name,\n       pg_size_pretty(pg_relation_size(s.indexrelid)) AS index_size,\n       s.idx_scan,\n       i.indisunique,\n       i.indisprimary\nFROM pg_stat_user_indexes s\nJOIN pg_index i ON i.indexrelid = s.indexrelid\nORDER BY pg_relation_size(s.indexrelid) DESC\nLIMIT 20;","what":"Affiche les plus grandes tables ventilées en total, heap, index et TOAST (+FSM/VM) avec leur part dans la base ; et les plus gros index avec leur nombre de parcours et les indicateurs unique/primary.","when":"Pour trouver d'où vient la croissance (données, index ou TOAST) lorsque l'utilisation du disque augmente, et pour repérer les gros index inutilisés.","caution":"Les fonctions de taille accèdent au système de fichiers pour chaque relation et peuvent être lentes dans les bases comptant des dizaines de milliers de tables. Un parent partitionné (relkind 'p') pèse lui-même 0 octet et est exclu ; pour le total d'une hiérarchie : SELECT pg_size_pretty(sum(pg_total_relation_size(relid))) FROM pg_partition_tree('sales'); Les gros index avec idx_scan = 0 sont candidats à la suppression, mais les compteurs ne couvrent que la période depuis la dernière réinitialisation des statistiques et uniquement ce serveur (l'utilisation sur les répliques n'est pas visible) ; les index unique/primary imposent des contraintes et ne sont pas supprimés.","tags":["table size","index size","toast","disk usage","pg_total_relation_size","storage"]},{"id":"extension-update-check","task":"Trouver les extensions ayant des mises à jour en attente et les mettre à jour","category":"Extensions","command":"SELECT e.extname,\n       e.extversion AS installed,\n       a.default_version AS available,\n       n.nspname AS schema_name,\n       format('ALTER EXTENSION %I UPDATE;', e.extname) AS fix\nFROM pg_extension e\nJOIN pg_available_extensions a ON a.name = e.extname\nJOIN pg_namespace n ON n.oid = e.extnamespace\nWHERE e.extversion IS DISTINCT FROM a.default_version\nORDER BY e.extname;\n\n-- Is there an update path?\nSELECT * FROM pg_extension_update_paths('pgstattuple') WHERE path IS NOT NULL;\n\nALTER EXTENSION pgstattuple UPDATE;","what":"Compare la version installée de chaque extension dans la base avec la version par défaut du paquet sur le serveur, et génère la commande ALTER EXTENSION pour celles ayant des mises à jour en attente.","when":"Après des mises à niveau mineures/majeures de PostgreSQL et des mises à jour de paquets d'extensions, et lors de la maintenance périodique.","caution":"Mettre à jour le paquet OS ne met pas à jour automatiquement la partie SQL ; ALTER EXTENSION ... UPDATE doit être exécuté séparément dans chaque base (particulièrement important après pg_upgrade). Le script de mise à jour peut prendre des verrous et modifier des objets (par ex. PostGIS) ; testez-le d'abord et lisez les notes de version. Pour passer à une version précise : ALTER EXTENSION pgstattuple UPDATE TO '1.5'; Certaines extensions (par ex. pg_stat_statements) exigent aussi un redémarrage pour recharger la bibliothèque partagée.","tags":["alter extension update","extension version","extension","pg_available_extensions","upgrade","update"]},{"id":"pg-stat-io-analysis","task":"Analyser l'origine des E/S avec pg_stat_io (type de backend, objet, contexte)","category":"Extensions","command":"SELECT backend_type,\n       object,\n       context,\n       reads,\n       pg_size_pretty(reads * op_bytes) AS read_size,\n       writes,\n       pg_size_pretty(writes * op_bytes) AS write_size,\n       extends,\n       hits,\n       round(100.0 * hits / NULLIF(hits + reads, 0), 1) AS hit_pct,\n       evictions,\n       reuses,\n       fsyncs,\n       round(read_time::numeric, 1) AS read_ms,\n       round(write_time::numeric, 1) AS write_ms\nFROM pg_stat_io\nWHERE coalesce(reads, 0) + coalesce(writes, 0) + coalesce(extends, 0) + coalesce(evictions, 0) > 0\nORDER BY coalesce(reads, 0) + coalesce(writes, 0) DESC;\n\n-- Who writes? Ideally most writes come from the checkpointer and background writer\nSELECT backend_type,\n       pg_size_pretty(sum(writes * op_bytes)) AS written,\n       round(100.0 * sum(writes) / NULLIF(sum(sum(writes)) OVER (), 0), 1) AS pct_of_writes,\n       sum(fsyncs) AS fsyncs\nFROM pg_stat_io\nWHERE object = 'relation' AND writes > 0\nGROUP BY backend_type\nORDER BY sum(writes) DESC;","what":"Affiche les lectures, écritures, extensions, évictions et fsyncs par type de backend (client backend, autovacuum, checkpointer...), objet (relation/relation temporaire) et contexte (normal, vacuum, bulkread, bulkwrite) ; et de quels types de processus proviennent les écritures.","when":"Lors d'un goulet d'E/S, pour déterminer si la charge vient des requêtes, d'autovacuum ou des checkpoints, et pour valider les réglages shared_buffers/bgwriter.","caution":"pg_stat_io couvre tout le cluster et est cumulé depuis la dernière réinitialisation ; faites la différence entre deux échantillons pour voir l'évolution (pour réinitialiser : SELECT pg_stat_reset_shared('io');). read_time/write_time ne sont renseignés qu'avec track_io_timing = on. reads inclut aussi les lectures servies par le page cache de l'OS. Des écritures ou évictions élevées pour client backend signifient que les backends écrivent eux-mêmes les buffers pour libérer de la place ; revoyez shared_buffers, bgwriter_lru_maxpages et les réglages de checkpoint. Les lignes avec context = 'vacuum'/'bulkread' reflètent l'utilisation des ring buffers ; les reuses y sont normaux.","since":"PostgreSQL 16","note":"pg_stat_io est arrivé en PG16. PG18 a supprimé la colonne op_bytes, ajouté read_bytes/write_bytes/extend_bytes et rapporte aussi les E/S WAL dans cette vue ; en PG18, utilisez read_bytes au lieu de reads * op_bytes.","tags":["pg_stat_io","io statistics","evictions","fsync","shared_buffers","io"]}]},"recipes/mysql.json":{"engineId":"mysql","engine":"MySQL / MariaDB","order":3,"recipes":[{"id":"rotate-binlog","task":"Faire tourner le binary log","category":"Binlog et journaux","command":"FLUSH BINARY LOGS;","what":"Ferme le fichier binary log courant et passe à un nouveau.","when":"Pour créer un point de départ binlog propre avant une sauvegarde, ou pour limiter la taille des fichiers.","caution":"Une rotation fréquente produit de nombreux petits fichiers binlog. La même commande fonctionne dans MariaDB ; FLUSH LOGS fait tourner tous les journaux.","tags":["binlog","rotate","flush","log"]},{"id":"purge-binlog","task":"Purger les anciens binary logs (PURGE)","category":"Binlog et journaux","command":"PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;","what":"Supprime du disque les fichiers binary log antérieurs à la date indiquée.","when":"Lorsque les binlogs remplissent le disque, pour nettoyer les anciens journaux devenus inutiles.","caution":"Si une réplique a encore besoin d'un binlog en cours de suppression, la réplication casse ; vérifiez d'abord avec SHOW REPLICA STATUS que toutes les répliques l'ont dépassé. Pour supprimer jusqu'à un fichier précis : PURGE BINARY LOGS TO 'mysql-bin.000123'; Pour un nettoyage automatique, utilisez le réglage binlog_expire_logs_seconds.","tags":["binlog","purge","cleanup","log"]},{"id":"show-binlogs","task":"Afficher l'état des binary logs","category":"Binlog et journaux","command":"SHOW BINARY LOGS;","what":"Liste tous les fichiers binary log du serveur et leur taille.","when":"Pour voir l'utilisation disque des binlogs et quels fichiers existent.","caution":"Pour le fichier binlog en cours d'écriture et sa position : SHOW BINARY LOG STATUS; (MySQL 8.2+). Les versions plus anciennes utilisent SHOW MASTER STATUS (déprécié en 8.2–8.3, supprimé en 8.4).","tags":["binlog","status","show","log"]},{"id":"enable-general-slow-log","task":"Activer le general log et le slow query log","category":"Binlog et journaux","command":"SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL general_log = 'ON';","what":"Active à l'exécution le slow query log (requêtes de plus d'une seconde) et le general log qui enregistre chaque requête.","when":"Pour analyser temporairement les requêtes lentes ou tout le trafic de requêtes.","caution":"general_log ajoute une surcharge importante en performances et en disque ; désactivez-le toujours une fois le diagnostic terminé (SET GLOBAL general_log='OFF'). Ces réglages sont réinitialisés au redémarrage ; utilisez SET PERSIST pour les rendre permanents.","tags":["slow-log","general-log","log","diagnostics"]},{"id":"innodb-redo-capacity","task":"Modifier la taille du redo log InnoDB","category":"Stockage","command":"SET GLOBAL innodb_redo_log_capacity = 2147483648;","what":"Définit dynamiquement à l'exécution la capacité totale du redo log InnoDB (en octets, ici 2 GB).","when":"Pour réduire la fréquence des checkpoints et améliorer les performances sous des charges à forte écriture.","caution":"innodb_redo_log_capacity est arrivé dans MySQL 8.0.30 et remplace les anciens réglages innodb_log_file_size + innodb_log_files_in_group. Sur les versions plus anciennes/MariaDB, innodb_log_file_size se modifie dans my.cnf et exige un redémarrage. Pour le rendre permanent : SET PERSIST innodb_redo_log_capacity = 2147483648;","since":"MySQL 8.0.30","note":"innodb_redo_log_capacity est arrivé dans MySQL 8.0.30 et a permis le redimensionnement dynamique (sans redémarrage) du redo log ; auparavant, il fallait définir innodb_log_file_size + innodb_log_files_in_group et redémarrer le serveur.","tags":["innodb","redo-log","capacity","storage"]},{"id":"innodb-undo-tablespace","task":"Ajouter / gérer un undo tablespace InnoDB","category":"Stockage","command":"CREATE UNDO TABLESPACE undo_003 ADD DATAFILE 'undo_003.ibu';","what":"Crée un nouvel undo tablespace InnoDB ; les undo logs sont gérés séparément des fichiers de données et deviennent tronquables.","when":"Pour gérer l'espace lorsque des transactions longues font gonfler l'undo log, ou pour déplacer l'undo vers un disque séparé.","caution":"Le nom de fichier doit se terminer par .ibu. Un undo tablespace doit être rendu inactif avant de pouvoir être supprimé : ALTER UNDO TABLESPACE undo_003 SET INACTIVE; puis DROP UNDO TABLESPACE undo_003; Il s'agit d'une fonctionnalité MySQL 8.0+.","since":"MySQL 8.0","note":"La création/suppression d'undo tablespaces en SQL (CREATE/DROP UNDO TABLESPACE) est arrivée dans MySQL 8.0 ; auparavant, le nombre d'undo tablespaces était fixé par innodb_undo_tablespaces et ne pouvait pas être géré à l'exécution.","tags":["innodb","undo-log","tablespace","storage"]},{"id":"create-tablespace","task":"Ajouter un general tablespace","category":"Stockage","command":"CREATE TABLESPACE ssd_ts ADD DATAFILE 'ssd_ts.ibd' ENGINE=InnoDB;","what":"Crée un general tablespace InnoDB que plusieurs tables peuvent partager.","when":"Pour regrouper des tables dans un fichier/disque précis, ou pour réduire le nombre de fichiers.","caution":"Si le chemin DATAFILE est relatif, il est créé sous datadir ; le placer sur un autre disque exige un chemin absolu et les permissions nécessaires. Pour y déplacer une table : ALTER TABLE customer TABLESPACE ssd_ts; Un tablespace ne peut être supprimé que lorsqu'il est vide.","tags":["tablespace","innodb","storage","create"]},{"id":"optimize-table","task":"OPTIMIZE d'une table","category":"Stockage","command":"OPTIMIZE TABLE customer;","what":"Reconstruit la table pour supprimer la fragmentation, récupérer l'espace inutilisé et mettre à jour les statistiques d'index.","when":"Pour récupérer de l'espace dans une table InnoDB gonflée par de nombreux DELETE/UPDATE.","caution":"Sur InnoDB, OPTIMIZE s'exécute en réalité comme un ALTER en ligne qui reconstruit la table, mais il nécessite tout de même des E/S et de l'espace disque temporaire ; les grandes tables exigent un espace libre égal à la taille de la table. Exécutez-le en période de faible trafic pour limiter le verrouillage.","tags":["optimize","table","defragment","storage"]},{"id":"table-schema-size","task":"Interroger la taille des tables et des schémas","category":"Stockage","command":"SELECT table_schema AS schema_name, table_name AS table_name, ROUND((data_length + index_length) / 1024 / 1024, 1) AS size_mb FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') ORDER BY (data_length + index_length) DESC LIMIT 20;","what":"Liste les 20 plus grandes tables par taille cumulée données + index (MB).","when":"Pour analyser l'utilisation du disque et trouver quelles tables occupent l'espace.","caution":"Les tailles d'information_schema sont approximatives pour InnoDB. Pour les totaux par schéma, faites GROUP BY table_schema uniquement, au lieu de table_name.","tags":["size","table","schema","information-schema"]},{"id":"analyze-table","task":"Mettre à jour les statistiques d'une table (ANALYZE TABLE)","category":"Maintenance","command":"ANALYZE TABLE customer;","what":"Recalcule les statistiques de distribution des index/clés de la table pour que l'optimiseur puisse choisir de meilleurs plans.","when":"Lorsque de mauvais plans de requête apparaissent après des modifications de données en masse.","caution":"Un bref metadata lock est pris sur la table pendant l'exécution. Sur InnoDB, il repose sur un échantillon ; augmentez innodb_stats_persistent_sample_pages pour plus de précision.","tags":["analyze","statistics","optimizer","maintenance"]},{"id":"rebuild-table","task":"Reconstruire une table/ses index","category":"Maintenance","command":"ALTER TABLE customer ENGINE=InnoDB;","what":"Reconstruit la table avec le même moteur ; recrée les index et les pages de données pour supprimer la fragmentation et le gonflement des index.","when":"Pour rafraîchir une table avec un ALTER nul, ou pour réparer une organisation de pages endommagée.","caution":"Les grandes tables exigent un espace disque temporaire égal à la taille de la table. Pour limiter le verrouillage : ALTER TABLE customer ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;","tags":["rebuild","alter","innodb","maintenance"]},{"id":"check-table","task":"Vérifier l'intégrité d'une table (CHECK TABLE)","category":"Maintenance","command":"CHECK TABLE customer;","what":"Vérifie la table et ses index à la recherche de corruption et renvoie un état.","when":"En cas d'erreurs inattendues ou lorsqu'une corruption est suspectée.","caution":"Un verrou de lecture est pris sur la table pendant la vérification. Si une corruption est détectée, InnoDB se répare généralement par dump/restore ; REPAIR TABLE ne fonctionne que pour des moteurs comme MyISAM/Archive.","tags":["check","integrity","corruption","maintenance"]},{"id":"set-global-persist","task":"Modifier une variable globale et la rendre persistante","category":"Paramètres","command":"SET PERSIST innodb_buffer_pool_size = 4294967296;","what":"Définit la variable à la fois sur le serveur en cours d'exécution et de façon persistante après redémarrage (mysqld-auto.cnf).","when":"Pour modifier durablement une variable serveur sans redémarrage.","caution":"SET PERSIST est une fonctionnalité MySQL 8.0+ ; MariaDB ne la prend pas en charge (il faut alors modifier my.cnf et redémarrer). Utilisez SET GLOBAL pour un changement uniquement à l'exécution. Les variables modifiables seulement au redémarrage exigent SET PERSIST_ONLY.","since":"MySQL 8.0","note":"SET PERSIST est arrivé dans MySQL 8.0 et rend une variable persistante en l'écrivant dans mysqld-auto.cnf ; auparavant (et dans MariaDB), la persistance exigeait de modifier my.cnf à la main et de redémarrer.","tags":["set-persist","global","variable","parameter"]},{"id":"show-variables","task":"Afficher la valeur courante d'une variable","category":"Paramètres","command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","what":"Affiche la valeur effective de la variable serveur indiquée (ou de celles correspondant au motif).","when":"Pour vérifier la valeur en vigueur d'un paramètre.","caution":"Utilisez SHOW GLOBAL VARIABLES pour la valeur globale plutôt que celle de la session. Pour voir d'où vient la valeur (fichier, ligne de commande, persist) : SELECT * FROM performance_schema.variables_info WHERE variable_name = 'innodb_buffer_pool_size';","tags":["show-variables","parameter","current-value","config"]},{"id":"flush-privileges","task":"Recharger les tables de privilèges (FLUSH PRIVILEGES)","category":"Paramètres","command":"FLUSH PRIVILEGES;","what":"Recharge en mémoire les tables de privilèges comme mysql.user après leur modification directe.","when":"Pour activer des changements effectués par INSERT/UPDATE manuel sur les tables de privilèges.","caution":"GRANT/REVOKE/CREATE USER appliquent déjà les privilèges immédiatement ; FLUSH PRIVILEGES après eux est inutile. Il n'est nécessaire que lorsque vous modifiez directement les tables de privilèges.","tags":["flush-privileges","grants","privileges","reload"]},{"id":"setup-replica","task":"Mettre en place une réplique (CHANGE REPLICATION SOURCE)","category":"Réplication","command":"CHANGE REPLICATION SOURCE TO SOURCE_HOST='source_host', SOURCE_PORT=3306, SOURCE_USER='replicator', SOURCE_PASSWORD='secret', SOURCE_AUTO_POSITION=1;","what":"Configure ce serveur comme réplique suivant la source indiquée avec l'auto-positionnement basé sur GTID.","when":"Pour mettre en place une nouvelle réplique ou changer la source de réplication.","caution":"Il s'agit de la terminologie MySQL 8.0.23+ ; les versions plus anciennes utilisent CHANGE MASTER TO ... MASTER_HOST (alias en 8.0.23–8.3, supprimé en 8.4). SOURCE_AUTO_POSITION=1 exige que GTID soit activé (gtid_mode=ON) ; sinon SOURCE_LOG_FILE et SOURCE_LOG_POS doivent être indiqués. La réplication doit être arrêtée avant d'exécuter la commande. Si l'utilisateur de réplication a été créé avec caching_sha2_password, défaut en 8.0+, la connexion exige TLS : ajoutez SOURCE_SSL=1 ou, sans TLS, définissez GET_SOURCE_PUBLIC_KEY=1 ; sinon le thread I/O ne parvient pas à se connecter et renvoie l'erreur 2061.","since":"MySQL 8.0.23","note":"CHANGE REPLICATION SOURCE TO (SOURCE_HOST etc.) est arrivé dans MySQL 8.0.23 ; auparavant, on utilisait CHANGE MASTER TO ... MASTER_HOST / MASTER_AUTO_POSITION.","tags":["replication","change-source","replica","setup"]},{"id":"start-stop-replica","task":"Démarrer / arrêter une réplique","category":"Réplication","command":"STOP REPLICA; START REPLICA;","what":"Arrête puis redémarre les threads de réplication I/O et SQL.","when":"Pour une maintenance, des changements de configuration ou pour corriger une erreur de réplication.","caution":"Il s'agit de la terminologie MySQL 8.0.22+ ; les versions plus anciennes utilisent STOP SLAVE / START SLAVE. Pour le thread SQL seul : STOP REPLICA SQL_THREAD; Après STOP REPLICA, vous pouvez exécuter CHANGE REPLICATION SOURCE.","since":"MySQL 8.0.22","note":"START REPLICA / STOP REPLICA sont arrivés dans MySQL 8.0.22 ; auparavant, on utilisait START SLAVE / STOP SLAVE (l'ancienne syntaxe fonctionne comme alias en 8.0.22–8.3 et a été supprimée en 8.4).","tags":["replication","start","stop","replica"]},{"id":"replica-status","task":"Afficher l'état de la réplique (SHOW REPLICA STATUS)","category":"Réplication","command":"SHOW REPLICA STATUS\\G","what":"Affiche en détail la connexion de la réplique à la source, l'état des threads, le retard et les éventuelles erreurs.","when":"Pour vérifier si la réplication est saine et de combien elle est en retard.","caution":"Il s'agit de la terminologie MySQL 8.0.22+ ; les versions plus anciennes utilisent SHOW SLAVE STATUS (alias en 8.0.22–8.3, supprimé en 8.4). Champs clés : Replica_IO_Running et Replica_SQL_Running doivent valoir 'Yes', Seconds_Behind_Source indique le retard, Last_Error indique l'erreur.","since":"MySQL 8.0.22","note":"SHOW REPLICA STATUS est arrivé dans MySQL 8.0.22 ; auparavant, on utilisait SHOW SLAVE STATUS et les champs s'appelaient par exemple Slave_IO_Running / Seconds_Behind_Master.","tags":["replication","status","replica","lag"]},{"id":"skip-gtid-transaction","task":"Sauter une transaction en échec (GTID)","category":"Réplication","command":"STOP REPLICA; SET GTID_NEXT='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:15'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC'; START REPLICA;","what":"Valide une transaction vide pour le GTID indiqué afin que cette transaction compte comme 'appliquée', et laisse la réplication poursuivre.","when":"Lorsqu'un événement dupliqué/inapplicable sur la réplique arrête la réplication, pour sauter cet événement.","caution":"Sauter une transaction crée une incohérence de données entre la réplique et la source ; ne le faites que si vous connaissez l'effet de l'événement sauté. Prenez la valeur GTID dans Last_Error / Retrieved_Gtid_Set de SHOW REPLICA STATUS. Si GTID est désactivé, utilisez SET GLOBAL sql_replica_skip_counter = 1; (8.0.26+ ; auparavant sql_slave_skip_counter, alias déprécié en 8.0.26+).","tags":["replication","gtid","skip","recovery"]},{"id":"kill-query-connection","task":"Tuer une requête ou une connexion (KILL)","category":"Session","command":"KILL QUERY 4567;","what":"Termine la requête en cours d'exécution sur l'ID de connexion donné sans fermer la connexion.","when":"Pour arrêter une requête longue/bloquée.","caution":"Pour fermer toute la connexion (session) : KILL CONNECTION 4567; (ou simplement KILL 4567). Si une transaction d'écriture en cours est tuée, elle est annulée (rollback), ce qui peut être long sur les grandes tables.","tags":["kill","query","connection","session"]},{"id":"process-list","task":"Afficher la liste des processus","category":"Session","command":"SELECT id, user, host, db, command, time, state, LEFT(info, 100) AS query FROM performance_schema.processlist WHERE command <> 'Sleep' ORDER BY time DESC;","what":"Liste toutes les connexions non inactives, la requête qu'elles exécutent et le temps écoulé.","when":"Pour diagnostiquer les sessions actives et les requêtes longues.","caution":"Raccourci : SHOW FULL PROCESSLIST; performance_schema.processlist fournit toutes les colonnes et peut être filtré avec WHERE. La colonne time est en secondes.","tags":["processlist","session","activity","performance-schema"]},{"id":"blocking-locks","task":"Afficher les verrous bloquants (data_lock_waits)","category":"Session","command":"SELECT r.trx_id AS waiting_trx, r.trx_mysql_thread_id AS waiting_thread, LEFT(r.trx_query,80) AS waiting_query, b.trx_id AS blocking_trx, b.trx_mysql_thread_id AS blocking_thread, LEFT(b.trx_query,80) AS blocking_query FROM performance_schema.data_lock_waits w JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id;","what":"Indique quelle transaction est bloquée par laquelle, avec les requêtes et identifiants de threads associés.","when":"Pour diagnostiquer les attentes de verrous et les ralentissements causés par des interblocages (deadlocks).","caution":"performance_schema.data_lock_waits est arrivé dans MySQL 8.0 ; les versions plus anciennes utilisent information_schema.innodb_lock_waits. Pour arrêter le thread bloquant, utilisez KILL blocking_thread;.","since":"MySQL 8.0","note":"performance_schema.data_lock_waits est arrivé dans MySQL 8.0 ; dans MySQL 5.7 et antérieur, information_schema.innodb_lock_waits servait à obtenir les mêmes informations.","tags":["blocking","lock","data-lock-waits","deadlock"]}]},"recipes/mysql-ext.json":{"engineId":"mysql","engine":"MySQL","order":2,"recipes":[{"id":"replica-applier-health","task":"Surveiller le retard de la réplique et les erreurs des workers d'apply (performance_schema)","category":"Réplication","command":"-- Receiver (I/O) thread: connection state and last error\nSELECT CHANNEL_NAME, SERVICE_STATE, LAST_ERROR_NUMBER,\n       LEFT(LAST_ERROR_MESSAGE, 100) AS last_error,\n       LAST_QUEUED_TRANSACTION, LAST_HEARTBEAT_TIMESTAMP\nFROM performance_schema.replication_connection_status;\n\n-- Applier workers: error, applied GTID and real lag\nSELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE, LAST_ERROR_NUMBER,\n       LEFT(LAST_ERROR_MESSAGE, 120) AS last_error,\n       LAST_APPLIED_TRANSACTION,\n       APPLYING_TRANSACTION,\n       IF(APPLYING_TRANSACTION = '', 0,\n          TIMESTAMPDIFF(SECOND, APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP, NOW(6))) AS current_lag_sec,\n       TIMESTAMPDIFF(MICROSECOND, LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,\n                     LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP) / 1000000 AS last_trx_lag_sec\nFROM performance_schema.replication_applier_status_by_worker\nORDER BY CHANNEL_NAME, WORKER_ID;","what":"Détaille par canal et par worker ce que SHOW REPLICA STATUS résume en une ligne : état du thread receiver, dernière erreur de chaque worker, GTID appliqué / en cours d'application et retard réel calculé à partir de l'horodatage de commit d'origine.","when":"Lorsque Seconds_Behind_Source semble peu fiable (oscille entre 0 et de grandes valeurs, NULL), pour trouver quel worker est bloqué sur une réplique multi-thread, et pour alimenter la supervision avec une métrique de retard exacte.","caution":"current_lag_sec n'a de sens que pendant l'application d'une transaction ; une réplique inactive renvoie 0. Pour savoir si le receiver est aussi en retard, comparez LAST_QUEUED_TRANSACTION avec le gtid_executed de la source. L'horodatage de commit d'origine provient de l'horloge de la source ; un décalage d'horloge entre serveurs (NTP) fausse le résultat. Seconds_Behind_Source reste proche de zéro au début d'une grosse transaction et bondit à la fin ; les transactions longues rendent cette métrique trompeuse.","note":"Les colonnes ORIGINAL_COMMIT_TIMESTAMP / END_APPLY_TIMESTAMP sont arrivées en 8.0.2 et couvrent toute la chaîne (source → intermédiaire → réplique) ; 5.7 n'a que Seconds_Behind_Master.","since":"MySQL 8.0","tags":["replication","lag","applier","worker","performance_schema","delay"]},{"id":"replica-parallel-applier","task":"Régler la réplique multi-thread (replica_parallel_workers)","category":"Réplication","command":"-- Replica: stop the applier, set worker count and commit order\nSTOP REPLICA SQL_THREAD;\nSET PERSIST replica_parallel_workers = 8;\nSET PERSIST replica_preserve_commit_order = ON;\nSTART REPLICA SQL_THREAD;\nSELECT COUNT(*) AS workers FROM performance_schema.replication_applier_status_by_worker;\n\n-- Source (8.0 only): switch dependency tracking to WRITESET (default in 8.4, variable removed)\nSET PERSIST binlog_transaction_dependency_tracking = WRITESET;","what":"Fixe le nombre de workers qui appliquent les transactions en parallèle sur la réplique ; replica_preserve_commit_order conserve un ordre de commit identique à celui de la source. Le suivi des dépendances WRITESET sur la source permet d'appliquer en parallèle des transactions touchant des lignes différentes, même si elles ont été validées en série sur la source.","when":"Lorsque la réplique ne suit pas le débit d'écriture de la source et que le retard s'accumule alors que l'applier fonctionne en pratique sur un seul thread.","caution":"Un nouveau nombre de workers prend effet au redémarrage de l'applier. Utiliser plus de workers que de cœurs CPU ajoute une surcharge de coordination ; commencez dans la plage 8–16 et surveillez le retard et les erreurs des workers. Une seule transaction énorme ou des DML lourds sur une table sans PK ne peuvent pas être parallélisés, ce qui est généralement la vraie cause d'un retard persistant. replica_preserve_commit_order=OFF donne plus de parallélisme mais autorise de brefs commits hors ordre sur la réplique ; laissez-le à ON sur les répliques qui servent des lectures.","note":"Depuis 8.0.27, les valeurs par défaut sont replica_parallel_workers=4, replica_parallel_type=LOGICAL_CLOCK et replica_preserve_commit_order=ON. Avant 8.0.26, les variables s'appelaient slave_parallel_workers / slave_preserve_commit_order. binlog_transaction_dependency_tracking a été déprécié en 8.0.35 et supprimé en 8.4, où le comportement WRITESET est celui par défaut.","since":"MySQL 8.0.26","tags":["replication","parallel","replica_parallel_workers","writeset","lag","mta"]},{"id":"replica-fix-row-conflict","task":"Réparer une réplique en échec : conflits de lignes 1062 / 1032","category":"Réplication","command":"-- 1) Failing worker, error and GTID\nSELECT WORKER_ID, LAST_ERROR_NUMBER, LEFT(LAST_ERROR_MESSAGE, 250) AS last_error, APPLYING_TRANSACTION\nFROM performance_schema.replication_applier_status_by_worker\nWHERE LAST_ERROR_NUMBER <> 0;\n\n-- 2) Shell: decode the row images of that transaction from the relay log\nmysqlbinlog --base64-output=DECODE-ROWS -vv --include-gtids='af6bc361-c353-11f1-8f12-f69e63ad6c0e:11' /var/lib/mysql/db2-relay-bin.0* | grep '^###'\n\n-- 3a) 1062 Duplicate entry: delete the extra row on the replica without binlogging\nSET SESSION sql_log_bin = 0;\nDELETE FROM shop.orders WHERE id = 20001;\n-- 3b) 1032 Can't find record: insert the missing row from the WHERE (before image) values\nINSERT INTO shop.orders (id, cust, amount, note, created) VALUES (6, 6, 9.00, 'n6', '2026-10-08 20:09:44');\nSET SESSION sql_log_bin = 1;\n\n-- 4) Start the applier and verify\nSTART REPLICA SQL_THREAD;\nSELECT SERVICE_STATE, LAST_ERROR_NUMBER FROM performance_schema.replication_applier_status_by_coordinator;","what":"Corrige les deux ruptures de réplication row-based les plus courantes (1062 clé dupliquée, 1032 ligne introuvable) sans sauter la transaction, en ramenant la ligne de la réplique à l'état attendu par la source ; la cohérence des données est préservée.","when":"Lorsque Replica_SQL_Running = No avec l'erreur 1062 / 1032 ; comme réparation propre au lieu de sauter le GTID avec une transaction vide.","caution":"La correction sur la réplique doit s'exécuter avec sql_log_bin=0 ; sinon elle crée un errant GTID qui pose problème au failover. Si super_read_only=ON, désactivez-le temporairement puis réactivez-le. Les valeurs before-image supposent binlog_row_image=FULL ; avec MINIMAL, seule la PK est journalisée et le reste de la ligne doit être lu sur la source. Un conflit dans une table signifie généralement qu'il y en a d'autres ; après la réparation, comparez la table avec la source à l'aide de pt-table-checksum. Chemin du relay log : SELECT @@relay_log_basename;","note":"replica_exec_mode=IDEMPOTENT absorbe silencieusement ces erreurs ; il est conçu pour NDB et des cas particuliers et ne doit pas rester activé en permanence. Si la transaction doit vraiment être sautée, voir la procédure skip-gtid-transaction.","since":"MySQL 8.0.22","tags":["replication","1062","1032","mysqlbinlog","relay log","broken replica"]},{"id":"gtid-errant-transactions","task":"Trouver et neutraliser les transactions errant GTID","category":"Réplication","command":"-- 1) On the source: get gtid_executed\nSELECT @@GLOBAL.gtid_executed;\n\n-- 2) On the replica: GTIDs missing on the source (empty result = clean)\nSELECT GTID_SUBTRACT(@@GLOBAL.gtid_executed,\n       'af6bc361-c353-11f1-8f12-f69e63ad6c0e:1-13') AS errant_gtids;\n\n-- 3) If the content is harmless: commit an empty transaction per errant GTID on the source\nSET GTID_NEXT = 'b030e5a6-c353-11f1-a887-523817ec642a:1'; BEGIN; COMMIT;\nSET GTID_NEXT = 'b030e5a6-c353-11f1-a887-523817ec642a:2'; BEGIN; COMMIT;\nSET GTID_NEXT = 'AUTOMATIC';","what":"Détecte par différence d'ensembles GTID les transactions exécutées directement sur une réplique sans équivalent sur la source (errant), et aligne les ensembles GTID de la topologie en les marquant par des transactions vides sur la source.","when":"Comme vérification de routine avant failover / switchover ; lorsque Orchestrator ou MySQL Shell refuse de promouvoir une réplique à cause d'errant GTIDs, ou qu'une réplique échoue avec 1236 en se rattachant à une nouvelle source.","caution":"Déterminez d'abord ce qu'a fait la transaction errant à partir du binlog de la réplique (mysqlbinlog --include-gtids=...). Si elle a modifié des données, la transaction vide ne fait que masquer le GTID et l'écart de données subsiste ; reconstruisez la réplique (CLONE) ou alignez les données à la main. Les transactions vides validées sur la source se propagent à toutes les répliques ; c'est voulu. Gardez super_read_only=ON sur les répliques pour éviter que cela se reproduise.","note":"Une fois la réplique promue, les autres répliques lui demandent ces GTID ; si ce binlog a été purgé, la réplication casse avec l'erreur 1236. C'est pourquoi les errant GTIDs sont un risque qui reste invisible jusqu'au failover.","since":"MySQL 5.6","tags":["gtid","errant","gtid_subtract","failover","replication","empty transaction"]},{"id":"replica-rebuild-clone","task":"Reconstruire une réplique de zéro avec le plugin CLONE","category":"Réplication","command":"-- Donor (healthy replica or source)\nINSTALL PLUGIN clone SONAME 'mysql_clone.so';\nCREATE USER 'clone_donor'@'%' IDENTIFIED BY 'Cl0ne#Donor';\nGRANT BACKUP_ADMIN ON *.* TO 'clone_donor'@'%';\n\n-- Recipient (replica to rebuild; its data is replaced)\nINSTALL PLUGIN clone SONAME 'mysql_clone.so';\nSET GLOBAL clone_valid_donor_list = 'db1:3306';\nCLONE INSTANCE FROM 'clone_donor'@'db1':3306 IDENTIFIED BY 'Cl0ne#Donor';\n\n-- After the restart: verify and start replication\nSELECT STATE, ERROR_NO, BINLOG_FILE, BINLOG_POSITION, GTID_EXECUTED FROM performance_schema.clone_status;\nSELECT STAGE, STATE, ROUND(DATA / 1024 / 1024) AS data_mb FROM performance_schema.clone_progress;\nCHANGE REPLICATION SOURCE TO SOURCE_HOST = 'db1', SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'Repl#2024', SOURCE_AUTO_POSITION = 1, SOURCE_SSL = 1;\nSTART REPLICA;","what":"Copie physiquement les données InnoDB du donneur par le réseau, redémarre le receveur et reporte l'état GTID ; produit une réplique cohérente sans étapes séparées de sauvegarde, transfert et restauration.","when":"Pour reconstruire une réplique corrompue, une réplique avec des errant GTIDs ou si en retard que ses binlogs ont été purgés, ou pour ajouter une nouvelle réplique.","caution":"Toutes les données du receveur (utilisateurs compris) sont remplacées par la copie du donneur ; le mot de passe root devient celui du donneur. Donneur et receveur doivent avoir la même série de versions, la même plateforme et le même ensemble de plugins (avant 8.0.37, exactement la même version corrective était requise). L'utilisateur du receveur qui exécute la commande a besoin de CLONE_ADMIN. À la fin du clonage, mysqld redémarre de lui-même ; sans superviseur comme systemd / mysqld_safe, il renvoie 3707 'Restart server failed (mysqld is not managed by supervisor process)'. Ce n'est pas un échec : le clonage est terminé, démarrez le serveur manuellement. Le donneur subit une forte charge d'E/S / réseau ; plafonnez-la avec clone_max_concurrency et clone_max_data_bandwidth. Avec GTID désactivé, utilisez BINLOG_FILE / BINLOG_POSITION de clone_status comme SOURCE_LOG_FILE / SOURCE_LOG_POS.","note":"Seules les données InnoDB sont clonées ; les tables MyISAM et CSV arrivent vides. Dans InnoDB Cluster, addInstance(..., {recoveryMethod: 'clone'}) utilise le même mécanisme.","since":"MySQL 8.0.17","tags":["clone","rebuild","replica","provisioning","gtid","reseed"]},{"id":"delayed-replica-recover","task":"Se remettre d'un DROP / DELETE erroné avec une réplique différée (SOURCE_DELAY + UNTIL)","category":"Réplication","command":"-- Setup: the replica applies events one hour behind\nSTOP REPLICA;\nCHANGE REPLICATION SOURCE TO SOURCE_DELAY = 3600;\nSTART REPLICA;\n\n-- During the incident: stop the applier (the receiver keeps fetching binlogs)\nSTOP REPLICA SQL_THREAD;\n\n-- Find the bad transaction's GTID in the source binlog, apply up to just before it\nCHANGE REPLICATION SOURCE TO SOURCE_DELAY = 0;\nSTART REPLICA SQL_THREAD UNTIL SQL_BEFORE_GTIDS = 'af6bc361-c353-11f1-8f12-f69e63ad6c0e:17';\nSELECT SERVICE_STATE FROM performance_schema.replication_applier_status_by_coordinator;","what":"Fait appliquer les événements par la réplique avec un retard fixe ; lorsqu'un DROP / DELETE erroné touche la source, vous faites avancer la réplique jusqu'à juste avant cette transaction et y récupérez les données perdues.","when":"Comme couche de récupération rapide contre l'erreur humaine sur les grosses bases où une restauration depuis sauvegarde prendrait des heures.","caution":"Lorsque le coordinator est OFF, les données sont dans l'état juste avant l'opération erronée ; exportez la table concernée avec mysqldump et chargez-la dans la source. Ensuite, remettez la réplique en mode différé ; si elle applique aussi la transaction erronée, les données sont perdues ici aussi. Une réplique différée ne doit pas être candidate au failover (excluez-la dans Orchestrator / Router / ProxySQL). Les relay logs s'accumulent pendant tout le délai ; dimensionnez le disque en conséquence.","note":"Avant 8.0.23, utilisez CHANGE MASTER TO MASTER_DELAY. Le délai est mesuré à partir du commit sur la source immédiate, donc des répliques intermédiaires différées dans une chaîne se cumulent. SQL_BEFORE_GTIDS s'arrête avant le GTID indiqué, SQL_AFTER_GTIDS l'applique aussi ; avant 8.2 (8.0 compris), utiliser SQL_AFTER_GTIDS avec un applier multi-thread émet un avertissement et la réplique passe en mode mono-thread.","since":"MySQL 8.0.23","tags":["delayed replica","source_delay","until","sql_before_gtids","recovery","drop table"]},{"id":"mysqldump-consistent","task":"Effectuer une sauvegarde logique cohérente avec les informations GTID via mysqldump","category":"Sauvegarde et PITR","command":"mysqldump -h db1 -u backup -p \\\n  --single-transaction --source-data=2 --set-gtid-purged=ON \\\n  --routines --events --triggers --hex-blob \\\n  --all-databases | gzip > /backup/full_$(date +%F_%H%M).sql.gz\n\n# Restore: GTID sets must not overlap on the target\nzcat /backup/full_2026-10-08_0200.sql.gz | mysql -h db2 -u root -p","what":"Exporte les tables InnoDB dans un snapshot cohérent unique et écrit en tête le fichier / la position binlog (en commentaire) et une instruction SET @@GLOBAL.GTID_PURGED, afin que le dump puisse initialiser une réplique ou servir de point de départ à un PITR basé sur les binlogs.","when":"Pour les bases jusqu'à quelques dizaines de GB, pour déplacer des schémas / tables, et pour une migration logique entre versions.","caution":"--single-transaction n'est cohérent que pour InnoDB ; les tables MyISAM sortent incohérentes. Un DDL (ALTER / RENAME / TRUNCATE) sur une table pendant le dump fait échouer le dump ou omettre la table. --source-data / --set-gtid-purged prennent un bref verrou global en lecture au début (privilège RELOAD requis) ; si une requête longue s'exécute à ce moment, toutes les écritures sont bloquées pendant l'attente du verrou. Un dump partiel (--databases) écrit quand même tous les GTID ; utilisez --set-gtid-purged=OFF pour déplacer un sous-ensemble vers un autre serveur. Le dump est mono-thread ; pour de gros volumes, préférez le dump de MySQL Shell ou XtraBackup.","note":"--source-data a remplacé --master-data en 8.0.26. mysqlpump a été déprécié en 8.0.34 et supprimé en 8.4 ; utilisez util.dumpInstance de MySQL Shell pour les sauvegardes logiques parallèles.","since":"MySQL 8.0.26","tags":["mysqldump","backup","single-transaction","gtid","logical backup","dump"]},{"id":"mysqlsh-dump-load","task":"Dump / chargement parallèle avec MySQL Shell (util.dumpInstance, loadDump)","category":"Sauvegarde et PITR","command":"# Parallel, zstd-compressed, consistent instance dump\nmysqlsh backup@db1:3306 -- util dump-instance /backup/dump_20261008 --threads=8 --compression=zstd --consistent=true\n# Single schema\nmysqlsh backup@db1:3306 -- util dump-schemas shop --outputUrl=/backup/shop_20261008 --threads=8\n\n# Load: local_infile must be temporarily ON on the target\nmysql -h db2 -u root -p -e \"SET GLOBAL local_infile = ON\"\nmysqlsh root@db2:3306 -- util load-dump /backup/dump_20261008 --threads=8 --skipBinlog=false\nmysql -h db2 -u root -p -e \"SET GLOBAL local_infile = OFF\"","what":"Découpe les tables en chunks et les exporte / charge avec de nombreux threads ; dump et restauration sont plusieurs fois plus rapides que mysqldump. Le répertoire de dump stocke les coordonnées GTID et binlog dans @.json.","when":"Pour les sauvegardes logiques de centaines de GB, la migration logique lors des mises à niveau, et les passages on-premise → cloud (RDS, HeatWave).","caution":"--consistent=true prend un bref verrou global en lecture / backup lock ; les écritures attendent si une requête longue est en cours. loadDump utilise LOAD DATA LOCAL, donc la cible a besoin de local_infile=ON, qu'il faut désactiver ensuite. Un chargement interrompu reprend là où il s'est arrêté (fichier de progression) ; utilisez --resetProgress pour repartir de zéro. Les comptes utilisateurs ne sont pas chargés par défaut (--loadUsers=true). --skipBinlog=true désactive la journalisation binlog pendant le chargement ; laissez-le à false si la cible a des répliques.","note":"Ce n'est pas une sauvegarde physique ; pour un PITR, appliquez les binlogs à partir de la valeur gtidExecuted du @.json du dump. Les options ocimds / compatibility produisent des dumps qui respectent les restrictions de HeatWave / RDS.","since":"MySQL Shell 8.0.21","tags":["mysqlsh","dumpinstance","loaddump","parallel","logical backup","migration"]},{"id":"xtrabackup-full-incremental","task":"Sauvegarde physique full + incrémentale et restauration avec XtraBackup","category":"Sauvegarde et PITR","command":"# Full backup\nxtrabackup --backup --host=127.0.0.1 --user=bkp --password='Bkp#2024' --target-dir=/backup/full --parallel=4\n# Incremental (based on the previous backup)\nxtrabackup --backup --host=127.0.0.1 --user=bkp --password='Bkp#2024' --target-dir=/backup/inc1 --incremental-basedir=/backup/full\n\n# Prepare: full and intermediate incrementals with --apply-log-only, final step without it\nxtrabackup --prepare --apply-log-only --target-dir=/backup/full\nxtrabackup --prepare --apply-log-only --target-dir=/backup/full --incremental-dir=/backup/inc1\nxtrabackup --prepare --target-dir=/backup/full\n\n# Restore: mysqld stopped and datadir empty\nsystemctl stop mysqld\nxtrabackup --copy-back --target-dir=/backup/full --datadir=/var/lib/mysql\nchown -R mysql:mysql /var/lib/mysql\nsystemctl start mysqld\ncat /backup/full/xtrabackup_binlog_info","what":"Effectue une sauvegarde physique à chaud en copiant les fichiers InnoDB et le redo log pendant que le serveur tourne ; une sauvegarde incrémentale ne contient que les pages modifiées depuis la sauvegarde précédente. L'étape prepare applique le redo et rend la sauvegarde cohérente.","when":"Pour les grosses bases nécessitant des restaurations rapides (plusieurs fois plus rapides qu'en logique), pour initialiser des répliques, et pour les stratégies full quotidienne + incrémentale horaire.","caution":"La version de XtraBackup doit correspondre à la série de versions du serveur (serveur 8.0 → XtraBackup 8.0, 8.4 → 8.4) ; si le serveur utilise un format de redo / data dictionary plus récent que le build de XtraBackup, la sauvegarde est refusée. Une sauvegarde non préparée est incohérente. Oublier --apply-log-only aux étapes intermédiaires rend les incrémentales suivantes inutilisables. Le datadir doit être vide pour la restauration ; --move-back économise du disque mais consomme la sauvegarde. Utilisateur de sauvegarde : BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT, et SELECT sur performance_schema.log_status, keyring_component_status, replication_group_members.","note":"xtrabackup_binlog_info contient le fichier / la position binlog et l'ensemble GTID de la sauvegarde ; le PITR rejoue les binlogs à partir de ce point. Pour envoyer en flux vers un autre hôte, utilisez --stream=xbstream | ssh host 'xbstream -x -C /backup'.","since":"XtraBackup 8.0","tags":["xtrabackup","physical backup","incremental","prepare","restore","percona"]},{"id":"pitr-binlog-replay","task":"Restauration à un instant donné avec les binlogs : trouver et sauter la transaction erronée","category":"Sauvegarde et PITR","command":"# 1) Find the bad event's GTID and position (narrow by time range)\nmysqlbinlog --base64-output=DECODE-ROWS -vv --start-datetime='2026-10-08 20:00:00' --stop-datetime='2026-10-08 20:30:00' /var/lib/mysql/binlog.000003 | grep -B12 -iE '^(DROP|TRUNCATE)' | grep -E 'GTID_NEXT|^# at|^#[0-9]{6}|^DROP|^TRUNCATE'\n\n# 2) Restore the backup on a separate server (the dump sets GTID_PURGED)\nzcat /backup/full_2026-10-08_0200.sql.gz | mysql -h restore-db -u root -p\n\n# 3a) Replay all binlogs skipping only the bad transaction\nmysqlbinlog --exclude-gtids='af6bc361-c353-11f1-8f12-f69e63ad6c0e:23' /binlogs/binlog.000003 /binlogs/binlog.000004 | mysql -h restore-db -u root -p\n# 3b) Or stop right before the bad event's GTID event\nmysqlbinlog --stop-position=537205 /binlogs/binlog.000003 | mysql -h restore-db -u root -p","what":"Après la restauration de la sauvegarde, rejoue les binlogs dans l'ordre pour amener la base au point voulu. Avec GTID activé, les transactions déjà présentes dans la sauvegarde sont sautées automatiquement ; --exclude-gtids retire uniquement l'opération erronée et conserve tous les changements qui la suivent.","when":"Après un DROP / DELETE / UPDATE erroné, pour récupérer sans perdre les changements effectués entre la sauvegarde et l'incident.","caution":"Restaurez sur un serveur séparé, pas en production ; vérifiez, puis ne déplacez que la table concernée. --start/--stop-datetime sont interprétés dans le fuseau horaire de la machine qui exécute mysqlbinlog. --stop-position ne s'applique qu'au dernier fichier indiqué ; pour plusieurs fichiers, rejouez entièrement les fichiers précédents dans une commande séparée. --exclude-gtids n'est sûr que pour une opération indépendante ; si des transactions ultérieures écrivent dans la table supprimée, elles échouent aussi. Les binlogs nécessaires ne doivent pas être purgés sur la source ; gardez une rétention des binlogs supérieure à au moins un intervalle de sauvegarde.","note":"Exécutez mysqlbinlog et mysql dans un seul pipe avec tous les fichiers dans une seule commande ; rejouer les fichiers un par un casse les tables temporaires et les transactions qui s'étendent sur plusieurs fichiers. Pour de très gros ensembles de binlogs, une alternative consiste à placer les binlogs comme relay logs sur le serveur de restauration et à les appliquer avec le thread SQL (parallèle).","since":"MySQL 8.0","tags":["pitr","mysqlbinlog","binlog","point in time","exclude-gtids","recovery"]},{"id":"innodb-cluster-status","task":"Vérifier la santé d'InnoDB Cluster / Group Replication","category":"InnoDB Cluster","command":"# Shell: topology, primary, fault tolerance, lag\nmysqlsh icadmin@db1:3306 -- cluster status --extended=1\nmysqlsh icadmin@db1:3306 -- cluster list-routers\n\n-- SQL: group members and queues from any member\nSELECT m.MEMBER_HOST, m.MEMBER_PORT, m.MEMBER_STATE, m.MEMBER_ROLE, m.MEMBER_VERSION,\n       s.COUNT_TRANSACTIONS_IN_QUEUE AS certify_queue,\n       s.COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE AS apply_queue,\n       s.COUNT_CONFLICTS_DETECTED AS conflicts\nFROM performance_schema.replication_group_members m\nJOIN performance_schema.replication_group_member_stats s ON s.MEMBER_ID = m.MEMBER_ID\nORDER BY m.MEMBER_ROLE, m.MEMBER_HOST;","what":"Affiche l'état global du cluster (OK / OK_NO_TOLERANCE / NO_QUORUM), la primaire, l'état de chaque membre et les files de certification / d'apply ; les files révèlent quel membre est en retard et le risque de flow control.","when":"Avant et après chaque intervention ; sur alerte, ralentissement des écritures, ou lorsqu'un membre affiche RECOVERING / ERROR / UNREACHABLE.","caution":"statusText doit indiquer 'can tolerate up to ONE failure' (3 membres). Avec NO_QUORUM, les écritures s'arrêtent ; n'utilisez forceQuorumUsingPartitionOf que si vous êtes certain que l'autre partition est arrêtée, sinon vous obtenez un split-brain. Un membre dont l'apply_queue ne cesse de croître déclenche le flow control et ralentit les écritures sur la primaire. Un RECOVERING prolongé signifie qu'une récupération clone / incrémentale est en cours ; pour ERROR, consultez l'error log et essayez cluster.rejoinInstance(). Les caractères spéciaux des mots de passe (#, @) doivent être encodés en URL dans l'URI (# → %23).","note":"MEMBER_COMMUNICATION_STACK est arrivé en 8.0.27 ; la pile MYSQL fait passer le trafic GR par 3306 avec le TLS et l'authentification utilisateur habituels (XCOM utilise un port séparé, généralement 33061). En 8.4, group_replication_consistency vaut par défaut BEFORE_ON_PRIMARY_FAILOVER et exit_state_action vaut par défaut OFFLINE_MODE.","since":"MySQL 8.0","tags":["innodb cluster","group replication","mysqlsh","status","flow control","quorum"]},{"id":"innodb-cluster-switch-primary","task":"Switchover planifié de la primaire dans InnoDB Cluster","category":"InnoDB Cluster","command":"# Shell: planned switchover\nmysqlsh icadmin@db1:3306 -- cluster set-primary-instance db2:3306\n\n-- SQL alternative: target member's server_uuid, 30 s timeout for open transactions\nSELECT MEMBER_HOST, MEMBER_ID, MEMBER_ROLE FROM performance_schema.replication_group_members;\nSELECT group_replication_set_as_primary('3f06e651-c356-11f1-8aac-927a9034ef99', 30);","what":"Déplace de façon contrôlée le rôle de primaire vers un autre membre en mode single-primary ; l'ancienne primaire passe en super_read_only, la nouvelle applique son arriéré et s'ouvre aux écritures. MySQL Router apprend automatiquement la nouvelle primaire.","when":"Avant une maintenance, un patch OS ou une mise à niveau de la primaire, et pour des déplacements matériels.","caution":"Les transactions ouvertes sur l'ancienne primaire sont tuées à l'expiration du timeout ; sans timeout, la bascule les attend et dure plus longtemps. Si l'application ne se connecte pas via Router, vous devez la rediriger vous-même. Avec BEFORE_ON_PRIMARY_FAILOVER, les écritures attendent que la nouvelle primaire ait résorbé son arriéré ; vérifiez d'abord dans cluster status que l'apply_queue est vide. Dans InnoDB Cluster, préférez la commande Shell ; elle reste cohérente avec les métadonnées.","note":"group_replication_set_as_primary est arrivé en 8.0.13 ; le paramètre timeout a été ajouté en 8.0.29. Lors d'un failover non planifié, GR élit lui-même la nouvelle primaire (group_replication_member_weight définit la préférence).","since":"MySQL 8.0.13","tags":["innodb cluster","switchover","primary","group replication","set_as_primary","maintenance"]},{"id":"innodb-cluster-reboot-outage","task":"Redémarrer le cluster après une panne complète (rebootClusterFromCompleteOutage)","category":"InnoDB Cluster","command":"# Are all members OFFLINE? (on every node)\nmysql -h db1 -u icadmin -p -e \"SELECT MEMBER_HOST, MEMBER_STATE FROM performance_schema.replication_group_members\"\n# Dry run first: shows the most up-to-date member and the planned steps\nmysqlsh icadmin@db1:3306 --js -e \"dba.rebootClusterFromCompleteOutage('prodCluster', {dryRun: true})\"\nmysqlsh icadmin@db1:3306 --js -e \"dba.rebootClusterFromCompleteOutage('prodCluster')\"\nmysqlsh icadmin@db1:3306 -- cluster status","what":"Réamorce le groupe après une coupure de courant ou un redémarrage simultané de tous les nœuds, lorsqu'aucun quorum n'existe ; choisit le membre ayant l'ensemble GTID le plus élevé et y rattache les autres.","when":"Lorsque cluster status signale 'Cluster is not reachable' et que tous les membres sont OFFLINE.","caution":"À n'utiliser que lorsque tous les membres sont OFFLINE ; si un membre est encore ONLINE, le groupe est vivant et les autres le rejoignent avec rejoinInstance. Démarrez tous les nœuds avant de l'exécuter : si un membre injoignable est plus à jour, ses transactions sont perdues. force: true ignore les membres manquants ; c'est un dernier recours. L'option primary: 'host:port' impose un membre précis comme primaire.","note":"Les options dryRun, primary et force sont arrivées dans Shell 8.0.31 ; les versions antérieures fonctionnaient par invites interactives. Même avec group_replication_start_on_boot=ON, le groupe ne se reforme pas tout seul après une panne complète ; l'amorçage doit être fait délibérément depuis un seul endroit.","since":"MySQL Shell 8.0.31","tags":["innodb cluster","outage","reboot","bootstrap","mysqlsh","disaster"]},{"id":"mysql-router-bootstrap","task":"Amorcer MySQL Router sur un InnoDB Cluster (bootstrap)","category":"Router et proxy","command":"# Generates config from metadata and creates the router account; --user is required when run as root\nmysqlrouter --bootstrap icadmin@db1:3306 --directory /etc/mysqlrouter/prod --account routeruser --account-create if-not-exists --name router01 --conf-bind-address 0.0.0.0 --user mysqlrouter\n/etc/mysqlrouter/prod/start.sh\n# Ports: 6446 RW, 6447 RO, 6450 RW split (classic); 6448 / 6449 X protocol\nmysql -h app-host -P 6446 -u app -p -e \"SELECT @@hostname\"\nmysql -h app-host -P 6447 -u app -p -e \"SELECT @@hostname\"\nmysqlsh icadmin@db1:3306 -- cluster list-routers","what":"Enregistre Router dans les métadonnées du cluster et génère le compte et la configuration ; l'application atteint la primaire courante via le port RW et les secondaires en round-robin via le port RO.","when":"Pour placer une couche de failover transparente devant un InnoDB Cluster / ClusterSet / ReplicaSet ; typiquement un Router par serveur d'application.","caution":"Router est sans état ; le faire tourner à côté de l'application évite un point de défaillance unique, ce que ne fait pas un Router central unique. Le compte de bootstrap n'est utilisé que pendant le bootstrap ; Router lit les métadonnées avec son propre compte (routeruser). Il apprend les changements de topologie via le metadata cache sans nouveau bootstrap. Le port de séparation RW (6450) envoie les lectures à l'intérieur d'une transaction vers la primaire et les autres vers les secondaires ; attention aux applications qui attendent le read-your-writes. La version de Router ne doit pas être plus ancienne que celle des serveurs MySQL du cluster.","note":"Le port de séparation lecture/écriture (6450) est arrivé en 8.2. Les connexions existantes sont coupées lors d'un failover ; l'application doit disposer d'une logique de reconnexion (retry).","since":"MySQL Router 8.0","tags":["mysql router","bootstrap","innodb cluster","failover","read write split","6446"]},{"id":"proxysql-rw-split","task":"Séparation lecture/écriture et contrôles de santé des répliques avec ProxySQL","category":"Router et proxy","command":"-- On MySQL (source): monitor user\nCREATE USER 'proxysql_mon'@'%' IDENTIFIED BY 'M0n#itor2024';\nGRANT USAGE, REPLICATION CLIENT ON *.* TO 'proxysql_mon'@'%';\n\n-- ProxySQL admin: mysql -u admin -p -h 127.0.0.1 -P 6032\nUPDATE global_variables SET variable_value = 'proxysql_mon' WHERE variable_name = 'mysql-monitor_username';\nUPDATE global_variables SET variable_value = 'M0n#itor2024' WHERE variable_name = 'mysql-monitor_password';\nLOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK;\nINSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup, check_type, comment) VALUES (10, 20, 'read_only', 'shop');\nINSERT INTO mysql_servers (hostgroup_id, hostname, port, max_replication_lag) VALUES (10, 'db1', 3306, 0), (20, 'db2', 3306, 30), (20, 'db3', 3306, 30);\nLOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;\nINSERT INTO mysql_users (username, password, default_hostgroup, transaction_persistent) VALUES ('app', 'App#Pass2024', 10, 1);\nLOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;\nINSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup, apply) VALUES (100, 1, '^SELECT .* FOR (UPDATE|SHARE)', 10, 1), (200, 1, '^SELECT ', 20, 1);\nLOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;\n\n-- Verify\nSELECT hostgroup_id, hostname, status FROM runtime_mysql_servers;\nSELECT hostgroup, srv_host, status, ConnUsed, Queries, Latency_us FROM stats_mysql_connection_pool;\nSELECT hostgroup, substr(digest_text, 1, 60) AS digest, count_star, sum_time / 1000 AS sum_ms FROM stats_mysql_query_digest ORDER BY sum_time DESC LIMIT 10;","what":"Gère automatiquement les hostgroups writer (10) et reader (20) en fonction du flag read_only ; route les SELECT vers les répliques et les lectures verrouillantes et écritures vers la source ; retire temporairement une réplique qui dépasse max_replication_lag (SHUNNED).","when":"Pour répartir la charge de lecture dans une topologie de réplication asynchrone sans toucher à l'application ; pour le pooling de connexions et le routage / cache / pare-feu par requête.","caution":"La configuration a 3 couches (MEMORY → RUNTIME → DISK) : sans LOAD ... TO RUNTIME rien ne change, sans SAVE ... TO DISK tout est perdu au redémarrage. Le contrôle read_only exige super_read_only=ON sur les répliques ; sinon les répliques sont déplacées dans le hostgroup writer. Envoyer tous les SELECT vers les répliques provoque des erreurs de lecture après écriture dues au retard ; écrire des règles uniquement pour les requêtes lourdes / de reporting à partir de stats_mysql_query_digest est plus sûr. L'interface d'administration est SQLite : les fonctions MySQL comme LEFT() n'existent pas, utilisez substr(). Changez le compte par défaut admin:admin (admin-admin_credentials).","note":"Le contrôle max_replication_lag exige REPLICATION CLIENT pour l'utilisateur de monitoring. Pour Group Replication / InnoDB Cluster, utilisez la table mysql_group_replication_hostgroups.","since":"ProxySQL 2.0","tags":["proxysql","read write split","hostgroup","query rules","connection pool","replication lag"]},{"id":"online-ddl-instant-inplace","task":"DDL en ligne : ALGORITHM=INSTANT / INPLACE, LOCK=NONE et suivi de progression","category":"Opérations en ligne","command":"-- Always state ALGORITHM/LOCK: unsupported operations fail immediately instead of silently falling back to COPY\nALTER TABLE shop.orders ADD COLUMN status TINYINT NOT NULL DEFAULT 0, ALGORITHM=INSTANT;\nALTER TABLE shop.orders RENAME COLUMN note TO remark, ALGORITHM=INSTANT;\nALTER TABLE shop.orders ADD INDEX ix_status (status), ALGORITHM=INPLACE, LOCK=NONE;\nALTER TABLE shop.orders MODIFY remark VARCHAR(200), ALGORITHM=INPLACE, LOCK=NONE;\n\n-- INSTANT row version counter (INSTANT is refused at 64, a rebuild resets it)\nSELECT NAME, TOTAL_ROW_VERSIONS FROM information_schema.INNODB_TABLES WHERE NAME = 'shop/orders';\n\n-- Progress of a long ALTER (enable the instruments once)\nUPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'stage/innodb/alter%';\nUPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE 'events_stages_%';\nSELECT t.PROCESSLIST_ID, s.EVENT_NAME, s.WORK_COMPLETED, s.WORK_ESTIMATED,\n       ROUND(100 * s.WORK_COMPLETED / NULLIF(s.WORK_ESTIMATED, 0), 1) AS pct_done\nFROM performance_schema.events_stages_current s\nJOIN performance_schema.threads t ON t.THREAD_ID = s.THREAD_ID\nWHERE s.EVENT_NAME LIKE 'stage/innodb/alter%';","what":"INSTANT ne modifie que le data dictionary et se termine immédiatement quelle que soit la taille de la table. INPLACE + LOCK=NONE traite la table sur place tout en autorisant les lectures et écritures concurrentes. La dernière requête montre à quelle étape se trouve un ALTER en cours et son pourcentage.","when":"Avant des changements de schéma sur des tables de production ; pour savoir quelles opérations peuvent s'exécuter sans interruption et estimer quand un long ALTER se terminera.","caution":"INSTANT et INPLACE prennent quand même un bref metadata lock exclusif au début et à la fin ; si une transaction longue détient la table, l'ALTER attend et toutes les requêtes qui le suivent s'accumulent (voir le diagnostic des metadata locks). Abaissez lock_wait_timeout dans la session (SET SESSION lock_wait_timeout = 5). Les DML arrivant pendant INPLACE sont journalisés jusqu'à innodb_online_alter_log_max_size ; en cas de dépassement, l'ALTER est annulé à la fin. Les changements de type de colonne, la conversion de charset et les changements de PK nécessitent COPY et bloquent les écritures ; sur les grandes tables, utilisez gh-ost / pt-online-schema-change. Sur une réplique, l'ALTER s'exécute en mono-thread pendant la même durée ; un gros ALTER INPLACE provoque un retard de réplique, INSTANT non.","note":"INSTANT ADD COLUMN est arrivé en 8.0.12 pour la dernière colonne uniquement ; depuis 8.0.29, les colonnes peuvent être ajoutées à n'importe quelle position et INSTANT DROP COLUMN est pris en charge. Allonger un VARCHAR ne touche que les métadonnées lorsque le nombre d'octets de longueur ne change pas (la limite de 255 octets n'est pas franchie) ; cela se termine immédiatement avec INPLACE, pas INSTANT. Une colonne indexée ne peut pas être supprimée avec INSTANT ; supprimez d'abord l'index.","since":"MySQL 8.0.29","tags":["online ddl","instant","inplace","alter table","lock=none","progress"]},{"id":"pt-online-schema-change","task":"ALTER non bloquant sur une grande table avec pt-online-schema-change","category":"Opérations en ligne","command":"# First --dry-run, then the same command with --execute; throttles on replica lag\npt-online-schema-change --alter \"ADD COLUMN region CHAR(2) NULL, ADD INDEX ix_region (region)\" \\\n  D=shop,t=orders,h=db1,u=osc,p='Osc#2024',s=1 \\\n  --max-lag=5 --check-replica-lag h=db2,u=osc,p='Osc#2024',s=1 \\\n  --max-load Threads_running=50 --critical-load Threads_running=200 \\\n  --set-vars lock_wait_timeout=5 --noversion-check --dry-run","what":"Crée une copie vide avec la nouvelle structure, copie les lignes par chunks, reproduit les changements pendant la copie au moyen de triggers et échange finalement les tables par un RENAME atomique. Il ralentit / se met en pause de lui-même lorsque la charge ou le retard des répliques dépasse les seuils.","when":"Pour les ALTER qui nécessitent COPY (type de colonne, charset, changement de PK) ou qui provoqueraient un long retard de réplique.","caution":"La table doit avoir une PK ou une clé unique. Les triggers ajoutent une surcharge à chaque écriture pendant la migration ; sur les tables à très forte écriture, gh-ost a moins d'impact. Si des tables enfants référencent cette table par clés étrangères, --alter-foreign-keys-method=auto est requis. Pour les comptes caching_sha2_password, ajoutez s=1 (TLS) au DSN ; sinon la connexion à la réplique échoue avec 'Authentication requires secure connection'. Le DSN de --check-replica-lag n'hérite pas de l'utilisateur / mot de passe du DSN principal ; indiquez-les. L'échange prend un bref metadata lock ; gardez lock_wait_timeout bas, il réessaie si le verrou ne peut pas être obtenu.","note":"Par défaut, les répliques sont découvertes via la processlist par IP ; si ces IP sont injoignables, utilisez une table DSN avec --recursion-method=dsn=D=percona,t=dsns. En 3.7, --check-slave-lag a été renommé --check-replica-lag (ancien nom déprécié).","since":"Percona Toolkit 3.7","tags":["pt-online-schema-change","pt-osc","percona toolkit","alter","trigger","online schema change"]},{"id":"gh-ost-migration","task":"Changement de schéma en ligne sans trigger avec gh-ost","category":"Opérations en ligne","command":"# Connects to a replica and reads its binlog; postpones cut-over until the flag file is removed\ngh-ost --host=db2 --port=3306 --user=ghost --password='Gh0st#2024' \\\n  --database=shop --table=orders \\\n  --alter=\"ADD COLUMN channel VARCHAR(20) NULL, ADD INDEX ix_channel (channel)\" \\\n  --max-load=Threads_running=50 --critical-load=Threads_running=200 --max-lag-millis=1500 \\\n  --exact-rowcount --concurrent-rowcount --default-retries=120 \\\n  --postpone-cut-over-flag-file=/tmp/ghost.postpone --panic-flag-file=/tmp/ghost.panic \\\n  --serve-socket-file=/tmp/gh-ost.shop.orders.sock --ssl --ssl-allow-insecure --execute\n# While running: status / throttle / cut-over\necho status | nc -U /tmp/gh-ost.shop.orders.sock\necho throttle | nc -U /tmp/gh-ost.shop.orders.sock\nrm /tmp/ghost.postpone","what":"Construit une table fantôme (ghost) et copie les lignes ; applique les changements concurrents en lisant le binlog au lieu d'utiliser des triggers. Le bridage, la pause et le moment du cut-over sont contrôlés à l'exécution via le socket ou des fichiers indicateurs.","when":"Sur les grandes tables à forte écriture où la surcharge des triggers est inacceptable et où vous voulez choisir vous-même le moment du cut-over (par ex. une fenêtre de faible trafic).","caution":"Exige binlog_format=ROW et binlog_row_image=FULL ; la réplique à laquelle il se connecte a besoin de log_replica_updates=ON. L'utilisateur a besoin de SUPER ou de REPLICATION CLIENT / REPLICATION SLAVE plus ALL sur la table. Les tables avec clés étrangères (parent ou enfant) ne sont pas prises en charge. Après le cut-over, l'ancienne table reste sous le nom _<table>_del ; supprimez-la manuellement après vérification (--ok-to-drop-table la supprime automatiquement). Les versions de gh-ost antérieures à 1.1.8 utilisent SHOW SLAVE STATUS et s'arrêtent avec l'erreur 1064 sur MySQL 8.4 ; la prise en charge de 8.4 est arrivée en 1.1.8. gh-ost crée lui-même le fichier indiqué dans --postpone-cut-over-flag-file ; le cut-over n'a pas lieu tant que le fichier n'est pas supprimé.","note":"Les options --test-on-replica et --migrate-on-replica permettent d'essayer et de vérifier d'abord la modification sur un réplica uniquement. Le motif du ralentissement (throttle) et l'ETA apparaissent dans la sortie de la commande 'status'.","tags":["gh-ost","online schema change","binlog","cut-over","alter","triggerless"]},{"id":"ps-top-digests","task":"Trouver les requêtes les plus coûteuses (digest performance_schema)","category":"Performances","command":"SELECT SCHEMA_NAME AS db,\n       LEFT(DIGEST_TEXT, 80) AS query,\n       COUNT_STAR AS calls,\n       ROUND(SUM_TIMER_WAIT / 1e12, 2) AS total_sec,\n       ROUND(AVG_TIMER_WAIT / 1e9, 2) AS avg_ms,\n       ROUND(QUANTILE_95 / 1e9, 2) AS p95_ms,\n       ROUND(100 * SUM_TIMER_WAIT / SUM(SUM_TIMER_WAIT) OVER (), 1) AS pct_total,\n       ROUND(SUM_ROWS_EXAMINED / NULLIF(COUNT_STAR, 0)) AS rows_exam_per_call,\n       ROUND(SUM_ROWS_SENT / NULLIF(COUNT_STAR, 0)) AS rows_sent_per_call,\n       SUM_NO_INDEX_USED AS no_index,\n       SUM_CREATED_TMP_DISK_TABLES AS tmp_disk,\n       LAST_SEEN\nFROM performance_schema.events_statements_summary_by_digest\nWHERE SCHEMA_NAME IS NOT NULL\nORDER BY SUM_TIMER_WAIT DESC\nLIMIT 20;\n\n-- Server-wide top wait events\nSELECT events, total, total_latency, avg_latency FROM sys.waits_global_by_latency LIMIT 10;","what":"Classe les requêtes normalisées par temps total avec le nombre d'appels, la latence moyenne et p95, la part du total, les lignes examinées / envoyées par appel, les exécutions sans index et les tables temporaires écrites sur disque. La deuxième requête montre sur quelles attentes (IO, verrous, redo) le temps est passé.","when":"Premier réflexe quand la charge CPU / IO augmente ou que les utilisateurs signalent que « la base est lente » ; fonctionne même si le slow log est désactivé.","caution":"Les valeurs sont cumulées depuis le dernier redémarrage ou TRUNCATE TABLE performance_schema.events_statements_summary_by_digest ; prenez deux instantanés et calculez la différence pour voir l'activité actuelle. La table est limitée à performance_schema_digests_size lignes ; une fois pleine, les nouvelles requêtes sont agrégées dans une seule ligne avec un DIGEST NULL. DIGEST_TEXT est tronqué à performance_schema_max_digest_length (1024) octets. Les temps sont en picosecondes. Un ratio rows_exam_per_call / rows_sent_per_call élevé est le signe le plus clair d'un index manquant.","note":"Les colonnes QUANTILE_95 / QUANTILE_99 et QUERY_SAMPLE_TEXT sont apparues en 8.0. Les mêmes données en unités lisibles sont disponibles dans les vues sys.statement_analysis et sys.statements_with_full_table_scans.","since":"MySQL 8.0","tags":["performance_schema","digest","top sql","slow query","sys","wait"]},{"id":"sys-session-activity","task":"Ce qui s'exécute en ce moment : sessions actives, latence, verrous et transactions (sys.session)","category":"Performances","command":"SELECT conn_id, user, db, command, state, time,\n       LEFT(current_statement, 100) AS current_statement,\n       statement_latency, progress, lock_latency,\n       rows_examined, full_scan, tmp_disk_tables,\n       trx_state, trx_latency, last_wait\nFROM sys.session\nWHERE command <> 'Sleep' OR trx_state = 'ACTIVE'\nORDER BY time DESC\nLIMIT 25;","what":"Enrichit SHOW PROCESSLIST avec les données de performance_schema : latence de l'instruction en cours, pourcentage de progression, temps d'attente de verrou, lignes examinées, scan complet / table temporaire sur disque et état de la transaction ouverte sur une seule ligne. Liste aussi les sessions en Sleep qui gardent une transaction ouverte.","when":"Dans les 30 premières secondes d'un incident : quelle requête s'exécute depuis combien de temps, qui attend des verrous, quelle session inactive garde une transaction ouverte.","caution":"sys.session effectue plusieurs jointures sur performance_schema ; avec des milliers de connexions, la requête elle-même peut prendre plusieurs secondes, utilisez donc les vues x$ et LIMIT pour une surveillance fréquente. progress n'est renseigné qu'avec les instruments de stage activés et pour les opérations qui rapportent leur progression, comme ALTER. Une session en Sleep avec trx_state=ACTIVE est une transaction oubliée par une application / un pool de connexions utilisant autocommit=0, la cause la plus fréquente des problèmes de metadata lock et de purge.","note":"sys.processlist renvoie les mêmes données en incluant les threads d'arrière-plan. Pour terminer : KILL QUERY conn_id (instruction seule) ou KILL conn_id (connexion, la transaction est annulée).","since":"MySQL 8.0","tags":["sys.session","processlist","active sessions","incident","trx","session"]},{"id":"metadata-lock-diagnose","task":"Diagnostiquer 'Waiting for table metadata lock' et trouver le vrai bloqueur","category":"Performances","command":"-- Waiter -> blocker pairs; filter out the ALTER's own SHARED_UPGRADABLE entry\nSELECT object_schema, object_name, waiting_pid, LEFT(waiting_query, 60) AS waiting_query,\n       waiting_lock_type, blocking_pid, blocking_lock_type, sql_kill_blocking_connection\nFROM sys.schema_table_lock_waits\nWHERE blocking_lock_type <> 'SHARED_UPGRADABLE';\n\n-- All MDLs on the table, owner age and open transaction\nSELECT ml.OBJECT_SCHEMA, ml.OBJECT_NAME, ml.LOCK_TYPE, ml.LOCK_DURATION, ml.LOCK_STATUS,\n       t.PROCESSLIST_ID AS conn_id, t.PROCESSLIST_COMMAND AS command, t.PROCESSLIST_TIME AS sec,\n       LEFT(t.PROCESSLIST_INFO, 60) AS query, trx.trx_started\nFROM performance_schema.metadata_locks ml\nJOIN performance_schema.threads t ON t.THREAD_ID = ml.OWNER_THREAD_ID\nLEFT JOIN information_schema.INNODB_TRX trx ON trx.trx_mysql_thread_id = t.PROCESSLIST_ID\nWHERE ml.OBJECT_TYPE = 'TABLE' AND ml.OBJECT_SCHEMA NOT IN ('performance_schema', 'mysql')\nORDER BY ml.OBJECT_NAME, ml.LOCK_STATUS DESC;","what":"Indique sur le metadata lock de quelle session un DDL comme ALTER / TRUNCATE / RENAME attend, ainsi que les requêtes en file derrière le DDL. Le bloqueur est généralement une session en Sleep qui a lu la table sans jamais fermer sa transaction.","when":"Quand, après le lancement d'un ALTER, toutes les requêtes sur cette table s'accumulent en 'Waiting for table metadata lock'.","caution":"Un DDL en attente bloque aussi les SELECT qui arrivent après lui ; décidez vite pendant l'incident : KILL de la session bloquante ou annulation de l'ALTER (KILL QUERY). Le PROCESSLIST_INFO du bloqueur peut être NULL (inactif) ; utilisez performance_schema.events_statements_history ou sys.session.last_statement pour voir sa dernière instruction. Prévention : exécutez le DDL après SET SESSION lock_wait_timeout = 5 pour que l'ALTER abandonne rapidement, et évitez les longues transactions ouvertes dans l'application.","note":"L'instrument wait/lock/metadata/sql/mdl est activé par défaut en 8.0 ; en 5.7, il doit être activé dans setup_instruments. sys.schema_table_lock_waits liste aussi le propre verrou SHARED_UPGRADABLE d'un ALTER comme bloqueur ; le vrai coupable est la session qui détient un SHARED_READ / SHARED_WRITE GRANTED.","since":"MySQL 8.0","tags":["metadata lock","mdl","alter","waiting for table metadata lock","lock","blocking"]},{"id":"innodb-deadlock-analysis","task":"Analyser les interblocages InnoDB et les journaliser tous","category":"Performances","command":"-- Latest deadlock: LATEST DETECTED DEADLOCK section (only the most recent is kept)\nSHOW ENGINE INNODB STATUS\\G\n-- Total deadlock counter\nSELECT NAME, COUNT FROM information_schema.INNODB_METRICS WHERE NAME = 'lock_deadlocks';\n-- Write every deadlock to the error log (logged at Note level, needs verbosity 3)\nSET PERSIST innodb_print_all_deadlocks = ON;\nSET PERSIST log_error_verbosity = 3;","what":"Montre les deux transactions de l'interblocage, le verrou que chacune détient et attend (index, lock_mode, enregistrement) et celle qu'InnoDB a annulée ; le compteur suit la fréquence et le journal d'erreurs conserve les interblocages passés.","when":"Quand l'application reçoit l'erreur 1213 'Deadlock found when trying to get lock' ou que les alertes d'interblocage augmentent.","caution":"LATEST DETECTED DEADLOCK ne conserve que le dernier événement et est perdu au redémarrage. Le nom d'index et le lock_mode (X, S, gap, insert intention) des lignes 'HOLDS THE LOCK(S)' / 'WAITING FOR THIS LOCK TO BE GRANTED' expliquent la cause ; 'WE ROLL BACK TRANSACTION (n)' indique la victime. Sur 1213, l'application doit rejouer toute la transaction. Correctifs durables : accéder aux lignes dans un ordre cohérent, garder les transactions courtes, utiliser le bon index pour réduire les gap locks et READ COMMITTED quand c'est approprié. log_error_verbosity=3 active aussi tous les messages Note ; surveillez la croissance du journal d'erreurs. La variable de statut Innodb_deadlocks n'existe pas dans MySQL Community ; utilisez INNODB_METRICS pour le compteur.","note":"Sous très forte concurrence, la détection d'interblocage elle-même peut devenir un goulot CPU ; innodb_deadlock_detect=OFF n'est envisagé qu'avec un innodb_lock_wait_timeout bas. Un lock wait timeout (1205) n'est pas un interblocage ; utilisez la procédure des verrous bloquants pour trouver le bloqueur.","since":"MySQL 8.0","tags":["deadlock","innodb","1213","innodb status","lock","error log"]},{"id":"long-trx-history-list","task":"Transactions longues et history list length (retard de purge)","category":"Performances","command":"-- Undo records waiting for purge (millions = trouble)\nSELECT NAME, COUNT AS history_list_length\nFROM information_schema.INNODB_METRICS WHERE NAME = 'trx_rseg_history_len';\n\n-- Oldest transactions, their owner and last SQL\nSELECT trx.trx_id, trx.trx_mysql_thread_id AS conn_id, trx.trx_state,\n       TIMESTAMPDIFF(SECOND, trx.trx_started, NOW()) AS age_sec,\n       trx.trx_rows_modified, trx.trx_rows_locked, trx.trx_isolation_level,\n       p.USER, p.HOST, p.COMMAND, p.TIME AS state_sec,\n       LEFT(COALESCE(trx.trx_query, esc.SQL_TEXT), 80) AS current_or_last_sql\nFROM information_schema.INNODB_TRX trx\nJOIN performance_schema.processlist p ON p.ID = trx.trx_mysql_thread_id\nLEFT JOIN performance_schema.threads th ON th.PROCESSLIST_ID = trx.trx_mysql_thread_id\nLEFT JOIN performance_schema.events_statements_current esc ON esc.THREAD_ID = th.THREAD_ID\nORDER BY trx.trx_started\nLIMIT 10;","what":"Montre le retard de la purge (history list length) et les transactions les plus anciennes qui la retiennent : propriétaire, âge, lignes modifiées et dernier SQL exécuté.","when":"Quand le tablespace undo grossit, quand les mêmes requêtes ralentissent avec le temps (elles parcourent d'anciennes versions de lignes) ou sur des alertes d'espace disque.","caution":"La cause la plus fréquente est une session en Sleep avec une transaction ouverte, ou un consistent snapshot oublié sous REPEATABLE READ (par ex. un mysqldump / rapport abandonné). Après KILL, le rollback dure autant que les lignes modifiées par la transaction ; tuer une transaction avec un trx_rows_modified élevé implique un rollback long et lourd en IO, et un redémarrage ne le raccourcit pas. L'undo est tronqué automatiquement une fois la purge rattrapée (innodb_undo_log_truncate=ON).","note":"performance_schema.processlist est apparu en 8.0.22 et remplace information_schema.PROCESSLIST, obsolète. 'History list length' dans SHOW ENGINE INNODB STATUS donne la même valeur.","since":"MySQL 8.0.22","tags":["history list length","purge","undo","long transaction","innodb_trx","idle in transaction"]},{"id":"slow-log-pt-query-digest","task":"Capturer un slow log complet de courte durée et l'analyser avec pt-query-digest","category":"Performances","command":"-- Temporary full capture (e.g. 10 minutes); only new connections are affected\nSET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';\nSET GLOBAL log_slow_extra = ON;\nSET GLOBAL long_query_time = 0;\nSET GLOBAL slow_query_log = ON;\n-- ... then return to the normal threshold\nSET GLOBAL long_query_time = 1;\n\n# Shell: top 10 queries by total time\npt-query-digest --limit=10 --order-by=Query_time:sum --noversion-check /var/lib/mysql/slow.log > /tmp/digest.txt\npt-query-digest --since='2026-10-08 09:00:00' --until='2026-10-08 10:00:00' --filter '$event->{db} && $event->{db} eq \"shop\"' --report-format=profile --noversion-check /var/lib/mysql/slow.log","what":"Capture toutes les requêtes pendant une courte fenêtre avec long_query_time=0 ; pt-query-digest les regroupe par empreinte et produit un rapport classé avec temps total / moyen / p95, lignes examinées et un exemple de requête.","when":"Quand les digests de performance_schema ne suffisent pas : analyse avec le texte complet des instructions et les vrais paramètres, comparaison avant / après un déploiement, revue hors ligne sur un autre hôte.","caution":"long_query_time est copié depuis la valeur globale à l'ouverture d'une session ; les connexions existantes d'un pool ne voient pas le nouveau seuil avant de se reconnecter. long_query_time=0 écrit des milliers d'entrées par seconde sur un serveur chargé ; surveillez l'espace disque et l'IO, gardez la fenêtre courte et revenez toujours en arrière. Copiez le fichier hors de la production pour l'analyse. L'utilisateur qui exécute pt-query-digest doit avoir un accès en lecture au fichier (mode 640, mysql:mysql).","note":"log_slow_extra (8.0.14) écrit des champs supplémentaires comme Bytes_sent, Read_first / Read_next et Created_tmp_disk_tables ; pt-query-digest les affiche dans le rapport. Pour l'activation / désactivation permanente, voir la procédure enable-general-slow-log.","since":"MySQL 8.0.14","tags":["slow log","pt-query-digest","long_query_time","percona toolkit","query analysis","slow query"]},{"id":"explain-analyze-optimizer-trace","task":"Déboguer un mauvais plan avec EXPLAIN ANALYZE et l'optimizer trace","category":"Performances","command":"-- Plan + actual time and rows (the query actually runs)\nEXPLAIN ANALYZE\nSELECT region, COUNT(*) FROM shop.big WHERE id < 20000 GROUP BY region;\n\n-- Why the optimizer chose this plan: optimizer trace\nSET SESSION optimizer_trace = 'enabled=on', SESSION optimizer_trace_max_mem_size = 1048576;\nSELECT * FROM shop.big WHERE region = 'TR' AND id > 100 LIMIT 5;\nSELECT JSON_PRETTY(JSON_EXTRACT(TRACE, '$**.considered_execution_plans')) AS plans,\n       MISSING_BYTES_BEYOND_MAX_MEM_SIZE\nFROM information_schema.OPTIMIZER_TRACE;\nSET SESSION optimizer_trace = 'enabled=off';","what":"EXPLAIN ANALYZE affiche côte à côte les valeurs estimées (cost, rows) et réelles (actual time, rows, loops) pour chaque étape du plan ; l'optimizer trace montre les chemins d'accès envisagés, leurs coûts et pourquoi chacun a été choisi ou rejeté.","when":"Quand une requête ralentit soudainement (changement de plan), que l'optimiseur choisit le mauvais index, ou pour trouver l'origine d'un écart entre lignes estimées et réelles.","caution":"EXPLAIN ANALYZE exécute réellement la requête ; prudence avec les requêtes lourdes en production. Il fonctionne avec SELECT, TABLE et UPDATE / DELETE multi-tables. Un écart de plus de 10x entre lignes estimées et réelles indique un problème de statistiques / d'histogrammes (ANALYZE TABLE, histogrammes). Côté interne d'une nested loop, actual rows est par boucle ; total = rows × loops. Si MISSING_BYTES_BEYOND_MAX_MEM_SIZE > 0, la trace a été tronquée ; augmentez optimizer_trace_max_mem_size. La trace ne contient que la dernière instruction de la même session.","note":"EXPLAIN FORMAT=TREE est apparu en 8.0.16, EXPLAIN ANALYZE en 8.0.18. Pour figer un plan, les optimizer hints (/*+ INDEX(t ix) */, JOIN_ORDER) offrent un contrôle plus fin que FORCE INDEX.","since":"MySQL 8.0.18","tags":["explain analyze","optimizer trace","execution plan","query tuning","index","plan"]},{"id":"histogram-statistics","task":"Créer et gérer des histogrammes pour les colonnes non indexées","category":"Index et statistiques","command":"ANALYZE TABLE shop.big UPDATE HISTOGRAM ON status WITH 64 BUCKETS;\n-- 8.4+: histogram refreshed automatically as data changes\nANALYZE TABLE shop.big UPDATE HISTOGRAM ON status WITH 64 BUCKETS AUTO UPDATE;\n\nSELECT SCHEMA_NAME, TABLE_NAME, COLUMN_NAME,\n       HISTOGRAM->>'$.\"histogram-type\"' AS type,\n       JSON_LENGTH(HISTOGRAM->'$.buckets') AS buckets,\n       HISTOGRAM->>'$.\"last-updated\"' AS last_updated,\n       HISTOGRAM->>'$.\"sampling-rate\"' AS sampling_rate\nFROM information_schema.COLUMN_STATISTICS;\n\nANALYZE TABLE shop.big DROP HISTOGRAM ON status;","what":"Stocke la distribution des valeurs d'une colonne (singleton ou equi-height) dans le dictionnaire de données ; l'optimiseur estime correctement la sélectivité des filtres sur les colonnes non indexées, ce qui améliore l'ordre des jointures et la qualité du plan.","when":"Quand EXPLAIN ANALYZE montre un grand écart entre lignes estimées et réelles sur des colonnes à distribution asymétrique (par ex. status, type) où l'ajout d'un index n'est pas souhaitable.","caution":"Sur les colonnes indexées, l'optimiseur utilise les index dives, donc un histogramme n'a généralement aucun effet (sauf si eq_range_index_dive_limit est dépassé). En mode MANUAL par défaut, l'histogramme n'est pas actualisé quand les données changent ; planifiez ANALYZE ... UPDATE HISTOGRAM. Les grandes tables sont échantillonnées selon histogram_generation_max_mem_size (sampling-rate < 1). ANALYZE TABLE prend un bref metadata lock ; la mise à jour d'un histogramme ne modifie pas les statistiques de la table (cardinalité des index).","note":"Les histogrammes sont apparus en 8.0 ; l'option AUTO UPDATE a été ajoutée en 8.4 et actualise l'histogramme avec le recalcul automatique des statistiques. Le maximum est de 1024 buckets ; s'il y a moins de valeurs distinctes que de buckets, un histogramme singleton est créé.","since":"MySQL 8.0","tags":["histogram","statistics","optimizer","analyze table","selectivity","cardinality"]},{"id":"invisible-redundant-index","task":"Trouver les index inutilisés / redondants, les rendre invisibles, les supprimer en sécurité","category":"Index et statistiques","command":"-- Candidate 1: indexes unused since the last restart\nSELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'shop';\n-- Candidate 2: redundant indexes that are a left prefix of another\nSELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, sql_drop_index\nFROM sys.schema_redundant_indexes WHERE table_schema = 'shop';\n\n-- Make it invisible before dropping; revert instantly if anything regresses\nALTER TABLE shop.big ALTER INDEX ix_region INVISIBLE;\nSELECT INDEX_NAME, IS_VISIBLE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'big' AND SEQ_IN_INDEX = 1;\n-- Try the invisible index for a single query\nEXPLAIN SELECT /*+ SET_VAR(optimizer_switch = 'use_invisible_indexes=on') */ COUNT(*) FROM shop.big WHERE region = 'TR';\nALTER TABLE shop.big ALTER INDEX ix_region VISIBLE;\n-- After a few business cycles (incl. month-end) without issues\nALTER TABLE shop.big DROP INDEX ix_region, ALGORITHM=INPLACE, LOCK=NONE;","what":"Liste les index inutilisés et les index couverts par un autre ; rendre un index invisible avant de le supprimer permet de tester le comportement de la production sans lui et de revenir en arrière instantanément (sans reconstruction) si nécessaire.","when":"Lors du nettoyage des index pour réduire le coût d'écriture, la pression sur le buffer pool et l'utilisation disque.","caution":"schema_unused_indexes s'appuie uniquement sur les statistiques collectées depuis le dernier redémarrage ; les requêtes peu fréquentes comme les rapports mensuels peuvent ne pas apparaître. Si les réplicas servent des charges différentes, vérifiez chaque serveur séparément. Un index invisible reste maintenu lors des DML (le coût d'écriture demeure) mais l'optimiseur l'ignore ; un index unique continue d'imposer l'unicité lorsqu'il est invisible. Une PRIMARY KEY ne peut pas être rendue invisible. Les requêtes qui nomment l'index dans FORCE INDEX / USE INDEX échouent dès qu'il est invisible.","note":"Changer la visibilité d'un index ne touche que les métadonnées et s'effectue immédiatement. La liste des index redondants se base sur l'ordre des colonnes ; l'index (a) est couvert par (a,b) mais (b) ne l'est pas.","since":"MySQL 8.0","tags":["invisible index","unused index","redundant index","sys","drop index","index cleanup"]},{"id":"buffer-pool-sizing","task":"Le buffer pool est-il assez grand : hit ratio, wait_free et redimensionnement en ligne","category":"Mémoire","command":"SELECT ROUND(@@innodb_buffer_pool_size / 1024 / 1024) AS bp_mb,\n       @@innodb_buffer_pool_chunk_size / 1024 / 1024 AS chunk_mb,\n       @@innodb_buffer_pool_instances AS instances;\nSELECT\n  ROUND(100 * (1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads')\n                 / NULLIF((SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'), 0)), 3) AS hit_pct,\n  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_free') AS pages_free,\n  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty') AS pages_dirty,\n  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_wait_free') AS wait_free;\n\n-- Online resize and status tracking\nSET PERSIST innodb_buffer_pool_size = 8589934592;\nSELECT VARIABLE_NAME, VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME LIKE 'Innodb_buffer_pool_resize_status%';","what":"Affiche la taille du buffer pool, le taux de succès du cache, le nombre de pages libres / sales et le compteur d'attente de pages libres (wait_free) ; le redimensionne sans redémarrage et suit la progression.","when":"Quand l'IO en lecture est élevé, après ajout / retrait de RAM ou lors de la planification de capacité.","caution":"Le hit ratio seul est trompeur ; même 99 % peut signifier des milliers de lectures disque par seconde. Regardez la croissance par seconde de Innodb_buffer_pool_reads ; wait_free > 0 signifie que le buffer pool est trop petit ou que les page cleaners ne suivent pas. La nouvelle taille est arrondie à un multiple de innodb_buffer_pool_chunk_size × instances. De brefs blocages sont possibles pendant un redimensionnement, davantage lors d'une réduction ; évitez les heures de pointe. Sur un serveur dédié, commencez à 60–75 % de la RAM ; laissez de la place pour la mémoire par connexion, le cache de l'OS et les autres processus, car le swap est toujours pire qu'un buffer pool plus petit.","note":"Innodb_buffer_pool_resize_status_code / _progress sont apparus en 8.0.31. innodb_dedicated_server=ON dimensionne automatiquement le buffer pool et la capacité redo à partir de la RAM. sys.innodb_buffer_stats_by_table montre l'utilisation du buffer pool par table ; elle parcourt INNODB_BUFFER_PAGE et est lourde sur les grands buffer pools.","since":"MySQL 8.0","tags":["buffer pool","innodb","memory","hit ratio","resize","sizing"]},{"id":"partition-maintenance","task":"Maintenance des partitions RANGE : ajouter les partitions futures, archiver / supprimer les anciennes","category":"Partitionnement","command":"-- Current partitions\nSELECT PARTITION_NAME, PARTITION_DESCRIPTION, TABLE_ROWS\nFROM information_schema.PARTITIONS\nWHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'events_log' ORDER BY PARTITION_ORDINAL_POSITION;\n\n-- Add a new month by splitting the empty pmax\nALTER TABLE shop.events_log REORGANIZE PARTITION pmax INTO (\n  PARTITION p2026_11 VALUES LESS THAN ('2026-12-01'),\n  PARTITION pmax VALUES LESS THAN (MAXVALUE));\n\n-- Move the old month into an archive table (instant), then drop the empty partition\nCREATE TABLE shop.events_log_2026_08 LIKE shop.events_log;\nALTER TABLE shop.events_log_2026_08 REMOVE PARTITIONING;\nALTER TABLE shop.events_log EXCHANGE PARTITION p2026_08 WITH TABLE shop.events_log_2026_08;\nALTER TABLE shop.events_log DROP PARTITION p2026_08;\n\n-- Pruning check: the partitions column should list a single partition\nEXPLAIN SELECT * FROM shop.events_log WHERE created >= '2026-10-01' AND created < '2026-11-01';","what":"Sur une table partitionnée par RANGE sur une date, ajoute la partition de la période suivante, déplace une ancienne période dans une table séparée en quelques millisecondes via EXCHANGE, et supprime la partition bien plus économiquement qu'un DELETE.","when":"Pour la rétention mensuelle et l'archivage des tables qui grossissent avec le temps, comme les logs, les événements et les pistes d'audit.","caution":"La colonne de partitionnement doit faire partie de chaque clé unique (PK comprise). REORGANIZE PARTITION pmax réécrit les données de pmax ; gardez pmax vide et créez les partitions à l'avance (par ex. 3 mois). DROP PARTITION est irréversible et n'est pas journalisé ligne par ligne ; sur les réplicas, il s'exécute comme un DDL. La cible d'EXCHANGE doit avoir la même structure et aucun partitionnement ; les lignes hors plage provoquent une erreur (WITHOUT VALIDATION est plus rapide mais peut laisser entrer des données erronées). Le DDL de partition prend un metadata lock au niveau de la table. Pour le pruning, utilisez la colonne de partitionnement dans le WHERE sans l'envelopper dans une fonction.","note":"En 8.0, le partitionnement n'est pris en charge que par InnoDB (et NDB). L'ajout des partitions futures / la suppression des anciennes est généralement automatisé avec l'Event Scheduler ou cron ; ALTER TABLE ... TRUNCATE PARTITION supprime les données mais conserve la partition.","since":"MySQL 8.0","tags":["partition","range","reorganize","exchange partition","retention","archive"]},{"id":"roles-least-privilege","task":"Construire un modèle de moindre privilège avec des rôles (CREATE ROLE, DEFAULT ROLE)","category":"Sécurité","command":"CREATE ROLE IF NOT EXISTS 'app_read', 'app_write';\nGRANT SELECT ON shop.* TO 'app_read';\nGRANT INSERT, UPDATE, DELETE ON shop.* TO 'app_write';\n\n-- Application account: network range, TLS required, roles active by default\nCREATE USER 'svc_app'@'10.0.%' IDENTIFIED BY 'S3cure#App2024' DEFAULT ROLE 'app_read', 'app_write' REQUIRE SSL;\nCREATE USER 'ana'@'%' IDENTIFIED BY 'An4#Report2024';\nGRANT 'app_read' TO 'ana'@'%';\nSET DEFAULT ROLE ALL TO 'ana'@'%';\n\n-- Show grants with roles expanded\nSHOW GRANTS FOR 'svc_app'@'10.0.%' USING 'app_read', 'app_write';\nSELECT FROM_USER, FROM_HOST, TO_USER, TO_HOST FROM mysql.role_edges;","what":"Accorde les privilèges à des rôles plutôt qu'à des utilisateurs individuels ; les comptes obtiennent leurs privilèges par appartenance à un rôle. Les modifications se font en un seul endroit, et l'audit comme le départ d'un collaborateur sont simplifiés.","when":"Lors de la création de nouveaux comptes d'application / d'équipe et du nettoyage d'anciens serveurs aux privilèges enchevêtrés.","caution":"Un rôle qui n'est pas un rôle par défaut n'est pas actif dans la session (CURRENT_ROLE() NONE) et l'utilisateur semble manquer de privilèges ; utilisez SET DEFAULT ROLE ALL ou activate_all_roles_on_login=ON. Un rôle est aussi un compte (verrouillé, sans mot de passe) et apparaît dans mysql.user. Utilisez une plage réseau au lieu de '%' pour les comptes d'application. N'accordez jamais ALL, SUPER, FILE, PROCESS ou CONNECTION_ADMIN à un compte d'application. Révoquer un privilège d'un rôle affecte aussi les sessions actives.","note":"mandatory_roles attribue automatiquement des rôles à tous les comptes. CREATE USER ... DEFAULT ROLE accorde les rôles et les rend par défaut en même temps.","since":"MySQL 8.0","tags":["role","grant","least privilege","default role","security","privilege"]},{"id":"password-policy-lockout","task":"Politique de mots de passe, verrouillage après échecs de connexion et rotation de mot de passe sans interruption","category":"Sécurité","command":"-- Password strength policy (component)\nINSTALL COMPONENT 'file://component_validate_password';\nSET PERSIST validate_password.policy = 'MEDIUM';\nSET PERSIST validate_password.length = 12;\nSELECT VALIDATE_PASSWORD_STRENGTH('Summer2024') AS score;\n\n-- Failed-login lockout, password lifetime, history\nALTER USER 'ana'@'%' FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1\n  PASSWORD EXPIRE INTERVAL 90 DAY PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY;\n\n-- Zero-downtime rotation: dual password\nALTER USER 'svc_app'@'10.0.%' IDENTIFIED BY 'N3w#AppSecret2025' RETAIN CURRENT PASSWORD;\n-- Once every application uses the new password\nALTER USER 'svc_app'@'10.0.%' DISCARD OLD PASSWORD;","what":"Active une politique qui rejette les mots de passe faibles, verrouille temporairement les comptes après des échecs de connexion consécutifs, définit la durée de vie et les règles de réutilisation des mots de passe ; avec les mots de passe doubles, le mot de passe d'un compte de service est changé sans interruption pendant que les applications sont mises à jour une par une.","when":"Pour les audits de sécurité / la conformité (PCI, ISO 27001) et la rotation des mots de passe des comptes de service.","caution":"La politique ne s'applique qu'aux mots de passe nouveaux / modifiés ; elle ne détecte pas les mots de passe faibles existants. Sur les comptes de service, FAILED_LOGIN_ATTEMPTS permet à un seul client mal configuré de verrouiller le compte et de provoquer une panne ; réservez-le aux comptes humains. PASSWORD_LOCK_TIME est en jours (UNBOUNDED : jusqu'au déverrouillage manuel) ; déverrouillez avec ALTER USER ... ACCOUNT UNLOCK. PASSWORD EXPIRE provoque des pannes inattendues sur les comptes de service ; préférez-y PASSWORD EXPIRE NEVER avec une rotation par mot de passe double.","note":"Les mots de passe doubles sont apparus en 8.0.14, FAILED_LOGIN_ATTEMPTS / PASSWORD_LOCK_TIME en 8.0.19. Le plugin validate_password de 5.7 a été remplacé par un composant en 8.0 ; les noms de variables utilisent un point (validate_password.policy).","since":"MySQL 8.0.19","tags":["password policy","validate_password","failed_login_attempts","dual password","security","password"]},{"id":"require-tls-transport","task":"Imposer TLS, trouver les connexions sans TLS, renouveler les certificats sans redémarrage","category":"Sécurité","command":"SELECT @@require_secure_transport, @@tls_version, @@ssl_ca, @@ssl_cert;\nSELECT * FROM performance_schema.tls_channel_status\nWHERE PROPERTY IN ('Enabled', 'Current_tls_version', 'Current_tls_cert', 'Ssl_server_not_after');\n\n-- Active connections and their TLS versions (empty = no TLS)\nSELECT t.PROCESSLIST_USER AS user, t.PROCESSLIST_HOST AS host, t.CONNECTION_TYPE, sv.VARIABLE_VALUE AS tls_version\nFROM performance_schema.threads t\nLEFT JOIN performance_schema.status_by_thread sv ON sv.THREAD_ID = t.THREAD_ID AND sv.VARIABLE_NAME = 'Ssl_version'\nWHERE t.TYPE = 'FOREGROUND' AND t.PROCESSLIST_USER IS NOT NULL;\n\nSET PERSIST require_secure_transport = ON;\n-- After copying the new certificate files\nALTER INSTANCE RELOAD TLS;","what":"Affiche la configuration TLS du serveur, la date d'expiration du certificat et si chaque connexion utilise TLS ; interdit totalement les connexions TCP non chiffrées et renouvelle les certificats sans arrêter le serveur.","when":"Pour les audits de sécurité / la conformité, avant l'expiration d'un certificat et pour repérer les anciens clients qui se connectent sans chiffrement.","caution":"require_secure_transport=ON n'affecte pas les connexions par socket Unix et mémoire partagée ; elles sont considérées comme sécurisées. Trouvez les clients qui se connectent sans TLS avant de l'activer, sinon ils échouent avec l'erreur 3159. ALTER INSTANCE RELOAD TLS n'affecte pas les connexions existantes, seulement les nouvelles ; si le fichier de certificat est invalide, la commande échoue et conserve l'ancien certificat. Le certificat auto-signé généré automatiquement ne peut pas être vérifié par les clients en VERIFY_CA / VERIFY_IDENTITY ; utilisez en production un certificat signé par votre CA.","note":"ALTER INSTANCE RELOAD TLS est apparu en 8.0.16, performance_schema.tls_channel_status en 8.0.21. Pour une exigence par compte, utilisez ALTER USER ... REQUIRE SSL (ou REQUIRE X509).","since":"MySQL 8.0.21","tags":["tls","ssl","require_secure_transport","certificate","encryption","cert"]},{"id":"risky-accounts-audit","task":"Auditer les comptes à risque : mot de passe vide, hôte '%', SUPER, mysql_native_password","category":"Sécurité","command":"SELECT user, host, plugin,\n       CONCAT_WS(', ',\n         IF(authentication_string = '' AND account_locked = 'N', 'EMPTY PASSWORD', NULL),\n         IF(host = '%', 'ANY HOST', NULL),\n         IF(plugin = 'mysql_native_password', 'NATIVE AUTH', NULL),\n         IF(Super_priv = 'Y', 'SUPER', NULL),\n         IF(Grant_priv = 'Y', 'GRANT OPTION', NULL),\n         IF(File_priv = 'Y', 'FILE', NULL),\n         IF(ssl_type = '', 'NO TLS REQUIRED', NULL),\n         IF(password_expired = 'Y', 'EXPIRED', NULL),\n         IF(account_locked = 'Y', 'LOCKED', NULL)) AS findings,\n       password_last_changed\nFROM mysql.user\nWHERE user NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema')\nORDER BY (Super_priv = 'Y') DESC, user;\n\n-- Accounts holding powerful dynamic privileges\nSELECT GRANTEE, PRIVILEGE_TYPE FROM information_schema.USER_PRIVILEGES\nWHERE PRIVILEGE_TYPE IN ('SUPER', 'SYSTEM_VARIABLES_ADMIN', 'CONNECTION_ADMIN', 'BINLOG_ADMIN', 'REPLICATION_SLAVE_ADMIN')\nORDER BY GRANTEE;","what":"Liste les constats de sécurité par compte sur une ligne : absence de mot de passe, connexion possible depuis n'importe où, ancien plugin d'authentification, privilèges SUPER / GRANT / FILE et absence d'exigence TLS ; la deuxième requête montre les privilèges dynamiques dangereux.","when":"Lors des audits de sécurité périodiques, sur un serveur nouvellement hérité et avant une mise à niveau vers 8.4.","caution":"La requête lit directement mysql.user et nécessite SELECT dessus. Les comptes de rôle sont verrouillés sans mot de passe, donc EMPTY PASSWORD n'est signalé que pour les comptes non verrouillés. Super_priv est le privilège statique SUPER ; comme 8.0 le divise en privilèges dynamiques, vérifiez aussi la deuxième requête. Les comptes marqués NATIVE AUTH doivent passer à caching_sha2_password avant 8.4 : ALTER USER 'u'@'h' IDENTIFIED WITH caching_sha2_password BY '...'; les anciens clients / connecteurs peuvent ne pas prendre en charge caching_sha2_password, testez-les d'abord.","note":"mysql_native_password a été déprécié en 8.0.34, est désactivé par défaut en 8.4 (activable avec mysql_native_password=ON) et a été supprimé en 9.0.","since":"MySQL 8.0","tags":["security audit","mysql.user","mysql_native_password","super","privileges","audit"]},{"id":"upgrade-checker-84","task":"Pré-vérification de mise à niveau 8.0 → 8.4 LTS (util.checkForServerUpgrade)","category":"Mise à niveau","command":"# State the target version explicitly; the Shell must be the same version as the target or newer\nmysqlsh root@db1:3306 -- util check-for-server-upgrade --target-version=8.4.10 --output-format=TEXT\n# JSON output for automation, including a my.cnf check\nmysqlsh root@db1:3306 -- util check-for-server-upgrade --target-version=8.4.10 --output-format=JSON --config-path=/etc/my.cnf > /tmp/upgrade_check.json","what":"Vérifie le serveur actuel par rapport à la version cible : variables et privilèges supprimés, paramètres aux nouvelles valeurs par défaut, anciennes méthodes d'authentification, définitions de colonnes invalides, problèmes de clés étrangères et de partitions. Les constats de niveau Error bloquent la mise à niveau.","when":"Avant chaque mise à niveau majeure / LTS ; relancez après avoir corrigé les constats.","caution":"Examinez aussi les avertissements : en 8.4, des valeurs par défaut comme innodb_io_capacity (10000), innodb_log_buffer_size (64 MB), innodb_adaptive_hash_index (OFF), innodb_change_buffering (none), innodb_buffer_pool_instances et group_replication_consistency changent et peuvent affecter les performances ; fixez dans my.cnf les valeurs dont vous voulez garder le comportement actuel. Les privilèges supprimés comme SET_USER_ID sont retirés pendant la mise à niveau. Mettez d'abord à niveau un réplica, observez-le sous charge, puis basculez ; le retour arrière binaire de 8.4 vers 8.0 n'est pas pris en charge, la solution de repli est donc une sauvegarde logique ou un réplica laissé sur l'ancienne version.","note":"Supprimés en 8.4 : l'ancienne syntaxe de réplication comme START / STOP SLAVE, SHOW SLAVE STATUS et CHANGE MASTER TO, default_authentication_plugin (remplacé par authentication_policy), binlog_transaction_dependency_tracking et mysqlpump. mysql_native_password est désactivé par défaut.","since":"MySQL Shell 8.0","tags":["upgrade","8.4","checkforserverupgrade","mysqlsh","lts","version upgrade"]},{"id":"charset-utf8mb4-convert","task":"Convertir les tables latin1 / utf8mb3 en utf8mb4","category":"Maintenance","command":"-- Columns that are not utf8mb4\nSELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE\nFROM information_schema.COLUMNS\nWHERE CHARACTER_SET_NAME IS NOT NULL AND CHARACTER_SET_NAME <> 'utf8mb4'\n  AND TABLE_SCHEMA NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema')\nORDER BY TABLE_SCHEMA, TABLE_NAME;\n\n-- Generate conversion DDL for tables whose default charset is not utf8mb4\nSELECT CONCAT('ALTER TABLE `', t.TABLE_SCHEMA, '`.`', t.TABLE_NAME,\n              '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, ALGORITHM=COPY, LOCK=SHARED;') AS ddl\nFROM information_schema.TABLES t\nJOIN information_schema.COLLATIONS c ON c.COLLATION_NAME = t.TABLE_COLLATION\nWHERE c.CHARACTER_SET_NAME <> 'utf8mb4' AND t.TABLE_TYPE = 'BASE TABLE'\n  AND t.TABLE_SCHEMA NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema');\n\n-- Schema default and a single table\nALTER DATABASE legacy CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;\nALTER TABLE legacy.cust CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, ALGORITHM=COPY, LOCK=SHARED;","what":"Trouve les colonnes et tables qui ne sont pas en utf8mb4 et génère le DDL de conversion ; CONVERT TO modifie à la fois la valeur par défaut de la table et chaque colonne de caractères, ce qui permet les emoji / caractères sur 4 octets.","when":"Sur les schémas migrés depuis 5.x, sur les erreurs 'Incorrect string value' (1366), et quand les jointures entre colonnes de collations différentes ne peuvent pas utiliser les index.","caution":"CONVERT TO réécrit la table en COPY et bloque les écritures pendant toute la durée (INPLACE n'est pas pris en charge) ; sur les grandes tables, exécutez le même --alter via pt-online-schema-change / gh-ost. utf8mb4 utilise 4 octets par caractère : un VARCHAR(255) indexé fait 1020 octets ; la limite est de 3072 octets avec le row format DYNAMIC / COMPRESSED et de 767 avec COMPACT / REDUNDANT, au-delà de quoi vous obtenez l'erreur 1071. Les colonnes VARCHAR / TEXT sont promues vers un type plus grand (TEXT → MEDIUMTEXT) si nécessaire pour garder la même capacité en caractères. Un changement de collation affecte le tri et la comparaison : utf8mb4_0900_ai_ci est insensible aux accents et à la casse, donc des valeurs auparavant distinctes peuvent entrer en collision sur un index unique (1062). Convertissez ensemble les tables jointes. La connexion cliente doit aussi utiliser utf8mb4.","note":"utf8 (utf8mb3) est déprécié en 8.0 ; depuis 8.0.30, il apparaît comme utf8mb3 dans SHOW CREATE TABLE. Pour les règles de collation turques (I avec et sans point), utilisez utf8mb4_tr_0900_ai_ci. La valeur par défaut en 8.0 est utf8mb4 / utf8mb4_0900_ai_ci.","since":"MySQL 8.0","tags":["utf8mb4","character set","collation","convert","emoji","charset"]}]},"recipes/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"recipes":[{"id":"backup-log","task":"Effectuer une sauvegarde du journal des transactions","category":"Sauvegarde et journal","command":"BACKUP LOG [Customer]\nTO DISK = N'D:\\Backup\\Customer_log_20260706_1200.trn'\nWITH COMPRESSION, CHECKSUM, STATS = 10;","what":"Effectue une sauvegarde du journal des transactions et (en mode de récupération FULL/BULK_LOGGED) tronque la partie utilisée du journal pour qu'elle puisse être réutilisée.","when":"Régulièrement en mode de récupération FULL (par ex. toutes les 15 min), pour la chaîne de restauration à un instant donné, et quand le fichier journal grossit.","caution":"N'a de sens qu'en mode de récupération FULL/BULK_LOGGED (en SIMPLE, le journal est tronqué automatiquement). Au moins une sauvegarde FULL doit exister au préalable pour que la chaîne de journaux ne soit pas rompue. Mettez en place une stratégie correcte de suppression/écrasement des fichiers de sauvegarde.","tags":["backup log","log backup","transaction log","trn","recovery","backup"]},{"id":"backup-full-diff","task":"Effectuer des sauvegardes FULL et DIFFERENTIAL","category":"Sauvegarde et journal","command":"-- Full backup:\nBACKUP DATABASE [Customer]\nTO DISK = N'D:\\Backup\\Customer_full_20260706.bak'\nWITH COMPRESSION, CHECKSUM, INIT, STATS = 10;\n\n-- Differential backup:\nBACKUP DATABASE [Customer]\nTO DISK = N'D:\\Backup\\Customer_diff_20260706.bak'\nWITH DIFFERENTIAL, COMPRESSION, CHECKSUM, INIT, STATS = 10;","what":"FULL sauvegarde toute la base de données ; DIFFERENTIAL sauvegarde les extents modifiés depuis la dernière FULL.","when":"FULL : sauvegarde de base quotidienne/hebdomadaire. DIFF : sauvegardes intermédiaires rapides entre les FULL.","caution":"La restauration d'une DIFF nécessite sa sauvegarde FULL de base. INIT écrase le fichier existant ; avec un mauvais nom de fichier, vous perdez une sauvegarde. CHECKSUM détecte tôt la corruption.","tags":["backup","full","differential","differential backup","bak","backup"]},{"id":"log-reuse-wait","task":"Pourquoi le fichier journal a grossi / pourquoi il ne se tronque pas","category":"Sauvegarde et journal","command":"SELECT name,\n       recovery_model_desc,\n       log_reuse_wait_desc\nFROM   sys.databases\nWHERE  name = N'Customer';\n\n-- Log space usage for all databases:\nSELECT DB_NAME(database_id) AS db, name, type_desc,\n       size*8/1024        AS size_mb,\n       CAST(FILEPROPERTY(name,'SpaceUsed') AS BIGINT)*8/1024 AS used_mb\nFROM   sys.master_files\nWHERE  type_desc = 'LOG';","what":"La colonne log_reuse_wait_desc indique pourquoi le journal des transactions ne peut pas être tronqué (par ex. LOG_BACKUP, ACTIVE_TRANSACTION, REPLICATION, AVAILABILITY_REPLICA).","when":"Pour trouver la cause racine quand le fichier journal ne cesse de grossir ou que le journal est plein (erreur 9002).","caution":"Si LOG_BACKUP s'affiche, la solution est d'effectuer une sauvegarde du journal, pas de réduire le fichier. Requête seule ; aucun risque.","tags":["log_reuse_wait_desc","log growth","sys.databases","log backup","recovery","diagnostics"]},{"id":"shrink-log","task":"Réduire le fichier journal","category":"Sauvegarde et journal","command":"-- First take a log backup so the log can be truncated (FULL model):\nBACKUP LOG [Customer] TO DISK = N'D:\\Backup\\Customer_log_pre_shrink.trn' WITH COMPRESSION;\n\n-- Find the logical name of the log file:\nSELECT name, type_desc FROM sys.database_files WHERE type_desc = 'LOG';\n\n-- Shrink (target 2048 MB):\nUSE [Customer];\nDBCC SHRINKFILE (N'Customer_log', 2048);","what":"Réduit la taille physique du fichier journal des transactions et rend l'espace au système d'exploitation.","when":"Quand le journal est resté durablement surdimensionné après une croissance anormale ponctuelle (grosse transaction/reconstruction d'index).","caution":"DANGEREUX/PRUDENCE : réduire régulièrement est une MAUVAISE pratique ; les performances chutent pendant que le journal regrossit et la fragmentation des VLF augmente. Effectuez d'abord une sauvegarde du journal, sinon la taille ne diminuera pas. Réduisez uniquement un fichier journal inutilement grossi, pas le fichier de données. Ensuite, regrossissez-le à une taille appropriée en une seule étape.","tags":["shrink","dbcc shrinkfile","shrink log","log","vlf","caution"]},{"id":"vlf-count","task":"Afficher le nombre de VLF (Virtual Log File)","category":"Sauvegarde et journal","command":"-- SQL Server 2016 SP2 / 2017 and later:\nSELECT DB_NAME(database_id) AS db,\n       COUNT(*)             AS vlf_count,\n       SUM(CASE WHEN vlf_active = 1 THEN 1 ELSE 0 END) AS active_vlf\nFROM   sys.dm_db_log_info(DB_ID(N'Customer'))\nGROUP BY database_id;\n\n-- For older versions: DBCC LOGINFO('Customer'); (row count = VLF count)","what":"Renvoie le nombre de fichiers journaux virtuels (VLF) dans le journal des transactions. Trop de VLF dégradent les performances de démarrage/récupération/sauvegarde du journal.","when":"Quand le journal est lent ou que le démarrage de la base est long ; lors de la revue du paramètre de croissance du journal.","caution":"Des centaines/milliers de VLF sont un signal d'alerte ; la solution est de réduire le journal puis de le regrossir par incréments suffisamment grands (par ex. par paliers de 8GB). Requête seule ; aucun risque.","since":"SQL Server 2016 SP2","note":"La DMV sys.dm_db_log_info est arrivée avec SQL Server 2016 SP2 et 2017 ; auparavant, les VLF étaient comptés à partir du nombre de lignes de la sortie non documentée de DBCC LOGINFO.","tags":["vlf","dm_db_log_info","dbcc loginfo","log","virtual log file","performance"]},{"id":"open-transaction","task":"Trouver la plus ancienne transaction ouverte (ACTIVE_TRANSACTION)","category":"Sauvegarde et journal","command":"USE [Customer];\nDBCC OPENTRAN;\n\n-- Detailed: show long-running open transactions and their owners:\nSELECT s.session_id, s.login_name, s.host_name, s.program_name,\n       t.transaction_id,\n       DATEDIFF(SECOND, t.transaction_begin_time, GETDATE()) AS duration_sec,\n       t.transaction_begin_time\nFROM   sys.dm_tran_active_transactions t\n  JOIN sys.dm_tran_session_transactions st ON st.transaction_id = t.transaction_id\n  JOIN sys.dm_exec_sessions s ON s.session_id = st.session_id\nORDER BY t.transaction_begin_time;","what":"Affiche la plus ancienne transaction ouverte (ni validée ni annulée) et la session qui la détient. Une transaction ouverte empêche la troncature du journal (LOG_REUSE_WAIT = ACTIVE_TRANSACTION).","when":"Pour trouver la cause racine quand le journal ne se réduit pas malgré les sauvegardes du journal et que log_reuse_wait_desc = ACTIVE_TRANSACTION.","caution":"Confirmez avec le responsable métier avant de faire un KILL sur la session qui détient une longue transaction ouverte ; le rollback peut durer longtemps. Requête seule ; aucun risque.","tags":["dbcc opentran","open transaction","active_transaction","dm_tran_active_transactions","log","diagnostics"]},{"id":"add-data-file","task":"Ajouter un fichier de données à une base","category":"Stockage","command":"ALTER DATABASE [Customer]\nADD FILE (\n  NAME = N'Customer_data2',\n  FILENAME = N'E:\\Data\\Customer_data2.ndf',\n  SIZE = 4GB,\n  FILEGROWTH = 512MB,\n  MAXSIZE = 100GB\n) TO FILEGROUP [PRIMARY];","what":"Ajoute un nouveau fichier de données (.ndf) à un filegroup existant pour augmenter la capacité et répartir les I/O.","when":"Quand le disque est plein, ou pour répartir les I/O sur plusieurs disques.","caution":"Les écritures dans le nouveau fichier sont équilibrées par proportional fill ; le fichier le plus vide se remplit en premier. Indiquez FILEGROWTH en MB fixes, pas en pourcentage. Définir MAXSIZE empêche une croissance incontrôlée.","tags":["add file","add data file","ndf","alter database","filegroup","storage"]},{"id":"add-filegroup","task":"Ajouter un filegroup","category":"Stockage","command":"-- 1) Create the filegroup:\nALTER DATABASE [Customer] ADD FILEGROUP [FG_Archive];\n\n-- 2) Add a file to it:\nALTER DATABASE [Customer]\nADD FILE (\n  NAME = N'Customer_archive1',\n  FILENAME = N'F:\\Data\\Customer_archive1.ndf',\n  SIZE = 8GB, FILEGROWTH = 1GB\n) TO FILEGROUP [FG_Archive];\n\n-- (Optional) make it the default filegroup:\n-- ALTER DATABASE [Customer] MODIFY FILEGROUP [FG_Archive] DEFAULT;","what":"Crée un nouveau filegroup contenant un fichier ; permet de placer des tables/index sur un disque/filegroup séparé.","when":"Pour séparer les données d'archive/froides, pour la sauvegarde/restauration au niveau filegroup et pour la séparation des I/O.","caution":"Un filegroup vide ne peut pas être sauvegardé/utilisé ; ajoutez-y au moins un fichier. Changer le filegroup par défaut modifie l'emplacement des nouveaux objets.","tags":["filegroup","filegroup","add filegroup","alter database","ndf","storage"]},{"id":"modify-file-growth","task":"Modifier la taille / l'autogrowth d'un fichier","category":"Stockage","command":"-- Grow (SIZE only grows; shrinking requires shrink, not a resize):\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_data', SIZE = 20GB);\n\n-- Set autogrowth (fixed MB recommended):\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_data', FILEGROWTH = 1GB, MAXSIZE = 200GB);\n\n-- Disable autogrowth:\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_log', FILEGROWTH = 0);","what":"Pré-agrandit un fichier de données/journal ou définit son incrément d'autogrowth/sa limite supérieure.","when":"Pour corriger des incréments de croissance petits et incontrôlés, pré-allouer de l'espace ou définir un MAXSIZE.","caution":"L'autogrowth en pourcentage produit des incréments très grands sur les gros fichiers ; utilisez des MB fixes. Sans Instant File Initialization, la croissance d'un fichier de données est bloquante. SIZE ne peut pas réduire un fichier (un shrink est nécessaire).","tags":["modify file","autogrowth","filegrowth","size","alter database","storage"]},{"id":"move-file","task":"Déplacer un fichier de données/journal vers un autre emplacement","category":"Stockage","command":"-- 1) Tell SQL Server the new path (nothing is moved yet):\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_data',\n             FILENAME = N'G:\\Data\\Customer_data.mdf');\n\n-- 2) Take the database offline:\nALTER DATABASE [Customer] SET OFFLINE WITH ROLLBACK IMMEDIATE;\n\n-- 3) Physically copy/move the file to the new path AT THE OPERATING SYSTEM level (G:\\Data\\...).\n\n-- 4) Bring it online:\nALTER DATABASE [Customer] SET ONLINE;","what":"Modifie l'emplacement physique d'un fichier de base de données : le nouveau chemin est défini dans les métadonnées, le fichier est déplacé physiquement pendant que la base est hors ligne, puis la base est remise en ligne.","when":"Lors du remplacement d'un disque ou d'un déplacement vers un autre lecteur pour gagner de l'espace.","caution":"OFFLINE + ROLLBACK IMMEDIATE coupe toutes les connexions actives ; prévoyez une interruption. Ne remettez pas la base ONLINE avant d'avoir réellement copié le fichier vers le nouveau chemin dans l'OS, sinon la base passe en SUSPECT. tempdb nécessite une autre méthode (MODIFY FILE + redémarrage du service SQL Server).","tags":["move file","move file","modify file","offline","online","mdf","ldf","storage"]},{"id":"add-tempdb-file","task":"Ajouter un fichier de données à tempdb","category":"Stockage","command":"-- Show current tempdb files:\nSELECT name, physical_name, size*8/1024 AS size_mb, growth\nFROM   sys.master_files WHERE database_id = DB_ID(N'tempdb');\n\n-- Add a new file (up to the number of CPU cores, equally sized):\nALTER DATABASE [tempdb]\nADD FILE (\n  NAME = N'tempdev2',\n  FILENAME = N'T:\\tempdb\\tempdb_mssql_2.ndf',\n  SIZE = 8GB, FILEGROWTH = 1GB\n);","what":"Ajoute un fichier de données supplémentaire à tempdb pour réduire la contention d'allocation (PFS/GAM/SGAM) et répartir les I/O.","when":"Sur les systèmes avec contention tempdb (attentes PAGELATCH_UP) ou forte utilisation de tempdb.","caution":"Tous les fichiers de données tempdb doivent avoir la MÊME taille et le même incrément de croissance (pour le proportional fill). Le fichier ajouté est utilisé immédiatement sans redémarrage ; si les fichiers existants diffèrent en taille, égalisez-les d'abord, sinon le nouveau fichier (vide) reçoit une charge disproportionnée. Le nombre de fichiers est généralement aligné sur le nombre de cœurs (jusqu'à 8) ; l'installation de SQL Server 2016+ le fait par défaut.","tags":["tempdb","add file","tempdb file","ndf","contention","pagelatch","storage"]},{"id":"index-rebuild-reorg","task":"Reconstruction / réorganisation d'index","category":"Maintenance","command":"-- Rebuild (high fragmentation > 30%). ONLINE requires Enterprise:\nALTER INDEX IX_Customer_Email ON dbo.Customer\n  REBUILD WITH (ONLINE = ON, MAXDOP = 4, SORT_IN_TEMPDB = ON);\n\n-- All indexes of a table:\nALTER INDEX ALL ON dbo.Customer REBUILD WITH (FILLFACTOR = 90);\n\n-- Reorganize (moderate fragmentation 5-30%, always online, can be stopped):\nALTER INDEX IX_Customer_Email ON dbo.Customer REORGANIZE;","what":"REBUILD recrée l'index de zéro (supprime complètement la fragmentation, actualise les statistiques) ; REORGANIZE compacte le niveau feuille sur place (léger, non bloquant).","when":"REORGANIZE pour 5-30 % de fragmentation, REBUILD au-delà de 30 %. Pendant les fenêtres de maintenance pour les grandes tables.","caution":"ONLINE = ON n'est disponible qu'en Enterprise/Developer ; utilisez OFF en Standard (la table est verrouillée). REBUILD génère beaucoup de journal et nécessite de l'espace. MAXDOP et SORT_IN_TEMPDB influent sur l'utilisation des ressources.","tags":["index","rebuild","reorganize","alter index","fragmentation","maintenance","online"]},{"id":"update-statistics","task":"Mettre à jour les statistiques","category":"Maintenance","command":"-- Single table, full scan:\nUPDATE STATISTICS dbo.Customer WITH FULLSCAN;\n\n-- Specific statistic, with sampling:\nUPDATE STATISTICS dbo.Customer IX_Customer_Email WITH SAMPLE 30 PERCENT;\n\n-- Bulk for the whole database (user tables), sampled:\nEXEC sys.sp_updatestats;","what":"Met à jour les statistiques de colonnes/index utilisées par l'optimiseur ; garantit des plans d'exécution et des estimations de cardinalité corrects.","when":"Après de gros changements/chargements de données, sur des régressions de plan ; une reconstruction d'index actualise déjà complètement les statistiques (inutile de le faire séparément).","caution":"FULLSCAN est précis mais coûteux ; il génère une charge I/O sur les grandes tables. sp_updatestats ne met à jour que les statistiques modifiées et utilise l'échantillonnage par défaut. Un nouvel UPDATE STATISTICS sur le même index après un REBUILD d'index est inutile.","tags":["update statistics","statistics","sp_updatestats","fullscan","optimizer","maintenance"]},{"id":"dbcc-checkdb","task":"Contrôle d'intégrité (DBCC CHECKDB)","category":"Maintenance","command":"-- Full integrity check:\nDBCC CHECKDB (N'Customer') WITH NO_INFOMSGS, ALL_ERRORMSGS;\n\n-- Lighter on large databases (physical consistency):\nDBCC CHECKDB (N'Customer') WITH PHYSICAL_ONLY, NO_INFOMSGS;","what":"Vérifie l'intégrité physique et logique de la base (structures de pages, allocation, cohérence des index) et signale la corruption.","when":"En maintenance régulière (hebdomadaire), en cas de suspicion de problème matériel/IO, après des erreurs 823/824.","caution":"Consomme beaucoup d'I/O et de tempdb ; exécutez-le dans une fenêtre de maintenance. Si des erreurs sont trouvées, REPAIR_ALLOW_DATA_LOSS doit être le DERNIER recours et PROVOQUE UNE PERTE DE DONNÉES ; envisagez d'abord une restauration depuis une bonne sauvegarde. PHYSICAL_ONLY est plus rapide mais saute les contrôles logiques.","tags":["dbcc checkdb","integrity","corruption","corruption","integrity","maintenance"]},{"id":"index-fragmentation","task":"Afficher la fragmentation des index","category":"Maintenance","command":"SELECT OBJECT_SCHEMA_NAME(ips.object_id) AS schema_name,\n       OBJECT_NAME(ips.object_id)        AS table_name,\n       i.name                            AS index_name,\n       ips.index_type_desc,\n       ROUND(ips.avg_fragmentation_in_percent,1) AS frag_pct,\n       ips.page_count\nFROM   sys.dm_db_index_physical_stats(DB_ID(N'Customer'), NULL, NULL, NULL, 'LIMITED') ips\n  JOIN sys.indexes i\n    ON i.object_id = ips.object_id AND i.index_id = ips.index_id\nWHERE  ips.avg_fragmentation_in_percent > 10\n  AND  ips.page_count > 1000\nORDER BY ips.avg_fragmentation_in_percent DESC;","what":"Liste le pourcentage de fragmentation et le nombre de pages des index ; base de la décision rebuild/reorganize.","when":"Avant la maintenance, pour décider quels index traiter.","caution":"La fragmentation est sans importance sur les petits index (< 1000 pages) ; ils sont filtrés. Le mode 'DETAILED' est plus précis mais très coûteux ; 'LIMITED' suffit généralement. Requête seule ; aucun risque.","tags":["fragmentation","dm_db_index_physical_stats","index","frag","maintenance","diagnostics"]},{"id":"kill-spid","task":"Tuer une session (SPID)","category":"Session","command":"-- First find the offending SPID:\nSELECT s.session_id, s.login_name, s.host_name, s.program_name,\n       r.status, r.command, r.wait_type, r.blocking_session_id,\n       DB_NAME(r.database_id) AS db\nFROM   sys.dm_exec_sessions s\n  LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id\nWHERE  s.is_user_process = 1;\n\n-- Kill (example SPID 63):\nKILL 63;\n\n-- Show rollback progress:\nKILL 63 WITH STATUSONLY;","what":"Termine le session_id (SPID) indiqué ; sa transaction ouverte est annulée.","when":"Pour arrêter une session qui détient des verrous, exécute une requête incontrôlée/longue ou ne libère pas sa connexion.","caution":"Sur une grosse transaction ouverte, le rollback peut durer longtemps et ne peut pas être annulé ; suivez la progression avec KILL WITH STATUSONLY. Ne tuez pas les SPID système (is_user_process=0). Vérifiez qu'il s'agit du bon SPID.","tags":["kill","spid","kill session","dm_exec_sessions","dm_exec_requests","session"]},{"id":"blocking-chain","task":"Trouver la chaîne de blocage (verrous)","category":"Session","command":"SELECT r.session_id            AS waiting_spid,\n       r.blocking_session_id  AS blocking_spid,\n       r.wait_type,\n       r.wait_time / 1000.0   AS wait_sec,\n       r.status, r.command,\n       DB_NAME(r.database_id) AS db,\n       t.text                 AS waiting_query\nFROM   sys.dm_exec_requests r\n  CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE  r.blocking_session_id <> 0\nORDER BY r.wait_time DESC;\n\n-- To find the head (root) blocker of the chain, look for the SPID whose\n-- own blocking_session_id is not set (it is not blocked itself).","what":"Liste les sessions en attente et les sessions qui les bloquent, avec le temps d'attente et la requête exécutée ; rend la chaîne de verrous visible.","when":"Pour trouver l'origine de la contention de verrous lors de plaintes du type \"l'application se fige / expire\".","caution":"Confirmez avec le responsable métier avant de tuer le bloqueur racine (le SPID qui n'est bloqué par personne mais en bloque d'autres). Requête seule ; aucun risque.","tags":["blocking","lock","lock","dm_exec_requests","blocking_session_id","session","chain"]},{"id":"active-queries","task":"Afficher les requêtes actives et leur coût","category":"Session","command":"SELECT r.session_id,\n       r.status,\n       r.command,\n       DB_NAME(r.database_id) AS db,\n       r.cpu_time,\n       r.total_elapsed_time / 1000 AS elapsed_sec,\n       r.reads, r.writes, r.logical_reads,\n       r.wait_type,\n       SUBSTRING(t.text,\n         (r.statement_start_offset/2)+1,\n         ((CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(t.text)\n            ELSE r.statement_end_offset END - r.statement_start_offset)/2)+1) AS running_statement\nFROM   sys.dm_exec_requests r\n  JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id\n  CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE  s.is_user_process = 1 AND r.session_id <> @@SPID\nORDER BY r.total_elapsed_time DESC;","what":"Affiche les requêtes utilisateur en cours avec CPU, temps écoulé, lectures/écritures et le texte exact de l'instruction.","when":"Pour trouver quelle requête consomme les ressources pendant un ralentissement en direct.","caution":"statement_start/end_offset extrait l'instruction exacte en cours (pas tout le batch). Requête seule ; aucun risque.","tags":["active queries","active query","dm_exec_requests","dm_exec_sql_text","cpu","session"]},{"id":"sp-configure-memory","task":"Configurer avec sp_configure (max server memory)","category":"Configuration","command":"-- Make advanced options visible:\nEXEC sys.sp_configure 'show advanced options', 1;\nRECONFIGURE;\n\n-- Set max server memory (MB). Example: allow 24 GB:\nEXEC sys.sp_configure 'max server memory (MB)', 24576;\nRECONFIGURE;\n\n-- Verify:\nEXEC sys.sp_configure 'max server memory (MB)';","what":"Plafonne la mémoire maximale que SQL Server peut utiliser pour le buffer pool ; laisse de la RAM au système d'exploitation et aux autres composants.","when":"Quand l'OS/les autres services du serveur manquent de mémoire, ou après ajout de RAM.","caution":"Laissez assez de RAM au système d'exploitation, à SSAS/SSIS, au CLR et aux piles de threads (généralement au moins 4-6 GB pour l'OS). Une valeur trop basse paralyse les performances de SQL Server. La modification n'est pas appliquée sans RECONFIGURE ; ce paramètre est dynamique (pas de redémarrage nécessaire).","tags":["sp_configure","max server memory","reconfigure","memory","configuration","memory"]},{"id":"reconfigure-general","task":"Paramètre sp_configure général et RECONFIGURE","category":"Configuration","command":"EXEC sys.sp_configure 'show advanced options', 1;\nRECONFIGURE;\n\n-- Example: cost threshold for parallelism and MAXDOP:\nEXEC sys.sp_configure 'cost threshold for parallelism', 50;\nEXEC sys.sp_configure 'max degree of parallelism', 4;\nRECONFIGURE;\n\n-- Show all configured/running values:\nSELECT name, value AS configured, value_in_use AS running, is_dynamic, is_advanced\nFROM   sys.configurations\nORDER BY name;","what":"Définit les valeurs de configuration au niveau serveur ; RECONFIGURE les applique à la valeur en cours.","when":"Pour modifier des paramètres serveur comme MAXDOP, le seuil de parallélisme ou optimize for ad hoc workloads.","caution":"Les paramètres avec is_dynamic = 0 nécessitent un redémarrage de SQL Server. RECONFIGURE WITH OVERRIDE ignore la validation pour certains paramètres ; utilisez-le avec prudence. Notez value_in_use avant toute modification.","tags":["sp_configure","reconfigure","maxdop","cost threshold","sys.configurations","configuration"]},{"id":"ag-failover","task":"Basculement d'un Availability Group","category":"Configuration","command":"-- Run on the TARGET (secondary) REPLICA - planned, no data loss (synchronous):\nALTER AVAILABILITY GROUP [AG_Customer] FAILOVER;\n\n-- Forced (asynchronous replica / primary unreachable, DATA LOSS risk):\n-- ALTER AVAILABILITY GROUP [AG_Customer] FORCE_FAILOVER_ALLOW_DATA_LOSS;\n\n-- Check status:\nSELECT ag.name, ar.replica_server_name,\n       rs.role_desc, rs.synchronization_health_desc, ar.availability_mode_desc\nFROM   sys.availability_groups ag\n  JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id\n  JOIN sys.dm_hadr_availability_replica_states rs ON rs.replica_id = ar.replica_id;","what":"Déplace le rôle primaire de l'Availability Group vers un autre réplica ; un basculement planifié vers un réplica synchrone se fait sans perte de données.","when":"Transfert de rôle pour maintenance/correctifs, basculement d'urgence quand le nœud primaire tombe.","caution":"La commande FAILOVER normale s'exécute sur le réplica SECONDAIRE qui prendra le relais. FORCE_FAILOVER_ALLOW_DATA_LOSS ne s'utilise que lorsque le primaire est injoignable ou sur un réplica asynchrone et peut PROVOQUER UNE PERTE DE DONNÉES ; soyez très prudent si la synchronisation n'est pas saine.","tags":["availability group","ag","failover","hadr","failover","alwayson","configuration"]},{"id":"change-recovery-model","task":"Changer le mode de récupération de la base","category":"Configuration","command":"-- Show the current model:\nSELECT name, recovery_model_desc FROM sys.databases WHERE name = N'Customer';\n\n-- FULL recovery (for point-in-time recovery):\nALTER DATABASE [Customer] SET RECOVERY FULL;\n\n-- SIMPLE recovery (log truncated automatically, no log backups needed):\n-- ALTER DATABASE [Customer] SET RECOVERY SIMPLE;\n\n-- BULK_LOGGED (temporary, for large bulk operations):\n-- ALTER DATABASE [Customer] SET RECOVERY BULK_LOGGED;","what":"Change le mode de récupération de la base ; détermine la gestion du journal et les options de récupération disponibles.","when":"FULL : quand la restauration à un instant donné/les sauvegardes du journal/AG sont nécessaires. SIMPLE : sur les systèmes sans besoin de point de récupération (dev/test, DW).","caution":"Après le passage en FULL, effectuez une sauvegarde FULL pour démarrer la chaîne de journaux et mettez en place des sauvegardes régulières du journal, sinon le journal continue de grossir. Passer de FULL à SIMPLE rompt la chaîne de journaux ; vous perdez la restauration à un instant donné. Les membres d'un AG doivent être en mode de récupération FULL.","tags":["recovery model","recovery full","simple","bulk_logged","alter database","configuration"]}]},"recipes/mssql-ext.json":{"engineId":"mssql","engine":"SQL Server","order":2,"recipes":[{"id":"ag-health-lag","task":"Surveiller la santé et le retard d'un availability group (log send / redo queue, RPO)","category":"Always On et HA","command":"-- Replica health (run on the primary to see all replicas):\nSELECT ag.name AS ag_name, ar.replica_server_name, ars.role_desc,\n       ar.availability_mode_desc, ar.failover_mode_desc,\n       ars.connected_state_desc, ars.synchronization_health_desc,\n       ars.operational_state_desc, ars.last_connect_error_description\nFROM   sys.availability_groups ag\n  JOIN sys.availability_replicas ar                ON ar.group_id = ag.group_id\n  JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id\nORDER BY ag.name, ars.role_desc;\n\n-- Per-database lag: log send queue (RPO), redo queue (RTO):\nSELECT ag.name AS ag_name, ar.replica_server_name, DB_NAME(drs.database_id) AS db,\n       drs.is_primary_replica, drs.synchronization_state_desc, drs.synchronization_health_desc,\n       drs.is_suspended, drs.suspend_reason_desc,\n       drs.log_send_queue_size  AS log_send_queue_kb,\n       drs.log_send_rate        AS log_send_kb_s,\n       drs.redo_queue_size      AS redo_queue_kb,\n       drs.redo_rate            AS redo_kb_s,\n       CAST(drs.redo_queue_size * 1.0 / NULLIF(drs.redo_rate, 0) AS DECIMAL(18,1)) AS est_redo_catchup_s,\n       drs.last_commit_time,\n       DATEDIFF(SECOND, drs.last_commit_time, pri.last_commit_time) AS data_loss_window_s,\n       drs.secondary_lag_seconds\nFROM   sys.dm_hadr_database_replica_states drs\n  JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id\n  JOIN sys.availability_groups ag   ON ag.group_id = drs.group_id\n  LEFT JOIN sys.dm_hadr_database_replica_states pri\n         ON pri.group_database_id = drs.group_database_id AND pri.is_primary_replica = 1\nORDER BY ag.name, db, drs.is_primary_replica DESC;\n\n-- WSFC quorum state:\nSELECT cluster_name, quorum_type_desc, quorum_state_desc FROM sys.dm_hadr_cluster;\nSELECT member_name, member_type_desc, member_state_desc, number_of_quorum_votes\nFROM   sys.dm_hadr_cluster_members;","what":"Affiche la santé de connexion et de synchronisation des réplicas ; par base, la log send queue, la redo queue, les débits d'envoi/redo, le temps estimé de rattrapage du redo et l'écart de dernier commit avec le primaire (fenêtre potentielle de perte de données) ; renvoie aussi l'état du quorum WSFC.","when":"Sur une alerte NOT_HEALTHY / NOT SYNCHRONIZING dans le tableau de bord AG, lors de la vérification du RPO/RTO avant un basculement planifié, ou quand les utilisateurs signalent des données obsolètes sur un secondaire lisible.","caution":"Une log_send_queue qui grossit représente la quantité de journal perdue si le primaire tombe (RPO) ; une redo_queue qui grossit représente le temps nécessaire pour que la base soit en ligne après le basculement (RTO). Sur un secondaire, seul le réplica local est visible ; exécutez-la sur le primaire pour la vue complète. data_loss_window_s n'a de sens que tant qu'il y a du trafic en écriture. Requête en lecture seule ; sûre.","since":"2016","note":"secondary_lag_seconds est une colonne 2016+. Si la redo queue ne cesse de grossir, vérifiez si le thread de redo sur le secondaire est bloqué (longues requêtes de lecture, DDL) et recherchez les attentes REDO_THREAD_PENDING_WORK / PARALLEL_REDO_*.","tags":["availability group","ag","log send queue","redo queue","synchronization","rpo"],"untested":true},{"id":"ag-forced-failover-dr","task":"Basculement forcé (FORCE_FAILOVER_ALLOW_DATA_LOSS) et reprise après basculement","category":"Always On et HA","command":"-- 1) On each candidate secondary: pick the most current one (highest end_of_log_lsn)\nSELECT ag.name AS ag_name, DB_NAME(drs.database_id) AS db,\n       drs.synchronization_state_desc, drs.end_of_log_lsn, drs.last_commit_time\nFROM   sys.dm_hadr_database_replica_states drs\n  JOIN sys.availability_groups ag ON ag.group_id = drs.group_id\nWHERE  drs.is_local = 1;\n\n-- 2) On the chosen replica (while the primary is unreachable):\nALTER AVAILABILITY GROUP [AG_Musteri] FORCE_FAILOVER_ALLOW_DATA_LOSS;\n\n-- 3) On the other secondaries, resume the suspended data movement:\nALTER DATABASE [Musteri] SET HADR RESUME;\n\n-- 4) When the old primary returns (now a SUSPENDED secondary):\n--    take a snapshot first to inspect the lost transactions, then resume\nCREATE DATABASE [Musteri_lost_snap]\n  ON (NAME = N'Musteri', FILENAME = N'D:\\Data\\Musteri_lost_snap.ss')\n  AS SNAPSHOT OF [Musteri];\nALTER DATABASE [Musteri] SET HADR RESUME;","what":"Fait d'un réplica DR le primaire en acceptant une perte de données, quand le primaire est entièrement perdu ; reprend ensuite le mouvement de données suspendu sur les autres réplicas et, au retour de l'ancien primaire, prend un snapshot pour examiner les transactions qui n'ont jamais atteint le nouveau primaire.","when":"Quand le data center primaire est hors service, qu'il ne reste qu'un réplica asynchrone, ou qu'un réplica synchrone n'est pas SYNCHRONIZED et qu'un basculement reste nécessaire.","caution":"PERTE DE DONNÉES : les transactions postérieures au dernier LSN durci sont perdues. Obtenez d'abord la validation du RPO par le métier. Après un basculement forcé, toutes les bases secondaires sont SUSPENDED ; le journal n'est pas tronqué et continue de grossir jusqu'à HADR RESUME. Si le quorum WSFC est perdu, démarrez d'abord le cluster avec un quorum forcé (PowerShell : Start-ClusterNode -FixQuorum), sinon la commande échoue. HADR RESUME sur l'ancien primaire supprime définitivement les transactions perdues ; ne reprenez pas sans avoir pris le snapshot.","note":"Pour un basculement planifié sans perte de données, utilisez la commande FAILOVER de la procédure ag-failover. Les AG avec CLUSTER_TYPE = NONE / EXTERNAL utilisent aussi FORCE_FAILOVER_ALLOW_DATA_LOSS ; en EXTERNAL (Pacemaker), le basculement est piloté par le gestionnaire de cluster. Un snapshot nécessite chaque fichier de données logique de la source ; pour les bases multi-fichiers, ajoutez une entrée ON par fichier de données.","tags":["force failover","allow data loss","dr","availability group","hadr resume","data loss"],"untested":true},{"id":"ag-add-db-autoseed","task":"Ajouter une base à un AG (automatic seeding) et suivre la progression du seeding","category":"Always On et HA","command":"-- Prerequisite (primary): FULL recovery and at least one FULL backup\nSELECT name, recovery_model_desc FROM sys.databases WHERE name = N'Musteri';\nBACKUP DATABASE [Musteri] TO DISK = N'D:\\Backup\\Musteri_pre_ag.bak' WITH COMPRESSION, CHECKSUM, INIT;\n\n-- 1) Primary: switch the replica to automatic seeding\nALTER AVAILABILITY GROUP [AG_Musteri]\n  MODIFY REPLICA ON N'SQL02' WITH (SEEDING_MODE = AUTOMATIC);\n\n-- 2) On each secondary: allow the AG to create databases\nALTER AVAILABILITY GROUP [AG_Musteri] GRANT CREATE ANY DATABASE;\n\n-- 3) Primary: add the database\nALTER AVAILABILITY GROUP [AG_Musteri] ADD DATABASE [Musteri];\n\n-- 4) Progress (primary or secondary):\nSELECT local_database_name, role_desc, internal_state_desc,\n       transferred_size_bytes / 1048576         AS transferred_mb,\n       database_size_bytes / 1048576            AS db_mb,\n       transfer_rate_bytes_per_second / 1048576 AS mb_s,\n       estimate_time_complete_utc, failure_message\nFROM   sys.dm_hadr_physical_seeding_stats;\n\n-- 5) Seeding history and failure reason:\nSELECT s.start_time, s.completion_time, adc.database_name, ar.replica_server_name,\n       s.current_state, s.failure_state_desc, s.error_code, s.number_of_attempts\nFROM   sys.dm_hadr_automatic_seeding s\n  JOIN sys.availability_databases_cluster adc ON adc.group_database_id = s.ag_db_id\n  JOIN sys.availability_replicas ar           ON ar.replica_id = s.ag_remote_replica_id\nORDER BY s.start_time DESC;","what":"Ajoute la base à l'AG en la diffusant vers les secondaires via l'endpoint (VDI) sans sauvegarde/restauration manuelle ; affiche le débit du seeding, l'achèvement estimé et le motif d'échec.","when":"Lors de l'ajout d'une nouvelle base à un AG, de l'ajout d'un nouveau réplica ou de la reconstruction d'une base secondaire cassée.","caution":"Le seeding sature le lien de l'endpoint et n'est pas compressé par défaut ; pour les très grandes bases ou un WAN faible, une sauvegarde/restauration manuelle + JOIN est plus prévisible. Le journal du primaire n'est pas tronqué pendant le seeding (log_reuse_wait_desc = AVAILABILITY_REPLICA). En 2016, le secondaire doit avoir les mêmes chemins de dossiers données/journal que le primaire ; en 2017+, si les fichiers du primaire sont dans les chemins par défaut de l'instance, le secondaire utilise ses propres chemins par défaut. Pour une base TDE, le certificat doit d'abord être restauré sur chaque secondaire. Sans GRANT CREATE ANY DATABASE sur le secondaire, le seeding reste en attente.","since":"2016","note":"La compression du seeding s'active avec le trace flag 9567 (coûte du CPU). Le début, la fin et les échecs du seeding sont écrits dans le journal d'erreurs de SQL Server ; pour un suivi détaillé, créez une session Extended Events séparée avec les événements hadr_automatic_seeding_* et hadr_physical_seeding_*. Revenez en arrière avec SEEDING_MODE = MANUAL.","tags":["automatic seeding","availability group","add database","seeding_mode","dm_hadr_physical_seeding_stats","ag"],"untested":true},{"id":"ag-listener-routing","task":"Vérifier le listener, ses IP et la configuration du read-only routing","category":"Always On et HA","command":"-- Listener, port and IP states:\nSELECT ag.name AS ag_name, l.dns_name, l.port, l.is_conformant,\n       ip.ip_address, ip.ip_subnet_mask, ip.state_desc\nFROM   sys.availability_group_listeners l\n  JOIN sys.availability_groups ag ON ag.group_id = l.group_id\n  LEFT JOIN sys.availability_group_listener_ip_addresses ip ON ip.listener_id = l.listener_id;\n\n-- Read-only routing list: where read intent goes when X is primary:\nSELECT ag.name AS ag_name, pr.replica_server_name AS when_primary_is,\n       rl.routing_priority, sr.replica_server_name AS route_to,\n       sr.read_only_routing_url, sr.secondary_role_allow_connections_desc\nFROM   sys.availability_read_only_routing_lists rl\n  JOIN sys.availability_replicas pr ON pr.replica_id = rl.replica_id\n  JOIN sys.availability_replicas sr ON sr.replica_id = rl.read_only_replica_id\n  JOIN sys.availability_groups ag   ON ag.group_id = pr.group_id\nORDER BY ag.name, when_primary_is, rl.routing_priority;\n\n-- Replica endpoint and connection settings:\nSELECT replica_server_name, endpoint_url, read_only_routing_url,\n       secondary_role_allow_connections_desc, seeding_mode_desc, session_timeout\nFROM   sys.availability_replicas;\n\n-- Routing test from the command line (-d is required with -K ReadOnly):\n-- sqlcmd -S tcp:AGL-Musteri,1433 -d Musteri -E -M -K ReadOnly -Q \"SELECT @@SERVERNAME\"","what":"Affiche le nom DNS du listener, le port et l'état cluster de chaque IP de sous-réseau ; pour chaque scénario de primaire de la liste de read-only routing, vers quel réplica et quelle URL les connexions read-intent sont routées.","when":"Quand les applications ne peuvent pas se connecter après un basculement, que les connexions ApplicationIntent=ReadOnly arrivent sur le primaire, ou qu'un nouveau sous-réseau/réplica a été ajouté.","caution":"Le read-only routing ne fonctionne que pour les connexions passant par le listener avec ApplicationIntent=ReadOnly et une base explicite ; les connexions directes à un nom d'instance ne sont pas routées. Un réplica dont read_only_routing_url est vide ne peut pas être une cible de routage. Sur un listener multi-sous-réseaux, les clients sans MultiSubnetFailover=True subissent des timeouts de connexion de 20-30 s après un basculement. Requête en lecture seule ; sûre.","note":"Pour l'équilibrage de charge 2016+, la liste de routage utilise des parenthèses imbriquées : READ_ONLY_ROUTING_LIST = (('SQL02','SQL03'),'SQL01'). Si les IP du listener sont OFFLINE, vérifiez les dépendances de ressources côté cluster (Failover Cluster Manager / Get-ClusterResource).","tags":["listener","read-only routing","applicationintent","multisubnetfailover","availability group","connection"],"untested":true},{"id":"logshipping-monitor","task":"Surveiller l'état et la latence du log shipping","category":"Always On et HA","command":"-- Summary (on the primary, secondary or monitor server):\nEXEC master.sys.sp_help_log_shipping_monitor;\n\n-- PRIMARY: when the last log backup was taken:\nSELECT p.primary_server, p.primary_database,\n       p.last_backup_date, p.last_backup_file,\n       DATEDIFF(MINUTE, p.last_backup_date, GETDATE()) AS backup_age_min,\n       p.backup_threshold                              AS threshold_min\nFROM   msdb.dbo.log_shipping_monitor_primary p;\n\n-- SECONDARY: copy and restore latency:\nSELECT s.secondary_server, s.secondary_database, s.primary_server,\n       s.last_copied_date, s.last_copied_file,\n       s.last_restored_date, s.last_restored_file,\n       s.last_restored_latency                           AS restore_latency_min,\n       DATEDIFF(MINUTE, s.last_restored_date, GETDATE()) AS since_last_restore_min,\n       s.restore_threshold                               AS threshold_min\nFROM   msdb.dbo.log_shipping_monitor_secondary s;\n\n-- Errors in the last 24 hours:\nSELECT e.database_name, e.agent_type, e.log_time, e.message\nFROM   msdb.dbo.log_shipping_monitor_error_detail e\nWHERE  e.log_time >= DATEADD(HOUR, -24, GETDATE())\nORDER BY e.log_time DESC;\n\n-- Secondary DB mode (RESTORING = NORECOVERY, is_in_standby = STANDBY):\nSELECT name, state_desc, is_in_standby FROM sys.databases WHERE name = N'Musteri';","what":"Affiche l'âge de la dernière sauvegarde du journal sur le primaire, le dernier fichier copié/restauré et la latence de restauration sur le secondaire avec leurs seuils, ainsi que les erreurs des jobs de sauvegarde/copie/restauration des dernières 24 heures.","when":"Quand le job d'alerte du log shipping se déclenche (erreurs 14420/14421), avant un test DR, ou pour indiquer le retard du secondaire.","caution":"Les tables de supervision ne contiennent que le côté vu par le serveur : la table primaire est alimentée sur le primaire/moniteur, la table secondaire sur le secondaire/moniteur. Une sauvegarde manuelle du journal (hors log shipping) rompt la chaîne et le job de restauration échoue en boucle ; utilisez COPY_ONLY pour les sauvegardes ponctuelles du journal. En mode STANDBY, les connexions utilisateur sont coupées avant chaque restauration. Requête en lecture seule ; sûre.","note":"Les colonnes de date sont en heure locale du serveur ; utilisez les colonnes *_utc pour comparer des serveurs dans des fuseaux horaires différents. Si le job de restauration journalise \"skipping log backup file\", ce fichier a déjà été appliqué ; pour trouver un vrai trou, vérifiez la séquence FirstLSN/LastLSN avec RESTORE HEADERONLY sur le secondaire.","tags":["log shipping","sp_help_log_shipping_monitor","restore latency","dr","secondary","latency"],"untested":true},{"id":"backup-copy-only","task":"Effectuer une sauvegarde ponctuelle sans rompre la chaîne (COPY_ONLY, striping, débit)","category":"Sauvegarde et journal","command":"-- Ad-hoc FULL that does not reset the differential base:\nBACKUP DATABASE [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_copyonly_20261008.bak'\nWITH COPY_ONLY, COMPRESSION, CHECKSUM, INIT, STATS = 10;\n\n-- Ad-hoc log backup that does not truncate the log:\nBACKUP LOG [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_copyonly_20261008.trn'\nWITH COPY_ONLY, COMPRESSION, CHECKSUM, INIT;\n\n-- Large DB: stripe across files and tune buffers:\nBACKUP DATABASE [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_s1.bak', DISK = N'E:\\Backup\\Musteri_s2.bak'\nWITH COPY_ONLY, COMPRESSION, CHECKSUM, INIT,\n     MAXTRANSFERSIZE = 4194304, BUFFERCOUNT = 64, STATS = 5;","what":"Effectue une sauvegarde FULL ou du journal autonome qui n'affecte pas la chaîne de sauvegarde régulière (base différentielle et chaîne de journaux) ; pour les grandes bases, le striping sur plusieurs fichiers et une taille de transfert plus grande raccourcissent la sauvegarde.","when":"Pour remettre une copie aux équipes test/dev, avant une modification risquée, ou quand une sauvegarde est demandée pour un audit ; chaque fois qu'il faut une sauvegarde sans toucher à la stratégie de sauvegarde planifiée.","caution":"Une FULL ponctuelle sans COPY_ONLY devient la base des DIFF suivantes ; si ce fichier est supprimé, les DIFF sont inutilisables. COPY_ONLY n'a aucun effet avec DIFFERENTIAL. La restauration d'une sauvegarde en stripes nécessite tous les fichiers ; en perdre un rend toute la sauvegarde inutilisable. BUFFERCOUNT x MAXTRANSFERSIZE de mémoire est alloué hors du buffer pool (256 MB dans l'exemple) ; réduisez-le sur un serveur sous pression mémoire.","note":"Pour une base TDE, la compression de sauvegarde nécessite un MAXTRANSFERSIZE supérieur à 65536 (2016+). Valeurs par défaut du serveur : sp_configure 'backup compression default' et 'backup checksum default'. En 2022 avec du matériel Intel QAT, WITH COMPRESSION (ALGORITHM = QAT_DEFLATE) est disponible.","tags":["copy_only","ad-hoc backup","stripe","maxtransfersize","buffercount","backup"]},{"id":"backup-to-url","task":"Sauvegarde vers URL (Azure Blob SAS / stockage compatible S3)","category":"Sauvegarde et journal","command":"-- A) Azure Blob: SAS credential (named after the container URL, SECRET without a leading '?')\nCREATE CREDENTIAL [https://dbabackup.blob.core.windows.net/sql]\nWITH IDENTITY = N'SHARED ACCESS SIGNATURE',\n     SECRET   = N'sv=2022-11-02&ss=b&srt=co&sp=rwdlac&se=2027-01-01T00:00:00Z&sig=XXXX';\n\nBACKUP DATABASE [Musteri]\nTO URL = N'https://dbabackup.blob.core.windows.net/sql/Musteri_full_1.bak',\n   URL = N'https://dbabackup.blob.core.windows.net/sql/Musteri_full_2.bak'\nWITH COMPRESSION, CHECKSUM, MAXTRANSFERSIZE = 4194304, BLOCKSIZE = 65536, STATS = 10;\n\n-- B) S3-compatible storage (2022+):\nCREATE CREDENTIAL [s3://s3.eu-central-1.amazonaws.com/dba-backup/sql]\nWITH IDENTITY = N'S3 Access Key',\n     SECRET   = N'<AccessKeyID>:<SecretKeyID>';\n\nBACKUP DATABASE [Musteri]\nTO URL = N's3://s3.eu-central-1.amazonaws.com/dba-backup/sql/Musteri_full.bak'\nWITH COMPRESSION, CHECKSUM, FORMAT, STATS = 10;\n\n-- Verify (uses the same credential):\nRESTORE VERIFYONLY\nFROM URL = N's3://s3.eu-central-1.amazonaws.com/dba-backup/sql/Musteri_full.bak'\nWITH CHECKSUM;","what":"Écrit la sauvegarde directement dans un stockage objet : vers Azure Blob en block blobs avec un credential SAS, ou en 2022 vers un stockage compatible API S3 (AWS S3, MinIO etc.) avec un credential à clé d'accès. Le credential est choisi automatiquement par correspondance avec le préfixe de l'URL.","when":"Pour les exigences de sauvegarde hors site, les scénarios de migration cloud/DR hybride, ou quand le stockage local de sauvegarde est insuffisant.","caution":"Sur Azure, un block blob contient au plus 50,000 blocs ; avec un MAXTRANSFERSIZE de 4 MB, cela fait environ 195 GB par fichier, répartissez donc les grandes bases sur plusieurs URL. Sur S3, un fichier a au plus 10,000 parties ; pour les grandes bases, augmentez MAXTRANSFERSIZE (jusqu'à 20 MB, nécessite COMPRESSION) et répartissez sur plusieurs URL (jusqu'à 64). S3 ne prend pas en charge INIT/append ; FORMAT écrase une sauvegarde existante du même nom. À l'expiration du SAS, les jobs de sauvegarde échouent avec une erreur ; inscrivez la date d'expiration au calendrier. Le secret du credential est stocké dans master ; les principaux disposant de CONTROL SERVER peuvent l'utiliser.","since":"2022","note":"La sauvegarde en block blob avec SAS est 2016+, la prise en charge de S3 est 2022+. L'ancienne méthode par clé du compte de stockage (IDENTITY = nom du compte) produit des page blobs et n'est pas recommandée. Pour S3, le certificat TLS de l'endpoint doit être approuvé par le serveur ; pour les solutions on-prem comme MinIO, le port figure dans l'URL (s3://minio.local:9000/bucket/...).","tags":["backup to url","azure blob","s3","credential","sas","cloud backup"],"untested":true},{"id":"backup-history-msdb","task":"Historique des sauvegardes depuis msdb et contrôle des sauvegardes manquantes","category":"Sauvegarde et journal","command":"-- Last FULL / DIFF / LOG per database (never backed up first):\nSELECT d.name AS db,\n       d.recovery_model_desc,\n       MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS last_full,\n       MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS last_diff,\n       MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS last_log,\n       DATEDIFF(MINUTE, MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END), GETDATE()) AS log_age_min\nFROM   sys.databases d\n  LEFT JOIN msdb.dbo.backupset b\n         ON b.database_name = d.name AND b.is_copy_only = 0\nWHERE  d.name <> N'tempdb'\nGROUP BY d.name, d.recovery_model_desc\nORDER BY last_full;\n\n-- Chain for one DB over the last 7 days (LSN, size, file):\nSELECT b.backup_start_date, b.backup_finish_date, b.type, b.is_copy_only,\n       b.first_lsn, b.last_lsn, b.database_backup_lsn,\n       CAST(b.backup_size / 1048576.0 AS DECIMAL(12,1))            AS size_mb,\n       CAST(b.compressed_backup_size / 1048576.0 AS DECIMAL(12,1)) AS compressed_mb,\n       b.has_backup_checksums, mf.physical_device_name\nFROM   msdb.dbo.backupset b\n  JOIN msdb.dbo.backupmediafamily mf ON mf.media_set_id = b.media_set_id\nWHERE  b.database_name = N'Musteri'\n  AND  b.backup_start_date >= DATEADD(DAY, -7, GETDATE())\nORDER BY b.backup_start_date;","what":"Liste pour chaque base l'heure de la dernière sauvegarde FULL/DIFF/LOG et l'âge de la sauvegarde du journal ; pour une base, la chaîne de sauvegarde avec la séquence LSN, la taille compressée, l'indicateur checksum et le chemin du fichier.","when":"Lors du contrôle du matin, pour savoir quels fichiers une restauration nécessite, pour répondre à \"à quand remonte la dernière sauvegarde\" lors d'un audit, ou pour voir si les sauvegardes du journal tournent pendant une alerte de journal plein.","caution":"msdb ne connaît que les sauvegardes prises par cette instance ; celles prises sur un autre serveur ou un autre réplica de l'AG ne sont pas visibles (vérifiez chaque réplica d'un AG). Le first_lsn d'une sauvegarde du journal doit être égal au last_lsn de la précédente ; un écart signifie que la chaîne est rompue. En récupération FULL, un last_log NULL signifie que le journal n'est jamais sauvegardé et va continuer de grossir. Requête en lecture seule ; sûre.","note":"L'historique msdb grossit sans limite et ralentit les jobs de sauvegarde ; purgez-le régulièrement avec EXEC msdb.dbo.sp_delete_backuphistory @oldest_date = '2026-01-01';. Valeurs de type : D = FULL, I = DIFF, L = LOG, F = fichier/filegroup.","tags":["backupset","msdb","backup history","last backup","lsn","backup chain"]},{"id":"backup-restore-progress","task":"Voir la progression et l'achèvement estimé d'une sauvegarde / restauration / d'un CHECKDB","category":"Sauvegarde et journal","command":"SELECT r.session_id, r.command, DB_NAME(r.database_id) AS db,\n       CAST(r.percent_complete AS DECIMAL(5,1))                     AS pct,\n       DATEADD(SECOND, r.estimated_completion_time / 1000, GETDATE()) AS eta,\n       r.total_elapsed_time / 60000                                 AS elapsed_min,\n       r.wait_type, r.wait_time, r.blocking_session_id\nFROM   sys.dm_exec_requests r\nWHERE  r.percent_complete > 0;\n\n-- Restore phases and throughput are in the error log:\nEXEC sys.xp_readerrorlog 0, 1, N'Restore';","what":"Affiche le pourcentage de progression, l'heure de fin estimée et l'attente en cours pour les opérations longues qui rapportent percent_complete (BACKUP, RESTORE, DBCC CHECKDB/SHRINK, ALTER INDEX REORGANIZE, rollback après KILL).","when":"Quand la sauvegarde nocturne déborde sur les heures ouvrées, pour répondre à \"quand cela va-t-il finir\" pendant une grosse restauration, ou pour suivre le rollback d'une session tuée.","caution":"L'estimation suppose une progression linéaire ; pendant les phases redo/undo d'une restauration et pendant l'initialisation à zéro du journal, le pourcentage peut stagner longtemps. Les fichiers journaux ne bénéficient pas de l'instant file initialization (sauf les croissances jusqu'à 64 MB en 2022) ; les restaurations avec de gros journaux passent longtemps dans la première phase. Requête en lecture seule ; sûre.","note":"Si wait_type est surtout BACKUPIO / ASYNC_IO_COMPLETION, le goulot est le disque cible/source ; un BACKUPTHREAD ou CPU élevé indique le coût de la compression. Pour voir en détail dans le journal d'erreurs la longue phase de mise à zéro d'une restauration, utilisez les trace flags 3004 + 3605.","tags":["percent_complete","backup progress","restore progress","eta","dm_exec_requests","progress"]},{"id":"tail-log-backup","task":"Effectuer une sauvegarde de fin du journal avant une restauration (NORECOVERY / NO_TRUNCATE)","category":"Restauration et récupération","command":"-- A) Healthy DB: back up the tail and put the DB into RESTORING (no new writes):\nBACKUP LOG [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_tail.trn'\nWITH NORECOVERY, CHECKSUM, INIT;\n\n-- B) Damaged DB / data file lost, log file intact:\n-- BACKUP LOG [Musteri]\n-- TO DISK = N'D:\\Backup\\Musteri_tail_damaged.trn'\n-- WITH NO_TRUNCATE, CONTINUE_AFTER_ERROR, INIT;\n\nSELECT name, state_desc FROM sys.databases WHERE name = N'Musteri';","what":"Sauvegarde la partie du journal écrite depuis la dernière sauvegarde du journal (la fin), afin que la chaîne de restauration atteigne le moment de la panne et que les dernières minutes de transactions ne soient pas perdues.","when":"Avant de restaurer par-dessus la même base, quand le disque des fichiers de données tombe en panne, ou lors de la bascule finale d'une migration.","caution":"NORECOVERY place la base en RESTORING et coupe l'accès des utilisateurs ; ne l'exécutez pas sur la mauvaise base. Restaurer WITH REPLACE sans sauvegarde de fin du journal supprime définitivement les transactions postérieures à la dernière sauvegarde du journal. Une sauvegarde prise avec NO_TRUNCATE peut contenir des métadonnées incomplètes ; exécutez DBCC CHECKDB après la restauration. Une sauvegarde de fin du journal n'est pas possible en récupération SIMPLE.","note":"Pour sortir la base de RESTORING sans restauration : RESTORE DATABASE [Musteri] WITH RECOVERY;. NO_TRUNCATE équivaut à COPY_ONLY + CONTINUE_AFTER_ERROR et tente de lire le journal même si la base ne peut pas être ouverte.","tags":["tail-log","backup log","norecovery","no_truncate","restore","recovery"]},{"id":"restore-pitr-stopat","task":"Restaurer à un instant donné (point-in-time restore, STOPAT)","category":"Restauration et récupération","command":"-- Restore under a side name, keep the original intact:\nRESTORE DATABASE [Musteri_PITR]\nFROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak'\nWITH MOVE N'Musteri'     TO N'D:\\Data\\Musteri_PITR.mdf',\n     MOVE N'Musteri_log' TO N'D:\\Log\\Musteri_PITR_log.ldf',\n     NORECOVERY, STATS = 10;\n\nRESTORE DATABASE [Musteri_PITR]\nFROM DISK = N'D:\\Backup\\Musteri_diff_20261008.bak' WITH NORECOVERY;\n\n-- All log backups in order, each with the same STOPAT:\nRESTORE LOG [Musteri_PITR] FROM DISK = N'D:\\Backup\\Musteri_log_1200.trn'\nWITH NORECOVERY, STOPAT = '2026-10-08T14:29:00';\nRESTORE LOG [Musteri_PITR] FROM DISK = N'D:\\Backup\\Musteri_log_1500.trn'\nWITH NORECOVERY, STOPAT = '2026-10-08T14:29:00';\n\nRESTORE DATABASE [Musteri_PITR] WITH RECOVERY;\n\n-- Alternative: step forward with STANDBY and read data at each point:\n-- RESTORE LOG [Musteri_PITR] FROM DISK = N'...' WITH STANDBY = N'D:\\Backup\\Musteri_PITR_undo.dat', STOPAT = '...';","what":"Applique FULL + DIFF (facultative) + sauvegardes du journal WITH NORECOVERY et arrête le rejeu du journal à l'heure indiquée ; ramène la base juste avant l'opération fautive (DELETE/UPDATE erroné, DROP).","when":"Pour récupérer des données supprimées/modifiées par erreur, examiner les données à un moment précis, ou pour un audit.","caution":"Restaurez sous un autre nom plutôt que par-dessus l'original, puis recopiez les lignes nécessaires ; si vous devez restaurer par-dessus, effectuez d'abord une sauvegarde de fin du journal. STOPAT est en heure locale du serveur. Le mode de récupération doit être FULL ; si la sauvegarde du journal contenant l'heure STOPAT inclut des opérations à journalisation minimale sous BULK_LOGGED, vous ne pouvez pas vous arrêter à l'intérieur de cette sauvegarde. Après RESTORE ... WITH RECOVERY, aucun autre journal ne peut être appliqué.","note":"Indiquer STOPAT sur chaque restauration du journal est le schéma recommandé ; le message d'information \"contains records that were logged before the designated point in time\" sur les fichiers antérieurs est normal. Pour s'arrêter sur une marque nommée (BEGIN TRAN ... WITH MARK), utilisez STOPATMARK / STOPBEFOREMARK. Utilisez fn_dump_dblog ou les logs applicatifs pour trouver l'heure exacte.","tags":["stopat","point-in-time","restore log","norecovery","standby","rollback to time"]},{"id":"restore-verify-header","task":"Inspecter et vérifier un fichier de sauvegarde (VERIFYONLY, HEADERONLY, FILELISTONLY)","category":"Restauration et récupération","command":"-- Readability and page checksum verification:\nRESTORE VERIFYONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak' WITH CHECKSUM;\n\n-- Backup sets in the file (type, LSN, date, version, compression):\nRESTORE HEADERONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak';\n\n-- Logical file names and sizes (needed for MOVE):\nRESTORE FILELISTONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak';\n\n-- Media info (stripe count: FamilyCount):\nRESTORE LABELONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak';","what":"Vérifie que la sauvegarde est lisible et que ses checksums de pages sont valides sans la restaurer ; affiche les jeux de sauvegarde qu'elle contient (Position, BackupType, FirstLSN/LastLSN, DatabaseVersion), les noms de fichiers logiques et les informations de stripes.","when":"Avant de restaurer une sauvegarde reçue d'une autre équipe, pour connaître les noms de fichiers pour MOVE, comme contrôle rapide après un job de sauvegarde, ou pour choisir le bon FILE = n quand un fichier contient plusieurs jeux.","caution":"VERIFYONLY ne garantit pas que la base est restaurable et cohérente ; il vérifie seulement la structure de la sauvegarde et (si elle a été prise WITH CHECKSUM) les checksums de pages. Pour une vraie assurance, faites des restaurations de test régulières + DBCC CHECKDB. Une sauvegarde d'une version plus récente ne peut pas être restaurée sur une plus ancienne (vérifiez DatabaseVersion). Opérations de lecture ; sûres.","note":"Si HEADERONLY indique HasBackupChecksums = 0, la sauvegarde a été prise sans CHECKSUM et VERIFYONLY WITH CHECKSUM ne peut pas valider les pages. Les fichiers écrits sans INIT accumulent plusieurs jeux ; passez WITH FILE = <Position> à la restauration.","tags":["restore verifyonly","headeronly","filelistonly","labelonly","backup verification","checksum"]},{"id":"page-restore-suspect","task":"Trouver les pages corrompues (suspect_pages) et restaurer des pages individuelles","category":"Restauration et récupération","command":"-- Pages that hit 823/824 or checksum errors:\nSELECT DB_NAME(sp.database_id) AS db, sp.file_id, sp.page_id,\n       CASE sp.event_type WHEN 1 THEN '823/824 error' WHEN 2 THEN 'bad checksum'\n            WHEN 3 THEN 'torn page'     WHEN 4 THEN 'restored'\n            WHEN 5 THEN 'repaired'      WHEN 7 THEN 'deallocated by DBCC' END AS event_desc,\n       sp.error_count, sp.last_update_date\nFROM   msdb.dbo.suspect_pages sp\nORDER BY sp.last_update_date DESC;\n\n-- Page restore (example 1:336): FULL + later logs + a new log backup\nRESTORE DATABASE [Musteri] PAGE = '1:336'\nFROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak' WITH NORECOVERY;\nRESTORE LOG [Musteri] FROM DISK = N'D:\\Backup\\Musteri_log_1200.trn' WITH NORECOVERY;\nBACKUP LOG [Musteri] TO DISK = N'D:\\Backup\\Musteri_log_pagetail.trn' WITH INIT;\nRESTORE LOG [Musteri] FROM DISK = N'D:\\Backup\\Musteri_log_pagetail.trn' WITH RECOVERY;\n\n-- Verify:\nDBCC CHECKDB (N'Musteri') WITH NO_INFOMSGS, ALL_ERRORMSGS;","what":"Liste depuis msdb.dbo.suspect_pages les pages ayant rencontré des erreurs I/O (823/824), de checksum ou de torn page ; répare des pages corrompues individuelles en les reprenant de la sauvegarde FULL et en les faisant avancer avec les journaux, sans restaurer toute la base.","when":"Sur des erreurs 823/824 dans le journal d'erreurs, des erreurs de cohérence limitées à quelques pages dans CHECKDB, ou une requête utilisateur qui échoue avec \"incorrect checksum\".","caution":"Nécessite une récupération FULL ou BULK_LOGGED et une chaîne de journaux ininterrompue. La restauration de pages en ligne est réservée à Enterprise/Developer ; dans les autres éditions, la base est hors ligne pendant la restauration. Les pages d'allocation (GAM/SGAM/PFS), les en-têtes de fichier et la boot page (1:9) ne peuvent pas être restaurés ainsi. 823/824 signifie généralement un problème de stockage ; ne clôturez pas l'incident sans vérifier le disque/contrôleur/SAN, sinon il se reproduira. Avec beaucoup de pages défectueuses, une restauration complète de la base est plus rapide.","note":"suspect_pages contient au plus 1,000 lignes ; une fois pleine, les nouvelles erreurs ne sont pas enregistrées, supprimez donc les anciennes lignes (event_type 4/5/7). Plusieurs pages sont séparées par des virgules : PAGE = '1:336, 1:337'. Dans un AG, une page corrompue sur le primaire est réparée automatiquement depuis un secondaire par l'automatic page repair (sys.dm_hadr_auto_page_repair).","tags":["suspect_pages","page restore","823","824","corruption","bad page"]},{"id":"top-queries-cpu","task":"Trouver les requêtes les plus coûteuses dans le plan cache (CPU, lectures, durée)","category":"Performances","command":"SELECT TOP (20)\n       qs.query_hash,\n       SUM(qs.execution_count)                                       AS execs,\n       SUM(qs.total_worker_time) / 1000                              AS total_cpu_ms,\n       SUM(qs.total_worker_time) / SUM(qs.execution_count) / 1000    AS avg_cpu_ms,\n       SUM(qs.total_elapsed_time) / SUM(qs.execution_count) / 1000   AS avg_ms,\n       SUM(qs.total_logical_reads) / SUM(qs.execution_count)         AS avg_reads,\n       SUM(qs.total_grant_kb) / SUM(qs.execution_count)              AS avg_grant_kb,\n       COUNT(DISTINCT qs.query_plan_hash)                            AS plan_count,\n       MAX(qs.last_execution_time)                                   AS last_exec,\n       MIN(SUBSTRING(st.text, qs.statement_start_offset / 2 + 1,\n           ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)\n             ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2) + 1)) AS statement_text,\n       MIN(DB_NAME(pa.dbid))                                         AS db\nFROM   sys.dm_exec_query_stats qs\n  CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st\n  OUTER APPLY (SELECT CONVERT(int, value) AS dbid FROM sys.dm_exec_plan_attributes(qs.plan_handle)\n               WHERE attribute = N'dbid') pa\nGROUP BY qs.query_hash\nORDER BY total_cpu_ms DESC;\n\n-- To rank by reads: ORDER BY SUM(qs.total_logical_reads) DESC\n-- Plan: SELECT query_plan FROM sys.dm_exec_query_plan(<plan_handle>);","what":"Regroupe les instructions du plan cache par query_hash (les requêtes ad hoc qui ne diffèrent que par les littéraux se réduisent à une ligne) et renvoie le CPU, la durée, les lectures logiques, le memory grant totaux et moyens ainsi que le nombre de plans.","when":"Quand le CPU est saturé, sur une plainte \"le serveur est lent\", pour les bases sans Query Store, ou quand il faut une vue globale du serveur sur toutes les bases.","caution":"Ne voit que les plans encore en cache : les requêtes dont les plans ont été évincés par un redémarrage, DBCC FREEPROCCACHE, la pression mémoire ou RECOMPILE sont absentes. Utilisez Query Store pour une analyse persistante par fenêtre temporelle. plan_count > 1 suggère une sensibilité aux paramètres ou des options SET différentes. Sur un très grand plan cache, la requête peut prendre quelques secondes ; sûre.","since":"2016","note":"total_grant_kb est une colonne 2016+. Pour les totaux par procédure stockée, utilisez sys.dm_exec_procedure_stats.","tags":["dm_exec_query_stats","top queries","cpu","query_hash","plan cache","expensive query"]},{"id":"wait-stats-top","task":"Statistiques d'attente : ce que le serveur attend","category":"Performances","command":"WITH w AS (\n    SELECT wait_type, waiting_tasks_count,\n           wait_time_ms / 1000.0                         AS wait_s,\n           (wait_time_ms - signal_wait_time_ms) / 1000.0 AS resource_s,\n           signal_wait_time_ms / 1000.0                  AS signal_s\n    FROM   sys.dm_os_wait_stats\n    WHERE  waiting_tasks_count > 0\n      AND  wait_type NOT LIKE N'SLEEP[_]%'\n      AND  wait_type NOT IN (\n           N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH',\n           N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT',\n           N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', N'FT_IFTS_SCHEDULER_IDLE_WAIT',\n           N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_WORK_QUEUE', N'LAZYWRITER_SLEEP',\n           N'LOGMGR_QUEUE', N'ONDEMAND_TASK_QUEUE', N'PVS_PREALLOCATE', N'REQUEST_FOR_DEADLOCK_SEARCH',\n           N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', N'SP_SERVER_DIAGNOSTICS_SLEEP',\n           N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'WAITFOR',\n           N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT', N'XE_LIVE_TARGET_TVF', N'PREEMPTIVE_XE_DISPATCHER',\n           N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE', N'QDS_SHUTDOWN_QUEUE',\n           N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'PWAIT_ALL_COMPONENTS_INITIALIZED',\n           N'PWAIT_EXTENSIBILITY_CLEANUP_TASK', N'STARTUP_DEPENDENCY_MANAGER', N'WAIT_XTP_CKPT_CLOSE')\n)\nSELECT TOP (20) wait_type, waiting_tasks_count,\n       CAST(wait_s AS DECIMAL(18,1))                                AS wait_s,\n       CAST(100.0 * wait_s / SUM(wait_s) OVER () AS DECIMAL(5,1))   AS pct,\n       CAST(resource_s AS DECIMAL(18,1))                            AS resource_s,\n       CAST(signal_s AS DECIMAL(18,1))                              AS signal_s,\n       CAST(1000.0 * wait_s / waiting_tasks_count AS DECIMAL(18,2)) AS avg_wait_ms\nFROM   w\nORDER BY wait_s DESC;\n\n-- Values accumulate since:\nSELECT sqlserver_start_time FROM sys.dm_os_sys_info;\n\n-- User requests waiting right now:\nSELECT wt.session_id, wt.wait_type, wt.wait_duration_ms, wt.resource_description, wt.blocking_session_id\nFROM   sys.dm_os_waiting_tasks wt\n  JOIN sys.dm_exec_sessions s ON s.session_id = wt.session_id\nWHERE  s.is_user_process = 1;","what":"Filtre les attentes d'arrière-plan bénignes et ventile le temps d'attente total du serveur par type d'attente avec pourcentage et part resource vs. signal (file CPU) ; montre aussi les requêtes utilisateur en attente à cet instant.","when":"Comme première étape d'une analyse de performance pour classer le goulot (I/O, CPU, verrou, journal, mémoire, parallélisme) ; pour les comparaisons de référence avant/après.","caution":"Les valeurs sont cumulées depuis le redémarrage ; pour un problème actuel, prenez deux instantanés et comparez le delta. DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR) réinitialise les compteurs pour tout le serveur et perturbe les outils de supervision ; évitez-le. Interprétation : PAGEIOLATCH_* = lectures disque, WRITELOG = disque du journal, LCK_M_* = blocage, CXPACKET/CXCONSUMER = parallélisme, RESOURCE_SEMAPHORE = memory grants, SOS_SCHEDULER_YIELD + signal élevé = pression CPU. Requête en lecture seule ; sûre.","note":"Pour les attentes par session, utilisez sys.dm_exec_session_wait_stats (2016+) ; pour les catégories d'attente par requête dans Query Store, utilisez sys.query_store_wait_stats (2017+).","tags":["wait stats","dm_os_wait_stats","waits","pageiolatch","writelog","performance"]},{"id":"deadlock-system-health","task":"Lire les interblocages passés depuis la session system_health (deadlock graph)","category":"Performances","command":"SET QUOTED_IDENTIFIER ON;  -- required for XML methods (sqlcmd/Agent default is OFF)\nWITH dl AS (\n    SELECT CAST(event_data AS XML) AS x\n    FROM   sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL)\n    WHERE  object_name = N'xml_deadlock_report'\n)\nSELECT TOP (20)\n       x.value('(event/@timestamp)[1]', 'datetime2')                                          AS utc_time,\n       x.value('(event/data/value/deadlock/victim-list/victimProcess/@id)[1]', 'varchar(50)')  AS victim,\n       x.value('count(event/data/value/deadlock/process-list/process)', 'int')                 AS process_count,\n       x.value('(event/data/value/deadlock/process-list/process/@currentdbname)[1]', 'sysname') AS db,\n       x.query('event/data/value/deadlock')                                                    AS deadlock_graph\nFROM   dl\nORDER BY utc_time DESC;","what":"Lit les événements xml_deadlock_report depuis la cible event_file de la session Extended Events system_health (active par défaut) ; pour chaque interblocage, renvoie l'heure (UTC), le processus victime, le nombre de participants, la base et le XML complet du deadlock graph.","when":"Quand l'application signale l'erreur 1205, lors de l'analyse de la fréquence des interblocages et des objets impliqués ; sur les serveurs sans trace flag 1222.","caution":"Les fichiers system_health tournent ; sur un serveur chargé, les anciens interblocages peuvent avoir disparu, mettez donc en place une session XE dédiée pour un archivage permanent. La lecture de nombreux fichiers coûte du CPU ; gardez TOP pendant les heures ouvrées. Les événements sont écrits avec un délai, un nouvel interblocage apparaît donc après quelques secondes. Lecture seule ; sûr.","note":"Cliquez sur la valeur deadlock_graph et enregistrez-la en .xdl pour l'ouvrir graphiquement dans SSMS. La valeur par défaut de l'event_file system_health est de 10 x 100 MB sur 2016 SP2 CU12 / 2017 CU20 / 2019 CU3 et ultérieurs (Standard/Enterprise) ; 4 x 5 MB sur les builds plus anciens et les autres éditions. Dans le graphe, inputbuf, waitresource (KEY/PAGE/OBJECT) et lockMode sont les champs clés ; la plupart des interblocages se corrigent avec un index manquant ou un ordre d'accès cohérent.","tags":["deadlock","system_health","xml_deadlock_report","extended events","1205","deadlock graph"]},{"id":"xe-long-query-session","task":"Créer une session Extended Events : capturer les requêtes lentes dans un fichier et les lire","category":"Performances","command":"SET QUOTED_IDENTIFIER ON;\nCREATE EVENT SESSION [dba_long_queries] ON SERVER\nADD EVENT sqlserver.sql_statement_completed (\n    ACTION (sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.database_name,\n            sqlserver.session_id, sqlserver.sql_text, sqlserver.username, sqlserver.query_hash)\n    WHERE (duration >= 1000000 AND sqlserver.is_system = 0)),   -- 1 s (microseconds)\nADD EVENT sqlserver.rpc_completed (\n    ACTION (sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.database_name,\n            sqlserver.session_id, sqlserver.sql_text, sqlserver.username, sqlserver.query_hash)\n    WHERE (duration >= 1000000 AND sqlserver.is_system = 0))\nADD TARGET package0.event_file (SET filename = N'D:\\XE\\dba_long_queries.xel',\n                                    max_file_size = 256, max_rollover_files = 4)\nWITH (MAX_MEMORY = 8 MB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,\n      MAX_DISPATCH_LATENCY = 15 SECONDS, STARTUP_STATE = OFF);\n\nALTER EVENT SESSION [dba_long_queries] ON SERVER STATE = START;\n\n-- Read:\nSELECT x.value('(event/@name)[1]', 'sysname')                                AS event_name,\n       x.value('(event/@timestamp)[1]', 'datetime2')                         AS utc_time,\n       x.value('(event/data[@name=\"duration\"]/value)[1]', 'bigint') / 1000   AS duration_ms,\n       x.value('(event/data[@name=\"cpu_time\"]/value)[1]', 'bigint') / 1000   AS cpu_ms,\n       x.value('(event/data[@name=\"logical_reads\"]/value)[1]', 'bigint')     AS logical_reads,\n       x.value('(event/action[@name=\"database_name\"]/value)[1]', 'sysname')  AS db,\n       x.value('(event/action[@name=\"client_app_name\"]/value)[1]', 'nvarchar(256)') AS app,\n       x.value('(event/action[@name=\"sql_text\"]/value)[1]', 'nvarchar(max)') AS sql_text\nFROM  (SELECT CAST(event_data AS XML) AS x\n       FROM sys.fn_xe_file_target_read_file(N'D:\\XE\\dba_long_queries*.xel', NULL, NULL, NULL)) t\nORDER BY duration_ms DESC;\n\n-- When done:\n-- ALTER EVENT SESSION [dba_long_queries] ON SERVER STATE = STOP;\n-- DROP EVENT SESSION [dba_long_queries] ON SERVER;","what":"Crée une session Extended Events légère qui écrit sur disque les instructions et appels RPC de plus d'une seconde avec l'application, l'hôte, l'utilisateur, la base et le texte SQL ; transforme les fichiers .xel en lignes avec T-SQL.","when":"Pour les plaintes de lenteur intermittente, quand vous avez besoin de détails client (app/hôte) que Query Store n'affiche pas, et en remplacement de SQL Trace/Profiler.","caution":"Un seuil très bas (par ex. 0) remplit vite les fichiers et ajoute une surcharge sur un serveur chargé ; filtrez dans le prédicat WHERE, pas après coup. L'action sql_text est coûteuse à fort volume. Le dossier doit être accessible en écriture au compte de service SQL Server. duration et cpu_time sont en microsecondes. Arrêtez/supprimez la session une fois terminé ; avec STARTUP_STATE = OFF, elle ne démarre pas après un redémarrage.","note":"Pour se limiter à une base, ajoutez sqlserver.database_name = N'Musteri' à la clause WHERE. Pour un aperçu rapide, utilisez SSMS > Management > Extended Events > Watch Live Data.","tags":["extended events","xe","event session","slow query","sql_statement_completed","event_file"]},{"id":"tempdb-contention","task":"Diagnostiquer la contention et l'utilisation d'espace de tempdb","category":"Performances","command":"-- 1) Current tempdb page latch waits (2:1:1 PFS, 2:1:2 GAM, 2:1:3 SGAM):\nSELECT wt.session_id, wt.wait_type, wt.wait_duration_ms, wt.resource_description\nFROM   sys.dm_os_waiting_tasks wt\nWHERE  wt.wait_type LIKE N'PAGELATCH[_]%'\n  AND  wt.resource_description LIKE N'2:%';\n\n-- 2) Sessions using the most tempdb (MB):\nSELECT TOP (20) su.session_id, s.login_name, s.program_name,\n       (su.user_objects_alloc_page_count - su.user_objects_dealloc_page_count) * 8 / 1024         AS user_obj_mb,\n       (su.internal_objects_alloc_page_count - su.internal_objects_dealloc_page_count) * 8 / 1024 AS internal_obj_mb\nFROM   sys.dm_db_session_space_usage su\n  JOIN sys.dm_exec_sessions s ON s.session_id = su.session_id\nWHERE  s.is_user_process = 1\nORDER BY su.user_objects_alloc_page_count + su.internal_objects_alloc_page_count\n       - su.user_objects_dealloc_page_count - su.internal_objects_dealloc_page_count DESC;\n\n-- 3) Overall breakdown and version store:\nSELECT SUM(unallocated_extent_page_count) * 8 / 1024       AS free_mb,\n       SUM(user_object_reserved_page_count) * 8 / 1024     AS user_obj_mb,\n       SUM(internal_object_reserved_page_count) * 8 / 1024 AS internal_obj_mb,\n       SUM(version_store_reserved_page_count) * 8 / 1024   AS version_store_mb\nFROM   tempdb.sys.dm_db_file_space_usage;\n\nSELECT DB_NAME(database_id) AS db, reserved_page_count * 8 / 1024 AS version_store_mb\nFROM   sys.dm_tran_version_store_space_usage\nORDER BY reserved_page_count DESC;\n\n-- 4) File count / equal sizing and memory-optimized metadata:\nSELECT name, type_desc, size * 8 / 1024 AS size_mb, growth * 8 / 1024 AS growth_mb, is_percent_growth\nFROM   tempdb.sys.database_files;\nSELECT SERVERPROPERTY('IsTempdbMetadataMemoryOptimized') AS tempdb_metadata_memopt;","what":"Affiche les attentes PAGELATCH sur les pages d'allocation de tempdb (PFS/GAM/SGAM) et les tables système, les sessions qui utilisent le plus tempdb (tables temporaires vs. débordements sort/hash), la base qui gonfle le version store, et la configuration des fichiers.","when":"Quand les attentes PAGELATCH_UP/EX augmentent sous de fortes charges de tables temporaires, quand tempdb se remplit (1105/9002), ou quand le version store grossit.","caution":"Les pages 2:1:1, 2:1:2, 2:1:3 et les pages PFS aux multiples de 8088 signifient une contention d'allocation : la solution est plusieurs fichiers de données de taille égale avec une croissance égale (un par cœur jusqu'à 8, puis par ajouts de 4). Pour la contention sur les pages des tables système, 2019+ propose les métadonnées tempdb optimisées en mémoire (redémarrage nécessaire). Si le version store grossit, trouvez la plus ancienne transaction snapshot ouverte ; réduire tempdb n'est pas une solution. Requête en lecture seule ; sûre.","note":"Métadonnées optimisées en mémoire : ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; + redémarrage (2019+). 2022 a ajouté les mises à jour GAM/SGAM sans latch, ce qui réduit fortement la contention. La transaction qui retient le version store : sys.dm_tran_active_snapshot_database_transactions ORDER BY elapsed_time_seconds DESC.","tags":["tempdb","pagelatch","pfs","gam","version store","contention"]},{"id":"qs-enable-config","task":"Activer Query Store, appliquer les paramètres recommandés et vérifier son état","category":"Query Store","command":"ALTER DATABASE [Musteri] SET QUERY_STORE = ON;\nALTER DATABASE [Musteri] SET QUERY_STORE (\n    OPERATION_MODE = READ_WRITE,\n    QUERY_CAPTURE_MODE = AUTO,\n    MAX_STORAGE_SIZE_MB = 2048,\n    INTERVAL_LENGTH_MINUTES = 15,\n    DATA_FLUSH_INTERVAL_SECONDS = 900,\n    CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),\n    SIZE_BASED_CLEANUP_MODE = AUTO,\n    MAX_PLANS_PER_QUERY = 200,\n    WAIT_STATS_CAPTURE_MODE = ON);\n\n-- State: if actual_state dropped to READ_ONLY, check readonly_reason\nUSE [Musteri];\nSELECT actual_state_desc, desired_state_desc, readonly_reason,\n       current_storage_size_mb, max_storage_size_mb,\n       query_capture_mode_desc, size_based_cleanup_mode_desc, wait_stats_capture_mode_desc\nFROM   sys.database_query_store_options;","what":"Active Query Store dans la base ; commence à conserver les textes de requêtes, plans, statistiques d'exécution et d'attente par intervalles de temps. Les options définissent la limite de stockage, le nettoyage et le mode de capture.","when":"Quand une nouvelle base passe en production, avant une mise à niveau de version/niveau de compatibilité (pour une référence), ou en cas de suspicion de régression de plan.","caution":"Quand MAX_STORAGE_SIZE_MB est atteint, Query Store passe silencieusement en READ_ONLY (readonly_reason = 65536) et cesse la collecte ; surveillez current_storage_size_mb. QUERY_CAPTURE_MODE = ALL remplit vite le stockage et ajoute une surcharge sous de fortes charges ad hoc. Les systèmes OLTP très chargés peuvent présenter des attentes QDS_*. Le passer à OFF conserve les données collectées ; CLEAR les supprime.","since":"2016","note":"En 2022, Query Store est activé en READ_WRITE par défaut sur les bases nouvellement créées. En 2019+, QUERY_CAPTURE_MODE = CUSTOM accepte des seuils (EXECUTION_COUNT, TOTAL_COMPILE_CPU_TIME_MS, TOTAL_EXECUTION_CPU_TIME_MS). Query Store pour les secondaires lisibles (ALTER DATABASE ... FOR SECONDARY SET QUERY_STORE = ON) est une préversion limitée en 2022, activée uniquement avec le trace flag 12606 et non prise en charge en production ; 2025 ne nécessite pas de trace flag (toujours en préversion).","tags":["query store","enable","max_storage_size_mb","query_capture_mode","read_only","qds"]},{"id":"qs-regressed-queries","task":"Trouver les régressions de plan avec Query Store (même requête, plans différents)","category":"Query Store","command":"USE [Musteri];\n-- Queries with more than one plan in the last 24 hours, per-plan performance:\nWITH plan_stats AS (\n    SELECT p.query_id, p.plan_id, p.is_forced_plan,\n           SUM(rs.count_executions) AS execs,\n           SUM(rs.avg_duration * rs.count_executions) / SUM(rs.count_executions) / 1000.0     AS avg_ms,\n           SUM(rs.avg_cpu_time * rs.count_executions) / SUM(rs.count_executions) / 1000.0     AS avg_cpu_ms,\n           SUM(rs.avg_logical_io_reads * rs.count_executions) / SUM(rs.count_executions)      AS avg_reads,\n           MAX(rs.last_execution_time) AS last_exec\n    FROM   sys.query_store_plan p\n      JOIN sys.query_store_runtime_stats rs ON rs.plan_id = p.plan_id\n      JOIN sys.query_store_runtime_stats_interval i\n        ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id\n    WHERE  i.start_time >= DATEADD(HOUR, -24, SYSUTCDATETIME())\n    GROUP BY p.query_id, p.plan_id, p.is_forced_plan\n)\nSELECT ps.query_id, ps.plan_id, ps.is_forced_plan, ps.execs,\n       CAST(ps.avg_ms AS DECIMAL(18,2))     AS avg_ms,\n       CAST(ps.avg_cpu_ms AS DECIMAL(18,2)) AS avg_cpu_ms,\n       CAST(ps.avg_reads AS BIGINT)         AS avg_reads,\n       CAST(ps.avg_ms / NULLIF(MIN(ps.avg_ms) OVER (PARTITION BY ps.query_id), 0) AS DECIMAL(18,1)) AS x_slower_than_best,\n       ps.last_exec,\n       OBJECT_NAME(q.object_id)     AS module,\n       LEFT(qt.query_sql_text, 120) AS query_text\nFROM   plan_stats ps\n  JOIN sys.query_store_query q       ON q.query_id = ps.query_id\n  JOIN sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id\nWHERE  ps.query_id IN (SELECT query_id FROM plan_stats GROUP BY query_id HAVING COUNT(*) > 1)\nORDER BY ps.query_id, ps.avg_ms;","what":"Trouve les requêtes exécutées avec plus d'un plan au cours des dernières 24 heures ; met côte à côte, pour chaque plan, le nombre d'exécutions, la durée/CPU/lectures moyennes pondérées et combien de fois il est plus lent que le meilleur plan.","when":"Quand une requête \"était rapide hier et lente aujourd'hui\", après une mise à jour des statistiques, un changement d'index, une mise à niveau du niveau de compatibilité ou un correctif.","caution":"La durée moyenne dépend aussi de la distribution des données ; en comparant des plans exécutés avec des valeurs de paramètres différentes, regardez aussi avg_reads (parameter sniffing). Les fenêtres de temps sont en UTC. Les résultats sont statistiquement faibles pour les plans peu exécutés. Requête en lecture seule ; sûre.","since":"2016","note":"SSMS > Query Store > Regressed Queries fait la même chose graphiquement. En 2022 avec le niveau de compatibilité 160, l'optimisation Parameter Sensitive Plan (PSP) crée plusieurs query_id variantes pour une instruction ; reliez-les au parent avec sys.query_store_query_variant (parent_query_id). En 2017+, l'automatic tuning (FORCE_LAST_GOOD_PLAN) peut corriger automatiquement ces régressions.","tags":["query store","plan regression","regressed queries","parameter sniffing","query_store_runtime_stats","plan"]},{"id":"qs-force-plan-hints","task":"Forcer un bon plan ou attacher un hint avec Query Store","category":"Query Store","command":"USE [Musteri];\n-- Force the good plan (query_id / plan_id from the regression query):\nEXEC sys.sp_query_store_force_plan @query_id = 42, @plan_id = 7;\n\n-- Forced plans and failure reason:\nSELECT p.query_id, p.plan_id, p.is_forced_plan, p.plan_forcing_type_desc,\n       p.force_failure_count, p.last_force_failure_reason_desc\nFROM   sys.query_store_plan p\nWHERE  p.is_forced_plan = 1;\n\n-- Undo:\nEXEC sys.sp_query_store_unforce_plan @query_id = 42, @plan_id = 7;\n\n-- 2022+: attach / remove a hint without changing code:\nEXEC sys.sp_query_store_set_hints @query_id = 42, @query_hints = N'OPTION (RECOMPILE)';\nSELECT query_id, query_hint_text, last_query_hint_failure_reason_desc\nFROM   sys.query_store_query_hints;\nEXEC sys.sp_query_store_clear_hints @query_id = 42;","what":"Force un plan Query Store précis pour la requête choisie ; l'optimiseur utilise ce plan à chaque compilation. En 2022, vous pouvez aussi attacher des hints comme RECOMPILE, MAXDOP ou OPTIMIZE FOR sans modifier le code de l'application.","when":"Quand une régression de plan nécessite une correction urgente en production, en attendant qu'une correction durable (index, réécriture de requête, statistiques) soit prête.","caution":"Un plan forcé peut se dégrader avec la croissance des données et n'est pas une correction durable ; réexaminez-le régulièrement. Si un index utilisé par le plan est supprimé/renommé, le forçage échoue (last_force_failure_reason_desc = NO_INDEX etc.) et la requête se compile normalement. Après forçage, un plan \"moralement équivalent\" peut apparaître sous un nouveau plan_id ; c'est attendu. Le forçage fonctionne pendant que Query Store est READ_ONLY, mais vérifiez après une restauration ou un basculement d'AG.","since":"2016","note":"Les Query Store hints sont 2022+ (plus tôt dans Azure SQL). Les hints remplacent les plan guides ; si un hint ne peut pas être appliqué, la requête s'exécute sans lui et la raison apparaît dans last_query_hint_failure_reason_desc. Le forçage de plan sur les secondaires lisibles (@replica_group_id) nécessite Query Store pour les réplicas secondaires, qui en 2022 est une préversion limitée activée avec le trace flag 12606.","tags":["force plan","sp_query_store_force_plan","query store hints","plan forcing","regression","recompile"]},{"id":"missing-index-dmv","task":"Lire les suggestions d'index manquants depuis les DMV (avec réserves)","category":"Maintenance","command":"USE [Musteri];\nSELECT TOP (25)\n       mid.statement                                   AS table_name,\n       mid.equality_columns, mid.inequality_columns, mid.included_columns,\n       migs.user_seeks, migs.user_scans,\n       CAST(migs.avg_total_user_cost AS DECIMAL(18,2)) AS avg_query_cost,\n       migs.avg_user_impact                            AS est_impact_pct,\n       CAST(migs.user_seeks * migs.avg_total_user_cost * migs.avg_user_impact / 100.0 AS DECIMAL(18,0)) AS score,\n       migs.last_user_seek,\n       N'CREATE INDEX [IX_' + OBJECT_NAME(mid.object_id, mid.database_id) + N'_missing_'\n         + CAST(mid.index_handle AS NVARCHAR(10)) + N'] ON ' + mid.statement + N' ('\n         + ISNULL(mid.equality_columns, N'')\n         + CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN N', ' ELSE N'' END\n         + ISNULL(mid.inequality_columns, N'') + N')'\n         + ISNULL(N' INCLUDE (' + mid.included_columns + N')', N'') AS create_stmt_draft\nFROM   sys.dm_db_missing_index_details mid\n  JOIN sys.dm_db_missing_index_groups mig       ON mig.index_handle = mid.index_handle\n  JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle\nWHERE  mid.database_id = DB_ID()\nORDER BY score DESC;\n\n-- 2019+: the query text behind each suggestion:\nSELECT TOP (25) mid.statement AS table_name, misq.group_handle, misq.user_seeks,\n       SUBSTRING(st.text, misq.last_statement_start_offset / 2 + 1, 300) AS statement_text\nFROM   sys.dm_db_missing_index_group_stats_query misq\n  JOIN sys.dm_db_missing_index_groups mig  ON mig.index_group_handle = misq.group_handle\n  JOIN sys.dm_db_missing_index_details mid ON mid.index_handle = mig.index_handle\n  CROSS APPLY sys.dm_exec_sql_text(misq.last_sql_handle) st\nWHERE  mid.database_id = DB_ID()\nORDER BY misq.user_seeks DESC;","what":"Note les suggestions d'index manquants enregistrées par l'optimiseur à la compilation selon l'utilisation, le coût estimé et l'impact, et génère une instruction CREATE INDEX préliminaire ; en 2019+, montre aussi la requête derrière chaque suggestion.","when":"Après une nouvelle version de l'application, pour un examen rapide des besoins en index des requêtes à forte lecture, ou comme liste de départ pour une revue de conception des index.","caution":"N'appliquez pas les suggestions aveuglément : l'optimiseur ignore les index existants, produit souvent des suggestions quasi dupliquées (qui se chevauchent) et des listes INCLUDE trop larges, et ordonne les colonnes par égalité/inégalité plutôt que par sélectivité. Chaque nouvel index coûte en écritures et en stockage ; envisagez d'abord d'élargir un index existant. Les DMV sont réinitialisées au redémarrage, à la mise hors ligne de la base et lors de changements d'index sur la table. Requête en lecture seule ; sûre.","note":"Avant de créer un index suggéré, comparez-le aux index existants de la table (sp_helpindex ou sys.index_columns) et examinez-le avec la procédure des index inutilisés. L'élément MissingIndex du XML du plan donne la même information par requête.","tags":["missing index","missing index dmv","dm_db_missing_index_details","index suggestion","create index","tuning"]},{"id":"unused-indexes","task":"Trouver les index inutilisés / en écriture seule","category":"Maintenance","command":"USE [Musteri];\nSELECT OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,\n       OBJECT_NAME(i.object_id)        AS table_name,\n       i.name                          AS index_name,\n       ISNULL(us.user_seeks, 0) + ISNULL(us.user_scans, 0) + ISNULL(us.user_lookups, 0) AS reads,\n       ISNULL(us.user_updates, 0)      AS writes,\n       ps.used_mb,\n       us.last_user_seek, us.last_user_scan,\n       N'DROP INDEX ' + QUOTENAME(i.name) + N' ON ' + QUOTENAME(OBJECT_SCHEMA_NAME(i.object_id))\n         + N'.' + QUOTENAME(OBJECT_NAME(i.object_id)) + N';' AS drop_stmt_draft\nFROM   sys.indexes i\n  LEFT JOIN sys.dm_db_index_usage_stats us\n         ON us.database_id = DB_ID() AND us.object_id = i.object_id AND us.index_id = i.index_id\n  CROSS APPLY (SELECT CAST(SUM(p.used_page_count) * 8 / 1024.0 AS DECIMAL(18,1)) AS used_mb\n               FROM sys.dm_db_partition_stats p\n               WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps\nWHERE  OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1\n  AND  i.type_desc = N'NONCLUSTERED'\n  AND  i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0\n  AND  ISNULL(us.user_seeks, 0) + ISNULL(us.user_scans, 0) + ISNULL(us.user_lookups, 0) = 0\nORDER BY writes DESC, ps.used_mb DESC;\n\n-- Usage counters since:\nSELECT sqlserver_start_time FROM sys.dm_os_sys_info;","what":"Liste les index nonclustered qui n'ont pas été lus (seek/scan/lookup = 0) depuis le redémarrage mais subissent toujours un coût d'écriture, avec leur taille, et génère une instruction DROP INDEX préliminaire.","when":"Pour les INSERT/UPDATE lents sur des tables à forte écriture, pour réduire le stockage et la durée de maintenance (rebuild, CHECKDB), ou avant une consolidation des index.","caution":"Les compteurs sont réinitialisés au redémarrage, à la mise hors ligne/détachement de la base et (sur certains anciens builds 2012/2014) à la reconstruction d'index ; les index utilisés uniquement par les rapports de fin de mois/d'année semblent \"inutilisés\" sur une courte fenêtre. Observez au moins un cycle métier complet avant de supprimer, et dans un AG vérifiez séparément l'utilisation sur les secondaires lisibles (les compteurs sont par réplica). Les index uniques imposent des contraintes même sans être lus ; ils sont exclus. Requête en lecture seule ; sûre.","note":"Désactiver avec ALTER INDEX ... DISABLE avant de supprimer facilite le retour arrière (la définition reste ; REBUILD le rétablit). Rechercher le nom de l'index dans Query Store ou le plan cache indique s'il est référencé par un hint.","tags":["unused index","write-only index","dm_db_index_usage_stats","drop index","write overhead","index"]},{"id":"stats-health","task":"Santé des statistiques : date de mise à jour, nombre de lignes modifiées","category":"Maintenance","command":"USE [Musteri];\nSELECT OBJECT_SCHEMA_NAME(s.object_id) AS schema_name,\n       OBJECT_NAME(s.object_id)        AS table_name,\n       s.name                          AS stats_name,\n       s.auto_created, s.has_filter, s.no_recompute,\n       sp.last_updated,\n       sp.rows, sp.rows_sampled,\n       CAST(100.0 * sp.rows_sampled / NULLIF(sp.rows, 0) AS DECIMAL(5,1))         AS sample_pct,\n       sp.modification_counter,\n       CAST(100.0 * sp.modification_counter / NULLIF(sp.rows, 0) AS DECIMAL(9,1)) AS modified_pct,\n       CAST(CASE WHEN 500 + 0.20 * sp.rows < SQRT(1000.0 * sp.rows) THEN 500 + 0.20 * sp.rows\n                 ELSE SQRT(1000.0 * sp.rows) END AS BIGINT)                       AS auto_update_threshold,\n       sp.persisted_sample_percent\nFROM   sys.stats s\n  CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp\nWHERE  OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1\nORDER BY sp.modification_counter DESC;\n\nSELECT name, is_auto_create_stats_on, is_auto_update_stats_on, is_auto_update_stats_async_on\nFROM   sys.databases\nWHERE  name = DB_NAME();","what":"Affiche pour chaque statistique l'heure de la dernière mise à jour, le taux d'échantillonnage, les lignes modifiées depuis la dernière mise à jour et le seuil auquel l'auto update se déclenche (seuil dynamique pour compat 130+).","when":"Pour les plans avec un grand écart entre lignes estimées et réelles, après un gros chargement de données, ou pour vérifier l'efficacité du job de maintenance des statistiques.","caution":"La formule du seuil s'applique au niveau de compatibilité 130+ (MIN(500 + 20%, SQRT(1000 x rows))) ; les niveaux inférieurs utilisent la règle 20% + 500. Les statistiques jamais mises à jour (tables vides) renvoient NULL. Les colonnes asymétriques avec un sample_pct faible peuvent nécessiter FULLSCAN ou PERSIST_SAMPLE_PERCENT. Requête en lecture seule ; sûre.","since":"2016","note":"persisted_sample_percent reflète UPDATE STATISTICS ... WITH PERSIST_SAMPLE_PERCENT = ON (2016 SP1 CU4 / 2017 CU1+). Voir la procédure update-statistics pour les commandes de mise à jour.","tags":["statistics","stats health","dm_db_stats_properties","modification_counter","auto update","cardinality"]},{"id":"checkdb-strategy","task":"Stratégie DBCC CHECKDB : dernier contrôle réussi, découpage du travail, interprétation des résultats","category":"Maintenance","command":"-- Last successful CHECKDB (1900-01-01 = never):\nSELECT name, DATABASEPROPERTYEX(name, 'LastGoodCheckDbTime') AS last_good_checkdb\nFROM   sys.databases\nWHERE  state_desc = N'ONLINE' AND name <> N'tempdb'\nORDER BY last_good_checkdb;\n\n-- Estimated tempdb requirement:\nDBCC CHECKDB (N'Musteri') WITH ESTIMATEONLY;\n\n-- Split a large DB across days: allocation + catalog + table by table CHECKTABLE\nUSE [Musteri];\nDBCC CHECKALLOC (N'Musteri') WITH NO_INFOMSGS;\nDBCC CHECKCATALOG (N'Musteri') WITH NO_INFOMSGS;\nDBCC CHECKTABLE (N'dbo.Siparis') WITH NO_INFOMSGS, ALL_ERRORMSGS;\n\n-- Get results as rows (error rows with object and page):\nDBCC CHECKDB (N'Musteri') WITH TABLERESULTS, NO_INFOMSGS, ALL_ERRORMSGS, MAXDOP = 4;","what":"Affiche l'heure du dernier CHECKDB réussi pour chaque base, estime l'espace tempdb nécessaire, permet de découper le contrôle des grandes bases en CHECKALLOC + CHECKCATALOG + CHECKTABLE, et renvoie les résultats sous forme de lignes (objet, index, page).","when":"Lors de l'audit de la maintenance, pour les très grandes bases qui ne tiennent pas dans la fenêtre de maintenance, ou pour déterminer l'étendue d'une corruption après 823/824.","caution":"La phrase \"repair_allow_data_loss is the minimum repair level\" dans le résultat signifie que la réparation supprimera des données ; envisagez d'abord une restauration depuis une bonne sauvegarde (ou une restauration de pages). Si seuls des index nonclustered (index_id > 1) sont touchés, supprimer et recréer l'index corrige le problème sans perte de données. Après avoir trouvé une corruption, examinez le sous-système disque. PHYSICAL_ONLY saute les contrôles logiques ; un CHECKDB complet reste nécessaire périodiquement. En découpant avec CHECKTABLE, la couverture est incomplète si CHECKALLOC/CHECKCATALOG ne sont pas exécutés aussi.","note":"DATABASEPROPERTYEX(..., 'LastGoodCheckDbTime') est disponible sur 2016 SP2, 2017 CU9 et 2019+ ; sur les builds plus anciens, lisez dbi_dbccLastKnownGood depuis DBCC DBINFO WITH TABLERESULTS. Pour décharger la production, exécuter CHECKDB sur une copie restaurée depuis une sauvegarde (par ex. sur un serveur de test) valide aussi la base de production. L'option MAXDOP est 2014 SP2 / 2016+.","tags":["dbcc checkdb","lastgoodcheckdbtime","checktable","corruption","repair_allow_data_loss","integrity"]},{"id":"resource-governor","task":"Limiter la charge de reporting avec Resource Governor (pool, workload group, classifier)","category":"Configuration","command":"USE master;\n-- 1) Pool and workload group:\nCREATE RESOURCE POOL [pool_report]\nWITH (MIN_CPU_PERCENT = 0, MAX_CPU_PERCENT = 30, CAP_CPU_PERCENT = 40,\n      MIN_MEMORY_PERCENT = 0, MAX_MEMORY_PERCENT = 25);\nCREATE WORKLOAD GROUP [wg_report]\nWITH (IMPORTANCE = LOW, REQUEST_MAX_MEMORY_GRANT_PERCENT = 15,\n      MAX_DOP = 2, REQUEST_MAX_CPU_TIME_SEC = 600)\nUSING [pool_report];\nGO\n-- 2) Classifier function (in master, SCHEMABINDING required):\nCREATE FUNCTION dbo.fn_rg_classifier()\nRETURNS sysname\nWITH SCHEMABINDING\nAS\nBEGIN\n    DECLARE @grp sysname = N'default';\n    IF SUSER_SNAME() = N'rapor_user' OR APP_NAME() LIKE N'Power BI%'\n        SET @grp = N'wg_report';\n    RETURN @grp;\nEND;\nGO\n-- 3) Enable:\nALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = dbo.fn_rg_classifier);\nALTER RESOURCE GOVERNOR RECONFIGURE;\n\n-- 4) Which session landed in which group:\nSELECT s.session_id, s.login_name, s.program_name, wg.name AS workload_group, rp.name AS pool\nFROM   sys.dm_exec_sessions s\n  JOIN sys.dm_resource_governor_workload_groups wg ON wg.group_id = s.group_id\n  JOIN sys.dm_resource_governor_resource_pools rp  ON rp.pool_id = wg.pool_id\nWHERE  s.is_user_process = 1;\n\n-- Rollback:\n-- ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = NULL); ALTER RESOURCE GOVERNOR DISABLE;","what":"Dirige les sessions de logins ou d'applications spécifiques vers un workload group séparé à la connexion ; protège la charge OLTP en plafonnant le CPU, les memory grants, le DOP et le temps CPU par requête de ce groupe.","when":"Quand les requêtes de reporting/BI affament l'OLTP, quand l'ETL chevauche la charge des heures ouvrées, ou pour le partage de ressources sur un serveur multi-tenant.","caution":"La fonction classifier s'exécute à chaque nouvelle connexion ; une fonction lente ou en échec ralentit ou casse chaque login (aucune requête ni accès aux tables à l'intérieur). En cas de dysfonctionnement, connectez-vous via la DAC et définissez CLASSIFIER_FUNCTION = NULL. La classification n'a lieu qu'à la connexion ; les sessions existantes ne sont pas affectées. CAP_CPU_PERCENT est un plafond strict appliqué même quand le CPU est inactif. MAX_CPU_PERCENT ne s'applique qu'en cas de contention CPU. Resource Governor est une fonctionnalité Enterprise/Developer.","note":"Le dépassement de REQUEST_MAX_CPU_TIME_SEC ne déclenche par défaut qu'un événement (cpu_threshold_exceeded) ; avec le trace flag 2422 (2016 SP2 / 2017 CU3+), la requête est interrompue. Pour les limites I/O, utilisez MAX_IOPS_PER_VOLUME sur le pool (2014+). Statistiques : sys.dm_resource_governor_resource_pools / _workload_groups.","tags":["resource governor","resource pool","workload group","classifier","cpu limit","resource management"]},{"id":"tde-enable","task":"Mettre en place TDE : master key, certificat, sauvegarde du certificat et progression du chiffrement","category":"Sécurité","command":"USE master;\nIF NOT EXISTS (SELECT 1 FROM sys.symmetric_keys WHERE name = N'##MS_DatabaseMasterKey##')\n    CREATE MASTER KEY ENCRYPTION BY PASSWORD = N'<GucluParola_1>';\nCREATE CERTIFICATE [TDE_Cert_2026]\n    WITH SUBJECT = N'TDE certificate', EXPIRY_DATE = '20361231';\n\n-- Back it up NOW: without cert + private key the backups cannot be restored anywhere\nBACKUP CERTIFICATE [TDE_Cert_2026]\n    TO FILE = N'D:\\Keys\\TDE_Cert_2026.cer'\n    WITH PRIVATE KEY (FILE = N'D:\\Keys\\TDE_Cert_2026.pvk',\n                      ENCRYPTION BY PASSWORD = N'<GucluParola_2>');\n\nUSE [Musteri];\nCREATE DATABASE ENCRYPTION KEY\n    WITH ALGORITHM = AES_256\n    ENCRYPTION BY SERVER CERTIFICATE [TDE_Cert_2026];\nALTER DATABASE [Musteri] SET ENCRYPTION ON;\n\n-- Progress and certificate backup date:\nSELECT DB_NAME(dek.database_id) AS db, dek.encryption_state_desc, dek.percent_complete,\n       dek.encryption_scan_state_desc, c.name AS cert_name, c.expiry_date, c.pvt_key_last_backup_date\nFROM   sys.dm_database_encryption_keys dek\n  LEFT JOIN master.sys.certificates c ON c.thumbprint = dek.encryptor_thumbprint;\n\n-- On the target server (before restore / AG secondary):\n-- CREATE CERTIFICATE [TDE_Cert_2026] FROM FILE = N'D:\\Keys\\TDE_Cert_2026.cer'\n--   WITH PRIVATE KEY (FILE = N'D:\\Keys\\TDE_Cert_2026.pvk', DECRYPTION BY PASSWORD = N'<GucluParola_2>');","what":"Crée une database master key et un certificat dans master, sauvegarde le certificat avec sa clé privée, crée une database encryption key AES-256 et chiffre au repos les fichiers de données et du journal ; affiche la progression du scan de chiffrement en arrière-plan.","when":"Pour les exigences de chiffrement des données au repos comme le RGPD/PCI, et contre le vol des fichiers de sauvegarde et des fichiers MDF/LDF.","caution":"Si la sauvegarde du certificat + clé privée ou son mot de passe est perdue, la base TDE et toutes ses sauvegardes deviennent DÉFINITIVEMENT illisibles ; conservez la sauvegarde dans un coffre sécurisé loin du serveur. Activer TDE sur n'importe quelle base chiffre aussi tempdb (un faible coût CPU à l'échelle du serveur). Le scan de chiffrement est lourd en I/O ; en 2019+, il peut être mis en pause avec ALTER DATABASE ... SET ENCRYPTION SUSPEND/RESUME. Pour AG/log shipping, le certificat doit d'abord être installé sur chaque serveur cible. TDE ne chiffre pas les données en mémoire ni sur le réseau.","note":"L'expiration du certificat n'arrête pas un TDE existant, mais pour une rotation, créez un nouveau certificat et rechiffrez avec ALTER DATABASE ENCRYPTION KEY ENCRYPTION BY SERVER CERTIFICATE [new] (seule la DEK est rechiffrée, pas les données). encryption_state_desc et encryption_scan_state_desc sont des colonnes 2019+ ; sur les versions plus anciennes, utilisez encryption_state (3 = chiffré, 2 = en cours). TDE est disponible en édition Standard depuis 2019.","tags":["tde","transparent data encryption","certificate","backup certificate","encryption","dek"]},{"id":"sql-server-audit","task":"Mettre en place SQL Server Audit : connexions, modifications de permissions et accès aux tables","category":"Sécurité","command":"USE master;\nCREATE SERVER AUDIT [Audit_DBA]\nTO FILE (FILEPATH = N'D:\\Audit\\', MAXSIZE = 256 MB, MAX_ROLLOVER_FILES = 20, RESERVE_DISK_SPACE = OFF)\nWITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);\nALTER SERVER AUDIT [Audit_DBA] WITH (STATE = ON);\n\n-- Server-wide (covers all databases):\nCREATE SERVER AUDIT SPECIFICATION [AuditSpec_Server]\nFOR SERVER AUDIT [Audit_DBA]\n    ADD (FAILED_LOGIN_GROUP),\n    ADD (SERVER_ROLE_MEMBER_CHANGE_GROUP),\n    ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP),\n    ADD (SERVER_PRINCIPAL_CHANGE_GROUP),\n    ADD (DATABASE_PRINCIPAL_CHANGE_GROUP),\n    ADD (SERVER_PERMISSION_CHANGE_GROUP),\n    ADD (DATABASE_PERMISSION_CHANGE_GROUP),\n    ADD (AUDIT_CHANGE_GROUP)\nWITH (STATE = ON);\n\n-- Database: access to a sensitive table:\nUSE [Musteri];\nCREATE DATABASE AUDIT SPECIFICATION [AuditSpec_Musteri]\nFOR SERVER AUDIT [Audit_DBA]\n    ADD (SELECT, INSERT, UPDATE, DELETE ON OBJECT::[dbo].[Siparis] BY [public])\nWITH (STATE = ON);\n\n-- Read:\nSELECT event_time, action_id, succeeded, server_principal_name, database_name,\n       schema_name, object_name, LEFT(statement, 200) AS stmt, client_ip, application_name\nFROM   sys.fn_get_audit_file(N'D:\\Audit\\Audit_DBA_*.sqlaudit', DEFAULT, DEFAULT)\nWHERE  event_time >= DATEADD(HOUR, -24, SYSUTCDATETIME())\nORDER BY event_time DESC;","what":"Crée un server audit qui écrit dans un fichier ; enregistre au niveau serveur les échecs de connexion, les appartenances aux rôles, les modifications de principaux et de permissions, et au niveau base les accès SELECT/DML à une table choisie, puis lit les enregistrements avec T-SQL.","when":"Pour les exigences d'audit/conformité (RGPD, PCI, SOX), pour les questions \"qui a lu/modifié cette table\", ou en cas de suspicion d'élévation de privilèges.","caution":"L'audit des SELECT sur une table très sollicitée génère des enregistrements très vite et coûte en performances ; restreignez-le par objet et principal. ON_FAILURE = SHUTDOWN arrête le serveur quand l'audit ne peut pas être écrit ; ne l'utilisez que si c'est imposé. Le dossier d'audit doit être accessible en écriture au compte de service SQL Server et non supprimable par des non-DBA. event_time est en UTC. Une database audit specification est restaurée avec la base mais reste orpheline si le GUID du server audit ne correspond pas.","note":"Les database audit specifications sont disponibles en édition Standard depuis 2016 SP1. Incluez toujours AUDIT_CHANGE_GROUP pour que les modifications de l'audit lui-même, y compris sa désactivation, soient enregistrées.","tags":["sql server audit","server audit","audit specification","fn_get_audit_file","auditing","compliance"]},{"id":"orphaned-users-fix","task":"Trouver les utilisateurs orphelins et les rattacher à des logins (après restauration / AG)","category":"Sécurité","command":"USE [Musteri];\n-- Database users without a matching login:\nSELECT dp.name AS user_name, dp.type_desc, dp.authentication_type_desc, dp.sid, dp.create_date\nFROM   sys.database_principals dp\n  LEFT JOIN sys.server_principals sp ON sp.sid = dp.sid\nWHERE  sp.sid IS NULL\n  AND  dp.type IN ('S', 'U', 'G')\n  AND  dp.authentication_type_desc IN (N'INSTANCE', N'WINDOWS')\n  AND  dp.principal_id > 4;\n\n-- If a login with the same name exists, remap the SID:\nALTER USER [app_user] WITH LOGIN = [app_user];\n\n-- Permanent fix: generate CREATE LOGIN with SID + password hash on the source, run it on the target:\nSELECT N'CREATE LOGIN ' + QUOTENAME(sp.name)\n     + N' WITH PASSWORD = ' + CONVERT(NVARCHAR(MAX), LOGINPROPERTY(sp.name, 'PasswordHash'), 1)\n     + N' HASHED, SID = ' + CONVERT(NVARCHAR(MAX), sp.sid, 1)\n     + N', CHECK_POLICY = OFF;' AS create_login_stmt\nFROM   sys.server_principals sp\nWHERE  sp.type = 'S' AND sp.name = N'app_user';","what":"Trouve les utilisateurs de base dont le SID ne correspond à aucun login serveur (orphelins) ; rattache un utilisateur à un login existant et génère un script pour recréer les logins sur un autre serveur avec le même SID et le même hash de mot de passe.","when":"Après la restauration/l'attachement d'une base sur un autre serveur, quand l'application reçoit \"Login failed for user\" après un basculement AG/log shipping, ou lors d'une migration de serveur.","caution":"ALTER USER ... WITH LOGIN remplace le SID de l'utilisateur par celui du login ; dans un AG, cette modification se propage du primaire vers les secondaires, mais si les logins des serveurs secondaires ont des SID différents, le problème revient au basculement suivant. La correction durable consiste à créer les logins avec le même SID sur chaque réplica. Le hash du mot de passe est sensible ; stockez le script de façon sécurisée. Les utilisateurs de base contenue (authentication_type = DATABASE) n'ont pas besoin de login et ne sont pas listés.","note":"sp_change_users_login est obsolète ; utilisez ALTER USER WITH LOGIN. Pour un dbo orphelin (login du propriétaire de la base manquant), utilisez ALTER AUTHORIZATION ON DATABASE::[Musteri] TO [sa];. Pour une migration en masse des logins, utilisez PowerShell dbatools : Copy-DbaLogin -Source SQL01 -Destination SQL02.","tags":["orphaned user","alter user with login","sid","login failed","after restore","sp_change_users_login"]},{"id":"permission-audit","task":"Audit des logins et permissions : membres sysadmin, permissions à risque, mots de passe faibles","category":"Sécurité","command":"-- 1) Server role memberships:\nSELECT r.name AS server_role, m.name AS member, m.type_desc, m.is_disabled, m.create_date\nFROM   sys.server_role_members rm\n  JOIN sys.server_principals r ON r.principal_id = rm.role_principal_id\n  JOIN sys.server_principals m ON m.principal_id = rm.member_principal_id\nORDER BY r.name, m.name;\n\n-- 2) Risky server permissions:\nSELECT pr.name AS grantee, pr.type_desc, pe.permission_name, pe.state_desc\nFROM   sys.server_permissions pe\n  JOIN sys.server_principals pr ON pr.principal_id = pe.grantee_principal_id\nWHERE  pe.permission_name IN (N'CONTROL SERVER', N'IMPERSONATE ANY LOGIN', N'ALTER ANY LOGIN',\n                              N'ALTER ANY SERVER ROLE', N'ALTER SETTINGS', N'IMPERSONATE')\n   OR  pe.state_desc = N'GRANT_WITH_GRANT_OPTION';\n\n-- 3) SQL login health (policy, lockout, password = login name):\nSELECT sl.name, sl.is_disabled, sl.is_policy_checked, sl.is_expiration_checked,\n       LOGINPROPERTY(sl.name, 'PasswordLastSetTime') AS pwd_last_set,\n       LOGINPROPERTY(sl.name, 'IsLocked')            AS is_locked,\n       LOGINPROPERTY(sl.name, 'BadPasswordCount')    AS bad_pwd_count,\n       PWDCOMPARE(sl.name, sl.password_hash)         AS pwd_equals_name,\n       PWDCOMPARE(N'', sl.password_hash)             AS pwd_empty\nFROM   sys.sql_logins sl;\n\n-- 4) Database role memberships and explicit object permissions:\nUSE [Musteri];\nSELECT r.name AS db_role, m.name AS member, m.type_desc\nFROM   sys.database_role_members rm\n  JOIN sys.database_principals r ON r.principal_id = rm.role_principal_id\n  JOIN sys.database_principals m ON m.principal_id = rm.member_principal_id\nWHERE  m.name <> N'dbo'\nORDER BY r.name, m.name;\n\nSELECT pr.name AS grantee, pe.class_desc, pe.permission_name, pe.state_desc,\n       OBJECT_SCHEMA_NAME(pe.major_id) AS schema_name,\n       CASE pe.class WHEN 1 THEN OBJECT_NAME(pe.major_id) WHEN 3 THEN SCHEMA_NAME(pe.major_id) END AS object_name\nFROM   sys.database_permissions pe\n  JOIN sys.database_principals pr ON pr.principal_id = pe.grantee_principal_id\nWHERE  (pr.name NOT IN (N'public', N'dbo') AND NOT (pe.class = 0 AND pe.permission_name = N'CONNECT'))\n   OR  (pr.name = N'public' AND OBJECT_SCHEMA_NAME(pe.major_id) <> N'sys')\nORDER BY pr.name, pe.class_desc;","what":"Liste les appartenances aux rôles serveur, les permissions à risque comme CONTROL SERVER/IMPERSONATE, les logins SQL avec politique de mot de passe désactivée, verrouillés, ou dont le mot de passe est égal au nom du login ou vide ; dans la base, les appartenances aux rôles et les permissions explicites sur les objets non système.","when":"Pour les revues d'accès périodiques, avant un audit, après le départ d'un membre de l'équipe, ou en cas de suspicion d'incident de sécurité.","caution":"Les permissions accordées via des groupes Windows apparaissent sous le nom du groupe ; pour voir les personnes, utilisez EXEC xp_logininfo N'DOMAIN\\Group', 'members';. Vérifiez les dépendances applicatives avant de révoquer quoi que ce soit. PWDCOMPARE ne fonctionne que pour les logins SQL et nécessite CONTROL SERVER. Requête en lecture seule ; sûre.","note":"Pour les permissions effectives d'un principal : EXECUTE AS USER = N'app_user'; SELECT * FROM fn_my_permissions(NULL, 'DATABASE'); REVERT;. En 2022, les nouveaux rôles granulaires (##MS_ServerStateReader##, ##MS_DefinitionReader## etc.) accordent des droits de supervision sans sysadmin.","tags":["permission audit","sysadmin","server_role_members","database_permissions","pwdcompare","security"]},{"id":"ledger-tables","task":"Créer une table ledger, lire son historique de modifications et vérifier son intégrité","category":"Sécurité","command":"USE [Musteri];\n-- Ledger verification requires snapshot isolation:\nALTER DATABASE [Musteri] SET ALLOW_SNAPSHOT_ISOLATION ON;\n\n-- Updatable ledger table (history table maintained automatically):\nCREATE TABLE dbo.Hesap (\n    HesapId int           NOT NULL PRIMARY KEY,\n    Bakiye  decimal(18,2) NOT NULL\n)\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Hesap_History), LEDGER = ON);\n\n-- Append-only ledger (UPDATE/DELETE are blocked):\nCREATE TABLE dbo.IslemLog (Id bigint IDENTITY, Mesaj nvarchar(400))\nWITH (LEDGER = ON (APPEND_ONLY = ON));\n\n-- Who changed what, and when:\nSELECT t.commit_time, t.principal_name, l.HesapId, l.Bakiye, l.ledger_operation_type_desc\nFROM   dbo.Hesap_Ledger l\n  JOIN sys.database_ledger_transactions t ON t.transaction_id = l.ledger_transaction_id\nORDER BY t.commit_time DESC;\n\n-- Take a digest (store it somewhere trusted outside) and verify:\nEXEC sys.sp_generate_database_ledger_digest;\nEXEC sys.sp_verify_database_ledger\n     N'{\"database_name\":\"Musteri\",\"block_id\":0,\"hash\":\"0x...\",\"last_transaction_commit_time\":\"...\",\"digest_time\":\"...\"}';","what":"Crée une table ledger qui enregistre les modifications de lignes dans une chaîne de hachage cryptographique ; la vue _Ledger indique qui a effectué chaque INSERT/UPDATE/DELETE et quand, et la vérification par rapport à un digest stocké prouve que les données n'ont pas été modifiées après coup (y compris par les DBA).","when":"Pour les données dont l'immuabilité doit être prouvable : enregistrements financiers, pistes d'audit, enregistrements de contrats/d'approbations.","caution":"Ledger ne peut pas être désactivé après la création de la table ; une table ledger ne peut pas être convertie en table ordinaire, et une table ledger supprimée est conservée en tant que dropped ledger table. Un updatable ledger écrit une ligne d'historique à chaque modification et le stockage grossit. Si les digests ne sont pas stockés hors de la base dans un stockage immuable (WORM, Azure immutable blob), la vérification n'a aucune valeur probante. sp_verify_database_ledger échoue avec l'erreur 37498 sans snapshot isolation.","since":"2022","note":"sp_generate_database_ledger_digest renvoie du JSON ; sp_verify_database_ledger accepte un digest unique ou un tableau JSON et peut vérifier une seule table avec @table_name. Pour le stockage automatique des digests : ALTER DATABASE SCOPED CONFIGURATION SET LEDGER_DIGEST_STORAGE_ENDPOINT = '<blob url>'. CREATE DATABASE ... WITH LEDGER = ON fait de chaque table une table ledger par défaut.","tags":["ledger","append-only","sp_verify_database_ledger","digest","immutable","tamper-evident"]},{"id":"agent-job-failures","task":"Historique des échecs de jobs SQL Agent et état de la dernière exécution","category":"SQL Agent","command":"-- Job steps that failed / were cancelled in the last 24 hours:\nSELECT j.name AS job_name, h.step_id, h.step_name,\n       msdb.dbo.agent_datetime(h.run_date, h.run_time) AS run_at,\n       STUFF(STUFF(RIGHT('000000' + CAST(h.run_duration AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':') AS duration_hhmmss,\n       CASE h.run_status WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' WHEN 2 THEN 'Retry'\n                         WHEN 3 THEN 'Canceled' WHEN 4 THEN 'In progress' END AS status,\n       h.sql_message_id, h.sql_severity,\n       LEFT(h.message, 500) AS message\nFROM   msdb.dbo.sysjobhistory h\n  JOIN msdb.dbo.sysjobs j ON j.job_id = h.job_id\nWHERE  h.run_status IN (0, 3)\n  AND  msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(HOUR, -24, GETDATE())\nORDER BY run_at DESC;\n\n-- Each job's last outcome, running now, next run:\nSELECT j.name, j.enabled,\n       CASE lr.run_status WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' WHEN 3 THEN 'Canceled' END AS last_outcome,\n       lr.run_at AS last_run_at,\n       CASE WHEN a.start_execution_date IS NOT NULL AND a.stop_execution_date IS NULL THEN 'RUNNING' END AS running_now,\n       a.next_scheduled_run_date\nFROM   msdb.dbo.sysjobs j\n  LEFT JOIN msdb.dbo.sysjobactivity a\n         ON a.job_id = j.job_id AND a.session_id = (SELECT MAX(session_id) FROM msdb.dbo.syssessions)\n  OUTER APPLY (SELECT TOP (1) h.run_status, msdb.dbo.agent_datetime(h.run_date, h.run_time) AS run_at\n               FROM msdb.dbo.sysjobhistory h\n               WHERE h.job_id = j.job_id AND h.step_id = 0\n               ORDER BY h.instance_id DESC) lr\nORDER BY j.name;\n\n-- Is the Agent service running:\nSELECT servicename, status_desc, startup_type_desc FROM sys.dm_server_services;","what":"Affiche les étapes de jobs en échec ou annulées au cours des dernières 24 heures avec leur message d'erreur ; le dernier résultat de chaque job, s'il est en cours d'exécution et sa prochaine exécution planifiée ; ainsi que l'état du service Agent.","when":"Lors du contrôle du matin, sur une alerte de job de sauvegarde/maintenance, pour savoir \"le job de nuit a-t-il tourné\", ou après un basculement pour vérifier que les jobs s'exécutent sur le bon réplica.","caution":"L'historique des jobs dans msdb est limité (par défaut 1,000 lignes au total, 100 par job) ; les jobs fréquents évincent rapidement les anciens échecs, augmentez donc la limite dans les propriétés de l'Agent. La colonne message est tronquée à 4,000 caractères ; définissez un fichier de sortie sur l'étape du job pour les sorties longues. Si le service Agent est arrêté, aucun job planifié ne s'exécute et aucun ne signale d'erreur. Requête en lecture seule ; sûre.","note":"msdb.dbo.agent_datetime n'est pas documenté mais très utilisé. Dans un AG, les jobs doivent exister sur chaque réplica et vérifier le rôle dès la première étape avec sys.fn_hadr_is_primary_replica(N'Musteri'). dbatools : Get-DbaAgentJobHistory -SqlInstance SQL01 -StartDate (Get-Date).AddDays(-1) -OutcomeType Failed.","tags":["sql agent","job history","sysjobhistory","job failure","msdb","scheduled job"]}]},"recipes/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"recipes":[{"id":"rs-add-member","task":"Ajouter un membre au replica set","category":"Replica Set","command":"rs.add({ host: \"mongo2:27017\", priority: 1, votes: 1 })","what":"Ajoute un nouveau membre (secondaire) au replica set existant et lance une synchronisation initiale.","when":"Lors de l'ajout d'un nouveau nœud pour augmenter la capacité de lecture ou pour la redondance.","caution":"Le nouveau membre récupère les données depuis le primaire pendant la synchronisation initiale ; avec de grands volumes, cela génère une charge réseau et disque.","tags":["replica","rs.add","member","member","add"]},{"id":"rs-remove-member","task":"Retirer un membre du replica set","category":"Replica Set","command":"rs.remove(\"mongo2:27017\")","what":"Retire l'hôte indiqué de la configuration du replica set.","when":"Lors de la mise hors service d'un nœud ou du remplacement du matériel.","caution":"Ne retirez pas le primaire ; retirer des membres de façon à rompre la majorité peut rendre le set indisponible en lecture et en écriture.","tags":["replica","rs.remove","member","member","remove"]},{"id":"rs-step-down","task":"Rétrograder le primaire (stepDown)","category":"Replica Set","command":"rs.stepDown(60)","what":"Rétrograde le primaire actuel en secondaire pendant le nombre de secondes indiqué et déclenche une nouvelle élection.","when":"Pour transférer le rôle primaire à un autre nœud avant une maintenance planifiée.","caution":"Une brève interruption des écritures se produit pendant le stepDown ; l'application doit se reconnecter.","tags":["replica","stepdown","primary","failover","step down"]},{"id":"rs-set-priority","task":"Modifier la priorité d'un membre","category":"Replica Set","command":"cfg = rs.conf(); cfg.members[1].priority = 2; rs.reconfig(cfg)","what":"Modifie la priorité du membre indiqué pour devenir primaire lors des élections.","when":"Quand vous voulez qu'un nœud précis soit le primaire préféré.","caution":"reconfig peut déclencher une élection et provoquer un bref changement de primaire/une interruption des écritures.","tags":["replica","priority","reconfig","priority","election"]},{"id":"rs-status","task":"Afficher l'état du replica set","category":"Replica Set","command":"rs.status()","what":"Affiche l'état, les rôles, le retard de réplication et la santé des membres du set.","when":"Pour vérifier la santé et le retard de la réplication.","caution":"Commande en lecture seule ; aucun risque.","tags":["replica","status","status","health","rs.status"]},{"id":"rs-hidden-member","task":"Configurer un membre caché / sans droit de vote","category":"Replica Set","command":"cfg = rs.conf(); cfg.members[2].hidden = true; cfg.members[2].priority = 0; cfg.members[2].votes = 0; rs.reconfig(cfg)","what":"Rend un membre caché (invisible pour l'application), sans droit de vote et de priorité 0, pour ne l'utiliser que pour les sauvegardes/l'analytique.","when":"Quand vous voulez isoler les sauvegardes ou les requêtes analytiques.","caution":"Un membre caché doit avoir la priorité 0 ; sinon reconfig échoue.","tags":["replica","hidden","votes","hidden","non-voting"]},{"id":"rs-resync","task":"Resynchroniser un membre de zéro","category":"Replica Set","command":"// On the SECONDARY to be resynced (NEVER on the PRIMARY):\ndb.getSiblingDB(\"admin\").shutdownServer()\n// At the OS level: empty the dbPath contents (or move them elsewhere as a backup)\n// and restart mongod with the same configuration; the member starts an initial sync automatically.\n// Monitor progress (stateStr: STARTUP2 -> SECONDARY):\nrs.status()","what":"MongoDB n'a pas de commande de resync distincte ; arrêter le secondaire, vider son répertoire de données et le redémarrer lance une nouvelle synchronisation initiale depuis le primaire (ou une source de synchronisation).","when":"Quand un membre prend tellement de retard qu'il sort de la fenêtre de l'oplog (RECOVERING / too stale) ou que ses données deviennent incohérentes.","caution":"Toutes les données locales du membre sont supprimées puis récupérées à nouveau ; ne le faites que sur un secondaire et assurez-vous que la majorité reste disponible. La durée de synchronisation et la charge réseau/disque sont élevées ; pour de très grands volumes, l'initialisation depuis un snapshot du système de fichiers d'un membre sain est plus rapide.","tags":["replica","resync","initialsync","sync","resync"]},{"id":"sh-add-shard","task":"Ajouter un shard au cluster","category":"Sharding","command":"sh.addShard(\"shardRS/mongoShard1:27018\")","what":"Ajoute un nouveau shard (replica set) au cluster shardé.","when":"Pour faire évoluer horizontalement la capacité de stockage ou de débit.","caution":"Après l'ajout d'un shard, le balancer commence à migrer les chunks ; cela génère une charge IO supplémentaire.","tags":["sharding","addshard","shard","add","cluster"]},{"id":"sh-shard-collection","task":"Sharder une collection","category":"Sharding","command":"sh.shardCollection(\"shop.customer\", { customerId: \"hashed\" })","what":"Sharde la collection indiquée sur la shard key donnée.","when":"Quand vous voulez répartir une collection en croissance sur plusieurs shards.","caution":"La shard key peut être changée avec reshardCollection en 5.0+ (et étendue avec refineCollectionShardKey en 4.4+), mais c'est coûteux car toutes les données sont redistribuées ; un mauvais choix de clé entraîne une répartition inégale.","tags":["sharding","shardcollection","key","customer","shard key"]},{"id":"sh-start-balancer","task":"Démarrer le balancer","category":"Sharding","command":"sh.startBalancer()","what":"Active le processus balancer qui répartit les chunks.","when":"Quand vous voulez réactiver l'équilibrage après une maintenance.","caution":"Le balancer génère une charge IO et réseau pendant la migration des chunks ; définissez une fenêtre d'équilibrage en dehors des heures de pointe.","tags":["sharding","balancer","start","start","rebalance"]},{"id":"sh-stop-balancer","task":"Arrêter le balancer","category":"Sharding","command":"sh.stopBalancer()","what":"Désactive le balancer qui effectue les migrations de chunks.","when":"Pour arrêter les migrations avant une sauvegarde, une maintenance ou un chargement massif de données.","caution":"S'il reste désactivé longtemps, les shards se déséquilibrent ; n'oubliez pas de le réactiver ensuite.","tags":["sharding","balancer","stop","stop","maintenance"]},{"id":"sh-status","task":"Afficher l'état du sharding","category":"Sharding","command":"sh.status()","what":"Résume les shards, les bases, les collections shardées et la répartition des chunks.","when":"Pour examiner l'état de répartition du cluster et la santé du balancer.","caution":"Commande en lecture seule ; aucun risque.","tags":["sharding","status","status","chunk","sh.status"]},{"id":"sh-add-zone","task":"Ajouter une zone (tag) et lui assigner une plage","category":"Sharding","command":"sh.addShardToZone(\"shard0000\", \"TR\"); sh.updateZoneKeyRange(\"shop.customer\", { region: \"TR\" }, { region: \"TR~\" }, \"TR\")","what":"Assigne un shard à une zone et associe une plage de clés précise à cette zone (zone sharding).","when":"Quand vous voulez conserver des données sur des shards précis selon la géographie ou le matériel.","caution":"Les plages de zone doivent correspondre à la shard key ; une mauvaise plage empêche la migration des chunks.","tags":["sharding","zone","tag","region","range"]},{"id":"sh-move-chunk","task":"Déplacer un chunk manuellement","category":"Sharding","command":"sh.moveChunk(\"shop.customer\", { customerId: 1000 }, \"shard0001\")","what":"Déplace manuellement le chunk contenant la clé indiquée vers le shard cible.","when":"Pour corriger une répartition inégale ou placer des données précises.","caution":"Un déplacement manuel peut entrer en conflit avec le balancer ; les migrations de gros chunks génèrent une charge IO et un bref verrouillage.","tags":["sharding","movechunk","chunk","move","manual"]},{"id":"compact-collection","task":"Compacter une collection","category":"Maintenance","command":"db.runCommand({ compact: \"customer\" })","what":"Réécrit la collection et ses index sur disque, en récupérant l'espace libre.","when":"Quand vous voulez récupérer de l'espace disque après de nombreux delete/update.","caution":"En 4.4+, compact ne bloque pas les lectures/écritures ; il ne bloque que certaines opérations DDL sur la même collection (drop, createIndex, dropIndex, etc.) ; il génère toutefois une forte IO et ne rend pas toujours l'espace au système d'exploitation. Exécutez-le si possible sur un secondaire et dans une fenêtre de maintenance.","tags":["compact","storage","maintenance","disk","storage"]},{"id":"create-index-background","task":"Créer un index (non bloquant)","category":"Maintenance","command":"db.customer.createIndex({ email: 1 }, { name: \"idx_email\" })","what":"Crée un index sur le champ indiqué ; depuis MongoDB 4.2, la construction d'index ne bloque pas les écritures par défaut.","when":"Lors de l'ajout d'un nouvel index pour améliorer les performances des requêtes.","caution":"Sur les grandes collections, la construction d'index consomme du CPU et de l'IO ; la charge sur le primaire peut augmenter.","since":"MongoDB 4.2","note":"MongoDB 4.2 a introduit un nouveau processus de construction d'index, et la création d'index ne bloque plus les écritures par défaut ; l'ancienne option { background: true } est donc inutile et ignorée à partir de 4.2.","tags":["index","createindex","background","create","performance"]},{"id":"collection-stats","task":"Afficher la taille des index et des collections","category":"Maintenance","command":"db.customer.stats({ indexDetails: true })","what":"Indique la taille de la collection, la taille de stockage et la taille de chaque index.","when":"Pour analyser l'utilisation du stockage et le coût des index.","caution":"Commande en lecture seule ; aucun risque.","tags":["stats","index","size","collection","storage"]},{"id":"log-rotate","task":"Faire tourner le fichier de log","category":"Maintenance","command":"db.adminCommand({ logRotate: 1 })","what":"Ferme le fichier de log actuel et en ouvre un nouveau (rotation des logs).","when":"Quand le fichier de log a grossi ou avant l'archivage.","caution":"En cas d'utilisation conjointe avec la rotation des logs de l'OS, vérifiez la configuration pour éviter les conflits.","tags":["log","logrotate","rotate","maintenance","archive"]},{"id":"current-op-long","task":"Trouver les opérations longues","category":"Maintenance","command":"db.currentOp({ \"active\": true, \"secs_running\": { $gt: 5 } })","what":"Liste les opérations actives qui s'exécutent depuis plus de 5 secondes.","when":"Pour identifier les requêtes problématiques lors de lenteurs ou de blocages.","caution":"Commande en lecture seule ; aucun risque.","tags":["currentop","long","long-running","operation","diagnostics"]},{"id":"kill-op","task":"Tuer une opération","category":"Maintenance","command":"db.killOp(12345)","what":"Termine l'opération en cours avec l'opid indiqué.","when":"Quand une requête bloque le système ou s'exécute bien trop longtemps.","caution":"Tuer une opération d'écriture peut laisser des modifications partielles ; évitez de tuer les opérations système/de réplication.","tags":["killop","kill","operation","kill","diagnostics"]},{"id":"set-parameter","task":"Modifier un paramètre d'exécution","category":"Paramètres","command":"db.adminCommand({ setParameter: 1, cursorTimeoutMillis: 600000 })","what":"Modifie un paramètre d'exécution sans redémarrer le serveur.","when":"Quand vous voulez ajuster temporairement un comportement du serveur.","caution":"La modification n'est pas persistante ; au redémarrage, elle revient à la valeur du fichier de configuration. Une mauvaise valeur peut dégrader les performances.","tags":["setparameter","parameter","runtime","setting","admin"]},{"id":"set-profiling","task":"Définir le niveau de profiling","category":"Paramètres","command":"db.setProfilingLevel(1, { slowms: 100 })","what":"Enregistre les requêtes lentes (plus de 100 ms) dans la collection system.profile (niveau 1).","when":"Quand vous voulez diagnostiquer les requêtes lentes.","caution":"Le niveau 2 enregistre toutes les opérations et ajoute une surcharge importante ; les données de profiling occupent de l'espace disque.","tags":["profiling","slowms","profiler","slow","diagnostics"]}]},"recipes/mongodb-ext.json":{"engineId":"mongodb","engine":"MongoDB","order":2,"recipes":[{"id":"rs-oplog-window","task":"Mesurer la fenêtre de l'oplog et le retard de réplication","category":"Replica Set","command":"// Oplog size and the time span it covers (oplog window):\nrs.printReplicationInfo()\n// Lag of each secondary relative to the primary:\nrs.printSecondaryReplicationInfo()\n// Lag in seconds and sync source, for monitoring/scripts:\nconst s = rs.status(); const p = s.members.find(m => m.stateStr === \"PRIMARY\");\ns.members.filter(m => m.stateStr === \"SECONDARY\").map(m => ({ host: m.name, lagSec: (p.optimeDate - m.optimeDate) / 1000, syncSource: m.syncSourceHost }))\n// Window (hours):\ndb.getReplicationInfo().timeDiffHours","what":"Indique combien d'heures d'écritures l'oplog contient (fenêtre de l'oplog) et de combien de secondes chaque secondaire est en retard sur le primaire, avec sa source de synchronisation.","when":"Sur une alerte de retard de réplication, avant une maintenance planifiée pour calculer combien de temps un membre peut rester arrêté, et après des jobs d'écriture massive.","caution":"Si un membre reste arrêté plus longtemps que la fenêtre de l'oplog, il devient 'too stale' et nécessite une synchronisation initiale. Les jobs d'update/delete massifs peuvent réduire la fenêtre en quelques heures ; évaluez la fenêtre d'après sa valeur en pic de charge. Le calcul du retard repose sur la différence d'optimeDate ; si le primaire ne reçoit aucune écriture, le retard ne paraît pas élevé mais la fenêtre ne se réduit pas non plus.","since":"MongoDB 4.4","note":"rs.printSlaveReplicationInfo() a été renommé rs.printSecondaryReplicationInfo() en 4.4. Si la fenêtre est trop petite, agrandissez l'oplog avec replSetResizeOplog (voir rs-oplog-resize). Les membres différés apparaissent en retard de secondaryDelaySecs (slaveDelay en 4.4) ; excluez-les des alertes de retard.","tags":["oplog","oplog window","replication lag","delay","secondary","printReplicationInfo"],"untested":true},{"id":"rs-oplog-resize","task":"Redimensionner l'oplog en ligne (replSetResizeOplog)","category":"Replica Set","command":"// Current oplog size (MB):\ndb.getSiblingDB(\"local\").oplog.rs.stats().maxSize / 1024 / 1024\n// Resize the oplog to 50 GB (in MB; run on each member separately, secondaries first):\ndb.adminCommand({ replSetResizeOplog: 1, size: 51200 })\n// Minimum retention period (hours), independent of size:\ndb.adminCommand({ replSetResizeOplog: 1, minRetentionHours: 48 })\n// Verify the result:\nrs.printReplicationInfo()","what":"Agrandit ou réduit l'oplog d'un membre en cours d'exécution sans redémarrage ; minRetentionHours conserve les entrées de l'oplog pendant une durée minimale même si la limite de taille est dépassée.","when":"Quand la fenêtre de l'oplog ne couvre pas la durée de la maintenance ou de la synchronisation initiale, ou que des jobs d'écriture massive réduisent la fenêtre.","caution":"La commande n'affecte que le membre sur lequel elle s'exécute et n'est pas répliquée ; appliquez-la sur chaque membre. La fenêtre doit aussi être suffisante sur les membres qui servent de sources de synchronisation. La réduction ne rend pas immédiatement l'espace disque à l'OS ; un compact sur oplog.rs est nécessaire pour cela. Avec minRetentionHours, l'oplog peut dépasser size selon le volume d'écriture ; dimensionnez le disque en conséquence.","since":"MongoDB 4.4","note":"La nouvelle taille persiste après redémarrage ; le minRetentionHours défini par la commande non, définissez donc aussi storage.oplogMinRetentionHours pour le conserver. size doit être d'au moins 990 MB. Modifier replication.oplogSizeMB dans la configuration ne prend effet qu'à la création initiale de l'oplog.","tags":["oplog","resize","replSetResizeOplog","minRetentionHours","size","oplog window"],"untested":true},{"id":"rs-force-reconfig","task":"Forcer la reconfiguration d'un replica set après perte de la majorité (force reconfig)","category":"Replica Set","command":"// On a surviving member with the most recent optime (runs as a secondary):\nrs.status().members.map(m => ({ host: m.name, state: m.stateStr, health: m.health, optime: m.optimeDate }))\ncfg = rs.conf()\ncfg.members.map((m, i) => ({ i: i, host: m.host }))\n// Keep only the reachable members (example: members 0 and 2):\ncfg.members = [cfg.members[0], cfg.members[2]]\nrs.reconfig(cfg, { force: true })\n// Verify the new primary election:\nrs.status().members.map(m => ({ host: m.name, state: m.stateStr }))","what":"Quand la majorité des membres votants est définitivement perdue (par ex. un data center entier a disparu), construit une nouvelle configuration à partir des membres survivants pour que le set puisse de nouveau élire un primaire.","when":"Uniquement dans un scénario de sinistre : la majorité ne reviendra pas et le set est bloqué en lecture seule.","caution":"C'est un dernier recours. Si les membres perdus reviennent plus tard avec l'ancienne configuration, un split-brain et un rollback peuvent se produire ; isolez-les du réseau et, lors de leur retour, effacez leurs données et ajoutez-les avec une synchronisation initiale. Choisissez le membre ayant l'optime le plus récent ; sinon les écritures non validées par la majorité sur les autres membres tombent en rollback. N'utilisez pas force pour les changements de membres courants.","since":"MongoDB 4.4","note":"force reconfig incrémente la version de configuration d'un grand nombre aléatoire et est la seule forme de reconfig pouvant s'exécuter sur un secondaire. En 4.4+, les reconfig normales peuvent ajouter ou retirer au plus un membre votant à la fois ; force n'est pas soumis à cette limite.","tags":["force reconfig","majority","disaster","recovery","split-brain","rs.reconfig"],"untested":true},{"id":"rs-rolling-maintenance","task":"Maintenance en rolling sur un replica set (redémarrage, correctif OS, mise à niveau mineure)","category":"Replica Set","command":"// 1) Before starting: all members healthy, low lag, and an oplog window longer than the maintenance\nrs.printSecondaryReplicationInfo()\n// 2) Clean shutdown on the SECONDARY under maintenance (then OS/binary work, restart with the same config):\ndb.getSiblingDB(\"admin\").shutdownServer({ timeoutSecs: 60 })\n// 3) Wait until the member is SECONDARY and lag has closed, then move to the next secondary:\nrs.status().members.map(m => ({ host: m.name, state: m.stateStr, health: m.health }))\n// 4) When all secondaries are done, on the PRIMARY: hand over to a secondary that catches up within 30 s\nrs.stepDown(120, 30)\n// 5) The old primary is now a SECONDARY: apply steps 2 and 3 to it","what":"Donne l'ordre de mise en maintenance des membres un par un, les secondaires d'abord et le primaire en dernier, afin que redémarrages, correctifs OS ou mises à jour des binaires se fassent sans mettre le set hors service.","when":"Lors des mises à jour OS/noyau planifiées, des mises à niveau de version mineure, des modifications de paramètres mongod ou de la maintenance disque/matérielle.","caution":"N'arrêtez jamais plus d'un membre à la fois ; dans un set de 3 membres, perdre deux membres fait perdre la majorité. Après chaque membre, confirmez l'état SECONDARY et que le retard est résorbé. stepDown provoque une courte interruption des écritures ; assurez-vous que les drivers se reconnectent avec les retryable writes. S'il y a un arbitre, traitez-le dans la même séquence.","since":"MongoDB 4.4","note":"rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs) : le second paramètre est le temps pendant lequel le primaire attend qu'un secondaire le rattrape ; si aucun ne le rattrape, stepDown échoue et le primaire reste. Pour empêcher un membre précis de devenir primaire pendant la maintenance, exécutez rs.freeze(seconds) sur celui-ci.","tags":["rolling","maintenance","restart","stepDown","upgrade","patching"],"untested":true},{"id":"sh-balancer-window","task":"État du balancer, fenêtre active et équilibrage par collection","category":"Sharding","command":"// Is the balancer enabled, is it running a round right now:\nsh.getBalancerState()\ndb.adminCommand({ balancerStatus: 1 })\n// Run the balancer only between 01:00 and 05:00 (on mongos):\ndb.getSiblingDB(\"config\").settings.updateOne({ _id: \"balancer\" }, { $set: { activeWindow: { start: \"01:00\", stop: \"05:00\" } } }, { upsert: true })\n// Remove the window:\ndb.getSiblingDB(\"config\").settings.updateOne({ _id: \"balancer\" }, { $unset: { activeWindow: true } })\n// Disable / enable balancing for a single collection:\nsh.disableBalancing(\"shop.musteri\")\nsh.enableBalancing(\"shop.musteri\")\n// Whether the collection is balanced (4.4+):\ndb.adminCommand({ balancerCollectionStatus: \"shop.musteri\" })","what":"Indique si le balancer est activé et exécute un cycle, limite les migrations de chunks à une fenêtre horaire et désactive ou active l'équilibrage pour une seule collection.","when":"Quand les migrations de chunks nuisent aux performances en journée, pendant la fenêtre de sauvegarde ou pour isoler un problème de migration sur une collection.","caution":"Une fenêtre trop étroite ne suit pas la croissance des données et les shards se déséquilibrent ; vérifiez régulièrement l'équilibre avec balancerCollectionStatus. Une migration commencée avant la fermeture de la fenêtre se poursuit jusqu'à la fin. Si l'équilibrage par collection reste désactivé, cette collection continue de grossir sur un seul shard.","since":"MongoDB 6.0","note":"Les heures de activeWindow sont évaluées dans le fuseau horaire local du primaire du replica set des config servers. En 6.0.3+, le balancer décide selon la différence de taille des données entre shards, et non selon le nombre de chunks ; le nombre de chunks dans sh.status() peut paraître inégal alors que les données sont équilibrées.","tags":["balancer","activeWindow","window","migration","sharding","balancerCollectionStatus"],"untested":true},{"id":"sh-jumbo-chunks","task":"Trouver les chunks jumbo, les découper et effacer l'indicateur","category":"Sharding","command":"// Chunks flagged as jumbo (5.0+ config.chunks links to the collection by uuid):\nconst uuid = db.getSiblingDB(\"config\").collections.findOne({ _id: \"shop.musteri\" }).uuid;\ndb.getSiblingDB(\"config\").chunks.find({ uuid: uuid, jumbo: true }, { min: 1, max: 1, shard: 1 })\n// If the chunk can be split, split it at the median:\nsh.splitFind(\"shop.musteri\", { musteriId: 1000 })\n// Last resort for an indivisible chunk, clear the flag manually (a successful split already clears it):\ndb.adminCommand({ clearJumboFlag: \"shop.musteri\", find: { musteriId: 1000 } })\n// If a single key value is too large, refine the key (4.4+):\ndb.adminCommand({ refineCollectionShardKey: \"shop.musteri\", key: { musteriId: 1, siparisId: 1 } })","what":"Liste les chunks marqués jumbo parce que le balancer n'a pas pu les déplacer, découpe ceux qui peuvent l'être et efface l'indicateur jumbo ; montre comment affiner la shard key pour les énormes chunks à valeur unique.","when":"Quand un shard reste nettement plus grand que les autres, que jumbo apparaît dans la sortie de sh.status(), ou que le balancer signale des erreurs de migration.","caution":"Un chunk dont tous les documents partagent la même valeur de shard key ne peut pas être découpé ; la seule correction durable est de changer la shard key avec refineCollectionShardKey ou reshardCollection. refineCollectionShardKey nécessite qu'un index supportant la nouvelle clé existe au préalable. Effacer l'indicateur jumbo sans découper amène le balancer à réessayer le même chunk et à échouer de nouveau.","since":"MongoDB 5.0","note":"Les shard keys à faible cardinalité ou concentrées sur une valeur (par ex. status, country) sont la cause la plus fréquente des chunks jumbo. Avant 5.0, les documents config.chunks sont interrogés par le champ ns. clearJumboFlag est apparu en 4.2.3/4.0.15.","tags":["jumbo","chunk","split","clearJumboFlag","shard key","refineCollectionShardKey"],"untested":true},{"id":"sh-shard-key-check","task":"Vérifier le choix de la shard key et la répartition des données (getShardDistribution, analyzeShardKey)","category":"Sharding","command":"// Data and document distribution of the collection across shards:\ndb.getSiblingDB(\"shop\").musteri.getShardDistribution()\n// Cardinality, frequency and monotonicity analysis of the current or candidate shard key:\ndb.adminCommand({ analyzeShardKey: \"shop.musteri\", key: { musteriId: 1 }, keyCharacteristics: true, readWriteDistribution: false })\n// For read/write distribution, enable query sampling first (10 samples per second):\ndb.adminCommand({ configureQueryAnalyzer: \"shop.musteri\", mode: \"full\", samplesPerSecond: 10 })\ndb.adminCommand({ analyzeShardKey: \"shop.musteri\", key: { musteriId: 1 }, keyCharacteristics: false, readWriteDistribution: true })\n// Turn sampling off when done:\ndb.adminCommand({ configureQueryAnalyzer: \"shop.musteri\", mode: \"off\" })","what":"Affiche le pourcentage des données d'une collection sur chaque shard ; analyzeShardKey indique la cardinalité, les valeurs les plus fréquentes et la monotonie de la shard key actuelle ou candidate, et si les requêtes échantillonnées ciblent un seul shard ou tous les shards (scatter-gather).","when":"Avant de sharder une nouvelle collection, quand un shard chaud ou une croissance inégale est constaté, et avant de décider d'un reshardCollection.","caution":"analyzeShardKey nécessite un index supportant la clé et échantillonne les documents sur les grandes collections ; il ajoute une charge de lecture en période de pointe. Tant que l'échantillonnage des requêtes est actif, les échantillons sont écrits dans config.sampledQueries ; désactivez-le une fois l'analyse terminée. Une clé croissante monotone (ObjectId, timestamp) concentre toutes les insertions sur un seul shard.","since":"MongoDB 7.0","note":"analyzeShardKey et configureQueryAnalyzer sont apparus en 7.0 et fonctionnent aussi sur les replica sets. Une bonne shard key a une cardinalité élevée, une faible fréquence, n'est pas monotone et apparaît dans le filtre des requêtes fréquentes. Une clé hashed corrige la répartition mais transforme les requêtes par plage en scatter-gather.","tags":["shard key","analyzeShardKey","getShardDistribution","cardinality","distribution","hot shard"],"untested":true},{"id":"explain-execstats","task":"Inspecter un plan de requête avec les statistiques d'exécution réelles (explain executionStats)","category":"Performances","command":"db.musteri.find({ sehir: \"Ankara\", durum: \"aktif\" }).sort({ kayitTarihi: -1 }).explain(\"executionStats\")\n// Summary: winning plan, returned documents, examined keys/documents and time\nconst e = db.musteri.find({ sehir: \"Ankara\", durum: \"aktif\" }).sort({ kayitTarihi: -1 }).explain(\"executionStats\");\n({ plan: e.queryPlanner.winningPlan, n: e.executionStats.nReturned, keys: e.executionStats.totalKeysExamined, docs: e.executionStats.totalDocsExamined, ms: e.executionStats.executionTimeMillis })\n// For an aggregation:\ndb.musteri.explain(\"executionStats\").aggregate([{ $match: { sehir: \"Ankara\" } }, { $group: { _id: \"$durum\", n: { $sum: 1 } } }])","what":"Exécute la requête et affiche le plan gagnant (IXSCAN/COLLSCAN, étape SORT), le nombre de documents renvoyés, les clés d'index et documents examinés, et le temps pris.","when":"Pour analyser une requête qui paraît lente dans le profiler ou le log, et pour vérifier qu'un nouvel index est réellement utilisé.","caution":"executionStats exécute réellement la requête ; faire un explain d'une requête lourde en pointe ajoute aussi de la charge. Pour les commandes d'écriture (update/delete/findAndModify), explain ne modifie pas les données mais exécute le plan. Un ratio totalDocsExamined/nReturned élevé ou une étape SORT en mémoire dans le plan signifie que l'index n'est pas adapté.","since":"MongoDB 4.4","note":"Appliquez la règle ESR lors de la conception d'index composés : champs d'Égalité d'abord, puis Sort, puis Range. Utilisez explain(\"allPlansExecution\") pour voir aussi les plans rejetés. En 7.0+, quand une requête s'exécute sur le Slot-Based Engine, winningPlan contient les champs queryPlan et slotBasedPlan.","tags":["explain","executionStats","IXSCAN","COLLSCAN","plan","index"]},{"id":"profiler-analyze","task":"Extraire les requêtes et formes de requêtes les plus lentes depuis les données du profiler","category":"Performances","command":"// Recent slow operations (in the db where profiling is on; see set-profiling):\ndb.system.profile.find({ millis: { $gte: 100 } }, { ts: 1, ns: 1, op: 1, millis: 1, planSummary: 1, keysExamined: 1, docsExamined: 1, nreturned: 1, appName: 1 }).sort({ ts: -1 }).limit(20)\n// Total time per query shape (those consuming the most time):\ndb.system.profile.aggregate([\n  { $group: { _id: { ns: \"$ns\", plan: \"$planSummary\", shape: \"$queryHash\" }, n: { $sum: 1 }, totalMs: { $sum: \"$millis\" }, maxMs: { $max: \"$millis\" }, avgDocs: { $avg: \"$docsExamined\" } } },\n  { $sort: { totalMs: -1 } },\n  { $limit: 10 }\n])\n// Queries not using an index (COLLSCAN):\ndb.system.profile.find({ planSummary: \"COLLSCAN\" }, { ns: 1, millis: 1, command: 1 }).sort({ millis: -1 }).limit(10)","what":"À partir des enregistrements de la collection system.profile, liste les opérations lentes récentes, le temps total par forme de requête (queryHash) et les requêtes COLLSCAN qui n'utilisent aucun index.","when":"Sur une plainte de lenteur, pour trouver quelle requête consomme le temps du serveur et déterminer les besoins en index.","caution":"system.profile est une collection capped de 1 MB par défaut et est propre à chaque base ; sur un système chargé, les enregistrements tournent en quelques minutes, analysez donc pendant que le profiling est actif ou juste après. Le niveau de profiling 2 ou slowms 0 écrit chaque opération et ajoute une surcharge importante. Les enregistrements ne sont conservés que sur le membre où ils ont été exécutés et ne sont pas répliqués.","since":"MongoDB 4.4","note":"Même avec le profiling désactivé, les opérations au-dessus de slowms (100 ms par défaut) sont écrites dans le log mongod comme 'Slow query' ; attr.durationMillis et attr.planSummary dans le log donnent le même diagnostic. En 8.0+, un champ planCacheShapeHash est aussi écrit pour la forme de la requête.","tags":["profiler","system.profile","slow query","top queries","COLLSCAN","queryHash"]},{"id":"index-usage-hide","task":"Trouver les index inutilisés et les masquer avant de les supprimer (hideIndex)","category":"Performances","command":"// Usage counter per index (on this member, since the last restart):\ndb.musteri.aggregate([{ $indexStats: {} }, { $project: { name: 1, ops: \"$accesses.ops\", since: \"$accesses.since\" } }, { $sort: { ops: 1 } }])\n// Hide it from the planner before dropping (the index keeps being maintained):\ndb.musteri.hideIndex(\"idx_email\")\n// Revert immediately if problems appear:\ndb.musteri.unhideIndex(\"idx_email\")\n// If the observation period passes without issues:\ndb.musteri.dropIndex(\"idx_email\")","what":"$indexStats indique combien de fois chaque index a été utilisé ; hideIndex masque un index au query planner sans le supprimer, ce qui permet de tester de façon réversible l'effet de sa suppression.","when":"Lors du nettoyage des index inutiles qui ajoutent une surcharge d'écriture et du stockage.","caution":"Les compteurs sont propres à chaque membre et réinitialisés au redémarrage ; si les lectures vont vers les secondaires, vérifiez tous les membres et observez une période suffisamment longue (rapports de fin de mois compris). Les index uniques ou TTL avec ops 0 peuvent rester nécessaires : la contrainte d'unicité et les suppressions TTL n'apparaissent pas dans le compteur. Un index masqué reste maintenu lors des écritures, et un index unique masqué impose toujours la contrainte.","since":"MongoDB 4.4","note":"hideIndex nécessite featureCompatibilityVersion 4.4 ou supérieur. L'index _id ne peut pas être masqué. Le démasquage avec unhideIndex ne nécessite pas de reconstruction et prend effet immédiatement.","tags":["indexStats","hideIndex","unused index","index cleanup","dropIndex","index"]},{"id":"connections-by-app","task":"Compter les connexions par application et client (diagnostic de tempête de connexions)","category":"Performances","command":"// Total, available and rejected connection counts:\ndb.serverStatus().connections\n// Count connections by application name and client IP:\ndb.getSiblingDB(\"admin\").aggregate([\n  { $currentOp: { allUsers: true, idleConnections: true } },\n  { $group: { _id: { app: \"$appName\", client: { $arrayElemAt: [{ $split: [\"$client\", \":\"] }, 0] } }, conns: { $sum: 1 }, active: { $sum: { $cond: [\"$active\", 1, 0] } } } },\n  { $sort: { conns: -1 } },\n  { $limit: 20 }\n])","what":"Affiche le nombre total et disponible de connexions sur le serveur, puis regroupe les connexions ouvertes par appName et IP client avec le nombre de connexions actives.","when":"Quand le nombre de connexions bondit soudainement, sur des erreurs 'connection refused' ou de timeout du pool, pour trouver quel serveur d'application fuit des connexions.","caution":"idleConnections: true renvoie aussi toutes les connexions inactives, donc sur les systèmes avec des milliers de connexions la sortie est volumineuse ; c'est pourquoi la synthèse avec $group est importante. Les lignes avec un client null sont des threads internes. Chaque connexion consomme environ 1 MB de mémoire ; une tempête de connexions fait aussi croître la mémoire hors du cache WiredTiger.","since":"MongoDB 4.4","note":"La correction durable se situe généralement côté driver : maxPoolSize, maxIdleTimeMS et le nombre d'instances de l'application. Si les applications ne définissent pas appName, il est difficile de les distinguer ; demandez appName=<service> dans la chaîne de connexion. net.maxIncomingConnections et la valeur ulimit -n de l'OS fixent la limite supérieure stricte.","tags":["connections","connection count","currentOp","appName","connection storm","pool"]},{"id":"index-build-monitor","task":"Surveiller et interrompre une construction d'index en cours","category":"Maintenance","command":"// Index builds in progress and their completion percentage:\ndb.getSiblingDB(\"admin\").aggregate([\n  { $currentOp: { allUsers: true } },\n  { $match: { $or: [{ \"command.createIndexes\": { $exists: true } }, { desc: /^IndexBuildsCoordinator/ }] } },\n  { $project: { opid: 1, desc: 1, ns: 1, secs_running: 1, msg: 1, progress: 1, \"command.indexes.name\": 1 } }\n])\n// Abort the build (on the primary; in a replica set it stops on all members):\ndb.musteri.dropIndex(\"idx_email\")","what":"Liste les constructions d'index en cours avec le nombre de documents parcourus et le pourcentage de progression (progress.done / progress.total) ; si nécessaire, interrompt la construction avec dropIndex.","when":"Pour estimer quand un createIndex lancé sur une grande collection se terminera, ou pour arrêter une construction qui génère de la charge en pointe.","caution":"Interrompez une construction d'index avec dropIndex/dropIndexes, pas avec killOp ; dans un replica set, exécutez-le sur le primaire pour que l'interruption se propage à tous les membres. Certaines opérations DDL sur la collection attendent pendant la construction. Un court verrou exclusif est pris à la fin de la construction ; sous des écritures très intenses, ce moment peut provoquer de la latence.","since":"MongoDB 4.4","note":"Dans les replica sets 4.4+, une construction d'index s'exécute simultanément sur tous les membres porteurs de données et se valide une fois commitQuorum (votingMembers par défaut) satisfait ; si un secondaire est en retard, la construction ne se termine pas non plus sur le primaire. En 5.0+, une construction interrompue par un arrêt propre reprend là où elle s'était arrêtée après le redémarrage.","tags":["index build","createIndex","progress","monitor","dropIndex","commitQuorum"]},{"id":"rolling-index-build","task":"Construction d'index en rolling sur un replica set (membre par membre)","category":"Maintenance","command":"// 1) Shut down a SECONDARY; comment out replication.replSetName and start it standalone on another port:\n//    mongod --config /etc/mongod.conf --port 27217 --setParameter disableLogicalSessionCacheRefresh=true\n// 2) Build the index on the standalone member:\ndb.getSiblingDB(\"shop\").musteri.createIndex({ email: 1 }, { name: \"idx_email\" })\n// 3) Restart with the normal configuration; wait until it is SECONDARY and lag has closed:\nrs.printSecondaryReplicationInfo()\n// 4) When all secondaries are done, step down the primary and apply steps 1-3 to the old primary:\nrs.stepDown(120, 30)","what":"Construit l'index sur chaque membre séparément en exécutant temporairement le membre en standalone hors du set ; le primaire ne supporte jamais la charge de la construction.","when":"Quand l'impact de la construction distribuée par défaut sur le primaire et sur le retard de réplication est inacceptable pour une très grande collection.","caution":"La durée de maintenance de chaque membre doit être inférieure à la fenêtre de l'oplog ; sinon le membre est too stale à son retour. Lors de la construction d'un index unique en rolling, arrêtez les écritures sur la collection pendant toute la construction ; sinon certains membres peuvent se retrouver avec des documents en violation. Démarrez le membre standalone sur un port différent pour que les applications ne s'y connectent pas.","since":"MongoDB 4.4","note":"En 4.4+, la construction par défaut est déjà non bloquante et s'exécute simultanément sur tous les membres ; la méthode rolling n'est nécessaire que si cette charge est inacceptable. Dans un cluster shardé, la procédure est effectuée séparément pour chaque replica set de shard, avec le balancer arrêté au préalable.","tags":["rolling index","index build","standalone","replica","createIndex","maintenance"],"untested":true},{"id":"ttl-index","task":"Créer un index TTL, modifier son expiration et surveiller les suppressions TTL","category":"Maintenance","command":"// Automatically delete documents lastSeen + 3600 s later:\ndb.oturum.createIndex({ lastSeen: 1 }, { expireAfterSeconds: 3600, name: \"ttl_lastSeen\" })\n// Change the expiry without rebuilding the index:\ndb.runCommand({ collMod: \"oturum\", index: { name: \"ttl_lastSeen\", expireAfterSeconds: 7200 } })\n// TTL indexes on the collection and their expiry:\ndb.oturum.getIndexes().filter(i => i.expireAfterSeconds !== undefined)\n// TTL monitor statistics (deleted documents, passes):\ndb.serverStatus().metrics.ttl","what":"Crée un index TTL qui supprime automatiquement les documents un temps donné après un champ de date, modifie l'expiration avec collMod sans reconstruction, et indique combien de documents le TTL monitor a supprimés.","when":"Pour nettoyer des données à durée limitée comme les sessions, les logs ou les jetons temporaires sans écrire de job de suppression dans l'application, et pour comprendre pourquoi une collection en croissance ne diminue pas.","caution":"Le TTL monitor s'exécute toutes les 60 secondes par défaut ; la suppression n'est pas instantanée. Le champ doit être de type Date (ou un tableau de Dates) ; les dates en chaîne ou les documents sans le champ ne sont jamais supprimés. Raccourcir l'expiration entraîne la suppression d'un grand nombre de documents d'un coup, une charge d'écriture et un retard de réplication. Les suppressions n'ont lieu que sur le primaire et sont écrites dans l'oplog.","since":"MongoDB 5.1","note":"Un index TTL doit porter sur un seul champ (le TTL composé n'est pas pris en charge). Ajouter expireAfterSeconds à un index existant non TTL sur un seul champ avec collMod est possible en 5.1+ ; modifier l'expiration d'un TTL existant fonctionne aussi sur les versions plus anciennes. Pour supprimer à une heure précise, utilisez expireAfterSeconds: 0 et stockez l'heure de suppression dans le champ.","tags":["ttl","expireAfterSeconds","collMod","auto delete","index","metrics.ttl"]},{"id":"wt-cache-pressure","task":"Mesurer la pression sur le cache WiredTiger (remplissage, taux de pages sales, éviction par l'application)","category":"Performances","command":"const c = db.serverStatus().wiredTiger.cache;\nconst max = c[\"maximum bytes configured\"];\n({\n  maxGB: (max / 1024 ** 3).toFixed(1),\n  usedPct: (100 * c[\"bytes currently in the cache\"] / max).toFixed(1),\n  dirtyPct: (100 * c[\"tracked dirty bytes in the cache\"] / max).toFixed(1),\n  appThreadEvictedPages: c[\"pages evicted by application threads\"],\n  pagesReadIntoCache: c[\"pages read into cache\"]\n})\n// Read/write ticket usage and queue:\ndb.serverStatus().wiredTiger.concurrentTransactions","what":"Affiche la taille du cache WiredTiger, ses pourcentages de remplissage et de pages sales, le nombre de pages que les threads applicatifs ont été contraints d'évincer, et l'état des tickets de lecture/écriture.","when":"Quand les lectures disque augmentent, que la latence grimpe soudainement ou que le débit d'écriture chute ; lors du dimensionnement du cache.","caution":"Dès que le remplissage dépasse 80 % (eviction_target), l'éviction en arrière-plan démarre ; à 95 % (eviction_trigger), les threads applicatifs participent aussi à l'éviction et les requêtes ralentissent. Pour les données sales, 5 % est la cible et 20 % le déclencheur ; être constamment proche de 20 % signifie que la capacité d'écriture disque est insuffisante. Un compteur pages evicted by application threads qui augmente correspond à une latence ressentie directement par les utilisateurs. Les valeurs sont des compteurs cumulés ; regardez la différence entre deux échantillons.","since":"MongoDB 7.0","note":"La taille de cache par défaut est la plus grande valeur entre (RAM - 1 GB) / 2 et 256 MB ; dans les conteneurs, elle est calculée à partir de la limite cgroup. Augmenter storage.wiredTiger.engineConfig.cacheSizeGB réduit le page cache de l'OS ; dépasser la moitié de la RAM aide rarement. En 7.0, le nombre de tickets est ajusté dynamiquement ; en 8.0+, les métriques de tickets apparaissent aussi sous serverStatus().queues.execution.","tags":["wiredtiger","cache","eviction","dirty","tickets","memory"]},{"id":"dump-restore-archive","task":"Effectuer une sauvegarde archive avec mongodump/mongorestore et restaurer une seule collection","category":"Sauvegarde","command":"# Single database, one compressed archive file (password is prompted):\nmongodump --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --db=shop --gzip --archive=/backup/shop_$(date +%F).archive.gz\n# Dry run before restoring the archive under a different name:\nmongorestore --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --gzip --archive=/backup/shop_2026-10-08.archive.gz --nsFrom=\"shop.*\" --nsTo=\"shop_restore.*\" --dryRun\n# Restore a single collection from the archive under a new name:\nmongorestore --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --gzip --archive=/backup/shop_2026-10-08.archive.gz --nsInclude=\"shop.musteri\" --nsFrom=\"shop.*\" --nsTo=\"shop_restore.*\" --numInsertionWorkersPerCollection=4\n# Restore under the same name, dropping the existing collection:\nmongorestore --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --gzip --archive=/backup/shop_2026-10-08.archive.gz --nsInclude=\"shop.musteri\" --drop","what":"Sauvegarde une base dans un seul fichier archive gzip ; restaure toute la base ou une seule collection depuis l'archive, éventuellement sous un autre nom (nsFrom/nsTo).","when":"Pour les sauvegardes logiques, le déplacement de données entre environnements ou la récupération d'une collection supprimée ou corrompue par erreur.","caution":"mongodump est une sauvegarde logique : il est lent sur les grands volumes et les index sont reconstruits lors de la restauration. Un dump pris sans --oplog n'est pas cohérent à un instant donné sur un système actif. --drop supprime la collection cible avant le début de la restauration ; restaurez d'abord sous un autre nom avec nsTo et vérifiez. La restauration écrit sur le primaire et peut provoquer un retard de réplication.","since":"MongoDB 4.4","note":"Depuis 4.4, les database tools (mongodump/mongorestore) sont versionnés séparément du serveur (100.x). Ajoutez readPreference=secondary à l'URI pour décharger le primaire de la lecture pendant la sauvegarde. Pour déplacer aussi les utilisateurs et rôles, utilisez un dump complet de l'instance avec --restoreDbUsersAndRoles ou sauvegardez la base admin séparément.","tags":["mongodump","mongorestore","backup","archive","nsFrom","restore"]},{"id":"pitr-oplog-replay","task":"Restauration à un instant donné avec rejeu de l'oplog (revenir avant un drop/delete erroné)","category":"Sauvegarde","command":"# 1) Full backup + oplog written during the backup (whole instance, from a replica set member):\nmongodump --uri=\"mongodb://admin@mongo2:27017/?authSource=admin&readPreference=secondary\" --oplog --gzip --out=/backup/full\n# 2) Find the time of the bad operation (mongosh): db.getSiblingDB(\"local\").oplog.rs.find({ \"o.drop\": \"musteri\" }).sort({ $natural: -1 }).limit(1)\n# 3) Dump the oplog after the backup (t: start time of the full backup, epoch seconds):\nmongodump --uri=\"mongodb://admin@mongo2:27017/?authSource=admin\" --db=local --collection=oplog.rs --query='{\"ts\":{\"$gte\":{\"$timestamp\":{\"t\":1791400000,\"i\":1}}}}' --out=/backup/oplog\n# 4) Restore the full backup together with its own oplog:\nmongorestore --uri=\"mongodb://admin@target:27017/?authSource=admin\" --gzip --oplogReplay /backup/full\n# 5) Replay the oplog up to the bad operation (oplogLimit is exclusive):\nmkdir /backup/pitr && mv /backup/oplog/local/oplog.rs.bson /backup/pitr/oplog.bson\nmongorestore --uri=\"mongodb://admin@target:27017/?authSource=admin\" --oplogReplay --oplogLimit=1791403600:1 /backup/pitr","what":"Rend la sauvegarde complète cohérente avec --oplog, puis rejoue l'oplog accumulé après la sauvegarde jusqu'à l'horodatage de l'opération erronée (drop, suppression massive), ramenant les données à ce moment.","when":"Quand une collection a été supprimée par erreur ou qu'un update/delete massif défectueux a été exécuté et qu'il faut le dernier état avant les dégâts.","caution":"Restaurez vers une cible séparée, pas le système en production, et déplacez les données nécessaires après vérification. L'oplog depuis le début de la sauvegarde jusqu'à l'opération erronée doit encore être dans la fenêtre de l'oplog ; les opérations sorties de la fenêtre ne peuvent pas être récupérées. --oplog ne fonctionne qu'avec un dump complet de l'instance (il ne peut pas être combiné avec --db). Une PITR cohérente avec mongodump n'est pas possible sur un cluster shardé ; un snapshot du système de fichiers ou un produit de sauvegarde est nécessaire.","since":"MongoDB 4.4","note":"--oplogLimit=<seconds>:<ordinal> n'applique pas les entrées à partir de l'horodatage donné ; passer directement le ts de l'opération erronée l'exclut. Les valeurs ts apparaissent dans l'oplog sous la forme Timestamp(t, i). Faites le dump de l'oplog juste après l'opération erronée, avant que la fenêtre ne se referme.","tags":["pitr","point in time","oplogReplay","oplogLimit","rollback","mongorestore"],"untested":true},{"id":"user-role-admin","task":"Définir un rôle personnalisé, créer un utilisateur, accorder/révoquer des rôles et changer le mot de passe","category":"Sécurité","command":"use admin\ndb.createRole({\n  role: \"opsRead\",\n  privileges: [\n    { resource: { db: \"shop\", collection: \"\" }, actions: [\"find\", \"listCollections\", \"listIndexes\", \"collStats\", \"dbStats\"] },\n    { resource: { cluster: true }, actions: [\"serverStatus\", \"inprog\"] }\n  ],\n  roles: []\n})\ndb.createUser({ user: \"shop_app\", pwd: passwordPrompt(), roles: [{ role: \"readWrite\", db: \"shop\" }, { role: \"opsRead\", db: \"admin\" }] })\ndb.grantRolesToUser(\"shop_app\", [{ role: \"clusterMonitor\", db: \"admin\" }])\ndb.revokeRolesFromUser(\"shop_app\", [{ role: \"clusterMonitor\", db: \"admin\" }])\ndb.changeUserPassword(\"shop_app\", passwordPrompt())\ndb.getUser(\"shop_app\", { showPrivileges: true })\ndb.runCommand({ usersInfo: { forAllDBs: true } })","what":"Définit un rôle personnalisé avec des actions précises sur la base et le cluster, crée un utilisateur avec ce rôle et des rôles intégrés, accorde et révoque des rôles, change le mot de passe et liste les utilisateurs de toutes les bases.","when":"Lors de la création de comptes d'application et de supervision selon le moindre privilège, pendant les revues d'accès ou la rotation des mots de passe.","caution":"La base où l'utilisateur est défini (ici admin) sert d'authSource ; elle doit correspondre à la chaîne de connexion de l'application. N'accordez pas de rôles larges comme root/dbOwner aux comptes d'application. Les changements de rôles s'appliquent immédiatement aux sessions existantes ; un changement de mot de passe ne coupe pas les connexions ouvertes et s'applique aux nouvelles.","since":"MongoDB 4.4","note":"passwordPrompt() demande le mot de passe sans le laisser dans l'historique du shell ni dans la liste des processus. Dans les replica sets et clusters shardés, les utilisateurs sont gérés via le primaire/mongos et sont répliqués. Utilisez showPrivileges: true dans getUser pour voir les privilèges effectifs d'un utilisateur.","tags":["createUser","createRole","grantRolesToUser","privileges","rbac","password"]},{"id":"fcv-upgrade","task":"Vérifier featureCompatibilityVersion (fCV) et la séquence de mise à niveau majeure","category":"Mise à niveau","command":"// 1) Current fCV (must be the same on all members; targetVersion appears if a transition is incomplete):\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// 2) Upgrade binaries in a rolling fashion (secondaries -> stepDown -> old primary); fCV stays old\ndb.version()\n// 3) After the observation period, raise fCV on the primary (on mongos in a sharded cluster):\ndb.adminCommand({ setFeatureCompatibilityVersion: \"7.0\", confirm: true })\n// 4) If a rollback is needed, fCV is lowered BEFORE the binaries:\ndb.adminCommand({ setFeatureCompatibilityVersion: \"6.0\", confirm: true })","what":"Affiche le fCV en vigueur ; donne l'ordre dans lequel s'effectuent la mise à niveau des binaires et celle du fCV, qui sont des étapes distinctes.","when":"Lors de la planification d'une mise à niveau de version majeure (par ex. 6.0 -> 7.0), quand de nouvelles fonctionnalités sont indisponibles après une mise à niveau, ou avant de passer à la majeure suivante.","caution":"Les versions majeures ne peuvent pas être sautées : pour 5.0 -> 7.0, passez d'abord en 6.0 et réglez le fCV sur 6.0. Une fois le fCV relevé, les fichiers de données peuvent passer au nouveau format et le retour arrière des binaires devient plus difficile ; ne relevez le fCV qu'après plusieurs jours de fonctionnement sans problème de la nouvelle version. Le retour arrière des binaires n'est pas officiellement pris en charge en 7.0+ Community ; fondez le plan de repli sur une sauvegarde. Vérifiez la compatibilité des drivers avant la mise à niveau.","since":"MongoDB 7.0","note":"Depuis 7.0, setFeatureCompatibilityVersion nécessite confirm: true. Si une transition de fCV échoue, targetVersion/previousVersion apparaît dans la sortie de getParameter ; relancez la même commande pour la terminer. La valeur du fCV est répliquée au sein du replica set.","tags":["fcv","featureCompatibilityVersion","upgrade","major upgrade","major","downgrade"]}]},"recipes/redis.json":{"engineId":"redis","engine":"Redis","order":6,"recipes":[{"id":"bgrewriteaof","task":"Réécrire (compacter) le fichier AOF","category":"Persistance","command":"BGREWRITEAOF","what":"Réécrit le fichier AOF en arrière-plan sous une forme plus compacte.","when":"Quand le fichier AOF a gonflé, pour raccourcir le temps de redémarrage.","caution":"L'utilisation de la mémoire et de l'IO augmente pendant le fork ; prudence aux heures de pointe.","tags":["aof","rewrite","persistence","persistence"]},{"id":"bgsave","task":"Prendre un snapshot RDB en arrière-plan","category":"Persistance","command":"BGSAVE","what":"Écrit un snapshot RDB sur disque en arrière-plan à l'aide d'un processus enfant forké.","when":"Quand vous voulez effectuer une sauvegarde ou créer un snapshot.","caution":"Au moment du fork, l'utilisation mémoire peut augmenter temporairement à cause du copy-on-write.","tags":["rdb","bgsave","snapshot","persistence","backup"]},{"id":"save","task":"Prendre un snapshot RDB synchrone","category":"Persistance","command":"SAVE","what":"Écrit un snapshot RDB sur disque de façon synchrone sur le thread principal.","when":"Uniquement avant une maintenance/un arrêt, quand aucun client n'est servi.","caution":"SAVE bloque tout le serveur et aucune commande n'est traitée avant la fin ; préférez BGSAVE en production.","tags":["rdb","save","snapshot","blocking","persistence"]},{"id":"lastsave","task":"Afficher l'heure du dernier snapshot réussi","category":"Persistance","command":"LASTSAVE","what":"Renvoie l'horodatage Unix de la dernière sauvegarde RDB réussie.","when":"Pour vérifier si un BGSAVE est terminé.","caution":"Commande en lecture seule ; aucun risque.","tags":["lastsave","rdb","timestamp","check","persistence"]},{"id":"config-rewrite","task":"Écrire la configuration en cours sur disque","category":"Persistance","command":"CONFIG REWRITE","what":"Rend persistantes dans le fichier redis.conf les modifications faites à l'exécution avec CONFIG SET.","when":"Quand vous voulez que les modifications à l'exécution survivent à un redémarrage.","caution":"Écrase le fichier redis.conf existant ; le fichier doit être défini et accessible en écriture.","tags":["config","rewrite","persistent","conf","setting"]},{"id":"config-appendonly","task":"Activer ou désactiver AOF","category":"Persistance","command":"CONFIG SET appendonly yes","what":"Active (yes) ou désactive (no) la persistance AOF à l'exécution.","when":"Quand vous voulez activer ou désactiver la persistance AOF sans redémarrage.","caution":"Désactiver AOF affaiblit la garantie de durabilité ; à l'activation, la réécriture AOF initiale génère une charge IO.","tags":["aof","appendonly","config","persistence","setting"]},{"id":"config-save-rdb","task":"Définir les règles de snapshot RDB","category":"Persistance","command":"CONFIG SET save \"900 1 300 10 60 10000\"","what":"Définit les règles (secondes/nombre de modifications) qui déclenchent les snapshots RDB automatiques.","when":"Quand vous voulez modifier la fréquence des snapshots automatiques.","caution":"Si vous passez une valeur vide (\"\"), la sauvegarde RDB automatique est entièrement désactivée et la persistance peut être perdue.","tags":["rdb","save","config","snapshot","setting"]},{"id":"replicaof","task":"Faire d'un nœud un réplica","category":"Réplication","command":"REPLICAOF redis-master 6379","what":"Fait de ce nœud un réplica du master à l'hôte et au port indiqués et démarre la synchronisation.","when":"Lors de l'ajout d'un nouveau réplica de lecture ou de l'attribution des rôles.","caution":"Les données existantes du nœud qui devient réplica sont abandonnées et une synchronisation complète est récupérée depuis le master.","since":"Redis 5.0","note":"La commande REPLICAOF a remplacé SLAVEOF dans Redis 5.0 ; l'ancienne commande SLAVEOF fonctionne toujours comme alias.","tags":["replicaof","replica","replication","master","sync"]},{"id":"replicaof-no-one","task":"Promouvoir un réplica en master","category":"Réplication","command":"REPLICAOF NO ONE","what":"Rompt le lien de réplication du nœud, le transformant en master autonome.","when":"Quand vous voulez promouvoir un réplica dans un scénario de basculement.","caution":"Après la promotion, gérez la topologie avec soin pour éviter toute divergence de données (split-brain) avec l'ancien master.","since":"Redis 5.0","note":"REPLICAOF NO ONE a remplacé SLAVEOF NO ONE dans Redis 5.0 ; l'ancien SLAVEOF NO ONE fonctionne toujours comme alias.","tags":["replicaof","promote","master","failover","replication"]},{"id":"cluster-failover","task":"Effectuer un basculement manuel","category":"Cluster","command":"CLUSTER FAILOVER","what":"Exécutée sur un réplica, lance un basculement contrôlé en promouvant ce réplica en master.","when":"Quand vous voulez transférer le rôle de master à un réplica avant une maintenance.","caution":"Cette commande doit être exécutée sur le réplica cible, pas sur le master lui-même.","tags":["cluster","failover","promote","maintenance","replication"]},{"id":"cluster-meet","task":"Ajouter un nœud au cluster","category":"Cluster","command":"CLUSTER MEET 10.0.0.12 6379","what":"Intègre le nœud à l'IP et au port indiqués dans le réseau gossip du cluster.","when":"Lors de l'ajout d'un nouveau nœud au cluster (montée en charge).","caution":"Si aucune attribution de slots (ADDSLOTS) ni aucun reshard n'est effectué après l'ajout du nœud, il ne reçoit aucune donnée.","tags":["cluster","meet","node","add","node"]},{"id":"cluster-addslots","task":"Attribuer des slots du cluster","category":"Cluster","command":"CLUSTER ADDSLOTS 0 1 2 3 4 5","what":"Attribue les hash slots indiqués à ce nœud master.","when":"Lors de la configuration manuelle d'un nouveau nœud master ou de la correction de la répartition des slots.","caution":"Un même slot ne peut pas être attribué à plusieurs nœuds ; si les 16384 slots ne sont pas tous attribués, le cluster passe à l'état down.","tags":["cluster","addslots","slot","assign","node"]},{"id":"info-replication","task":"Afficher l'état de la réplication","category":"Cluster","command":"INFO replication","what":"Affiche le rôle (master/replica), les réplicas connectés et les informations d'offset de réplication.","when":"Pour vérifier le retard de réplication et l'état du lien.","caution":"Commande en lecture seule ; aucun risque.","tags":["info","replication","status","replication","lag"]},{"id":"cluster-info","task":"Afficher l'état du cluster","category":"Cluster","command":"CLUSTER INFO","what":"Affiche l'état global du cluster (ok/fail), le nombre de slots attribués et le nombre de nœuds connus.","when":"Pour vérifier la santé du cluster et l'intégrité des slots.","caution":"Commande en lecture seule ; aucun risque.","tags":["cluster","info","status","slot","health"]},{"id":"config-maxmemory","task":"Définir maxmemory et la politique","category":"Mémoire","command":"CONFIG SET maxmemory 4gb maxmemory-policy allkeys-lru","what":"Définit la limite mémoire et la politique d'éviction appliquée quand la limite est dépassée. Redis 7.0+ accepte plusieurs paramètres dans un seul CONFIG SET ; sur les versions plus anciennes, exécutez deux commandes CONFIG SET distinctes.","when":"Quand vous voulez plafonner l'utilisation mémoire et définir le comportement d'éviction.","caution":"Avec la politique noeviction, les écritures échouent une fois la limite atteinte ; une mauvaise limite peut entraîner une perte de données/une interruption.","tags":["maxmemory","policy","memory","eviction","config"]},{"id":"memory-doctor","task":"Analyser la mémoire","category":"Mémoire","command":"MEMORY DOCTOR","what":"Analyse l'utilisation de la mémoire et produit un rapport lisible sur les problèmes possibles.","when":"Pour le diagnostic en cas de suspicion d'utilisation mémoire élevée ou de fragmentation.","caution":"Analyse en lecture seule ; pour plus de détails, vous pouvez aussi utiliser la commande MEMORY STATS.","tags":["memory","doctor","memory","analysis","diagnostics"]},{"id":"slowlog-get","task":"Afficher le journal des commandes lentes","category":"Maintenance","command":"SLOWLOG GET 10","what":"Renvoie les 10 entrées de commandes lentes les plus récentes (durée, heure, arguments) du slowlog.","when":"Pour identifier les commandes lentes et étudier les goulots de performance.","caution":"Commande en lecture seule ; utilisez SLOWLOG RESET pour effacer les entrées.","tags":["slowlog","get","slow","diagnostics","performance"]},{"id":"slowlog-config","task":"Définir le seuil des commandes lentes","category":"Maintenance","command":"CONFIG SET slowlog-log-slower-than 10000","what":"Définit le seuil de durée, en microsecondes, à partir duquel une commande est enregistrée dans le slowlog (10000 = 10 ms).","when":"Quand vous voulez ajuster la sensibilité du slowlog.","caution":"Le régler à 0 enregistre chaque commande et ajoute une surcharge ; -1 désactive le slowlog.","tags":["slowlog","threshold","threshold","config","slow"]},{"id":"slowlog-reset","task":"Réinitialiser le journal des commandes lentes","category":"Maintenance","command":"SLOWLOG RESET","what":"Efface toutes les entrées du slowlog.","when":"Pour effacer les entrées afin de démarrer une nouvelle fenêtre d'observation.","caution":"L'historique existant des commandes lentes est définitivement supprimé.","tags":["slowlog","reset","reset","clear","maintenance"]},{"id":"client-kill","task":"Tuer une connexion client","category":"Maintenance","command":"CLIENT KILL ADDR 10.0.0.5:53210","what":"Termine la connexion client à l'adresse indiquée.","when":"Quand vous voulez terminer un client bloqué ou au comportement anormal.","caution":"La connexion est coupée immédiatement ; les opérations en cours peuvent rester incomplètes. Assurez-vous de choisir la bonne adresse.","tags":["client","kill","client","kill","maintenance"]},{"id":"client-list","task":"Lister les clients connectés","category":"Maintenance","command":"CLIENT LIST","what":"Liste tous les clients connectés avec des détails comme l'adresse, le temps d'inactivité et la dernière commande.","when":"Pour inspecter les connexions ou trouver un client problématique.","caution":"Commande en lecture seule ; aucun risque.","tags":["client","list","client","list","connection"]},{"id":"config-get","task":"Afficher la valeur d'un paramètre","category":"Exécution","command":"CONFIG GET maxmemory-policy","what":"Renvoie la valeur actuelle d'un ou plusieurs paramètres de configuration (motifs glob pris en charge).","when":"Pour vérifier la valeur d'exécution d'un paramètre.","caution":"Commande en lecture seule ; aucun risque.","tags":["config","get","parameter","view","runtime"]},{"id":"config-set","task":"Modifier la valeur d'un paramètre","category":"Exécution","command":"CONFIG SET appendfsync everysec","what":"Modifie un paramètre de configuration à l'exécution sans redémarrage.","when":"Quand vous voulez ajuster immédiatement un comportement du serveur.","caution":"La modification est temporaire ; exécutez CONFIG REWRITE pour la rendre persistante. Une mauvaise valeur peut dégrader les performances/la durabilité.","tags":["config","set","parameter","runtime","setting"]}]},"recipes/redis-ext.json":{"engineId":"redis","engine":"Redis","order":2,"recipes":[{"id":"mem-fragmentation","task":"Analyser l'utilisation mémoire et la fragmentation, activer l'active defrag","category":"Mémoire","command":"INFO memory\n# Fields to check: used_memory_human, used_memory_rss_human, mem_fragmentation_ratio, mem_fragmentation_bytes, allocator_frag_ratio\nMEMORY STATS\n# If fragmentation is high and persistent, enable active defrag (requires jemalloc):\nCONFIG SET activedefrag yes\nCONFIG SET active-defrag-ignore-bytes 100mb\nCONFIG SET active-defrag-threshold-lower 10\n# Ask the allocator to return free pages to the OS:\nMEMORY PURGE","what":"Affiche l'écart entre la mémoire allouée par Redis aux données (used_memory) et le RSS vu par l'OS, le taux de fragmentation et la ventilation de la mémoire ; active l'active defrag à l'exécution si nécessaire.","when":"Quand le RSS est nettement supérieur au jeu de données, que la mémoire ne baisse pas après des suppressions/expirations massives, ou qu'un risque d'OOM apparaît.","caution":"mem_fragmentation_ratio est trompeur sur les petits jeux de données (par ex. 4.0 sur 2 MB de données) ; regardez mem_fragmentation_bytes pour la valeur absolue. Un ratio inférieur à 1 signifie du swap, pas de la fragmentation, et provoque de la latence. L'active defrag consomme du CPU ; plafonnez-le avec active-defrag-cycle-max. CONFIG REWRITE ou redis.conf est nécessaire pour le rendre persistant.","since":"Redis 4.0","note":"L'active defrag ne fonctionne qu'avec les builds compilés avec le jemalloc fourni avec Redis. allocator_frag_ratio indique la fragmentation interne que le defrag peut corriger ; allocator_rss_ratio indique l'espace que l'allocateur n'a pas rendu à l'OS. La fragmentation croît plus vite sur les charges qui mettent fréquemment à jour des valeurs de taille variable.","tags":["memory","ram","fragmentation","activedefrag","rss","info memory"]},{"id":"bigkeys-memkeys","task":"Trouver les grosses clés (--bigkeys, --memkeys, MEMORY USAGE)","category":"Mémoire","command":"# Largest keys per type by element count:\nredis-cli -h redis1 -p 6379 --bigkeys -i 0.01\n# By memory size:\nredis-cli -h redis1 -p 6379 --memkeys -i 0.01\n# Size and length together, top 20 keys (redis-cli 7.4+):\nredis-cli -h redis1 -p 6379 --keystats --top 20 -i 0.01\n# Memory usage (SAMPLES 0: all elements) and encoding of a single suspicious key:\nredis-cli -h redis1 -p 6379 MEMORY USAGE kullanici:1001:sepet SAMPLES 0\nredis-cli -h redis1 -p 6379 OBJECT ENCODING kullanici:1001:sepet","what":"Parcourt l'espace de clés avec SCAN et signale, par type, les clés ayant le plus d'éléments et le plus de mémoire ; affiche la consommation mémoire réelle et l'encodage d'une clé suspecte.","when":"Quand la mémoire croît de façon inattendue, qu'une seule commande (HGETALL, SMEMBERS, DEL) provoque de la latence, ou qu'un nœud d'un cluster utilise plus de mémoire que les autres.","caution":"Le parcours utilise SCAN et ne bloque pas le serveur mais parcourt tout l'espace de clés ; -i 0.01 marque une pause tous les 100 appels SCAN pour réduire la charge. Supprimer une grosse clé avec DEL bloque le thread principal ; utilisez UNLINK. MEMORY USAGE avec SAMPLES 0 est en O(N) sur les grandes collections. L'exécuter sur un réplica ne charge pas le master.","since":"Redis 6.0","note":"--bigkeys regarde le nombre d'éléments, --memkeys le MEMORY USAGE ; pour des types comme list/hash, ils peuvent mettre en évidence des clés différentes. Les petits hashes/zsets/lists utilisent l'encodage listpack et occupent beaucoup moins de mémoire ; une fois des seuils comme hash-max-listpack-entries dépassés, ils sont convertis en hashtable. Dans un cluster, exécutez-le sur chaque master séparément.","tags":["bigkeys","memkeys","big key","memory usage","keystats","large key"]},{"id":"hotkeys-lfu","task":"Trouver les clés chaudes (--hotkeys, OBJECT FREQ)","category":"Performances","command":"# Requires an LFU-based policy; note the current policy:\nCONFIG GET maxmemory-policy\nCONFIG SET maxmemory-policy allkeys-lfu\n# Hottest keys by access frequency:\nredis-cli -h redis1 -p 6379 --hotkeys -i 0.01\n# LFU counter of a single key:\nOBJECT FREQ urun:populer:liste\n# Revert to the previous policy when the analysis is done:\nCONFIG SET maxmemory-policy noeviction","what":"Liste les clés les plus fréquemment accédées à l'aide des compteurs LFU ; affiche le compteur de fréquence d'accès d'une clé.","when":"Quand le CPU d'un nœud approche 100 %, qu'un slot/nœud d'un cluster reçoit bien plus de trafic que les autres, ou que la bande passante réseau est saturée par une seule clé.","caution":"--hotkeys ne fonctionne que si maxmemory-policy est *-lfu. Changer la politique modifie aussi le comportement d'éviction : si maxmemory est défini et que la politique précédente était volatile-* ou noeviction, allkeys-lfu peut aussi évincer des clés sans TTL. Gardez l'analyse courte et rétablissez la politique. Les compteurs LFU sont logarithmiques et décroissent avec le temps ; ce ne sont pas des nombres d'accès absolus.","since":"Redis 4.0","note":"Après le changement de politique, un certain trafic est nécessaire avant que les compteurs LFU deviennent significatifs. Si la politique ne peut pas être changée, un court échantillon MONITOR est une alternative, mais MONITOR réduit fortement le débit sur les systèmes chargés. Pour les clés chaudes en lecture, le cache côté client (CLIENT TRACKING) ou le découpage de la clé est la correction durable.","tags":["hotkeys","hot keys","lfu","object freq","hot key","cpu"]},{"id":"scan-unlink-pattern","task":"Trouver les clés correspondant à un motif sans KEYS et les supprimer en sécurité (SCAN + UNLINK)","category":"Maintenance","command":"# Cursor-based scan instead of KEYS (call again with the returned cursor until it returns 0):\nSCAN 0 MATCH oturum:* COUNT 1000 TYPE string\n# Number of keys matching the pattern:\nredis-cli -h redis1 -p 6379 --scan --pattern 'oturum:*' --count 1000 | wc -l\n# Bulk delete in batches of 500, freeing memory in the background:\nredis-cli -h redis1 -p 6379 --scan --pattern 'oturum:*' --count 1000 | xargs -r -n 500 redis-cli -h redis1 -p 6379 UNLINK","what":"Parcourt l'espace de clés avec un curseur sans bloquer le serveur, compte les clés correspondant à un motif et un type, et les supprime en masse avec UNLINK, en libérant la mémoire en arrière-plan.","when":"Lors du nettoyage des clés laissées par une ancienne version de l'application, pour savoir combien de clés existent sous un préfixe, ou quand KEYS serait sinon nécessaire en production.","caution":"KEYS renvoie tout l'espace de clés d'un coup et bloque le serveur pendant plusieurs secondes sur de gros volumes ; ne l'utilisez pas en production. SCAN n'offre aucune garantie complète pour les clés ajoutées/supprimées pendant le parcours et peut renvoyer la même clé plusieurs fois. Les noms de clés contenant des espaces ou des guillemets cassent avec xargs. Vérifiez le nombre et quelques échantillons avant de supprimer ; il n'y a pas d'annulation.","since":"Redis 4.0","note":"L'option TYPE de SCAN est apparue en 6.0. COUNT est une indication et ne garantit pas le nombre d'éléments renvoyés. Dans un cluster, --scan ne parcourt que le nœud connecté ; exécutez-le sur chaque master. UNLINK libère les grosses valeurs dans un thread séparé via lazyfree ; pour les petites clés, il coûte autant que DEL.","tags":["scan","unlink","keys","bulk delete","pattern","match"]},{"id":"latency-diagnosis","task":"Diagnostiquer l'origine de la latence (LATENCY DOCTOR, histogramme, commandstats)","category":"Performances","command":"# Enable the latency monitor (record events above 100 ms):\nCONFIG SET latency-monitor-threshold 100\nLATENCY LATEST\nLATENCY HISTORY command\nLATENCY DOCTOR\n# Per-command latency distribution and percentiles:\nLATENCY HISTOGRAM GET SET HGETALL\nINFO latencystats\n# Most expensive commands by total CPU time:\nredis-cli -h redis1 -p 6379 INFO commandstats | sort -t= -k3 -rn | head\n# On the server host, baseline latency outside Redis (VM/steal detection, 30 s):\nredis-cli --intrinsic-latency 30\nLATENCY RESET","what":"Le latency monitor enregistre les pics de latence issus d'événements comme le fork, le fsync AOF, le cycle d'expiration et les commandes lentes ; affiche les percentiles de latence par commande et les commandes les plus coûteuses en temps CPU total ; mesure la latence de base au niveau de l'hôte.","when":"Quand l'application voit des pics de latence p99 et que les plaintes de lenteur persistent alors que le SLOWLOG est vide.","caution":"latency-monitor-threshold vaut 0 par défaut (désactivé) ; rétablissez l'ancienne valeur après le diagnostic. SLOWLOG ne mesure que le temps d'exécution des commandes et ne voit pas les attentes réseau ou de file ; les événements LATENCY capturent aussi les blocages internes au serveur comme le fork et le fsync. --intrinsic-latency consomme du CPU et doit s'exécuter sur la machine qui héberge le serveur Redis.","since":"Redis 7.0","note":"LATENCY HISTOGRAM et INFO latencystats sont apparus en 7.0 (latency-tracking est activé par défaut). Si les événements fork dominent LATENCY HISTORY, regardez la taille du jeu de données et les THP (transparent huge pages) ; si les événements aof-fsync-always/aof-write sont élevés, regardez la latence disque. Les commandes avec un usec_per_call élevé dans commandstats indiquent un usage en O(N).","tags":["latency","slowness","latency doctor","histogram","commandstats","intrinsic-latency"]},{"id":"eviction-oom-diag","task":"Diagnostiquer l'éviction, l'expiration et les erreurs OOM","category":"Mémoire","command":"INFO stats\n# Fields to check: evicted_keys, expired_keys, expired_stale_perc, keyspace_hits, keyspace_misses, total_eviction_exceeded_time (7.0+)\nINFO keyspace\nCONFIG GET maxmemory\nCONFIG GET maxmemory-policy\nINFO memory\n# The value that triggers eviction: used_memory - mem_not_counted_for_evict, compared with maxmemory","what":"Affiche le nombre de clés évincées et expirées, le taux de succès du cache, la part des clés avec TTL, et la valeur mémoire qui déclenche l'éviction par rapport à maxmemory.","when":"Quand l'application reçoit 'OOM command not allowed when used memory > maxmemory', que le taux de succès du cache baisse, ou que des clés disparaissent de façon inattendue.","caution":"Avec la politique noeviction, les commandes d'écriture reçoivent une erreur OOM une fois maxmemory atteint, tandis que les lectures continuent de fonctionner. Avec les politiques volatile-*, les clés sans TTL ne sont jamais évincées ; s'il n'y a aucune clé avec TTL, le comportement est celui de noeviction (vérifiez expires dans INFO keyspace). Ne réglez pas maxmemory trop près de la RAM physique : laissez de la place pour les buffers de réplication et le copy-on-write pendant le fork.","since":"Redis 6.0","note":"Taux de succès = keyspace_hits / (keyspace_hits + keyspace_misses). Si evicted_keys croît régulièrement pendant que le taux de succès baisse, la mémoire est insuffisante. Un expired_stale_perc élevé signifie une part importante de clés expirées pas encore supprimées ; le cycle d'expiration peut être accéléré avec active-expire-effort. Les buffers des réplicas et le buffer AOF sont inclus dans mem_not_counted_for_evict.","tags":["eviction","oom","maxmemory","expire","hit ratio","evicted_keys"]},{"id":"client-memory","task":"Trouver les connexions qui gonflent la mémoire (output buffer, query buffer, maxmemory-clients)","category":"Mémoire","command":"INFO clients\n# Top 10 connections by tot-mem (tot-mem, omem, qbuf, id, addr, user, cmd, age):\nredis-cli -h redis1 -p 6379 CLIENT LIST | awk '{for(i=1;i<=NF;i++){split($i,a,\"=\"); f[a[1]]=a[2]} print f[\"tot-mem\"], f[\"omem\"], f[\"qbuf\"], f[\"id\"], f[\"addr\"], f[\"user\"], f[\"cmd\"], f[\"age\"]}' | sort -rn | head -10\nCLIENT LIST TYPE pubsub\nCONFIG GET client-output-buffer-limit\n# Upper limit for the total memory of all clients (largest clients are closed when exceeded):\nCONFIG SET maxmemory-clients 5%","what":"Trie les connexions par mémoire totale (tot-mem), output buffer (omem) et query buffer (qbuf) ; affiche les connexions pub/sub et les limites de buffer ; définit une limite supérieure globale pour la mémoire des clients.","when":"Quand used_memory croît sans croissance des données, que mem_clients_normal paraît élevé, ou qu'un client pub/sub/MONITOR consommant lentement remplit la mémoire.","caution":"Un omem élevé désigne généralement un client qui lit lentement les résultats (grosse réponse KEYS/HGETALL, abonné pub/sub lent, MONITOR laissé ouvert). Quand maxmemory-clients est dépassé, les clients détenant le plus de mémoire sont fermés sans avertissement ; les liens réplica et master sont exclus mais les connexions applicatives peuvent tomber. Utilisez CLIENT NO-EVICT on pour les connexions critiques.","since":"Redis 7.0","note":"maxmemory-clients est apparu en 7.0 et vaut 0 par défaut (illimité). client-output-buffer-limit applique des limites hard/soft aux classes pubsub et replica ; les clients normaux sont illimités par défaut. Fermez une connexion problématique avec CLIENT KILL ID <id> (voir client-kill).","tags":["client list","omem","output buffer","maxmemory-clients","connections","pubsub"]},{"id":"persistence-health","task":"Diagnostiquer l'état d'erreur RDB/AOF, la durée du fork et la cause de MISCONF","category":"Persistance","command":"INFO persistence\n# Critical fields: rdb_last_bgsave_status, aof_last_bgrewrite_status, aof_last_write_status, rdb_changes_since_last_save, aof_delayed_fsync\nINFO stats\n# latest_fork_usec: duration of the last fork (microseconds), total_forks\nCONFIG GET stop-writes-on-bgsave-error\nCONFIG GET dir\nCONFIG GET appendfsync","what":"Indique si le dernier snapshot RDB et la dernière réécriture/écriture AOF ont réussi, combien de clés ont changé depuis la dernière sauvegarde, le nombre de fsync retardés et la durée du fork.","when":"Quand l'application reçoit 'MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk', que BGSAVE/BGREWRITEAOF échoue, ou qu'une latence liée au fork est observée.","caution":"Avec stop-writes-on-bgsave-error yes (par défaut), toutes les écritures sont rejetées après un BGSAVE en échec. Faire taire l'erreur en le réglant sur 'no' masque le risque de perte de données ; corrigez d'abord la cause : disque plein, pas de droit d'écriture sur dir, ou fork incapable d'obtenir de la mémoire (vm.overcommit_memory=0). Sur un gros jeu de données, le fork peut atteindre des centaines de millisecondes, pendant lesquelles Redis ne répond pas.","since":"Redis 7.0","note":"vm.overcommit_memory=1 et la désactivation des transparent huge pages sous Linux sont les recommandations officielles de Redis. Un aof_delayed_fsync qui croît signifie que le disque ne suit pas le fsync (appendfsync everysec). Les détails des erreurs figurent dans le log Redis ('Background saving error', 'Can't save in background: fork').","tags":["misconf","bgsave","aof","fork","persistence","durability"]},{"id":"aof-repair","task":"Réparer un Redis qui ne démarre pas à cause d'un AOF corrompu (redis-check-aof, redis-check-rdb)","category":"Persistance","command":"# With Redis stopped, copy the files first:\ncp -a /var/lib/redis/appendonlydir /var/lib/redis/appendonlydir.bak\n# Check (7.0+ multi-part AOF: pass the manifest file):\nredis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.manifest\n# Repair by truncating everything after the first corrupt point (asks for confirmation):\nredis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.manifest\n# Verify the RDB file:\nredis-check-rdb /var/lib/redis/dump.rdb","what":"Valide les fichiers AOF (base + incr + manifest) et le fichier RDB ; tronque la fin incomplète ou corrompue de l'AOF pour que Redis puisse redémarrer.","when":"Quand Redis refuse de démarrer avec 'Bad file format reading the append only file' ou 'Unexpected EOF' après une coupure de courant, un disque plein ou un arrêt brutal.","caution":"--fix supprime tout depuis le premier point corrompu jusqu'à la fin du fichier ; si la corruption se trouve au milieu du fichier, toutes les écritures ultérieures sont perdues. Vérifiez dans la sortie combien d'octets seront tronqués (ok_up_to, diff) avant de confirmer. Une dernière commande incomplète (par ex. un gros HSET) est entièrement abandonnée. Faites toujours une copie au préalable.","since":"Redis 7.0","note":"Avec aof-load-truncated yes (par défaut), un AOF tronqué uniquement à la fin est rogné automatiquement au démarrage ; redis-check-aof est généralement nécessaire quand la corruption est au milieu du fichier. Avec l'AOF multi-part de 7.0+, les fichiers sont dans le répertoire appenddirname et le fichier base peut être au format RDB. Pour un RDB corrompu, redis-check-rdb se contente de diagnostiquer et ne répare pas.","tags":["aof","redis-check-aof","corrupted","corrupt","repair","redis-check-rdb"]},{"id":"replication-backlog","task":"Corriger les coupures de réplication et une boucle sans fin de full resync (backlog, buffer des réplicas)","category":"Réplication","command":"INFO replication\n# Master: connected_slaves, slaveN:...,state=online,offset=,lag=  Replica: master_link_status, master_last_io_seconds_ago, master_sync_in_progress\nINFO stats\n# sync_full / sync_partial_ok / sync_partial_err: repeated full syncs = backlog too small\nCONFIG GET repl-backlog-size\nCONFIG SET repl-backlog-size 256mb\n# If the replica buffer exceeds its limit during a full sync, the link drops and sync starts over:\nCONFIG GET client-output-buffer-limit\nCONFIG SET client-output-buffer-limit \"replica 1gb 512mb 120\"","what":"Affiche l'état du lien, l'écart d'offset et les compteurs de synchronisation partielle/complète sur le master et le réplica ; agrandit le replication backlog pour que les courtes déconnexions ne se transforment pas en synchronisations complètes, et la limite de l'output buffer des réplicas pour qu'une synchronisation complète ne soit pas coupée en cours de route.","when":"Quand un réplica tourne en boucle sur 'MASTER <-> REPLICA sync started', que le compteur sync_full croît, ou que le log du master affiche 'scheduled to be closed ASAP for overcoming of output buffer limits'.","caution":"Chaque synchronisation complète implique un fork + la génération d'un RDB sur le master ; la boucle sollicite la mémoire et le disque du master. repl-backlog-size est alloué sur la mémoire du master. Relever la limite du buffer des réplicas signifie que le master peut utiliser autant de mémoire supplémentaire pendant une synchronisation complète ; incluez-la dans le calcul de maxmemory. CONFIG REWRITE ou redis.conf est nécessaire pour rendre les modifications persistantes.","since":"Redis 4.0","note":"Dimensionnement approximatif du backlog : volume d'écriture du master par seconde (croissance de master_repl_offset) x durée de déconnexion à tolérer. Le nom de classe 'replica' dans client-output-buffer-limit est accepté à la place de 'slave' depuis 5.0 ; la sortie de CONFIG GET peut encore afficher slave. Si le disque est lent, repl-diskless-sync yes (par défaut en 7.0+) envoie le RDB via le socket sans l'écrire sur disque.","tags":["replication","backlog","full sync","psync","replica","output buffer"]},{"id":"sentinel-status","task":"Vérifier l'état de Sentinel, le master actuel et le quorum","category":"Sentinel","command":"redis-cli -p 26379 SENTINEL MASTERS\nredis-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster\nredis-cli -p 26379 SENTINEL REPLICAS mymaster\nredis-cli -p 26379 SENTINEL SENTINELS mymaster\n# Are there enough sentinels (quorum and majority) for a failover:\nredis-cli -p 26379 SENTINEL CKQUORUM mymaster\nredis-cli -p 26379 INFO sentinel","what":"Liste les masters surveillés par Sentinel, l'adresse du master actuel, les réplicas connus et les autres sentinels ; vérifie que le quorum et la majorité nécessaires au basculement sont disponibles.","when":"Quand l'application se connecte au mauvais nœud, pour vérifier la topologie après un basculement, et avant chaque maintenance.","caution":"Chaque sentinel rapporte sa propre vue ; GET-MASTER-ADDR-BY-NAME doit renvoyer la même adresse sur tous les sentinels, sinon il y a une partition réseau ou une incohérence de configuration. s_down/o_down dans le champ flags ou disconnected sur un réplica nécessite une investigation urgente. Le port Sentinel (26379) ne doit être ouvert dans le pare-feu qu'entre sentinels et clients.","since":"Redis 5.0","note":"SENTINEL REPLICAS a remplacé SENTINEL SLAVES en 5.0 (l'ancien nom fonctionne toujours). Le nombre de sentinels doit être impair et d'au moins 3, répartis sur des domaines de panne différents (hôte/rack/AZ). Si Redis utilise des ACL/mots de passe, sentinel auth-user/auth-pass doit être défini dans sentinel.conf.","tags":["sentinel","master","quorum","ckquorum","topology","failover"],"untested":true},{"id":"sentinel-failover","task":"Effectuer un basculement manuel avec Sentinel et nettoyer la topologie","category":"Sentinel","command":"# First check quorum and an eligible replica:\nredis-cli -p 26379 SENTINEL CKQUORUM mymaster\nredis-cli -p 26379 SENTINEL REPLICAS mymaster\n# Manual failover without waiting for agreement from other sentinels:\nredis-cli -p 26379 SENTINEL FAILOVER mymaster\n# Verify the new master (must be the same on all sentinels):\nredis-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster\n# If old/removed replicas remain listed, reset sentinels one at a time, 30 s apart:\nredis-cli -p 26379 SENTINEL RESET mymaster","what":"Transfère le rôle de master au réplica le plus adapté même lorsque le master est sain ; vérifie ensuite le nouveau master et efface de la mémoire des sentinels les réplicas qui n'existent plus.","when":"Avant une maintenance planifiée sur l'hôte du master, lors d'un exercice de basculement, ou pour déplacer le master vers un autre data center.","caution":"Une interruption des écritures de quelques secondes se produit pendant le basculement, et les données écrites sur le master mais pas encore répliquées peuvent être perdues. Les réplicas avec replica-priority 0 ne sont jamais choisis ; si aucun réplica éligible n'existe, la commande renvoie une erreur. Si SENTINEL RESET s'exécute sur tous les sentinels en même temps, les informations de topologie sont temporairement perdues ; exécutez-le sur chacun séparément et de façon espacée. Assurez-vous que les clients utilisent un driver qui demande l'adresse du master à Sentinel.","since":"Redis 5.0","note":"SENTINEL FAILOVER ne demande pas l'accord des autres sentinels mais propage bien le nouveau config epoch. Ordre de sélection : replica-priority (la plus basse d'abord), offset de réplication (le plus grand d'abord), run ID. Quand l'ancien master revient après le basculement, Sentinel le reconfigure automatiquement en réplica du nouveau master.","tags":["sentinel","failover","manual failover","master","reset","switchover"],"untested":true},{"id":"cluster-check-fix","task":"Vérifier la santé du cluster et réparer les incohérences de slots (--cluster check/fix)","category":"Cluster","command":"redis-cli -h node1 -p 6379 CLUSTER INFO\nredis-cli -h node1 -p 6379 CLUSTER NODES\nredis-cli -h node1 -p 6379 CLUSTER SHARDS\n# Slot coverage, open (migrating/importing) slots and node consistency:\nredis-cli --cluster check node1:6379\n# Repair if there are open or unowned slots:\nredis-cli --cluster fix node1:6379\n# Which slot and node a key lives on (-c: follow redirects):\nredis-cli -h node1 -p 6379 CLUSTER KEYSLOT kullanici:1001\nredis-cli -c -h node1 -p 6379 GET kullanici:1001","what":"Vérifie que tous les nœuds voient la même carte des slots, que les 16384 slots sont tous attribués et qu'il n'y a pas de slots incomplets (migrating/importing) ; corrige les problèmes trouvés et indique sur quel slot/nœud se trouve une clé.","when":"Quand cluster_state:fail apparaît, que les clients reçoivent CLUSTERDOWN ou des erreurs MOVED/ASK constantes, et après un reshard interrompu.","caution":"--cluster fix peut attribuer des slots sans propriétaire à des nœuds arbitraires et déplacer des clés dans des slots ouverts ; examinez d'abord la sortie du check. Si un master et tous ses réplicas sont perdus, les données de ces slots sont perdues ; fix se contente d'attribuer le slot vide à un autre nœud. Avec cluster-require-full-coverage yes (par défaut), un seul slot sans propriétaire arrête les écritures sur tout le cluster.","since":"Redis 7.0","note":"CLUSTER SHARDS est apparu en 7.0 et remplace CLUSTER SLOTS. Si l'authentification est activée, ajoutez -a <password> ou --user/--pass aux commandes --cluster. Les indicateurs fail/pfail dans la sortie de CLUSTER NODES signifient que le nœud est vu comme injoignable par les autres.","tags":["cluster","check","fix","slot","clusterdown","cluster nodes"],"untested":true},{"id":"cluster-reshard","task":"Ajouter des nœuds, déplacer des slots et retirer des nœuds d'un cluster (add-node, reshard, rebalance)","category":"Cluster","command":"# Add the new master to the cluster (joins empty, with no slots):\nredis-cli --cluster add-node node7:6379 node1:6379\n# Attach a new replica to a specific master:\nredis-cli --cluster add-node node8:6379 node1:6379 --cluster-slave --cluster-master-id <master-node-id>\n# Move 1000 slots from all masters to the new node:\nredis-cli --cluster reshard node1:6379 --cluster-from all --cluster-to <node7-id> --cluster-slots 1000 --cluster-yes\n# Or rebalance automatically (give slots to empty masters too):\nredis-cli --cluster rebalance node1:6379 --cluster-use-empty-masters\n# Remove a node whose slots have been emptied:\nredis-cli --cluster del-node node1:6379 <node-id>","what":"Ajoute un nouveau master ou réplica au cluster, déplace en ligne des slots et leurs clés entre nœuds, rééquilibre automatiquement la répartition des slots et retire du cluster un nœud vidé.","when":"Pour faire évoluer horizontalement la mémoire/le débit, répartir la charge d'un nœud chaud, ou décommissionner un nœud.","caution":"La migration de slots transfère les clés une par une avec MIGRATE ; pendant le déplacement de grosses clés, les deux nœuds peuvent se bloquer brièvement. Pendant la migration, les clients reçoivent des redirections ASK ; un client compatible cluster est nécessaire. Si un reshard est interrompu, les slots restent à l'état migrating/importing ; terminez-le avec --cluster fix. Ne retirez pas avec del-node un master qui possède encore des slots ; déplacez d'abord ses slots.","since":"Redis 5.0","note":"Les ID de nœuds proviennent de CLUSTER NODES ou CLUSTER MYID. --cluster-pipeline définit combien de clés chaque appel MIGRATE déplace (10 par défaut). --cluster rebalance avec --cluster-weight permet de donner des poids différents aux nœuds. Les commandes redis-cli --cluster ont remplacé redis-trib.rb en 5.0.","tags":["cluster","reshard","rebalance","add-node","slot","del-node"],"untested":true},{"id":"acl-users","task":"Créer des utilisateurs ACL, tester les permissions et les rendre persistants","category":"Sécurité","command":"# Application user: only app:* keys and channels, dangerous commands excluded:\nACL SETUSER app_rw on >S3cret-Change ~app:* resetchannels &app:* +@read +@write -@dangerous\n# Monitoring user: no key access, only specific commands:\nACL SETUSER monitor on >M0n-Secret resetkeys resetchannels -@all +info +ping +client|list +slowlog|get +latency +config|get\n# Test permissions without running the command:\nACL DRYRUN app_rw GET app:1001\nACL GETUSER app_rw\nACL LIST\n# Make it persistent: ACL SAVE if aclfile is set, otherwise CONFIG REWRITE into redis.conf\nACL SAVE","what":"Crée des utilisateurs d'application et de supervision autorisés par préfixe de clé, canal pub/sub et catégorie de commandes ; teste avec DRYRUN si une commande est autorisée sans l'exécuter, et écrit les ACL sur disque.","when":"Lors du passage d'un mot de passe partagé unique (requirepass) à un accès par utilisateur, pour isoler les applications des clés des autres, ou pour créer un compte en lecture seule pour un outil de supervision.","caution":"L'ordre des règles compte : resetchannels supprime toutes les règles &channel données avant lui. -@dangerous bloque aussi des commandes comme KEYS, FLUSHALL, CONFIG et DEBUG ; vérifiez avec DRYRUN les commandes utilisées par l'application. Si l'utilisateur default reste activé avec nopass et +@all, les ACL n'offrent aucune protection ; exécutez ACL SETUSER default off après avoir créé un utilisateur admin. Dans redis-cli, si l'AUTH avec --user/--pass échoue, la connexion continue en tant qu'utilisateur default et la commande s'exécute quand même ; c'est pourquoi il faut utiliser DRYRUN pour les tests de permissions.","since":"Redis 7.0","note":"Les ACL sont apparues en 6.0 ; ACL DRYRUN, les selectors et les règles de sous-commandes (client|list) ont été ajoutés en 7.0. aclfile et les lignes user dans redis.conf ne peuvent pas être utilisés ensemble. Les mots de passe sont stockés sous forme de hachages SHA-256 ; ACL GETUSER n'affiche pas le mot de passe en clair.","tags":["acl","setuser","users","permissions","dryrun","security"]},{"id":"acl-log-denied","task":"Analyser les erreurs NOPERM et WRONGPASS (ACL LOG)","category":"Sécurité","command":"# Last 10 permission denials and failed logins (reason: auth/command/key/channel):\nACL LOG 10\n# User of this connection:\nACL WHOAMI\n# Cumulative denial counters:\nINFO stats\n# acl_access_denied_auth, acl_access_denied_cmd, acl_access_denied_key, acl_access_denied_channel\nACL LOG RESET","what":"Liste les accès refusés aux commandes, clés et canaux ainsi que les tentatives AUTH échouées avec nom d'utilisateur, objet, nombre de répétitions et informations client (IP, dernière commande) ; affiche les compteurs cumulés de refus.","when":"Quand l'application reçoit NOPERM ou WRONGPASS après un déploiement, pour trouver la commande cassée par une règle ACL durcie, ou pour détecter des tentatives de devinette de mot de passe.","caution":"ACL LOG réside en mémoire, est vidé au redémarrage et est limité à acllog-max-len entrées (128 par défaut) ; quand la même erreur se répète, count augmente au lieu de créer une nouvelle ligne. Le champ client-info contient l'IP du client et l'utilisateur utilisé ; notez ce dont vous avez besoin avant la réinitialisation.","since":"Redis 7.0","note":"Les compteurs acl_access_denied_* ont été ajoutés à INFO stats en 7.2 ; intégrez-les à la supervision pour alerter en cas d'augmentation. Les champs entry-id et timestamp-created/last-updated sont apparus en 7.2. Les tentatives de mauvais mot de passe apparaissent avec reason=auth, une permission de commande manquante avec reason=command.","tags":["acl log","noperm","wrongpass","access denied","auth","security"]},{"id":"keyspace-notifications","task":"Surveiller les événements de suppression et d'expiration de clés avec les keyspace notifications","category":"Maintenance","command":"CONFIG GET notify-keyspace-events\n# E: keyevent channel, x: expire events, g: generic commands (DEL, EXPIRE, RENAME):\nCONFIG SET notify-keyspace-events Exg\n# Listen to expire and del events in db 0:\nredis-cli -h redis1 -p 6379 --csv PSUBSCRIBE '__keyevent@0__:expired' '__keyevent@0__:del'\n# Turn off:\nCONFIG SET notify-keyspace-events \"\"","what":"Publie les événements sur les clés (expire, del, set, etc.) dans des canaux pub/sub pour observer en direct quelle clé a été supprimée ou a expiré et quand.","when":"Pour prouver quelle commande ou expiration supprime des clés quand elles « disparaissent toutes seules », ou pour alimenter des workflows qui dépendent des événements d'expiration (fermeture de session).","caution":"Les événements ajoutent une charge CPU et réseau ; 'A' (toutes les classes) est particulièrement coûteux sur les systèmes chargés, activez donc uniquement les classes nécessaires et désactivez-les ensuite. Le pub/sub est fire-and-forget : un abonné non connecté manque l'événement, et rien n'est persisté. L'événement expired est généré quand la clé est réellement supprimée (à l'accès ou par le cycle d'expiration en arrière-plan), pas au moment du TTL.","since":"Redis 2.8","note":"Ajoutez la classe e (evicted) pour les clés supprimées par éviction. Dans un cluster, les événements ne sont publiés que sur le nœud qui détient la clé ; abonnez-vous à chaque master séparément. Définissez notify-keyspace-events dans redis.conf pour un réglage persistant.","tags":["keyspace notifications","notify-keyspace-events","expired","pubsub","events","psubscribe"]}]},"recipes/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"recipes":[{"id":"flush","task":"Effectuer un flush d'un index","category":"Cycle de vie des index","command":"POST /customer/_flush","what":"Effectue un commit Lucene : écrit durablement sur disque, sous forme de segments, les données d'indexation en mémoire et tronque les entrées du translog qui ne sont plus nécessaires.","when":"Pour réduire le translog avant une maintenance ou un redémarrage.","caution":"Généralement effectué automatiquement ; un flush manuel entraîne une légère hausse de l'IO.","tags":["flush","translog","index","customer","maintenance"]},{"id":"force-merge","task":"Fusionner les segments d'un index (force merge)","category":"Cycle de vie des index","command":"POST /customer/_forcemerge?max_num_segments=1","what":"Fusionne les segments d'un index pour réduire leur nombre et améliorer les performances de lecture.","when":"Uniquement sur les index qui ne reçoivent plus d'écritures (lecture seule), pour améliorer les performances des requêtes.","caution":"Consomme beaucoup d'IO et de CPU ; ne l'exécutez jamais sur des index activement écrits. Peut durer longtemps et ne peut pas être annulé.","tags":["forcemerge","segment","merge","merge segments","performance"]},{"id":"rollover","task":"Effectuer un rollover via un alias","category":"Cycle de vie des index","command":"POST /customer-alias/_rollover\n{\n  \"conditions\": {\n    \"max_age\": \"7d\",\n    \"max_docs\": 100000000,\n    \"max_primary_shard_size\": \"50gb\"\n  }\n}","what":"Quand l'index d'écriture pointé par l'alias remplit les conditions, crée un nouvel index et y fait pointer l'alias.","when":"Pour gérer la taille des index pour les données de séries temporelles (alternative/complément à ILM).","caution":"L'alias doit avoir un seul index d'écriture via is_write_index ; une mauvaise configuration bloque le rollover.","tags":["rollover","alias","index","time series","ilm"]},{"id":"set-refresh-interval","task":"Modifier l'intervalle de refresh","category":"Cycle de vie des index","command":"PUT /customer/_settings\n{\n  \"index\": { \"refresh_interval\": \"30s\" }\n}","what":"Définit la fréquence à laquelle l'index rend les nouveaux documents consultables (refresh).","when":"Pour augmenter l'intervalle afin d'accélérer l'indexation lors des chargements massifs.","caution":"-1 désactive complètement le refresh ; les documents ne deviennent pas consultables. Remettez une valeur raisonnable après le chargement.","tags":["refresh","interval","settings","bulk","refresh interval"]},{"id":"set-replicas","task":"Modifier le nombre de réplicas","category":"Cycle de vie des index","command":"PUT /customer/_settings\n{\n  \"index\": { \"number_of_replicas\": 2 }\n}","what":"Modifie dynamiquement le nombre de shards réplicas de l'index.","when":"Pour augmenter/réduire la redondance ou la capacité de lecture.","caution":"L'ajout de réplicas entraîne une utilisation disque et un trafic réseau supplémentaires (recovery) ; descendre à 0 supprime la redondance.","tags":["replica","settings","number_of_replicas","redundancy","capacity"]},{"id":"open-close-index","task":"Ouvrir ou fermer un index","category":"Cycle de vie des index","command":"POST /customer/_close\nPOST /customer/_open","what":"Ferme l'index aux lectures/écritures (en libérant des ressources) ou le rouvre.","when":"Pour réduire l'utilisation des ressources des index rarement utilisés ou pour modifier certains paramètres.","caution":"Un index fermé ne peut être ni interrogé ni écrit ; tant qu'il est fermé, il n'est accessible que via les snapshots.","tags":["open","close","index","open index","close index"]},{"id":"node-join-note","task":"Ajouter un nœud au cluster","category":"Cluster/Nœud","command":"GET /_cat/nodes?v","what":"Démarrer le nouveau nœud avec le même cluster.name et un discovery.seed_hosts correct dans elasticsearch.yml suffit ; cette commande vérifie que le nœud a rejoint le cluster.","when":"Pour vérifier l'appartenance lors de l'ajout d'un nouveau nœud au cluster.","caution":"En 7.0+, il n'y a pas de minimum_master_nodes ; la configuration de vote est gérée automatiquement. Ajoutez et retirez les nœuds master-eligible un par un, et n'en retirez jamais la moitié ou plus à la fois ; pour retirer définitivement plus d'un nœud master-eligible, utilisez d'abord POST _cluster/voting_config_exclusions?node_names=....","tags":["node","join","cluster","cat","add node"]},{"id":"node-decommission","task":"Vider un nœud (décommissionnement)","category":"Cluster/Nœud","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.exclude._name\": \"node-3\"\n  }\n}","what":"Vide en toute sécurité le nœud indiqué en déplaçant ses shards vers d'autres nœuds.","when":"Pour déplacer les données d'un nœud avant de le mettre hors service.","caution":"La relocalisation des shards génère une forte charge réseau/IO ; assurez-vous que les nœuds restants ont assez de disque et de capacité. Supprimez le paramètre avec null après le retrait du nœud. Les paramètres transient sont obsolètes depuis 7.16 ; utilisez persistent.","tags":["decommission","exclude","allocation","drain","node"]},{"id":"reroute-shard","task":"Déplacer un shard manuellement (reroute)","category":"Cluster/Nœud","command":"POST /_cluster/reroute\n{\n  \"commands\": [\n    {\n      \"move\": {\n        \"index\": \"customer\",\n        \"shard\": 0,\n        \"from_node\": \"node-1\",\n        \"to_node\": \"node-2\"\n      }\n    }\n  ]\n}","what":"Déplace manuellement un shard précis d'un nœud vers un autre.","when":"Pour corriger une répartition déséquilibrée des shards ou vider un nœud précis.","caution":"Peut entrer en conflit avec le rééquilibrage automatique ; le déplacement de gros shards génère une charge réseau et IO.","tags":["reroute","shard","move","relocate","cluster"]},{"id":"allocation-enable","task":"Activer ou désactiver l'allocation des shards","category":"Cluster/Nœud","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.enable\": \"primaries\"\n  }\n}","what":"Restreint l'allocation des shards à l'échelle du cluster (primaries : seuls les primaires sont alloués, none : rien) ou la ramène à la valeur par défaut (all / null).","when":"Avant un rolling restart, pour éviter des relocalisations inutiles de réplicas (la valeur officiellement recommandée est \"primaries\").","caution":"Pensez à rétablir ensuite le paramètre avec null (ou \"all\") ; s'il reste restreint, les réplicas restent non assignés. Les paramètres transient sont obsolètes ; utilisez persistent.","tags":["allocation","enable","restart","shard","cluster"]},{"id":"cluster-settings","task":"Modifier un paramètre du cluster","category":"Cluster/Nœud","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"indices.recovery.max_bytes_per_sec\": \"100mb\"\n  }\n}","what":"Modifie un paramètre persistent ou transient à l'échelle du cluster.","when":"Pour ajuster le comportement du cluster, comme la vitesse de recovery.","caution":"Les paramètres persistent survivent aux redémarrages ; les paramètres transient sont perdus et sont obsolètes depuis 7.16, ne les utilisez donc pas. Pour remettre un paramètre à sa valeur par défaut, réglez sa valeur sur null. Une mauvaise valeur affecte la stabilité du cluster.","tags":["cluster","settings","persistent","transient","setting"]},{"id":"snapshot-repo","task":"Enregistrer un dépôt de snapshots","category":"Snapshot","command":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": {\n    \"location\": \"/mnt/es_backups\"\n  }\n}","what":"Enregistre un dépôt dans lequel les snapshots seront stockés.","when":"Lors de la première mise en place de l'infrastructure de sauvegarde.","caution":"Pour un dépôt fs, le chemin doit être défini dans path.repo et accessible sur tous les nœuds master et data.","tags":["snapshot","repository","repo","backup","register"]},{"id":"snapshot-create","task":"Prendre un snapshot","category":"Snapshot","command":"PUT /_snapshot/backup_repo/snap1?wait_for_completion=false\n{\n  \"indices\": \"customer\",\n  \"include_global_state\": false\n}","what":"Effectue une sauvegarde à un instant donné (snapshot) des index indiqués dans le dépôt.","when":"Pour les sauvegardes régulières ou comme sauvegarde de sécurité avant un changement majeur.","caution":"wait_for_completion=true maintient longtemps la connexion HTTP en attente sur les grands index ; utilisez false pour les gros snapshots.","tags":["snapshot","create","backup","backup snapshot","take"]},{"id":"snapshot-restore","task":"Restaurer depuis un snapshot","category":"Snapshot","command":"POST /_snapshot/backup_repo/snap1/_restore\n{\n  \"indices\": \"customer\",\n  \"rename_pattern\": \"customer\",\n  \"rename_replacement\": \"customer_restored\"\n}","what":"Restaure des index depuis un snapshot dans le cluster (en les renommant éventuellement).","when":"Pour la récupération après une perte de données ou pour déplacer une sauvegarde vers un environnement de test.","caution":"Pour restaurer sous le même nom, l'index cible doit être fermé ou supprimé ; vous ne pouvez pas restaurer par-dessus un index ouvert.","tags":["snapshot","restore","restore snapshot","recovery","backup"]},{"id":"cache-clear","task":"Vider le cache","category":"Maintenance","command":"POST /customer/_cache/clear","what":"Vide les caches query, request et fielddata de l'index.","when":"Pour réduire la pression mémoire ou résoudre des problèmes de fielddata.","caution":"Après le vidage du cache, les premières requêtes sont plus lentes jusqu'à ce qu'il se réchauffe.","tags":["cache","clear","clear cache","fielddata","maintenance"]},{"id":"pending-tasks","task":"Afficher les tâches de cluster en attente","category":"Maintenance","command":"GET /_cluster/pending_tasks","what":"Liste les tâches de niveau cluster en attente de traitement sur le nœud master.","when":"Pour diagnostiquer la charge du master quand le cluster ralentit ou que les mises à jour se bloquent.","caution":"Commande en lecture seule ; aucun risque.","tags":["pending","tasks","master","diagnostics","pending tasks"]},{"id":"hot-threads","task":"Obtenir une analyse hot threads","category":"Maintenance","command":"GET /_nodes/hot_threads","what":"Affiche les stack traces des threads qui consomment le plus de CPU sur les nœuds.","when":"Pour diagnostiquer l'origine d'une forte utilisation CPU.","caution":"Commande en lecture seule ; la sortie peut être longue, restreignez-la avec le paramètre thread_type.","tags":["hotthreads","cpu","diagnostics","thread","performance"]},{"id":"cat-shards","task":"Afficher la répartition des shards","category":"Maintenance","command":"GET /_cat/shards?v&s=state","what":"Liste tous les shards avec l'index, le type (primary/replica), l'état et le nœud qui les héberge.","when":"Pour examiner le placement des shards et les shards non assignés.","caution":"Commande en lecture seule ; aucun risque.","tags":["cat","shards","distribution","state","shard"]},{"id":"allocation-explain","task":"Trouver pourquoi un shard n'est pas assigné","category":"Maintenance","command":"GET /_cluster/allocation/explain\n{\n  \"index\": \"customer\",\n  \"shard\": 0,\n  \"primary\": true\n}","what":"Explique en détail pourquoi un shard précis n'a pas pu être alloué (unassigned).","when":"Pour rechercher la raison des shards non assignés dans un cluster yellow/red.","caution":"Commande de diagnostic en lecture seule ; aucun risque.","tags":["allocation","explain","unassigned","diagnostics","shard"]},{"id":"cluster-health","task":"Afficher la santé du cluster","category":"Maintenance","command":"GET /_cluster/health","what":"Affiche la santé globale du cluster (green/yellow/red) et le nombre de shards actifs et non assignés.","when":"Pour vérifier rapidement l'état du cluster.","caution":"Commande en lecture seule ; aucun risque.","tags":["cluster","health","health check","status","green"]},{"id":"refresh-index","task":"Effectuer un refresh manuel d'un index","category":"Cycle de vie des index","command":"POST /customer/_refresh","what":"Rend immédiatement consultables les modifications en attente dans l'index.","when":"Quand vous voulez que les données soient interrogeables juste après un chargement massif.","caution":"Des refresh manuels fréquents peuvent produire de nombreux petits segments et dégrader les performances.","tags":["refresh","index","search","segment","customer"]}]},"recipes/elasticsearch-ext.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":2,"recipes":[{"id":"red-yellow-triage","task":"Procédure de tri d'un cluster red/yellow (quel index, quel shard, pourquoi)","category":"Cluster/Nœud","command":"GET _cluster/health?filter_path=status,unassigned_shards,initializing_shards,delayed_unassigned_shards,number_of_pending_tasks\nGET _health_report?verbose=false\nGET _cat/indices?v&health=red&h=health,index,pri,rep,docs.count,store.size\nGET _cat/indices?v&health=yellow&h=health,index,pri,rep\nGET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason,unassigned.for,node&s=state\n# Without a body, the call explains the first unassigned shard:\nGET _cluster/allocation/explain","what":"Donne une procédure de tri ordonnée, de l'état du cluster jusqu'aux index en cause, puis aux shards non assignés et à la raison de leur non-allocation (unassigned.reason, allocation explain) ; _health_report résume quel indicateur (disque, disponibilité des shards, master) est en mauvaise santé.","when":"Comme première réponse quand le cluster passe en red ou yellow.","caution":"Red : au moins un shard primaire n'est pas assigné ; les écritures dans cet index et les recherches touchant ce shard échouent. Yellow : tous les primaires sont présents mais des réplicas manquent ; les données sont accessibles mais sans redondance. Sur un cluster à un seul nœud, les index avec replicas > 0 restent yellow en permanence (décision same_shard). Si un nœud est parti récemment, les delayed_unassigned_shards sont retenus volontairement jusqu'à l'expiration de index.unassigned.node_left.delayed_timeout (1m par défaut).","since":"Elasticsearch 8.7","note":"_health_report est apparu en 8.7 ; sur les versions plus anciennes, utilisez les quatre premières étapes. Valeurs de unassigned.reason : NODE_LEFT (nœud parti), ALLOCATION_FAILED (limite de tentatives atteinte, voir retry-failed-allocation), INDEX_CREATED, CLUSTER_RECOVERED. Pour un shard précis, utilisez la procédure allocation-explain avec un corps index/shard/primary.","tags":["red","yellow","cluster health","unassigned","health report","triage"]},{"id":"retry-failed-allocation","task":"Réallouer les shards ayant atteint la limite de tentatives (reroute retry_failed)","category":"Cluster/Nœud","command":"# Attempt count and last failure details:\nGET _cluster/allocation/explain?filter_path=index,shard,unassigned_info.reason,unassigned_info.failed_allocation_attempts,unassigned_info.details,allocate_explanation\n# After fixing the cause, retry shards that hit the limit:\nPOST _cluster/reroute?retry_failed=true&metric=none\n# Recovery progress:\nGET _cat/recovery?v&active_only=true&h=index,shard,stage,source_node,target_node,bytes_percent,translog_ops_percent","what":"Affiche les détails d'échec des shards dont l'allocation a été stoppée après des échecs consécutifs (ALLOCATION_FAILED, max_retry) et relance l'allocation ; suit la progression de la recovery.","when":"Quand allocation explain affiche 'shard has exceeded the maximum number of retries' ; après résolution d'un disque plein, d'un translog corrompu ou d'un problème réseau passager.","caution":"Lancer retry_failed sans corriger la cause fait tenter 5 fois de plus le shard, qui échoue à nouveau ; résolvez d'abord l'erreur indiquée dans unassigned_info.details. Si de nombreux shards entrent en recovery en même temps, la charge réseau et disque augmente ; elle est limitée par cluster.routing.allocation.node_concurrent_recoveries et indices.recovery.max_bytes_per_sec.","since":"Elasticsearch 7.0","note":"La limite de tentatives est index.allocation.max_retries (5 par défaut). Elasticsearch réessaie aussi périodiquement, mais retry_failed supprime l'attente. metric=none supprime le volumineux cluster state de la réponse.","tags":["reroute","retry_failed","allocation failed","max_retries","unassigned","recovery"]},{"id":"allocate-stale-primary","task":"Allouer un shard primaire perdu en dernier recours (allocate_stale_primary)","category":"Cluster/Nœud","command":"# Which nodes hold copies of the primary (allocation id, store errors):\nGET _shard_stores/musteri?status=red\n# Last resort: promote a stale copy to primary; data loss is accepted:\nPOST _cluster/reroute\n{\n  \"commands\": [\n    {\n      \"allocate_stale_primary\": {\n        \"index\": \"musteri\",\n        \"shard\": 0,\n        \"node\": \"node-2\",\n        \"accept_data_loss\": true\n      }\n    }\n  ]\n}","what":"Quand le nœud détenant la copie primaire à jour est définitivement perdu, assigne comme primaire une copie plus ancienne (non in-sync) sur un autre nœud pour sortir l'index du red.","when":"Uniquement quand le nœud hébergeant le primaire ne peut pas être récupéré et que la restauration depuis un snapshot n'est pas possible ou prendrait plus de temps.","caution":"La copie obsolète ne contient pas les dernières écritures ; toutes les écritures postérieures à cette copie sont définitivement perdues. Si le nœud perdu revient plus tard, la copie plus récente qu'il contient est supprimée. Envisagez d'abord de récupérer le nœud et de restaurer depuis un snapshot. allocate_empty_primary crée le shard entièrement vide et supprime toutes les données de ce shard ; ne l'utilisez que lorsqu'aucune copie ne subsiste.","since":"Elasticsearch 7.0","note":"Dans la sortie de _shard_stores, le type d'allocation apparaît comme 'primary', 'replica' ou 'unused' ; ne choisissez pas de copies avec une store_exception. Vous pouvez d'abord exécuter la commande avec ?dry_run=true&explain=true pour voir les décisions. Après l'opération, comblez l'écart de données en réindexant côté application.","tags":["allocate_stale_primary","red","primary","data loss","shard_stores","reroute"],"untested":true},{"id":"disk-watermark","task":"Gérer les disk watermarks et lever le blocage d'écriture flood-stage","category":"Cluster/Nœud","command":"GET _cat/allocation?v&h=node,shards,disk.indices,disk.used,disk.avail,disk.percent&s=disk.percent:desc\nGET _cluster/settings?include_defaults=true&filter_path=*.cluster.routing.allocation.disk\n# Adjust thresholds temporarily (null returns to the default):\nPUT _cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.disk.watermark.low\": \"85%\",\n    \"cluster.routing.allocation.disk.watermark.high\": \"90%\",\n    \"cluster.routing.allocation.disk.watermark.flood_stage\": \"95%\"\n  }\n}\n# After freeing space, remove the flood-stage write block:\nPUT _all/_settings\n{\n  \"index.blocks.read_only_allow_delete\": null\n}","what":"Affiche l'utilisation disque par nœud et les valeurs effectives des watermarks ; ajuste les seuils low/high/flood_stage et lève le blocage read_only_allow_delete placé sur les index au stade flood.","when":"Quand les écritures sont rejetées avec 'TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block', que les nouveaux shards ne sont pas alloués, ou que des shards commencent à quitter un nœud plein.","caution":"Au-dessus de low, aucun nouveau shard n'est alloué au nœud ; au-dessus de high, des shards sont déplacés hors du nœud ; au-dessus de flood_stage, les index ayant un shard sur ce nœud passent en lecture seule. Relever les seuils ne fait que gagner du temps ; la correction durable consiste à supprimer d'anciens index, réduire les réplicas ou ajouter du disque/des nœuds. Si le disque se remplit complètement, des shards peuvent être corrompus.","since":"Elasticsearch 8.5","note":"En 7.4+, le blocage est levé automatiquement dès que l'utilisation disque repasse sous le high watermark ; une levée manuelle raccourcit l'attente. En 8.5+, les grands disques utilisent max_headroom (low 200GB, high 150GB, flood 100GB) comme espace libre absolu au lieu du pourcentage ; ces headrooms par défaut ne s'appliquent que tant que les watermarks restent à leurs valeurs par défaut, donc définir explicitement des pourcentages comme ci-dessus les désactive. Les watermarks s'appliquent aussi aux clusters à un seul nœud data.","tags":["disk watermark","flood_stage","read_only_allow_delete","disk full","allocation","cat allocation"]},{"id":"rolling-restart","task":"Rolling restart / rolling upgrade (nœud par nœud, sans déplacements de shards inutiles)","category":"Cluster/Nœud","command":"# 1) Disable replica allocation (only primaries are allocated):\nPUT _cluster/settings\n{\n  \"persistent\": { \"cluster.routing.allocation.enable\": \"primaries\" }\n}\n# 2) Optional: stop indexing and flush to speed up recovery:\nPOST _flush\n# 3) Stop one node, do the maintenance, start it; verify it joined:\nGET _cat/nodes?v&h=name,version,node.role,master,heap.percent\n# 4) Re-enable allocation:\nPUT _cluster/settings\n{\n  \"persistent\": { \"cluster.routing.allocation.enable\": null }\n}\n# 5) Wait for green before moving to the next node:\nGET _cluster/health?wait_for_status=green&timeout=10m","what":"Donne l'ordre de redémarrage ou de mise à niveau des nœuds un par un tout en empêchant que les réplicas soient recopiés vers d'autres nœuds quand un nœud est arrêté ; au retour du nœud, les shards se rétablissent rapidement depuis la copie locale.","when":"Lors du redémarrage séquentiel des nœuds pour un correctif OS, un changement JVM/heap, une modification d'elasticsearch.yml ou une mise à niveau de version mineure/majeure.","caution":"Ne passez pas au nœud suivant sans attendre le green ; avec deux nœuds arrêtés en même temps, toutes les copies d'un shard peuvent avoir disparu. Redémarrez les nœuds master-eligible en dernier et un par un ; si la majorité est perdue, le cluster ne peut pas élire de master. Si vous oubliez de réactiver allocation.enable, les réplicas restent non assignés. Lors d'une mise à niveau, les nœuds data sont mis à niveau en premier (frozen/cold d'abord, hot en dernier), puis les masters.","since":"Elasticsearch 7.16","note":"S'il y a des jobs ML, exécutez POST _ml/set_upgrade_mode?enabled=true au début et enabled=false à la fin. Le synced flush a été supprimé en 8.0 ; un _flush normal suffit. Utilisez persistent, car les paramètres transient sont obsolètes depuis 7.16. Pour des redémarrages courts, relever index.unassigned.node_left.delayed_timeout au lieu de désactiver l'allocation fonctionne aussi.","tags":["rolling restart","rolling upgrade","allocation","primaries","maintenance","upgrade"],"untested":true},{"id":"shard-sizing","task":"Auditer la taille et le nombre de shards (détection d'oversharding / de shards surdimensionnés)","category":"Maintenance","command":"# Largest shards (GB):\nGET _cat/shards?v&h=index,shard,prirep,state,docs,store,node&s=store:desc&bytes=gb\n# Indices by primary size:\nGET _cat/indices?v&h=index,pri,rep,docs.count,pri.store.size,store.size&s=pri.store.size:desc&bytes=gb\n# Shard count and disk per node:\nGET _cat/allocation?v&h=node,shards,disk.indices,disk.percent&s=shards:desc\nGET _cluster/stats?filter_path=indices.shards.total,indices.shards.primaries,nodes.count.data\nGET _cluster/settings?include_defaults=true&filter_path=*.cluster.max_shards_per_node","what":"Liste les plus gros shards, la taille des primaires des index, le nombre de shards par nœud, le nombre total de shards et la limite cluster.max_shards_per_node.","when":"Lors de la planification de capacité, quand les durées de recovery s'allongent ou que la pression sur le heap augmente, et sur l'erreur 'this action would add [x] shards, but this cluster currently has [y]/[z] maximum normal shards open'.","caution":"De nombreux très petits shards (oversharding) sollicitent le master et le heap ; de très gros shards étirent la recovery et le rééquilibrage sur des heures. Le nombre de shards primaires ne peut pas changer après la création de l'index ; shrink, split ou reindex est nécessaire pour le corriger. Relever max_shards_per_node supprime la limite mais ne résout pas le problème.","since":"Elasticsearch 8.3","note":"Recommandation générale d'Elastic : 10-50 GB par shard et au plus environ 200 millions de documents. En 8.3+, l'ancienne règle « 20 shards par GB de heap » a été remplacée par une estimation du heap fondée sur le nombre de champs mappés. Pour les séries temporelles, fixez la taille des shards avec max_primary_shard_size dans le rollover.","tags":["shard size","oversharding","cat shards","max_shards_per_node","capacity","sizing"]},{"id":"shrink-index","task":"Réduire (shrink) un index ayant trop de shards","category":"Cycle de vie des index","command":"# 1) Block writes on the source and gather all shard copies on one node:\nPUT log-2026.09/_settings\n{\n  \"index.number_of_replicas\": 0,\n  \"index.routing.allocation.require._name\": \"node-1\",\n  \"index.blocks.write\": true\n}\nGET _cluster/health/log-2026.09?wait_for_no_relocating_shards=true&timeout=30m\n# 2) Create a new index with fewer shards (the target must be a factor of the source):\nPOST log-2026.09/_shrink/log-2026.09-shrunk\n{\n  \"settings\": {\n    \"index.number_of_shards\": 1,\n    \"index.number_of_replicas\": 1,\n    \"index.routing.allocation.require._name\": null,\n    \"index.blocks.write\": null,\n    \"index.codec\": \"best_compression\"\n  }\n}","what":"Réduit le nombre de shards primaires d'un index qui ne reçoit plus d'écritures en créant des liens physiques de ses segments vers un nouvel index ; diminue le surcoût en heap et en nombre de shards.","when":"Quand des index quotidiens ont été créés avec plus de shards que nécessaire, que la limite du nombre de shards est proche, ou avant de déplacer d'anciens index vers le tier warm.","caution":"L'index source est fermé aux écritures pendant le shrink ; ne l'appliquez qu'aux index qui ne reçoivent plus d'écritures. Le nombre de shards cible doit être un diviseur du nombre source (8 -> 4, 2 ou 1). Le nœud où toutes les copies sont rassemblées doit disposer d'autant d'espace disque libre que l'index source. Avec replicas 0, le shard source n'a qu'une seule copie ; ne supprimez pas la source avant la fin de l'opération et le passage du nouvel index en green.","since":"Elasticsearch 7.0","note":"Après le shrink, supprimez la source ou basculez l'alias vers le nouvel index. L'opération est généralement rapide car le même nœud peut créer des liens physiques vers les fichiers sur le même système de fichiers ; sinon les fichiers sont copiés. Utilisez _split pour augmenter le nombre de shards, et l'action shrink de la phase warm pour l'automatiser avec ILM.","tags":["shrink","shard count","oversharding","index","blocks.write","warm"]},{"id":"ilm-policy-bootstrap","task":"Définir une politique ILM, l'attacher via un template et initialiser la chaîne de rollover","category":"Cycle de vie des index","command":"PUT _ilm/policy/log-policy\n{\n  \"policy\": {\n    \"phases\": {\n      \"hot\": { \"actions\": { \"rollover\": { \"max_primary_shard_size\": \"50gb\", \"max_age\": \"1d\" }, \"set_priority\": { \"priority\": 100 } } },\n      \"warm\": { \"min_age\": \"7d\", \"actions\": { \"shrink\": { \"number_of_shards\": 1 }, \"forcemerge\": { \"max_num_segments\": 1 }, \"set_priority\": { \"priority\": 50 } } },\n      \"delete\": { \"min_age\": \"30d\", \"actions\": { \"delete\": {} } }\n    }\n  }\n}\nPUT _index_template/log\n{\n  \"index_patterns\": [\"log-*\"],\n  \"priority\": 500,\n  \"template\": {\n    \"settings\": {\n      \"number_of_shards\": 1,\n      \"number_of_replicas\": 1,\n      \"index.lifecycle.name\": \"log-policy\",\n      \"index.lifecycle.rollover_alias\": \"log\"\n    }\n  }\n}\n# Create the first index with the write alias (its name must end with a number such as -000001):\nPUT log-000001\n{\n  \"aliases\": { \"log\": { \"is_write_index\": true } }\n}","what":"Crée une politique ILM avec les phases hot (rollover), warm (shrink + force merge) et delete, applique la politique et l'alias de rollover aux nouveaux index via un template, et crée le premier index de la chaîne avec l'alias d'écriture.","when":"Pour automatiser la taille, la rétention et la suppression des index pour des données de séries temporelles comme les logs ou les métriques.","caution":"min_age dans les phases warm et delete est compté à partir du rollover, et non de la création de l'index. Les modifications de politique ne s'appliquent aux index existants qu'après la fin de leur phase en cours. Shrink et forcemerge dans la phase warm génèrent une forte IO ; l'index ne doit pas recevoir d'écritures avant la fin du force merge. ILM s'exécute toutes les 10 minutes par défaut, les conditions ne sont donc pas évaluées instantanément.","since":"Elasticsearch 7.8","note":"Pour les nouveaux projets, un data stream obtient le même résultat plus simplement : ajoutez \"data_stream\": {} au template d'index, et aucun alias de rollover ni index d'amorçage n'est nécessaire. Les conditions de rollover sont évaluées sur des statistiques rafraîchies. Les composable index templates (_index_template) sont apparus en 7.8 ; l'ancienne API _template est obsolète.","tags":["ilm","lifecycle","rollover","policy","index template","retention"]},{"id":"ilm-explain-retry","task":"Diagnostiquer une étape ILM bloquée et la relancer (_ilm/explain, _ilm/retry)","category":"Cycle de vie des index","command":"# Indices waiting in an error step:\nGET log-*/_ilm/explain?only_errors=true\n# Phase/action/step state and failure reason for one index:\nGET log-000003/_ilm/explain?filter_path=indices.*.phase,indices.*.action,indices.*.step,indices.*.failed_step,indices.*.step_info,indices.*.is_auto_retryable_error\n# After fixing the cause (example: missing rollover alias setting):\nPUT log-000003/_settings\n{\n  \"index.lifecycle.rollover_alias\": \"log\"\n}\nPOST log-000003/_ilm/retry\n# Is ILM running (if STOPPED, POST _ilm/start):\nGET _ilm/status","what":"Affiche les index en attente à l'étape ERROR, la phase/action/étape où ils sont bloqués et la raison de l'échec (step_info) ; relance l'étape en échec une fois la cause corrigée.","when":"Quand le rollover ne se produit pas, que les anciens index ne sont pas supprimés, ou que le disque se remplit d'index qu'ILM aurait dû supprimer.","caution":"_ilm/retry ne fonctionne que pour les index à l'étape ERROR ; sinon il renvoie 'has not encountered an error'. Causes fréquentes : paramètre rollover_alias manquant ou alias ayant is_write_index sur un autre index, disque insuffisant sur un seul nœud pour le shrink, règle d'allocation (_tier_preference) impossible à satisfaire. Supprimer la politique ne retire pas la gestion ILM des index ; retirez d'abord le paramètre de politique des index.","since":"Elasticsearch 7.0","note":"Les erreurs avec is_auto_retryable_error à true sont relancées par ILM lui-même (failed_step_retry_count augmente). L'intervalle de contrôle ILM est défini par indices.lifecycle.poll_interval (10m par défaut). Utilisez POST <index>/_ilm/remove pour sortir un index d'ILM.","tags":["ilm","explain","retry","error step","rollover","lifecycle"]},{"id":"index-template-component","task":"Construire un template d'index à partir de component templates et simuler le résultat","category":"Cycle de vie des index","command":"PUT _component_template/app-mappings\n{\n  \"template\": {\n    \"mappings\": {\n      \"dynamic\": \"strict\",\n      \"properties\": {\n        \"@timestamp\": { \"type\": \"date\" },\n        \"level\": { \"type\": \"keyword\" },\n        \"msg\": { \"type\": \"text\" }\n      }\n    }\n  }\n}\nPUT _index_template/app-events\n{\n  \"index_patterns\": [\"app-events-*\"],\n  \"priority\": 500,\n  \"composed_of\": [\"app-mappings\"],\n  \"template\": { \"settings\": { \"number_of_shards\": 1, \"refresh_interval\": \"5s\" } }\n}\n# See the final settings and mapping before the index is created:\nPOST _index_template/_simulate_index/app-events-000001\nGET _cat/templates?v&s=name","what":"Définit un component template de mapping réutilisable, le compose dans un template d'index, et affiche les paramètres et le mapping finaux qui s'appliqueraient à un nom d'index donné sans créer l'index.","when":"Quand de nouveaux index sont créés avec un mauvais mapping ou un mauvais nombre de shards (explosion du dynamic mapping, text au lieu de keyword), et avant de déployer une modification de template.","caution":"Un template ne s'applique qu'aux index nouvellement créés ; le mapping des index existants ne change pas (voir reindex-alias-swap). Si deux templates correspondent au même motif, seul celui de plus haute priorité est utilisé ; ils ne sont pas fusionnés. dynamic: strict rejette les documents contenant des champs non définis ; si l'application envoie un nouveau champ, l'indexation échoue.","since":"Elasticsearch 7.8","note":"Le champ overlapping de la sortie de _simulate_index montre les autres templates correspondant au même motif qui sont écrasés. Gardez une priorité supérieure à 200 pour éviter les conflits avec les templates intégrés (logs-*-*, metrics-*-*). Les composable templates priment sur l'ancienne API _template.","tags":["index template","component template","simulate","mapping","priority","dynamic strict"]},{"id":"reindex-alias-swap","task":"Modifier un mapping sans interruption (reindex + bascule atomique de l'alias)","category":"Cycle de vie des index","command":"# 1) Target index with the new mapping (refresh off and replicas 0 for loading):\nPUT musteri-v2\n{\n  \"settings\": { \"number_of_shards\": 1, \"number_of_replicas\": 0, \"refresh_interval\": \"-1\" },\n  \"mappings\": { \"properties\": { \"kod\": { \"type\": \"keyword\" }, \"adet\": { \"type\": \"long\" } } }\n}\n# 2) Reindex in the background (tracked with the returned task id):\nPOST _reindex?wait_for_completion=false&slices=auto\n{\n  \"source\": { \"index\": \"musteri-v1\", \"size\": 1000 },\n  \"dest\": { \"index\": \"musteri-v2\", \"op_type\": \"create\" },\n  \"conflicts\": \"proceed\"\n}\nGET _tasks/<task_id>\n# 3) Restore settings and compare document counts:\nPUT musteri-v2/_settings\n{\n  \"index\": { \"refresh_interval\": null, \"number_of_replicas\": 1 }\n}\nPOST musteri-v2/_refresh\nGET _cat/indices/musteri-v1,musteri-v2?v&h=index,docs.count,pri.store.size\n# 4) Switch the alias to the new index in one atomic operation:\nPOST _aliases\n{\n  \"actions\": [\n    { \"remove\": { \"index\": \"musteri-v1\", \"alias\": \"musteri\" } },\n    { \"add\": { \"index\": \"musteri-v2\", \"alias\": \"musteri\", \"is_write_index\": true } }\n  ]\n}","what":"Crée un index avec le nouveau mapping, copie les données en arrière-plan avec reindex, compare le nombre de documents, et bascule l'alias utilisé par l'application vers le nouvel index en une seule opération atomique.","when":"Quand le type d'un champ existant doit changer (text -> keyword, integer -> long), qu'un analyzer doit changer ou que le nombre de shards doit être redéfini ; ces opérations ne peuvent pas se faire sur place sur l'index existant.","caution":"L'application doit se connecter à l'alias, pas au nom de l'index ; sinon la bascule nécessite un déploiement. Les nouvelles écritures sur la source pendant le reindex n'atteignent pas la cible : arrêtez les écritures, ou relancez le reindex avec op_type create + conflicts proceed juste avant la bascule pour combler l'écart (les mises à jour et suppressions ne sont pas reportées de cette façon). Le reindex génère une charge de lecture sur la source et d'écriture sur la cible ; limitez-le avec requests_per_second.","since":"Elasticsearch 7.0","note":"Les documents chargés pendant que refresh_interval vaut -1 n'apparaissent pas dans les comptages avant un refresh ; exécutez _refresh avant de comparer. Si _cat/count reçoit plusieurs index séparés par des virgules, il renvoie leur total ; utilisez _cat/indices pour une comparaison par index. La vitesse d'un reindex en cours peut être modifiée avec POST _reindex/<task_id>/_rethrottle?requests_per_second=500. wait_for_completion=false écrit le résultat dans l'index .tasks ; vérifiez response.failures avec GET _tasks/<task_id>.","tags":["reindex","alias","mapping change","zero downtime","_aliases","no downtime"]},{"id":"tasks-cancel","task":"Trouver, annuler ou limiter les tâches de recherche et de reindex longues","category":"Maintenance","command":"# Longest running tasks:\nGET _cat/tasks?v&detailed&h=action,task_id,parent_task_id,running_time,node,description&s=running_time:desc\n# Search, reindex and by-query tasks, grouped by parent:\nGET _tasks?actions=*search*,*reindex*,*byquery*&detailed=true&group_by=parents\n# Cancel a single task:\nPOST _tasks/<task_id>/_cancel\n# Cancel all search tasks (with care):\nPOST _tasks/_cancel?actions=*search*\n# Slow down a running reindex/update_by_query:\nPOST _reindex/<task_id>/_rethrottle?requests_per_second=500","what":"Trie par durée les tâches en cours dans le cluster, affiche les recherches et opérations bulk avec la description de leur requête ; annule une tâche choisie ou toutes les recherches, et limite les jobs reindex/by-query.","when":"Quand le thread pool search se remplit, que le CPU monte soudainement, qu'un dashboard sollicite le cluster avec une agrégation lourde, ou qu'un update_by_query affecte la production.","caution":"L'annulation est coopérative : la tâche s'arrête à son prochain point de contrôle ; certaines phases (par ex. force merge) ne peuvent pas être annulées. Annuler toutes les recherches renvoie des erreurs aux utilisateurs. Un reindex/by-query annulé n'annule pas les modifications déjà effectuées ; la cible contient des données partielles. Quand une tâche parente est annulée, ses tâches enfants le sont aussi.","since":"Elasticsearch 7.0","note":"Le paramètre detailed écrit le corps de la requête des tâches de recherche dans le champ description. Pour une protection durable, utilisez search.default_search_timeout, un timeout dans la requête de recherche et search.max_buckets pour les grosses agrégations. En 7.x+, les recherches dont la connexion HTTP se ferme sont annulées automatiquement.","tags":["tasks","cancel","kill query","rethrottle","long query","search"]},{"id":"search-slowlog","task":"Activer le slow log de recherche et d'indexation (seuils par index)","category":"Performances","command":"PUT musteri/_settings\n{\n  \"index.search.slowlog.threshold.query.warn\": \"2s\",\n  \"index.search.slowlog.threshold.query.info\": \"500ms\",\n  \"index.search.slowlog.threshold.fetch.warn\": \"1s\",\n  \"index.indexing.slowlog.threshold.index.warn\": \"1s\",\n  \"index.indexing.slowlog.source\": \"200\"\n}\nGET musteri/_settings/index.*slowlog*?flat_settings=true\n# Set thresholds to null to turn it off:\nPUT musteri/_settings\n{\n  \"index.search.slowlog.threshold.query.warn\": null,\n  \"index.search.slowlog.threshold.query.info\": null,\n  \"index.search.slowlog.threshold.fetch.warn\": null,\n  \"index.indexing.slowlog.threshold.index.warn\": null,\n  \"index.indexing.slowlog.source\": null\n}","what":"Pour un index donné, écrit dans le slow log les requêtes dépassant le seuil dans les phases query et fetch ainsi qu'à l'indexation, avec le corps de la requête.","when":"Pour trouver quelle requête provoque des recherches lentes, ou pour attraper les requêtes lourdes d'un dashboard ou d'un client précis.","caution":"Le seuil est mesuré par shard : sur un index avec de nombreux shards, chaque shard peut écrire sa propre ligne. Un seuil très bas (0ms) augmente fortement le volume de logs et l'IO ; désactivez-le avec null après le diagnostic. indexing.slowlog.source écrit la source du document dans le log ; réglez-le sur false s'il contient des données sensibles. Le paramètre ne s'applique qu'à l'index indiqué ; ajoutez-le au template pour les nouveaux index.","since":"Elasticsearch 8.0","note":"Les fichiers de slow log sont écrits dans le répertoire logs sous les noms <cluster>_index_search_slowlog.json et <cluster>_index_indexing_slowlog.json ; si log4j2 est dirigé vers la console, ils vont sur stdout. Le paramètre index.search.slowlog.level a été supprimé en 8.0 ; le niveau est déterminé par les seuils (warn/info/debug/trace). En 8.14+, index.search.slowlog.include.user: true écrit aussi l'utilisateur à l'origine de la requête dans le log. Pour examiner une requête en détail, ajoutez \"profile\": true à la requête de recherche.","tags":["slowlog","slow log","slow search","search","indexing","threshold"]},{"id":"rejections-heap-breaker","task":"Diagnostiquer les rejets 429, la pression sur le heap et les circuit breakers","category":"Performances","command":"# Thread pool queue and rejection counts (source of 429):\nGET _cat/thread_pool/write,search,get?v&h=node_name,name,active,queue,rejected,completed,size,queue_size&s=rejected:desc\nGET _cat/nodes?v&h=name,node.role,heap.percent,ram.percent,cpu,load_1m,disk.used_percent,master&s=heap.percent:desc\n# Circuit breaker trips and parent breaker usage:\nGET _nodes/stats/breaker?filter_path=nodes.*.name,nodes.*.breakers.*.tripped,nodes.*.breakers.parent\nGET _nodes/stats/jvm?filter_path=nodes.*.name,nodes.*.jvm.mem.heap_used_percent,nodes.*.jvm.gc.collectors.old\n# Fielddata memory usage (per field):\nGET _nodes/stats/indices/fielddata?fields=*&filter_path=nodes.*.indices.fielddata","what":"Affiche les requêtes rejetées parce que les files des thread pools write/search étaient pleines, l'état heap/CPU par nœud, les déclenchements de circuit breakers, le nombre d'old GC et l'utilisation mémoire du fielddata.","when":"Quand les clients reçoivent 429 Too Many Requests ou es_rejected_execution_exception, qu'une CircuitBreakingException ([parent] Data too large) apparaît, ou que les nœuds subissent de longues pauses GC.","caution":"Les compteurs rejected sont cumulés depuis le démarrage du nœud ; regardez l'augmentation entre deux échantillons. Relever queue_size masque les rejets mais augmente le heap et la latence ; la vraie correction consiste à réduire la taille/concurrence des bulk ou à ajouter de la capacité. Si le heap reste au-dessus de 85 % et que l'old GC s'exécute souvent, le nœud approche de l'OOM. Activer fielddata sur un champ text consomme rapidement le heap.","since":"Elasticsearch 7.0","note":"Le parent breaker vaut par défaut 95 % du heap (real memory circuit breaker, 7.0+). Les rejets d'écriture se concentrent généralement sur un seul nœud chaud : vérifiez la répartition des shards et le routage (_routing). Pour libérer temporairement le fielddata, utilisez POST _cache/clear?fielddata=true (voir cache-clear).","tags":["429","rejected","thread pool","circuit breaker","heap","fielddata"]},{"id":"slm-snapshot-monitor","task":"Automatiser les snapshots et la rétention avec SLM, suivre la progression des snapshots","category":"Snapshot","command":"# Take a snapshot every night at 01:30, keep for 30 days (at least 7, at most 60 snapshots):\nPUT _slm/policy/gece-yedek\n{\n  \"schedule\": \"0 30 1 * * ?\",\n  \"name\": \"<gece-{now/d}>\",\n  \"repository\": \"yedek_repo\",\n  \"config\": { \"indices\": [\"*\"], \"include_global_state\": true },\n  \"retention\": { \"expire_after\": \"30d\", \"min_count\": 7, \"max_count\": 60 }\n}\n# Run the policy now and see success/failure statistics:\nPOST _slm/policy/gece-yedek/_execute\nGET _slm/stats\n# Snapshot in progress and per-shard progress:\nGET _snapshot/yedek_repo/_current\nGET _snapshot/yedek_repo/_status\nGET _cat/snapshots/yedek_repo?v&s=end_epoch:desc\n# Verify that the repository is writable from all nodes:\nPOST _snapshot/yedek_repo/_verify","what":"Définit une politique SLM qui prend des snapshots selon une planification cron et supprime les anciens selon une règle de rétention ; affiche la progression par shard d'un snapshot en cours, les snapshots passés et l'accessibilité du dépôt.","when":"Lors de la mise en place de sauvegardes régulières, sur une alerte d'échec du snapshot nocturne, ou pour estimer quand un snapshot long se terminera.","caution":"La planification est évaluée en UTC. Si plusieurs clusters écrivent dans le même dépôt, ou si le dépôt est enregistré en écriture sur un autre cluster, il peut être corrompu ; utilisez readonly: true sur les autres clusters. La rétention ne supprime que les snapshots pris par SLM. include_global_state true peut aussi restaurer les templates et les paramètres persistent du cluster lors d'une restauration.","since":"Elasticsearch 7.5","note":"Les snapshots sont incrémentaux : seuls les segments absents du snapshot précédent sont copiés, donc des snapshots fréquents n'occupent pas un espace proportionnel dans le dépôt. SLM est apparu en 7.4, la rétention en 7.5. Sans nom de dépôt, _status renvoie tous les snapshots en cours dans le cluster ; avec le nom d'un snapshot passé, il lit depuis le dépôt et peut être lent.","tags":["slm","snapshot","retention","backup","schedule","snapshot status"],"untested":true},{"id":"security-user-role","task":"Créer un rôle et un utilisateur natif, changer le mot de passe, émettre une API key","category":"Sécurité","command":"POST _security/role/log_okuma\n{\n  \"cluster\": [\"monitor\"],\n  \"indices\": [\n    { \"names\": [\"log-*\"], \"privileges\": [\"read\", \"view_index_metadata\"] }\n  ]\n}\nPOST _security/user/rapor_kullanici\n{\n  \"password\": \"Degistir-123!\",\n  \"roles\": [\"log_okuma\"],\n  \"full_name\": \"Rapor Servisi\"\n}\nGET _security/user/rapor_kullanici\n# User making this request and its roles:\nGET _security/_authenticate\nPOST _security/user/rapor_kullanici/_password\n{\n  \"password\": \"Yeni-Sifre-456!\"\n}\n# Time-limited API key with restricted privileges:\nPOST _security/api_key\n{\n  \"name\": \"rapor-key\",\n  \"expiration\": \"30d\",\n  \"role_descriptors\": { \"log_okuma\": { \"indices\": [ { \"names\": [\"log-*\"], \"privileges\": [\"read\"] } ] } }\n}\n# If the elastic user's password is lost (shell on a node):\n# bin/elasticsearch-reset-password -u elastic -i","what":"Crée un rôle avec accès en lecture sur un motif d'index et un utilisateur du native realm détenant ce rôle, change le mot de passe, vérifie l'identité de l'appelant et émet une API key à durée limitée aux privilèges restreints.","when":"Lors de la création de comptes de reporting, d'application ou de supervision selon le moindre privilège, pendant la rotation des mots de passe, et lors du passage des services des utilisateurs aux API keys.","caution":"N'utilisez pas le superutilisateur elastic dans les applications. Les privilèges d'une API key ne peuvent pas dépasser ceux de l'utilisateur qui l'a créée ; role_descriptors ne fait que les restreindre. Une API key est limitée par un instantané des privilèges de son propriétaire au moment de sa création ; les changements de rôles ultérieurs n'affectent pas les clés existantes. Après un changement de mot de passe, les services utilisant l'ancien mot de passe reçoivent 401 ; planifiez la bascule. La valeur encoded de la réponse de l'API key n'est renvoyée qu'une seule fois ; conservez-la.","since":"Elasticsearch 8.0","note":"La sécurité est activée par défaut en 8.0+ et le mot de passe elastic est généré au premier démarrage. L'outil elasticsearch-reset-password est apparu en 8.0. Listez les API keys avec GET _security/api_key?name=rapor-key et révoquez-les avec DELETE _security/api_key. Le role mapping pour les utilisateurs des realms LDAP/AD/SAML se fait avec _security/role_mapping.","tags":["security","users","roles","api key","password","rbac"],"untested":true}]},"solver/slow-query.json":{"id":"slow-query","title":"Requête lente : diagnostic approfondi","icon":"🐢","category":"Performances","intro":"Une requête ou tout le système est lent. Affinez étape par étape : exécutez un contrôle à chaque nœud et choisissez la branche selon le résultat. Vous aboutissez à une cause racine + correction.","start":"scope","nodes":{"scope":{"kind":"step","title":"Qu'est-ce qui est lent ?","detail":"Une ou quelques requêtes précises sont-elles lentes, ou tout le système ? Cela oriente l'analyse vers l'une de deux pistes : « centrée sur la requête » ou « centrée sur les ressources/le goulot ».","choices":[{"label":"Une ou quelques requêtes précises sont lentes","next":"running"},{"label":"Tout le système est lent","next":"system"}]},"running":{"kind":"step","title":"La requête s'exécute-t-elle en ce moment ?","detail":"S'agit-il d'une requête en cours/bloquée en ce moment, ou d'une requête « généralement lente » ? Regardez les sessions actives.","command":{"oracle":"SELECT sid, serial#, sql_id, last_call_et AS running_sec, event, blocking_session\nFROM v$session WHERE status='ACTIVE' AND type='USER' ORDER BY last_call_et DESC;","postgres":"SELECT pid, now()-query_start AS dur, state, wait_event, query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start;","mysql":"SELECT id, time, state, info FROM information_schema.processlist WHERE command='Query' ORDER BY time DESC;","mssql":"SELECT session_id, total_elapsed_time/1000 AS sec, status, wait_type, blocking_session_id\nFROM sys.dm_exec_requests WHERE session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE is_user_process = 1) ORDER BY total_elapsed_time DESC;"},"choices":[{"label":"En cours / bloquée en ce moment","next":"blocked"},{"label":"Généralement lente ; je veux examiner son plan","next":"plan"}]},"blocked":{"kind":"step","title":"La requête attend-elle quelque chose ?","detail":"Si elle est active mais ne progresse pas, elle attend très probablement. blocking_session / wait_event est-il renseigné ?","command":{"oracle":"SELECT sid, blocking_session, event, seconds_in_wait\nFROM v$session WHERE sid=&target_sid;","postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event_type, wait_event\nFROM pg_stat_activity WHERE pid=&target_pid;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_resource\nFROM sys.dm_exec_requests WHERE session_id=&target;"},"choices":[{"label":"Bloquée par une autre session","next":"sol_blocking"},{"label":"En attente d'une ressource (IO / journal / latch)","next":"wait"},{"label":"Aucune attente ; consomme activement du CPU","next":"plan"}]},"wait":{"kind":"step","title":"Quelle attente domine ?","detail":"L'événement d'attente dominant indique le type de goulot.","command":{"oracle":"SELECT event, total_waits, time_waited FROM v$session_event WHERE sid=&target_sid ORDER BY time_waited DESC FETCH FIRST 5 ROWS ONLY;","postgres":"SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE state='active' GROUP BY 1,2 ORDER BY 3 DESC;","mssql":"SELECT TOP 5 wait_type, waiting_tasks_count, wait_time_ms FROM sys.dm_os_wait_stats ORDER BY wait_time_ms DESC;"},"choices":[{"label":"Attente IO (db file read / PAGEIOLATCH)","next":"sol_io"},{"label":"Attente journal / commit (log file sync / WRITELOG)","next":"sol_log"},{"label":"Verrou / enqueue (enq: TX / LCK)","next":"sol_blocking"}]},"plan":{"kind":"step","title":"Obtenir le plan d'exécution (EXPLAIN)","detail":"Récupérez le plan. Y a-t-il un full table scan, un ordre de jointure inattendu, ou un écart entre lignes estimées et réelles ?","command":{"oracle":"SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));","postgres":"EXPLAIN (ANALYZE, BUFFERS) <query>;","mysql":"EXPLAIN ANALYZE <query>;","mssql":"SET STATISTICS PROFILE ON; <query>; SET STATISTICS PROFILE OFF;","mongodb":"db.collection.find({...}).explain('executionStats')  // COLLSCAN or IXSCAN?"},"choices":[{"label":"Full scan / index manquant","next":"sol_index"},{"label":"C'était rapide hier ; le plan a changé soudainement","next":"sol_planchange"},{"label":"Traite trop de lignes / N+1","next":"sol_rewrite"},{"label":"Le plan semble bon mais c'est lent","next":"stats"}]},"stats":{"kind":"step","title":"Les statistiques sont-elles à jour ?","detail":"Un grand écart entre lignes estimées et réelles (E-rows vs A-rows) est le signe de statistiques obsolètes.","command":{"oracle":"SELECT table_name, num_rows, last_analyzed FROM dba_tables WHERE table_name='&TABLE_NAME';","postgres":"SELECT relname, last_analyze, last_autoanalyze, n_live_tup FROM pg_stat_user_tables WHERE relname='&table_name';","mysql":"SELECT table_name, table_rows, update_time FROM information_schema.tables WHERE table_name='&table_name';","mssql":"SELECT name, STATS_DATE(object_id, stats_id) AS last_updated FROM sys.stats WHERE object_id=OBJECT_ID('&table_name');"},"choices":[{"label":"Les statistiques sont obsolètes / anciennes","next":"sol_stats"},{"label":"Les statistiques sont à jour mais c'est toujours lent","next":"sol_deep"}]},"system":{"kind":"step","title":"À l'échelle du système : où est le goulot ?","detail":"Si tout est lent plutôt qu'une seule requête, cherchez un goulot de ressources : CPU, IO, mémoire, ou blocages généralisés ?","command":{"oracle":"SELECT metric_name, value FROM v$sysmetric WHERE metric_name IN ('Host CPU Utilization (%)','Average Active Sessions') AND intsize_csec=(SELECT MAX(intsize_csec) FROM v$sysmetric);","postgres":"SELECT state, wait_event_type, count(*) FROM pg_stat_activity GROUP BY 1,2 ORDER BY 3 DESC;","mssql":"SELECT TOP 5 wait_type, wait_time_ms FROM sys.dm_os_wait_stats ORDER BY wait_time_ms DESC;"},"choices":[{"label":"CPU proche de 100 %","next":"sol_cpu"},{"label":"Attente IO élevée","next":"sol_io"},{"label":"Pression mémoire / faible taux de succès du cache","next":"sol_mem"},{"label":"Nombreux blocages","next":"sol_blocking"}]},"sol_blocking":{"kind":"solution","title":"Cause racine : blocage","detail":"La requête attend un verrou détenu par une autre session. Trouvez la session en tête de chaîne (root blocker) et résolvez-la.","steps":["Trouvez la tête de la chaîne de blocage (la session qui n'attend pas elle-même mais fait attendre les autres).","Comprenez ce que fait le root blocker : une longue transaction non validée, ou 'idle in transaction' ?","Si l'application peut faire commit/rollback, déclenchez-le ; sinon, et si le travail est bloqué, terminez le root blocker.","Corrigez la cause racine : un index manquant peut provoquer un verrouillage étendu ; raccourcissez les transactions et revoyez le niveau d'isolation."],"related":["blocking-sessions","kill-session"],"playbook":"blocking"},"sol_io":{"kind":"solution","title":"Cause racine : goulot IO","detail":"La requête lit beaucoup sur disque. Généralement à cause de full scans (index manquant) ou d'une mémoire insuffisante (buffer cache).","steps":["Ajoutez un index adapté au filtre qui provoque le full scan (le plan doit passer à un index scan, IXSCAN).","Vérifiez le hit ratio du buffer cache / buffer pool ; augmentez la mémoire s'il est bas.","Réduisez les lectures inutiles de colonnes/lignes (colonnes nécessaires au lieu de SELECT *, pagination).","Si le stockage est réellement lent (latence IO élevée), améliorez la couche disque."],"related":["cache-hit-ratio","create-index"]},"sol_log":{"kind":"solution","title":"Cause racine : goulot commit / journal","detail":"Si 'log file sync' / 'WRITELOG' domine, les commits attendent les écritures redo/WAL.","steps":["Transformez les commits fréquents ligne par ligne en commits par lots ; réduisez les commits inutiles.","Accélérez le disque redo/WAL (disque séparé et rapide) ; revoyez la taille/le nombre des fichiers journaux.","Évaluez les paramètres synchronous_commit / commit_write selon la charge et le compromis de durabilité."],"related":["show-config-param"]},"sol_index":{"kind":"solution","title":"Cause racine : index manquant","detail":"Le plan effectue un full scan. Un index adapté sur les colonnes de filtre/jointure apporte un gain important.","steps":["Ajoutez en ligne un index adapté (composite si nécessaire) sur les colonnes WHERE/JOIN.","Après l'ajout de l'index, récupérez à nouveau le plan et vérifiez qu'il est passé à un index scan (IXSCAN).","Si la charge d'écriture est forte, évaluez le coût de l'index ; supprimez les index inutiles.","Un index n'aide pas sur les colonnes à faible sélectivité ; vérifiez la sélectivité."],"related":["create-index","explain-plan"]},"sol_planchange":{"kind":"solution","title":"Cause racine : régression de plan","detail":"La requête était rapide hier mais pas aujourd'hui ; le plan a changé. Généralement des statistiques obsolètes ou un changement de paramètre.","steps":["Collectez d'abord des statistiques fraîches (les statistiques obsolètes sont la cause la plus fréquente).","Figez le bon plan : Oracle SQL Plan Baseline / hint, MSSQL Query Store 'force plan', paramètres PG.","S'il y a eu une mise à niveau/un correctif/un changement de paramètre récent, examinez-le (comportement de l'optimiseur)."],"related":["update-statistics"]},"sol_rewrite":{"kind":"solution","title":"Cause racine : conception de la requête","detail":"Le plan est raisonnable mais la requête effectue intrinsèquement trop de travail (trop de lignes, N+1, jointures inutiles).","steps":["Réduisez le jeu de résultats : pagination (LIMIT/OFFSET ou keyset), filtrage précoce.","Transformez le schéma N+1 en une seule requête par lot ; simplifiez les jointures/sous-requêtes inutiles.","Envisagez une vue matérialisée / table de synthèse pour les rapports lourds récurrents."],"related":["explain-plan","materialized-view"]},"sol_stats":{"kind":"solution","title":"Cause racine : statistiques obsolètes","detail":"L'optimiseur fait de mauvaises estimations car les statistiques ne sont pas à jour.","steps":["Collectez manuellement les statistiques de la ou des tables concernées.","Vérifiez que le job de collecte automatique (autovacuum/auto stats/gather job) s'exécute.","Ajoutez la collecte des statistiques au workflow après les gros chargements de données soudains."],"related":["update-statistics"]},"sol_deep":{"kind":"solution","title":"Analyse approfondie","detail":"Si le plan et les statistiques sont corrects mais que c'est toujours lent, creusez davantage.","steps":["Activez une trace au niveau session : Oracle 10046, PG auto_explain, MySQL profiling, MSSQL Extended Events.","Examinez le parallélisme, les memory grants et les débordements sort/hash (tempdb/temp).","Écartez la couche matériel/OS (CPU steal, latence IO, réseau).","Évaluez la contention due au nombre de connexions et à la concurrence."],"related":["session-trace","top-wait-events"]},"sol_cpu":{"kind":"solution","title":"Cause racine : goulot CPU","detail":"Tout le système est limité par le CPU. Généralement, quelques requêtes mal planifiées consomment le CPU.","steps":["Trouvez les 10 requêtes les plus consommatrices de CPU et corrigez leurs plans (index/statistiques).","Limitez les requêtes incontrôlées (Resource Manager / Resource Governor).","Si l'utilisation reste élevée, faites évoluer la capacité (ajout de CPU, répartition des lectures sur des réplicas de lecture)."],"related":["top-slow-queries"],"playbook":"high-cpu"},"sol_mem":{"kind":"solution","title":"Cause racine : pression mémoire","detail":"Faible taux de succès du cache et/ou swap : la mémoire est insuffisante ou mal répartie.","steps":["Dimensionnez correctement le buffer pool / la SGA et la mémoire de session (work_mem/PGA) (Outils > Calculateur de mémoire).","Trouvez et corrigez la requête incontrôlée qui consomme la mémoire.","Si le système swappe, sortir du swap est la priorité ; ajoutez de la RAM si nécessaire."],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"}}},"solver/deadlock.json":{"id":"deadlock","title":"Interblocage / conflit de verrous (ORA-00060, 1213, 40P01)","icon":"♻️","category":"Verrous","intro":"Vous avez rencontré un interblocage (Oracle ORA-00060, MySQL 1213, PG 40P01, MSSQL 1205). Capturez le graphe, identifiez le schéma, empêchez la récidive. Les interblocages se résolvent par la conception — l'objectif est de réduire leur fréquence.","start":"capture","nodes":{"capture":{"kind":"step","title":"Capturez d'abord le deadlock graph","detail":"Quelles sessions (deux ou plus), quelles ressources, quels modes de verrou sont entrés en collision ? N'appliquez pas de corrections à l'aveugle sans avoir vu cela.","command":{"oracle":"-- Check the trace file referenced in the alert log (deadlock graph). For history:\nSELECT * FROM dba_hist_active_sess_history WHERE event LIKE 'enq: TX%' ORDER BY sample_time DESC FETCH FIRST 20 ROWS ONLY;","mysql":"SHOW ENGINE INNODB STATUS\\G  -- 'LATEST DETECTED DEADLOCK' section","postgres":"-- Deadlock errors are always written to postgresql.log as ERROR (log_lock_waits not required); log_lock_waits=on also logs lock waits longer than deadlock_timeout.","mssql":"SELECT XEvent.query('.') FROM (SELECT CAST(target_data AS XML) td FROM sys.dm_xe_session_targets t JOIN sys.dm_xe_sessions s ON s.address=t.event_session_address WHERE s.name='system_health') x CROSS APPLY td.nodes('//event[@name=\"xml_deadlock_report\"]') AS q(XEvent);"},"choices":[{"label":"J'ai le graphe — deux transactions verrouillent des ressources dans l'ordre inverse","next":"sol_order"},{"label":"De nombreuses transactions s'accumulent sur une seule ligne/table (hotspot)","next":"sol_hotspot"},{"label":"Il y a des verrous de plage étendus (de nombreuses lignes sont verrouillées)","next":"index_check"}]},"index_check":{"kind":"step","title":"La requête qui verrouille utilise-t-elle un index ?","detail":"Sans index, un UPDATE/DELETE verrouille de nombreuses lignes (voire toute la table) pour satisfaire la clause WHERE → source d'interblocages. Vérifiez son plan.","command":{"mysql":"EXPLAIN <the UPDATE/DELETE involved in the deadlock>;  -- type=ALL means a full scan, no index","postgres":"EXPLAIN <query>;  -- Seq Scan or Index Scan?","oracle":"SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id'));"},"choices":[{"label":"Pas d'index / elle fait un full scan","next":"sol_index"},{"label":"Il y a un index, mais le conflit persiste","next":"sol_order"}]},"sol_order":{"kind":"solution","title":"Cause racine : ordre de verrouillage inversé","detail":"La cause d'interblocage la plus fréquente : deux transactions accèdent aux ressources dans des ordres différents. Quand A→B et B→A se produisent en même temps, elles s'interbloquent.","steps":["Accédez aux ressources dans le MÊME ordre sur tous les chemins de code (par ex. toujours de l'id le plus bas → au plus haut).","Gardez les transactions courtes ; sortez de la transaction l'interaction utilisateur/le travail long.","Si nécessaire, prenez les verrous tôt dans un ordre déterministe (SELECT ... FOR UPDATE ordonné).","Ajoutez dans l'application un rejeu automatique (retry + backoff) pour la victime de l'interblocage — c'est obligatoire."],"related":["blocking-sessions","open-transactions"]},"sol_index":{"kind":"solution","title":"Cause racine : index manquant → verrous étendus","detail":"Un UPDATE/DELETE sans index verrouille plus de lignes que nécessaire et rend les interblocages bien plus probables.","steps":["Ajoutez un index sur la colonne de la clause WHERE ; le verrou est alors limité aux lignes concernées.","Récupérez à nouveau le plan et vérifiez que le full scan (Seq Scan / type=ALL) s'est transformé en accès par index.","Exécutez les mises à jour massives par petits lots (chunks)."],"related":["create-index","list-indexes"]},"sol_hotspot":{"kind":"solution","title":"Cause racine : hotspot","detail":"De nombreuses transactions s'accumulent en même temps sur une seule ressource comme une ligne/clé de compteur.","steps":["Répartissez la ligne chaude : utilisez une sequence/identity, découpez les compteurs (sharded counter).","Raccourcissez la durée des transactions pour que le verrou soit détenu moins longtemps.","Si possible, utilisez le verrouillage optimiste (version/timestamp) ou un schéma de file d'attente."],"related":["sequence","open-transactions"]}}},"solver/disk-full.json":{"id":"disk-full","title":"Disque / tablespace plein","icon":"💽","category":"Capacité","intro":"Vous avez obtenu 'unable to extend', 'disk full', 'could not extend' ou 'log full'. Pas de panique : trouvez d'abord CE QUI est plein, récupérez de l'espace en sécurité, puis corrigez durablement.","start":"what","nodes":{"what":{"kind":"step","title":"Qu'est-ce qui est plein ?","detail":"Nettoyer au mauvais endroit ne sert à rien. Mesurez d'abord l'utilisation : s'agit-il d'un datafile, du journal des transactions, d'archive/redo, de temp ou du disque de l'OS ?","command":{"oracle":"SELECT d.tablespace_name, ROUND((1-(d.alloc-NVL(f.free,0))/d.maxb)*100,1) AS pct_free\nFROM (SELECT tablespace_name, SUM(bytes) alloc, SUM(GREATEST(bytes,maxbytes)) maxb FROM dba_data_files GROUP BY tablespace_name) d LEFT JOIN (SELECT tablespace_name, SUM(bytes) free FROM dba_free_space GROUP BY tablespace_name) f ON f.tablespace_name = d.tablespace_name ORDER BY pct_free;","postgres":"SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY 2 DESC;  -- OS disk: df -h","mssql":"SELECT name, size/128.0 AS size_mb, size/128.0 - CAST(FILEPROPERTY(name,'SpaceUsed') AS int)/128.0 AS free_mb, type_desc\nFROM sys.database_files;","mysql":"SELECT table_schema, ROUND(SUM(data_length+index_length)/1048576,1) AS mb FROM information_schema.tables GROUP BY table_schema ORDER BY mb DESC;"},"choices":[{"label":"Datafile / tablespace plein","next":"data"},{"label":"Journal des transactions / archive-redo plein","next":"log"},{"label":"Temp / undo plein","next":"sol_temp"},{"label":"Disque de l'OS plein (pas la base)","next":"sol_os"}]},"data":{"kind":"step","title":"Quel est le plus gros consommateur ?","detail":"La plupart du temps, une seule table/un seul segment incontrôlé est le coupable. Trouvez les plus gros.","command":{"oracle":"SELECT segment_name, segment_type, ROUND(bytes/1048576,1) AS mb FROM dba_segments ORDER BY bytes DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;","mssql":"SELECT TOP 10 OBJECT_NAME(object_id) AS table_name, SUM(reserved_page_count)*8/1024 AS mb FROM sys.dm_db_partition_stats GROUP BY object_id ORDER BY mb DESC;"},"choices":[{"label":"Il y a une table anormalement grosse/incontrôlée","next":"sol_runaway"},{"label":"Croissance normale, l'espace est réellement épuisé","next":"sol_extend"},{"label":"Trop de lignes mortes / bloat","next":"sol_reclaim"}]},"log":{"kind":"step","title":"Pourquoi le journal grossit-il ?","detail":"Le journal/l'archive ne peut pas être nettoyé tant que la plus ancienne transaction/sauvegarde active n'est pas terminée. Pourquoi s'est-il accumulé ?","command":{"mssql":"SELECT name, log_reuse_wait_desc FROM sys.databases WHERE name=DB_NAME();  -- why can it not be truncated?","postgres":"SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained FROM pg_replication_slots ORDER BY 3 DESC;","oracle":"SELECT * FROM v$flash_recovery_area_usage;  -- FRA (archive) usage"},"choices":[{"label":"MSSQL : les sauvegardes du journal ne sont pas effectuées (LOG_BACKUP)","next":"sol_logbackup"},{"label":"PG : un replication slot bloqué retient le WAL","next":"sol_slot"},{"label":"Oracle : la zone des journaux archivés (FRA) est pleine","next":"sol_archive"},{"label":"Une longue transaction ouverte retient le journal","next":"sol_longtxn"}]},"sol_runaway":{"kind":"solution","title":"Cause racine : table/segment incontrôlé","detail":"Un seul objet a grossi de façon inattendue (insertions incontrôlées, table de file/log jamais purgée).","steps":["Vérifiez si l'objet est réellement nécessaire ; s'il s'agit d'une table de log/file, appliquez une rétention.","Purgez les anciennes données inutiles avec un drop de partition / TRUNCATE (après sauvegarde/approbation).","Stoppez la croissance incontrôlée dans l'application ; mettez en place une alerte de croissance."],"related":["largest-tables","create-partition"]},"sol_extend":{"kind":"solution","title":"Cause racine : réel besoin de capacité","detail":"Croissance normale ; il faut ajouter de l'espace.","steps":["Ajoutez de l'espace au datafile ou activez AUTOEXTEND (Oracle : ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE).","Si nécessaire, ajoutez un nouveau datafile/tablespace ou agrandissez le disque.","Mettez en place des alertes d'utilisation à 80 %/90 % pour éviter de nouvelles surprises."],"related":["free-space"]},"sol_reclaim":{"kind":"solution","title":"Cause racine : bloat / lignes mortes","detail":"L'espace des lignes supprimées/mises à jour n'a pas été récupéré.","steps":["Récupérez l'espace : PG VACUUM (pg_repack si nécessaire), Oracle SHRINK SPACE, MySQL OPTIMIZE TABLE, Mongo compact.","Rendez les paramètres d'autovacuum/de maintenance automatique plus agressifs.","VACUUM FULL / OPTIMIZE verrouillent la table — exécutez-les en dehors des heures de pointe."],"related":["reclaim-space","free-space"]},"sol_temp":{"kind":"solution","title":"Cause racine : temp / undo plein","detail":"Un gros sort/hash ou une longue transaction ouverte a rempli l'espace temporaire.","steps":["Trouvez la requête qui a fait exploser le temp (gros débordement sort/hash) et optimisez-la.","Trouvez la longue transaction ouverte ; c'est elle qui retient l'undo (Oracle).","Si nécessaire, agrandissez l'espace temp/undo ; revoyez le paramètre de rétention."],"related":["open-transactions","reclaim-space"]},"sol_os":{"kind":"solution","title":"Cause racine : disque de l'OS plein","detail":"Le problème se situe sur le disque du système d'exploitation, pas dans la base. Des logs, core dumps, anciennes sauvegardes ou fichiers de trace peuvent occuper l'espace.","steps":["Trouvez le système de fichiers plein avec df -h ; recherchez les plus gros répertoires (du -sh *).","Nettoyez les anciens fichiers de log/trace/core dump/sauvegarde (avec approbation).","Placez les répertoires de diagnostic de la base (Oracle ADR, logs PG) sous rotation."]},"sol_logbackup":{"kind":"solution","title":"Cause racine : sauvegardes du journal non effectuées (SQL Server)","detail":"En mode de récupération FULL, le journal grossit indéfiniment si aucune sauvegarde du journal n'est effectuée.","steps":["Effectuez une sauvegarde du journal (BACKUP LOG) — cela rend le journal réutilisable.","Si le mode de récupération doit réellement être SIMPLE, changez-le.","Réduisez le fichier journal uniquement si nécessaire (DBCC SHRINKFILE) et mettez en place un job régulier de sauvegarde du journal."],"related":["last-backup-status"],"playbook":"log-growth"},"sol_slot":{"kind":"solution","title":"Cause racine : replication slot abandonné (PG)","detail":"Un slot bloqué (inactif) empêche la suppression du WAL et fait gonfler pg_wal.","steps":["Vérifiez si le slot est réellement utilisé.","S'il est inutile, supprimez-le : SELECT pg_drop_replication_slot('slot_name');","Réparez le réplica actif pour que le slot avance ; mettez en place une alerte sur le retard du slot."],"related":["free-space"],"playbook":"log-growth"},"sol_archive":{"kind":"solution","title":"Cause racine : zone des journaux archivés (FRA) pleine (Oracle)","detail":"Les journaux archivés se sont accumulés et la FRA est pleine ; dès que l'archiver se bloque, la base peut cesser d'accepter les écritures.","steps":["Supprimez avec RMAN les anciens journaux archivés déjà sauvegardés (ne les supprimez JAMAIS sans sauvegarde).","Augmentez la taille de la FRA (db_recovery_file_dest_size) si nécessaire.","Mettez en place l'exécution régulière du job de nettoyage/sauvegarde des journaux archivés."],"related":["last-backup-status"]},"sol_longtxn":{"kind":"solution","title":"Cause racine : longue transaction ouverte","detail":"La plus ancienne transaction active empêche la récupération du journal/de l'undo.","steps":["Trouvez la plus ancienne transaction ouverte et faites-la valider/annuler (ou contactez son propriétaire).","Corrigez le comportement applicatif qui laisse des sessions 'idle in transaction' ; définissez un timeout.","Déplacez les longs rapports vers un réplica/une fenêtre séparé(e)."],"related":["open-transactions"],"playbook":"log-growth"}}},"solver/connection-storm.json":{"id":"connection-storm","title":"Tempête de connexions (trop de connexions)","icon":"🌊","category":"Capacité","intro":"Vous avez obtenu 'too many connections', 'remaining connection slots reserved' ou ORA-00018 ; les nouvelles connexions sont rejetées. Trouvez la source, soulagez la pression tout de suite, corrigez durablement.","start":"measure","nodes":{"measure":{"kind":"step","title":"Mesurez la situation : combien de connexions, et combien sont actives ?","detail":"Regardez d'abord le total des connexions, la limite et la répartition actives/inactives.","command":{"oracle":"SELECT status, COUNT(*) FROM v$session WHERE type='USER' GROUP BY status;\nSELECT name, value FROM v$parameter WHERE name IN ('processes','sessions');","postgres":"SELECT state, count(*) FROM pg_stat_activity GROUP BY state;\nSHOW max_connections;","mysql":"SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';","mssql":"SELECT status, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY status;","redis":"INFO clients"},"choices":[{"label":"La plupart des connexions sont idle / idle in transaction","next":"sol_idle"},{"label":"La plupart sont actives ; je dois trouver la source","next":"who"}]},"who":{"kind":"step","title":"Qui/d'où ? Regroupez les connexions par source","detail":"Généralement, le pool d'un seul serveur d'application a explosé ou il y a une fuite de connexions. Regardez la répartition par hôte/utilisateur/application.","command":{"postgres":"SELECT client_addr, usename, application_name, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC;","mysql":"SELECT host, user, count(*) FROM information_schema.processlist GROUP BY host, user ORDER BY 3 DESC;","mssql":"SELECT host_name, program_name, login_name, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY host_name, program_name, login_name ORDER BY 4 DESC;","oracle":"SELECT machine, program, COUNT(*) FROM v$session WHERE type='USER' GROUP BY machine, program ORDER BY 3 DESC;"},"choices":[{"label":"Une seule application/un seul hôte a fait exploser les connexions (pool)","next":"sol_pool"},{"label":"La charge a réellement augmenté ; toutes sont légitimes et actives","next":"sol_scale"}]},"sol_idle":{"kind":"solution","title":"Cause racine : accumulation de connexions inactives / fuite","detail":"Des connexions sont ouvertes et jamais fermées, ou laissées 'idle in transaction' ; elles occupent des slots pour rien.","steps":["Nettoyez les connexions longtemps idle / idle-in-transaction ; utilisez le slot réservé aux administrateurs.","Définissez des timeouts côté serveur : PG idle_in_transaction_session_timeout, MySQL wait_timeout, Oracle profile idle_time.","Assurez-vous que l'application ferme les connexions (et les transactions) ; corrigez la fuite à la source."],"related":["open-transactions","kill-session","connection-count"],"playbook":"connection-storm"},"sol_pool":{"kind":"solution","title":"Cause racine : le pool de l'application a explosé","detail":"Le connection pool d'un serveur d'application est illimité/trop grand ou fuit. C'est lui qui produit les connexions.","steps":["Immédiatement : tuez les connexions inactives de l'application en cause et redémarrez son pool.","Définissez une taille de pool raisonnable côté application (Outils > calculateur de connection pool).","Placez un pooler entre le serveur et la base (PgBouncer / ProxySQL / Oracle DRCP / Connection Manager (CMAN)).","N'augmentez pas aveuglément max_connections ; cela consomme de la mémoire et ne fait que repousser le problème."],"related":["connection-count","kill-session"],"playbook":"connection-storm"},"sol_scale":{"kind":"solution","title":"Cause racine : réelle augmentation de charge","detail":"Les connexions sont légitimes et actives ; la capacité est réellement insuffisante.","steps":["Réduisez le nombre de connexions atteignant la base en les multiplexant via un connection pooler.","Répartissez la charge de lecture sur des réplicas de lecture.","Si nécessaire, augmentez max_connections de façon contrôlée selon le budget mémoire ; faites aussi évoluer la mémoire du serveur."],"related":["connection-count"],"playbook":"connection-storm"}}},"solver/replication-lag.json":{"id":"replication-lag","title":"Retard de réplication / réplication arrêtée","icon":"🔁","category":"Réplication","intro":"Le standby/réplica prend du retard sur le primaire, ou la réplication s'est entièrement arrêtée. Cet arbre affine étape par étape le type de retard (croissant, constant ou arrêté) pour atteindre la cause racine, comme une erreur d'application, un goulot mono-thread, un conflit de recovery ou une corruption.","start":"root","nodes":{"root":{"kind":"step","title":"Comment le retard se comporte-t-il ?","detail":"Mesurez d'abord la situation. Regardez le flux du réplica et la métrique de retard : le retard augmente-t-il avec le temps, est-il bloqué à un niveau constant, ou l'application s'est-elle complètement arrêtée ? Cela répartit l'analyse en trois branches principales.","command":{"postgres":"SELECT client_addr, state, write_lag, flush_lag, replay_lag,\n       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS bytes_behind\nFROM pg_stat_replication;","mysql":"SHOW REPLICA STATUS\\G","oracle":"SELECT name, value, unit FROM v$dataguard_stats WHERE name IN ('apply lag','transport lag');","mssql":"SELECT database_id, is_local, synchronization_state_desc, redo_queue_size, log_send_queue_size\nFROM sys.dm_hadr_database_replica_states;","redis":"INFO replication","elasticsearch":"GET _cluster/health"},"choices":[{"label":"L'application s'est complètement arrêtée / il y a une erreur","next":"stopped"},{"label":"Le retard ne cesse d'augmenter (impossible de rattraper)","next":"growing"},{"label":"Le retard est constant mais une longue requête est suspectée","next":"standby_query"},{"label":"Très en retard / semble incohérent","next":"sol_rebuild"}]},"stopped":{"kind":"step","title":"Que dit l'erreur d'application ?","detail":"Si la réplication s'est arrêtée, il y a généralement une erreur dans un événement en cours d'application. Regardez le message d'erreur : s'agit-il d'une ligne/table absente sur la cible, d'un conflit de clé primaire, ou d'une coupure de connexion/réseau ?","command":{"mysql":"SHOW REPLICA STATUS\\G  -- Last_Error, Last_SQL_Error, Replica_IO_Running, Replica_SQL_Running","postgres":"SELECT status, last_msg_receipt_time, latest_end_lsn, slot_name\nFROM pg_stat_wal_receiver;","oracle":"SELECT process, status, sequence#, block# FROM v$managed_standby WHERE process LIKE 'MRP%';","elasticsearch":"GET _cat/recovery?v&active_only=true"},"choices":[{"label":"Erreur de ligne ou de table en conflit/manquante","next":"sol_apply_error"},{"label":"Réseau/connexion coupé(e), thread IO arrêté","next":"sol_connection"}]},"growing":{"kind":"step","title":"Pourquoi ne peut-il pas rattraper ?","detail":"Si le retard ne cesse d'augmenter, le réplica ne suit pas le rythme d'écriture du primaire. Le goulot est-il l'application mono-thread, ou le disque/l'IO du réplica est-il lent ? Regardez l'état du worker d'application et les attentes IO.","command":{"mysql":"SHOW REPLICA STATUS\\G  -- Seconds_Behind_Source and apply thread state\nSELECT * FROM performance_schema.replication_applier_status_by_worker;","postgres":"SELECT pid, wait_event_type, wait_event, state\nFROM pg_stat_activity WHERE backend_type='startup';","mssql":"SELECT database_id, redo_queue_size, redo_rate, log_send_queue_size\nFROM sys.dm_hadr_database_replica_states;"},"choices":[{"label":"Application mono-thread + forte charge d'écriture","next":"sol_parallel"},{"label":"Le disque/l'IO du réplica est lent","next":"sol_io"}]},"standby_query":{"kind":"step","title":"Y a-t-il une longue requête sur le standby ?","detail":"Une longue requête exécutée sur un standby en lecture seule peut bloquer l'application (recovery). Dans PostgreSQL, c'est ainsi que naît un conflit de recovery. Regardez les requêtes actives et les compteurs de conflits sur le standby.","command":{"postgres":"SELECT now()-query_start AS dur, pid, state, query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start;\nSELECT confl_snapshot, confl_lock, confl_bufferpin FROM pg_stat_database_conflicts WHERE datname=current_database();"},"choices":[{"label":"Oui, une longue requête bloque l'application","next":"sol_recovery_conflict"},{"label":"Non, aucune requête, mais il reste en retard","next":"sol_parallel"}]},"sol_apply_error":{"kind":"solution","title":"Cause racine : erreur d'application (conflit de données)","detail":"Le réplica est bloqué sur un événement qu'il ne peut pas appliquer : ligne absente sur la cible, conflit de clé primaire ou table manquante. Corrigez la cause de l'incohérence au lieu de sauter aveuglément l'événement.","steps":["Examinez l'erreur et l'événement bloqué : quelle table et quelle opération (delete/update/insert) a échoué ?","Trouvez la cause racine : données écrites manuellement sur la cible, DDL manquant (table/colonne), ou entrée binlog/WAL corrompue.","Alignez le schéma : ajoutez la table/colonne manquante ; comparez la ligne en conflit avec le primaire et corrigez-la.","Si vous devez sauter l'événement (par ex. un événement unique dans MySQL), documentez-le et vérifiez ensuite la cohérence des données du réplica.","Empêchez la récidive : interdisez les écritures directes sur la cible et exécutez les DDL via le primaire."],"related":["replication-status"],"playbook":"replication-lag"},"sol_connection":{"kind":"solution","title":"Cause racine : coupure de connexion / réseau","detail":"Le thread de réception (IO/receiver) ne parvient pas à se connecter au primaire. Le réseau, l'authentification ou des segments de journal accumulés/purgés peuvent être en cause.","steps":["Vérifiez l'accès réseau entre primaire et réplica ainsi que les privilèges de l'utilisateur de réplication.","Vérifiez que les segments WAL/binlog nécessaires existent toujours sur le primaire (rétention/expiration).","Dans PostgreSQL, vérifiez que le replication slot existe et est actif ; sans slot, le WAL a pu être supprimé trop tôt.","Redémarrez la connexion ; dès que les journaux circulent à nouveau, le retard devrait se résorber."],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_parallel":{"kind":"solution","title":"Cause racine : goulot de l'application mono-thread","detail":"Alors que le primaire écrit en parallèle, le réplica applique les modifications en une seule séquence et ne suit pas. Parallélisez l'application.","steps":["Activez l'application parallèle : dans MySQL, replica_parallel_workers + replica_parallel_type=LOGICAL_CLOCK.","Dans la réplication physique PostgreSQL, l'application est un processus unique ; réduisez le débit de génération sur le primaire en découpant les grosses transactions.","Déplacez les gros traitements batch vers les heures creuses ou découpez-les.","Surveillez la métrique de retard après la modification ; elle doit redescendre à zéro."],"related":["replication-status"],"playbook":"replication-lag"},"sol_io":{"kind":"solution","title":"Cause racine : goulot IO du réplica","detail":"Le thread d'application attend le disque, pas le CPU. Le stockage du réplica ne suit pas le rythme d'écriture du primaire.","steps":["Mesurez la latence/saturation IO du disque du réplica ; comparez-la avec celle du primaire.","Si le matériel du réplica est plus faible (disque lent, moins de RAM), alignez-le sur le primaire ; un matériel asymétrique est une cause fréquente.","Accélérez le disque pour l'application du redo/WAL ; dimensionnez correctement le buffer/cache.","Si cela ne s'améliore pas, réduisez la charge d'écriture ou répartissez le trafic de lecture sur un autre réplica."],"related":["replication-status"],"playbook":"replication-lag"},"sol_recovery_conflict":{"kind":"solution","title":"Cause racine : conflit de recovery sur le standby","detail":"Une longue requête en lecture seule sur le standby bloque l'application des modifications du primaire (conflit vacuum/verrou). PostgreSQL le résout en retardant l'application ou en annulant la requête.","steps":["Trouvez la longue requête bloquante ; si elle est vraiment nécessaire, attendez sa fin, sinon terminez-la.","Avec hot_standby_feedback=on, faites respecter les requêtes du standby par le primaire (au prix de bloat sur le primaire).","Sinon, ajustez max_standby_streaming_delay à la charge (équilibre entre tolérance au retard et annulation des requêtes).","Déplacez les longs rapports vers un réplica séparé où ils ne bloqueront pas l'application, ou à un autre moment."],"related":["replication-status","open-transactions"],"playbook":"replication-lag"},"sol_rebuild":{"kind":"solution","title":"Cause racine : réplica trop en retard / corrompu - reconstruisez-le","detail":"Le réplica est trop en retard pour se rétablir, les journaux nécessaires ont été supprimés, ou les données sont incohérentes. La voie la plus sûre est de le reconstruire à partir d'une sauvegarde récente.","steps":["Effectuez une sauvegarde de base actuelle depuis le primaire : PG pg_basebackup, MySQL clone/xtrabackup, Oracle RMAN duplicate.","Restaurez la sauvegarde sur le réplica et rétablissez la configuration de réplication (slot/utilisateur/position).","Démarrez la réplication et suivez la progression de la synchronisation initiale (rattrapage).","Une fois le retard à zéro, vérifiez la cohérence des données et le trafic de lecture, puis mettez-le en service."],"related":["replication-status","list-replicas","last-backup-status"],"playbook":"replication-lag"}}},"solver/ora-01555.json":{"id":"ora-01555","title":"ORA-01555 : Snapshot too old (Oracle)","icon":"⏳","category":"Oracle","intro":"L'erreur Oracle 'ORA-01555: snapshot too old' indique que les anciennes données (undo) dont une requête a besoin pour une lecture cohérente ont déjà été écrasées. Cet arbre affine l'origine de l'erreur (longue requête + UNDO insuffisant, rotation rapide de l'undo, ou LOB) et vous mène à la bonne correction.","start":"root","nodes":{"root":{"kind":"step","title":"Où l'erreur se produit-elle ?","detail":"Déterminez d'abord le contexte de l'erreur. L'ORA-01555 classique survient-elle pendant un SELECT/rapport long, ou lors de l'accès à une colonne LOB (CLOB/BLOB) ? L'ORA-01555 liée aux LOB relève d'un mécanisme différent (PCTVERSION/RETENTION) et se résout séparément.","command":{"oracle":"SELECT sql_id, elapsed_time/1e6 AS sec, sql_text\nFROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;"},"choices":[{"label":"Pendant une requête/un rapport long","next":"undo_size"},{"label":"Lors de l'accès à une colonne LOB","next":"sol_lob"}]},"undo_size":{"kind":"step","title":"L'UNDO est-il suffisant, la rétention est-elle appropriée ?","detail":"Une longue requête lit dans l'UNDO l'ancienne image des blocs modifiés depuis son démarrage. Si le tablespace UNDO est petit ou que undo_retention est plus court que la durée de la requête, l'ancien undo est écrasé. Mesurez l'utilisation de l'UNDO et la rétention.","command":{"oracle":"SHOW PARAMETER undo_retention;\nSELECT tablespace_name, SUM(bytes)/1024/1024 AS mb, status\nFROM dba_undo_extents GROUP BY tablespace_name, status;\nSELECT MAX(maxquerylen) AS longest_query_sec, MAX(tuned_undoretention) AS tuned_retention\nFROM v$undostat;"},"choices":[{"label":"L'UNDO est petit / la rétention est plus courte que la requête","next":"sol_undo"},{"label":"L'UNDO est suffisant mais les commits sont très fréquents","next":"sol_commits"},{"label":"La requête dure excessivement longtemps","next":"sol_query"}]},"sol_undo":{"kind":"solution","title":"Cause racine : UNDO / rétention insuffisant(e)","detail":"L'ancien undo nécessaire à la longue requête a été écrasé car la période de rétention a expiré ou le tablespace s'est rempli. Agrandissez l'UNDO et réglez la rétention pour couvrir la durée de la requête.","steps":["D'après la durée de la plus longue requête (v$undostat.maxquerylen), relevez undo_retention au-delà : ALTER SYSTEM SET undo_retention=10800;","Agrandissez le tablespace UNDO suffisamment pour absorber la rétention ou activez l'autoextend : ALTER DATABASE DATAFILE '...' AUTOEXTEND ON MAXSIZE ...;","Garantissez-la pour les longues requêtes critiques : ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;  (attention : si l'UNDO se remplit, les DML peuvent échouer).","Après la modification, relancez le même rapport et confirmez que l'ORA-01555 a disparu."],"related":["show-config-param"],"playbook":"ora-01555"},"sol_commits":{"kind":"solution","title":"Cause racine : rotation rapide de l'undo (commits fréquents)","detail":"Des DML intensifs avec des commits très fréquents recyclent rapidement les blocs d'undo et effacent l'ancienne image dont la longue requête a besoin. L'effet 'commit cleanout' aggrave la situation.","steps":["Réduisez les commits inutilement fréquents : utilisez des commits par lots au lieu de valider chaque ligne dans une boucle.","Pour ralentir la rotation de l'undo, ne planifiez pas les charges DML lourdes en même temps que le long rapport.","Augmentez undo_retention et, si nécessaire, protégez l'ancien undo avec RETENTION GUARANTEE.","Déplacez le rapport hors de la fenêtre d'écriture intensive ou ancrez-le à un point de lecture cohérent."],"related":["open-transactions","show-config-param"],"playbook":"ora-01555"},"sol_query":{"kind":"solution","title":"Cause racine : durée de requête excessive","detail":"La requête dure si longtemps que même un undo_retention raisonnable ne suffit pas. La correction durable consiste à accélérer la requête ou à déporter sa charge.","steps":["Optimisez la requête : corrigez les index manquants, les mauvais plans ou les full scans inutiles ; raccourcissez sa durée.","Découpez le gros traitement (par date/plage) pour que chaque lot tienne dans la fenêtre d'undo.","Déplacez le rapport lourd vers un standby/réplica ou un créneau de lecture séparé — afin qu'il n'entre pas en collision avec les DML actifs.","Une fois la durée acceptable, réglez undo_retention en conséquence."],"related":["explain-plan"],"playbook":"ora-01555"},"sol_lob":{"kind":"solution","title":"Cause racine : rétention LOB / PCTVERSION","detail":"Une ORA-01555 lors de la lecture d'un LOB indique que l'espace contenant les anciennes versions du LOB (RETENTION ou PCTVERSION) est insuffisant. Les LOB ont un mécanisme de versionnage distinct.","steps":["Affichez le paramètre actuel de la colonne LOB : SELECT retention, pctversion FROM dba_lobs WHERE table_name='...' AND column_name='...';","Relevez le paramètre approprié selon le mode d'undo AUTO/MANUAL : ALTER TABLE t MODIFY LOB (my_column) (RETENTION);  ou augmentez PCTVERSION en mode MANUAL.","Les LOB utilisant RETENTION se basent sur la valeur undo_retention de la base — maintenez-la aussi suffisante.","Raccourcissez les opérations longues qui accèdent au LOB ; testez de nouveau l'erreur après la modification."],"related":["show-config-param"],"playbook":"ora-01555"}}},"solver/backup-restore.json":{"id":"backup-restore","title":"Choix de sauvegarde et de restauration","icon":"💾","category":"Sauvegarde et restauration","intro":"Un arbre de décision qui vous guide étape par étape dans ce que vous devez faire avec les sauvegardes (effectuer une sauvegarde, restaurer, revenir à un instant précis - PITR, ou vérifier une sauvegarde) et vous mène aux bonnes commandes et étapes pour chaque base de données.","start":"root","nodes":{"root":{"kind":"step","title":"Que voulez-vous faire ?","detail":"Choisissez votre objectif. Effectuez-vous une nouvelle sauvegarde, restaurez-vous une sauvegarde existante, revenez-vous à un instant précis (juste avant une panne), ou vérifiez-vous que votre sauvegarde est saine ?","choices":[{"label":"Je veux EFFECTUER une sauvegarde","next":"take"},{"label":"Je veux RESTAURER une sauvegarde","next":"restore"},{"label":"Revenir à un instant précis (PITR)","next":"pitr"},{"label":"Je veux VÉRIFIER une sauvegarde","next":"verify"}]},"take":{"kind":"step","title":"Quel type de sauvegarde ?","detail":"Une sauvegarde logique (dump) est portable et adaptée aux restaurations sélectives mais lente sur de gros volumes. Une sauvegarde physique (niveau fichier/bloc) convient aux grandes bases et à une récupération rapide. Choisissez selon vos besoins.","choices":[{"label":"Sauvegarde logique (dump / export)","next":"sol_logical_backup"},{"label":"Sauvegarde physique / complète","next":"sol_physical_backup"}]},"sol_logical_backup":{"kind":"solution","title":"Effectuer une sauvegarde logique (dump)","detail":"Exporte les données sous forme d'instructions SQL/BSON. Idéal pour migrer entre versions/plateformes et restaurer une seule table/collection.","steps":["PostgreSQL : pg_dump -Fc -d mydb -f backup.dump  (pg_dumpall pour tout le cluster).","Oracle : expdp app_user/password directory=DP_DIR dumpfile=backup.dmp schemas=APP_SCHEMA logfile=exp.log","MySQL/MariaDB : mysqldump --single-transaction --routines --triggers mydb > backup.sql","MongoDB : mongodump --db mydb --out /backup/dir","Redis : prenez un snapshot RDB en arrière-plan avec BGSAVE (dump.rdb).","Elasticsearch : enregistrez d'abord un dépôt de snapshots, puis PUT _snapshot/my_repo/snap1?wait_for_completion=true","Déplacez la sauvegarde vers un emplacement sûr et notez sa taille/son état d'achèvement."],"related":["logical-dump","last-backup-status"],"playbook":"backup-restore"},"sol_physical_backup":{"kind":"solution","title":"Effectuer une sauvegarde physique / complète","detail":"Copie au niveau bloc/fichier des datafiles. Rapide pour les grandes bases et fondement de la PITR et de la récupération complète.","steps":["Oracle : RMAN> BACKUP DATABASE PLUS ARCHIVELOG;  (avec les journaux archivés).","PostgreSQL : pg_basebackup -D /backup/base -Fp -Xs -P  (avec l'archivage WAL activé).","SQL Server : BACKUP DATABASE [mydb] TO DISK='D:\\backup\\db.bak' WITH CHECKSUM, COMPRESSION;","MySQL/MariaDB : xtrabackup --backup --target-dir=/backup/base  (copie physique cohérente).","MongoDB : prenez un snapshot du système de fichiers ou un snapshot cohérent depuis un membre du replica set.","Copiez la sauvegarde vers un emplacement hors ligne/distant et notez l'état de la dernière sauvegarde."],"related":["last-backup-status"],"playbook":"backup-restore"},"restore":{"kind":"solution","title":"Restaurer une sauvegarde","detail":"Restaurez sur la cible la sauvegarde la plus adaptée dont vous disposez. Vérifiez d'abord l'intégrité et l'horodatage de la sauvegarde, puis chargez-la avec la commande adaptée au moteur.","steps":["Vérifiez la date et l'intégrité de la sauvegarde à restaurer (connaissez votre fenêtre de perte de données).","PostgreSQL : pg_restore -d new_db backup.dump   (pour un dump SQL : psql -d new_db -f backup.sql).","Oracle : RMAN> RESTORE DATABASE; RECOVER DATABASE; ALTER DATABASE OPEN;  (import logique avec impdp).","MySQL/MariaDB : mysql mydb < backup.sql   (physique : xtrabackup --copy-back).","SQL Server : RESTORE DATABASE [mydb] FROM DISK='D:\\backup\\db.bak' WITH RECOVERY, REPLACE;","MongoDB : mongorestore --db mydb /backup/dir","Après la restauration, validez les données avec des comptages de lignes/contrôles applicatifs."],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"pitr":{"kind":"solution","title":"Revenir à un instant précis (PITR)","detail":"Vous voulez revenir, à la seconde près, juste avant une suppression/corruption accidentelle. Il vous faut une sauvegarde complète + les journaux de transactions jusqu'à ce point (journaux archivés / WAL / binlog).","steps":["Déterminez précisément l'heure cible (juste avant l'incident) et vérifiez que les journaux nécessaires sont disponibles.","Oracle : RMAN> RESTORE DATABASE; RECOVER DATABASE UNTIL TIME \"TO_DATE('2026-07-04 10:00:00','YYYY-MM-DD HH24:MI:SS')\"; ALTER DATABASE OPEN RESETLOGS;","PostgreSQL : restaurez la sauvegarde de base, puis définissez recovery_target_time='2026-07-04 10:00:00' et appliquez le WAL jusqu'à ce point.","SQL Server : RESTORE ... WITH NORECOVERY; puis RESTORE LOG ... WITH STOPAT='2026-07-04 10:00:00', RECOVERY;","MySQL/MariaDB : restaurez la sauvegarde complète, puis appliquez le binlog jusqu'à ce point avec mysqlbinlog --stop-datetime='2026-07-04 10:00:00'.","Après l'ouverture, vérifiez que les données correspondent à l'instant attendu."],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"verify":{"kind":"solution","title":"Vérifier une sauvegarde","detail":"Une sauvegarde n'en est une qu'une fois sa restaurabilité prouvée. Testez régulièrement l'intégrité et la restaurabilité réelle.","steps":["Oracle : RMAN> RESTORE DATABASE VALIDATE;  et pour la cohérence des blocs VALIDATE CHECK LOGICAL DATABASE;","SQL Server : effectuez les sauvegardes WITH CHECKSUM et exécutez RESTORE VERIFYONLY ... WITH CHECKSUM (ne vérifie que le fichier de sauvegarde) ; pour une vraie preuve, restaurez la sauvegarde sur un serveur de test et exécutez-y DBCC CHECKDB([mydb]) WITH NO_INFOMSGS; (CHECKDB vérifie une base active/restaurée, pas le fichier de sauvegarde).","PostgreSQL : restaurez réellement la sauvegarde sur un serveur séparé/temporaire et confirmez que pg_restore se termine sans erreur.","MySQL/MongoDB : restaurez le dump dans un environnement de test et comparez le nombre de lignes/collections et des échantillons de données.","Planifiez des « exercices de restauration » réguliers — effectuer des sauvegardes ne suffit pas, prouvez aussi la restauration.","Consignez les résultats et la date de la dernière vérification réussie."],"related":["last-backup-status","restore-dump"],"playbook":"backup-restore"}}},"solver/upgrade-wizard.json":{"id":"upgrade-wizard","title":"Assistant de mise à niveau de version","icon":"⬆️","category":"Cycle de vie","intro":"Correctif ou majeure, sur place ou par migration ? Planifiez la mise à niveau étape par étape ; chaque branche couvre les vérifications préalables, la méthode et les travaux post-mise à niveau. Répétez-la ici avant de toucher à la production.","start":"kind","nodes":{"kind":{"kind":"step","title":"Quel type de montée de version effectuez-vous ?","detail":"Une version patch/mineure (au sein de la même version majeure, par ex. 16.2 -> 16.4, ou un CU SQL Server) présente un faible risque et se fait généralement sur place. Une version majeure (par ex. 15 -> 16) peut impliquer des changements de format des données et exige une planification plus soignée.","command":{"oracle":"SELECT * FROM v$version;\nSELECT patch_id, action, status FROM dba_registry_sqlpatch ORDER BY action_time DESC;","postgres":"SELECT version();","mysql":"SELECT VERSION();","mssql":"SELECT SERVERPROPERTY('ProductVersion') AS v, SERVERPROPERTY('ProductUpdateLevel') AS cu;","elasticsearch":"GET /"},"choices":[{"label":"Version mineure / patch (même version majeure, CU)","next":"patch_prep"},{"label":"Version majeure (la version majeure change)","next":"major_method"},{"label":"Je ne connais pas la différence ; expliquez-moi","next":"explain"}]},"explain":{"kind":"step","title":"Correctif ou majeure ?","detail":"Correctif : la version majeure reste la même (16.2 -> 16.4) ; le format des datafiles ne change pas, généralement remplacement des binaires + redémarrage. Majeure : la version majeure change (15 -> 16) ; le catalogue/format peut changer, pg_upgrade / AutoUpgrade / dump-restore est nécessaire, et la compatibilité de l'application doit être testée.","choices":[{"label":"C'est un correctif (même version majeure)","next":"patch_prep"},{"label":"C'est une majeure (la version majeure change)","next":"major_method"}]},"patch_prep":{"kind":"step","title":"Votre sauvegarde et votre plan de retour arrière sont-ils prêts ?","detail":"Même si un correctif présente peu de risque, la règle d'or de toute mise à niveau est : d'abord une sauvegarde complète (et un snapshot si possible). Il vous faut un point de retour si quelque chose tourne mal.","choices":[{"label":"Oui, sauvegarde + retour arrière prêts","next":"sol_patch"},{"label":"Non, pas encore","next":"sol_backupfirst"}]},"sol_backupfirst":{"kind":"solution","title":"D'abord la sauvegarde + le plan de retour arrière","detail":"Mettez en place le filet de sécurité avant de commencer la mise à niveau.","steps":["Effectuez une sauvegarde complète (et si possible un snapshot du stockage / un guaranteed restore point Oracle).","Notez les étapes de retour arrière : si quelque chose tourne mal, comment revenir à l'ancienne version ?","Finalisez la fenêtre de maintenance et le plan de communication, puis passez à l'étape du correctif."],"related":["logical-dump","last-backup-status"]},"sol_patch":{"kind":"solution","title":"Appliquer le correctif","detail":"Un correctif au sein de la même version majeure. L'appliquer en rolling sur un cluster (en commençant par les réplicas) réduit l'interruption.","steps":["Oracle : installez le nouveau RU avec opatch, puis appliquez les modifications SQL avec 'datapatch -verbose'.","PostgreSQL : mise à niveau mineure = installer les nouveaux binaires et redémarrer le service (pas d'initdb/dump nécessaire).","MySQL : mettez à jour le paquet ; en 8.0, le serveur effectue la mise à niveau automatiquement au démarrage.","SQL Server : exécutez l'installeur CU/GDR correspondant, puis redémarrez.","MongoDB/Redis/Elasticsearch : rolling restart ; les nœuds secondaires d'abord, le primaire en dernier.","Vérifiez ensuite la version et l'application ; si vous constatez des erreurs, exécutez le plan de retour arrière."],"related":["apply-patch","check-version"],"playbook":"upgrade-rollback"},"major_method":{"kind":"step","title":"Méthode de mise à niveau majeure","detail":"Deux voies principales : (1) Sur place : conversion sur le serveur existant (pg_upgrade, Oracle AutoUpgrade/DBUA, MySQL in-place). Rapide mais retour arrière difficile. (2) Par migration : construire un nouveau serveur sur la nouvelle version, y déplacer les données (par dump/restore ou réplication), puis basculer. Plus sûr, avec une interruption minimale.","choices":[{"label":"Je veux d'abord lancer une vérification de préparation/compatibilité","next":"precheck"},{"label":"Sur place","next":"sol_inplace"},{"label":"Par migration vers un nouveau serveur (interruption minimale)","next":"sol_migrate"}]},"precheck":{"kind":"step","title":"Vérification de compatibilité / de préparation","detail":"Avant la mise à niveau, recueillez les avertissements de la version cible : fonctionnalités supprimées (dépréciées), objets invalides, niveau de compatibilité.","command":{"oracle":"-- AutoUpgrade analyze mode: java -jar autoupgrade.jar -config cfg -mode analyze","postgres":"-- pg_upgrade --check   (with the old and new binary paths)","mysql":"-- MySQL Shell: util.checkForServerUpgrade('root@localhost:3306', {targetVersion: '8.4.0'})","mssql":"-- Compatibility report with Data Migration Assistant (DMA)","elasticsearch":"GET _migration/deprecations"},"choices":[{"label":"Sortie propre, continuer","next":"major_method"},{"label":"Il y a des avertissements/incompatibilités","next":"sol_fixwarn"}]},"sol_fixwarn":{"kind":"solution","title":"Corrigez d'abord les avertissements","detail":"Résolvez chaque avertissement du rapport AVANT la mise à niveau ; sinon la mise à niveau peut échouer en cours de route ou l'application peut casser.","steps":["Mettez à jour les objets et requêtes qui utilisent des fonctionnalités/syntaxes supprimées (dépréciées).","Compilez/corrigez les objets invalides ; résolvez les dépendances manquantes.","Planifiez les étapes de niveau de compatibilité / feature version pour la version cible.","Une fois les avertissements levés, relancez la vérification préalable et revenez au choix de la méthode."],"related":["check-upgrade-readiness","set-compatibility"]},"sol_inplace":{"kind":"solution","title":"Mise à niveau majeure sur place","detail":"Conversion sur le serveur existant. Rapide, mais le retour arrière implique généralement une restauration depuis la sauvegarde, un snapshot est donc indispensable.","steps":["Effectuez une sauvegarde complète + snapshot/guaranteed restore point (le seul moyen de revenir en arrière).","Oracle : AutoUpgrade (mode deploy) ou DBUA. PostgreSQL : pg_upgrade (--link est rapide mais sans retour possible). MySQL : nouveaux binaires + mise à niveau in-place automatique. SQL Server : setup /ACTION=upgrade.","Exécutez les tâches post-mise à niveau (voir les éléments liés ci-dessous).","Validez l'application sous charge réelle ; en cas de régression de plan, figez le comportement de l'optimiseur."],"related":["upgrade-major","post-upgrade-tasks"],"playbook":"upgrade-rollback"},"sol_migrate":{"kind":"solution","title":"Mise à niveau par migration (interruption minimale)","detail":"Construisez un nouveau serveur sur la nouvelle version, déplacez les données, puis basculez avec un cutover court. La méthode la plus sûre ; l'ancien système reste intact.","steps":["Construisez le nouveau serveur sur la version cible et préparez le schéma.","Transférez le chargement initial par dump/restore ou réplication ; puis maintenez la synchronisation par réplication continue (CDC / réplication logique).","Dans une courte fenêtre de maintenance : arrêtez les écritures, attendez le delta restant, et dirigez l'application vers le nouveau serveur (cutover).","Vérifiez la migration (comptages de lignes/checksums) ; en cas de problème, revenez immédiatement à l'ancien serveur."],"related":["setup-cdc-replication","verify-migration","export-for-migration"]}}},"solver/migration-wizard.json":{"id":"migration-wizard","title":"Assistant de migration (migration de données/schéma)","icon":"🚚","category":"Cycle de vie","intro":"Vous migrez des données/un schéma. Est-ce homogène ou hétérogène, et quelle est votre tolérance à l'interruption ? Planifiez étape par étape : évaluer → migrer → vérifier. Répétez d'abord le cutover ici.","start":"kind","nodes":{"kind":{"kind":"step","title":"Depuis quoi et vers quoi migrez-vous ?","detail":"Homogène : le même moteur de base de données (par ex. Oracle 19 → Oracle 23, ou PostgreSQL vers un autre serveur). Hétérogène : un moteur différent (par ex. Oracle → PostgreSQL, MySQL → SQL Server). Les migrations hétérogènes exigent beaucoup plus de travail de compatibilité (types de données, fonctions, PL/SQL).","choices":[{"label":"Même moteur, nouveau serveur/nouvelle version (homogène)","next":"downtime"},{"label":"Vers un moteur différent (hétérogène)","next":"hetero"},{"label":"Je suis en phase de planification ; comment l'aborder ?","next":"downtime"}]},"downtime":{"kind":"step","title":"Quelle est votre tolérance à l'interruption ?","detail":"Cela détermine la méthode de migration. S'il existe une fenêtre de maintenance, la voie la plus simple est dump/restore. Si une interruption quasi nulle est requise, vous chargez les données initiales, les maintenez synchronisées par réplication continue (CDC) et effectuez un cutover court.","choices":[{"label":"Il existe une fenêtre de maintenance (l'interruption est acceptable)","next":"sol_offline"},{"label":"Une interruption quasi nulle est requise","next":"sol_online"}]},"hetero":{"kind":"step","title":"Avez-vous évalué la compatibilité des schémas et des types ?","detail":"Dans une migration hétérogène, le vrai travail n'est pas de copier les données mais de résoudre les différences : types de données (VARCHAR2↔TEXT), fonctions (NVL↔COALESCE), valeurs auto-incrémentées, PL/SQL ↔ PL/pgSQL, fonctionnalités supprimées/différentes.","choices":[{"label":"J'ai cartographié les différences de schéma/types","next":"sol_hetero"},{"label":"Je ne les ai pas encore évaluées","next":"sol_assess"}]},"sol_assess":{"kind":"solution","title":"Évaluez d'abord la compatibilité","detail":"Avant de migrer, cartographiez les différences entre source et cible ; si cette étape est sautée, la migration cale à mi-chemin.","steps":["Produisez le diff de schéma : tables, contraintes, index, vues, procédures.","Faites la correspondance des types de données et des fonctions (le module DBAtlas > Types & Fonctions est conçu exactement pour cela).","Décidez comment les fonctionnalités propres au moteur (identity/sequence, partitionnement, PL/SQL, triggers) seront traitées sur la cible.","Choisissez un outil de conversion (ora2pg, pgloader, AWS SCT/DMS) et faites un essai avec un petit échantillon."],"related":["compare-schema","heterogeneous-migration"]},"sol_offline":{"kind":"solution","title":"Migration hors ligne (dump / restore)","detail":"La méthode homogène la plus simple et la plus fiable : effectuer une sauvegarde cohérente, la charger sur la cible, vérifier, basculer.","steps":["Effectuez un export COHÉRENT depuis la source (pg_dump -Fc, expdp FLASHBACK_SCN, mysqldump --single-transaction, BACKUP, mongodump --oplog).","Chargez-le sur la cible (pg_restore -j, impdp, mysql <, RESTORE, mongorestore).","Vérifiez la migration : les comptages de lignes et les checksums critiques correspondent-ils entre source et cible ?","Pendant la fenêtre de maintenance, dirigez l'application vers la cible (cutover) ; en cas de problème, revenez à la source."],"related":["export-for-migration","import-for-migration","verify-migration"]},"sol_online":{"kind":"solution","title":"Interruption minimale (CDC / réplication)","detail":"Chargez les données initiales, puis maintenez la cible synchronisée avec la source par réplication continue ; l'interruption n'a lieu que pendant le court cutover.","steps":["Chargez les données initiales (baseline) par dump/restore ou snapshot.","Mettez en place la réplication continue : PG réplication logique (PUBLICATION/SUBSCRIPTION), MySQL réplication binlog, Oracle GoldenGate, MSSQL réplication transactionnelle/CDC, Mongo change streams/mongosync.","Attendez et surveillez jusqu'à ce que la cible rattrape la source et que le retard soit ~0.","Cutover court : arrêtez les écritures sur la source, attendez le delta restant, dirigez l'application vers la cible, vérifiez."],"related":["setup-cdc-replication","verify-migration"]},"sol_hetero":{"kind":"solution","title":"Migration hétérogène (vers un autre moteur)","detail":"Convertissez le schéma, déplacez les données, adaptez la logique, vérifiez. Procédez par phases ; ne migrez pas tout d'un seul coup.","steps":["Convertissez le schéma : avec un outil (ora2pg, pgloader, AWS SCT) ou à la main ; adaptez les types et contraintes à la cible.","Déplacez les données : chargement en masse ou outil ; partitionnez/découpez les grandes tables.","Adaptez la logique côté serveur : PL/SQL → PL/pgSQL, équivalents de fonctions/triggers.","Vérifiez minutieusement : comptages de lignes, échantillons de données, résultats des requêtes critiques ; testez l'application sur la cible.","Si l'interruption doit être réduite, maintenez la cible synchronisée avec une couche CDC après la conversion, puis basculez."],"related":["heterogeneous-migration","verify-migration","setup-cdc-replication"]}}},"solver/high-cpu.json":{"id":"high-cpu","title":"CPU élevé","icon":"🔥","category":"Performances","intro":"Le CPU du serveur est saturé. Vérifiez d'abord que c'est bien la base qui le consomme, puis suivez les branches jusqu'à la cause racine : requêtes mal planifiées, régression de plan, concurrence excessive ou processus hors base.","start":"root","nodes":{"root":{"kind":"step","title":"La base consomme-t-elle réellement le CPU ?","detail":"Vérifiez côté système d'exploitation quel processus consomme le CPU. L'essentiel de la charge est-il dans les processus de la base, ou dans un processus voisin (sauvegarde, antivirus, autre application) ? Cela divise l'analyse en deux.","command":{"oracle":"-- OS side: top -o %CPU  (or: top -c)\n-- DB-side verification:\nSELECT ROUND(value,1) AS host_cpu_util_pct FROM v$sysmetric WHERE metric_name='Host CPU Utilization (%)' AND rownum=1;","postgres":"-- OS: top -c  /  ps -eo pcpu,pid,cmd --sort=-pcpu | head\n-- DB active session count:\nSELECT count(*) FROM pg_stat_activity WHERE state='active';","mysql":"-- OS: top -c  /  mysqladmin processlist\nSHOW GLOBAL STATUS LIKE 'Threads_running';","mssql":"-- OS: Task Manager / typeperf\nSELECT record_id, SQLProcessUtilization AS sql_cpu_pct, (100 - SystemIdle - SQLProcessUtilization) AS other_cpu_pct FROM (SELECT record.value('(./Record/@id)[1]','int') AS record_id, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]','int') AS SystemIdle, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]','int') AS SQLProcessUtilization FROM (SELECT CONVERT(xml, record) AS record FROM sys.dm_os_ring_buffers WHERE ring_buffer_type='RING_BUFFER_SCHEDULER_MONITOR') AS x) AS y ORDER BY record_id DESC;","mongodb":"// OS: top -c  |  db.serverStatus().metrics\ndb.serverStatus().opcounters","redis":"INFO cpu","elasticsearch":"GET _nodes/stats/os,process"},"choices":[{"label":"Oui, les processus de la base consomment le CPU","next":"db_confirm"},{"label":"Non, un processus voisin / un job de sauvegarde le consomme","next":"sol_os"}]},"db_confirm":{"kind":"step","title":"Comment la charge est-elle apparue ?","detail":"Si la base consomme le CPU, identifiez le schéma : quelques requêtes lourdes dominent-elles, le problème a-t-il commencé soudainement après un changement, ou y a-t-il une forte concurrence/contention de connexions ?","choices":[{"label":"Quelques requêtes lourdes consomment le CPU","next":"topcpu"},{"label":"Apparu soudainement ; il y a eu un déploiement / changement de paramètre récent","next":"sol_regression"},{"label":"Nombreuses connexions / contention de concurrence","next":"sol_concurrency"}]},"topcpu":{"kind":"step","title":"Trouver les requêtes qui consomment le plus de CPU","detail":"Récupérez les requêtes en tête par consommation CPU totale. Puis déterminez si chacune est lourde par appel (mauvais plan) ou peu coûteuse par appel mais appelée très fréquemment.","command":{"oracle":"SELECT sql_id, executions, ROUND(cpu_time/1e6,1) AS cpu_sec, ROUND(cpu_time/GREATEST(executions,1)/1000,2) AS cpu_ms_per_exec, sql_text\nFROM v$sql ORDER BY cpu_time DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT queryid, calls, ROUND(total_exec_time::numeric,1) AS total_ms, ROUND(mean_exec_time::numeric,2) AS mean_ms, query\nFROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;","mysql":"SELECT DIGEST_TEXT, COUNT_STAR AS calls, ROUND(SUM_CPU_TIME/1e12,2) AS cpu_sec, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_sec\nFROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;","mssql":"SELECT TOP 10 qs.execution_count, qs.total_worker_time/1000 AS cpu_ms, qs.total_worker_time/qs.execution_count/1000 AS cpu_ms_per_exec, SUBSTRING(st.text,(qs.statement_start_offset/2)+1,50) AS stmt\nFROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY qs.total_worker_time DESC;","mongodb":"db.setProfilingLevel(1, { slowms: 100 });  // then: db.system.profile.find().sort({ millis: -1 }).limit(10)"},"choices":[{"label":"Lourde par appel ; mauvais plan / full scan","next":"sol_query"},{"label":"Peu coûteuse par appel mais appelée très fréquemment","next":"sol_frequency"}]},"sol_query":{"kind":"solution","title":"Cause racine : des requêtes mal planifiées consomment le CPU","detail":"Quelques requêtes consomment un CPU excessif par appel à cause de full scans ou d'un mauvais ordre de jointure. Corriger leurs plans est la solution principale.","steps":["Obtenez les plans (EXPLAIN) des requêtes les plus consommatrices de CPU ; recherchez les full scans, un mauvais ordre de jointure ou des écarts entre lignes estimées et réelles.","Ajoutez des index adaptés sur les colonnes de filtre/jointure ; vérifiez que le plan passe à un index scan (IXSCAN).","Si l'optimiseur fait de mauvaises estimations, collectez des statistiques fraîches.","Limitez les requêtes incontrôlées (Oracle Resource Manager / MSSQL Resource Governor) et réécrivez la requête si nécessaire."],"related":["top-slow-queries","explain-plan","create-index","update-statistics"],"playbook":"high-cpu"},"sol_frequency":{"kind":"solution","title":"Cause racine : fréquence d'appel excessive","detail":"Les requêtes sont individuellement peu coûteuses mais appelées des milliers de fois par seconde ; c'est de là que vient le coût CPU total. Généralement un schéma N+1 ou un chemin critique sans cache.","steps":["Identifiez les instructions les plus fréquemment appelées (calls / execution_count) et trouvez le chemin applicatif appelant.","Transformez le schéma N+1 en requête par lot ; réduisez les lectures répétées avec un cache applicatif/de résultats.","Utilisez des prepared statements pour réduire le coût des analyses répétées (hard parse).","Envisagez une vue matérialisée / table de synthèse pour les requêtes lourdes fréquentes et invariables."],"related":["top-slow-queries","explain-plan"],"playbook":"high-cpu"},"sol_regression":{"kind":"solution","title":"Cause racine : régression de plan / statistiques obsolètes","detail":"Le système était normal hier ; le CPU a grimpé après un déploiement, un changement de paramètre ou un chargement de données. L'optimiseur a peut-être basculé vers un plan moins bon.","steps":["Identifiez le changement : déploiement récent, mise à niveau/correctif, changement de paramètre ou gros chargement de données.","Collectez des statistiques fraîches pour les tables concernées (les statistiques obsolètes sont la cause la plus fréquente).","Figez le bon plan : Oracle SQL Plan Baseline / hint, MSSQL Query Store 'force plan'.","Si le changement vient d'un paramètre, annulez-le et réévaluez de façon contrôlée."],"related":["update-statistics","explain-plan","show-config-param"],"playbook":"high-cpu"},"sol_concurrency":{"kind":"solution","title":"Cause racine : forte concurrence / contention de connexions","detail":"Les requêtes individuelles sont acceptables, mais trop de sessions s'exécutent en même temps ; plus de sessions actives que de cœurs CPU provoque des changements de contexte et une contention latch/spin.","steps":["Comparez le nombre de sessions actives au nombre de cœurs CPU ; avec plus de sessions actives que de cœurs, une file d'exécution se forme.","Placez un connection pooler entre l'application et la base (PgBouncer / ProxySQL) et définissez une taille de pool raisonnable.","En cas de parallélisme excessif, limitez le degré de parallélisme (Oracle parallel_degree_limit / parallel_max_servers, MSSQL MAXDOP).","Soulagez le CPU du nœud primaire en répartissant la charge de lecture sur des réplicas de lecture."],"related":["connection-count","active-sessions"],"playbook":"connection-storm"},"sol_os":{"kind":"solution","title":"Cause racine : un processus hors base consomme le CPU","detail":"Ce n'est pas la base mais un processus voisin qui consomme le CPU : un job de sauvegarde/compression, un scan antivirus, un expéditeur de logs ou une autre application sur la même machine.","steps":["Identifiez le processus coupable avec un outil de l'OS (top / Gestionnaire des tâches).","S'il s'agit d'un job de sauvegarde, de collecte de statistiques ou de maintenance, replanifiez-le en dehors des heures de pointe.","Ajoutez les répertoires de données à la liste d'exclusion des processus antivirus/d'analyse.","Si le conflit est permanent, déplacez la base vers un serveur séparé disposant de ses propres ressources."],"related":["os-resource"],"playbook":"high-cpu"}}},"solver/memory-pressure.json":{"id":"memory-pressure","title":"Pression mémoire / OOM","icon":"🧠","category":"Performances","intro":"La mémoire du serveur s'épuise, le système swappe ou l'OOM killer est actif. Déterminez d'abord qui consomme la mémoire : mémoire allouée (buffer pool/SGA), mémoire de session (work_mem/PGA), l'OS dans son ensemble, ou une situation propre à Redis ?","start":"root","nodes":{"root":{"kind":"step","title":"Qui consomme la mémoire ?","detail":"Séparez l'origine de la pression mémoire par couche. La mémoire allouée (fixe) est-elle trop grande, la mémoire par session explose-t-elle, un processus hors base la dévore-t-il, ou la gestion mémoire propre à Redis est-elle en jeu ?","command":{"oracle":"SELECT component, ROUND(current_size/1024/1024) AS mb FROM v$memory_dynamic_components WHERE current_size>0 ORDER BY current_size DESC;","postgres":"SHOW shared_buffers; SHOW work_mem; SHOW effective_cache_size;\nSELECT count(*) FROM pg_stat_activity WHERE state='active';","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';\nSHOW VARIABLES LIKE 'sort_buffer_size'; SHOW VARIABLES LIKE 'join_buffer_size';\nSHOW STATUS LIKE 'Threads_connected';","mssql":"SELECT physical_memory_in_use_kb/1024 AS used_mb, large_page_allocations_kb/1024 AS large_mb FROM sys.dm_os_process_memory;\nSELECT cntr_value/1024 AS target_mb FROM sys.dm_os_performance_counters WHERE counter_name='Target Server Memory (KB)';","mongodb":"db.serverStatus().wiredTiger.cache","redis":"INFO memory","elasticsearch":"GET _nodes/stats/jvm,os"},"choices":[{"label":"La mémoire allouée (buffer pool / SGA) domine","next":"allocated"},{"label":"La mémoire de session (work_mem / PGA) explose","next":"sol_session"},{"label":"Un processus hors base / l'OS dans son ensemble","next":"sol_os"},{"label":"Propre à Redis (maxmemory / éviction / fragmentation)","next":"sol_redis"}]},"allocated":{"kind":"step","title":"La mémoire totale configurée dépasse-t-elle la RAM physique ?","detail":"Si la mémoire allouée fixe est importante, il y a deux possibilités : soit le total configuré (buffer pool + sessions + OS) dépasse la RAM physique (sur-allocation), soit la taille est raisonnable mais le taux de succès du cache reste faible. Additionnez les paramètres et comparez-les à la RAM.","command":{"oracle":"SHOW PARAMETER sga_target;  SHOW PARAMETER pga_aggregate_target;\nSELECT ROUND(value/1024/1024/1024,1) AS host_ram_gb FROM v$osstat WHERE stat_name='PHYSICAL_MEMORY_BYTES';","postgres":"SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','work_mem','maintenance_work_mem','max_connections');","mysql":"SELECT ROUND(@@innodb_buffer_pool_size/1024/1024/1024,1) AS pool_gb, ROUND((@@sort_buffer_size+@@join_buffer_size)/1024/1024,1) AS per_conn_mb, @@max_connections AS max_conn;","mssql":"SELECT value_in_use AS max_server_mem_mb FROM sys.configurations WHERE name='max server memory (MB)';\nSELECT total_physical_memory_kb/1024 AS ram_mb FROM sys.dm_os_sys_memory;"},"choices":[{"label":"Oui, le total configuré dépasse la RAM (sur-allocation)","next":"sol_overcommit"},{"label":"Non, la taille est raisonnable mais le taux de succès du cache est faible","next":"sol_cache"}]},"sol_overcommit":{"kind":"solution","title":"Cause racine : sur-allocation de la mémoire","detail":"La somme buffer pool/SGA + mémoire de session + besoins de l'OS dépasse la RAM physique. Le système swappe, ou l'OOM killer tue le processus.","steps":["Refaites le budget mémoire : réservez une part pour l'OS, puis dimensionnez correctement la mémoire allouée (Outils > Calculateur de mémoire).","Abaissez la cible du buffer pool/de la SGA et le plafond de la mémoire de session pour qu'ils tiennent dans la limite de la RAM.","Appliquez les modifications de façon contrôlée et vérifiez que l'utilisation du swap et les événements OOM ont cessé.","Si la capacité est durablement insuffisante, ajoutez de la RAM ou répartissez la charge."],"related":["show-config-param","cache-hit-ratio"],"playbook":"memory-pressure"},"sol_cache":{"kind":"solution","title":"Cause racine : cache insuffisant (faible taux de succès du cache)","detail":"La mémoire allouée ne dépasse pas la RAM mais est petite par rapport au working set ; le hit ratio du buffer cache est faible et la base lit constamment sur disque.","steps":["Mesurez le hit ratio du buffer cache / buffer pool ; s'il reste durablement bas (par ex. sous 90 %), le cache est trop petit.","Si de la RAM est disponible, agrandissez le buffer pool/la SGA de façon contrôlée et mesurez à nouveau le hit ratio.","Si les données chaudes sont réellement volumineuses, réduisez les lectures inutiles : ajoutez des index, sélectionnez uniquement les colonnes nécessaires au lieu de SELECT *, paginez.","Si l'agrandissement met la RAM sous tension, rééquilibrez la mémoire de session par rapport à la mémoire allouée."],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"},"sol_session":{"kind":"solution","title":"Cause racine : explosion de la mémoire de session","detail":"La mémoire par session comme work_mem/PGA, multipliée par une forte concurrence, épuise la RAM. Chaque opération de sort/hash peut allouer cette mémoire séparément.","steps":["Estimez le pire cas : mémoire de session × sessions concurrentes × nombre d'opérations ; comparez-le à la RAM.","Abaissez le paramètre global de mémoire de session (work_mem / sort_buffer_size / PGA target).","Pour les requêtes lourdes qui ont réellement besoin de gros sorts/hashes, relevez le paramètre uniquement au niveau de la session.","Limitez la concurrence avec un pooler de connexions pour réduire la demande mémoire totale."],"related":["show-config-param","connection-count"],"playbook":"memory-pressure"},"sol_redis":{"kind":"solution","title":"Cause racine : gestion de la mémoire Redis","detail":"Dans Redis, used_memory a atteint maxmemory, l'éviction est active ou la fragmentation est élevée (used_memory_rss >> used_memory). La politique et la fragmentation doivent être traitées.","steps":["Lisez used_memory, maxmemory et mem_fragmentation_ratio avec INFO memory.","Définissez une politique d'éviction adaptée à l'usage : CONFIG SET maxmemory-policy allkeys-lru (cache) ou noeviction (données persistantes).","Utilisez des TTL pour que les clés expirées soient nettoyées, et réduisez les grosses clés (BIG KEYS).","Si la fragmentation est élevée, activez la défragmentation active (CONFIG SET activedefrag yes) ou redémarrez dans une fenêtre de maintenance adaptée."],"related":["show-config-param"],"playbook":"memory-pressure"},"sol_os":{"kind":"solution","title":"Cause racine : consommation mémoire hors base de données","detail":"La mémoire n'est pas consommée par la base de données mais par l'OS dans son ensemble ou un processus voisin : une autre application, la pression du cache du système de fichiers ou un processus auxiliaire qui fuit.","steps":["Identifiez le processus qui consomme le plus de mémoire avec un outil de l'OS (top / free / Gestionnaire des tâches).","Si le coupable est un processus hors base, déplacez-le, limitez-le (cgroup / job object) ou corrigez la fuite mémoire.","Si l'OOM killer tue le processus de la base, revoyez les paramètres OOM et garantissez de la mémoire à la base.","Si le conflit persiste, déplacez la base vers un serveur distinct disposant de sa propre mémoire."],"related":["os-resource"],"playbook":"memory-pressure"}}},"solver/install-setup.json":{"id":"install-setup","title":"Nouvelle installation et socle sécurisé","icon":"🛠️","category":"Cycle de vie","intro":"Les étapes omises lors de la préparation d'une base nouvellement installée pour la production provoquent les pannes les plus coûteuses. Choisissez l'étape où vous en êtes et obtenez une checklist ordonnée pour la configuration initiale, le durcissement de la sécurité, la durabilité et la supervision.","start":"root","nodes":{"root":{"kind":"step","title":"À quelle étape en êtes-vous ?","detail":"Préparer une nouvelle installation pour la production comporte quatre étapes : configuration initiale, durcissement de la sécurité, durabilité (sauvegarde/réplication) et supervision. Sur laquelle travaillez-vous actuellement ? Il est recommandé de toutes les parcourir dans l'ordre.","command":{"oracle":"SELECT banner_full FROM v$version; SHOW PARAMETER compatible;","postgres":"SELECT version(); SHOW server_encoding; SHOW data_directory;","mysql":"SELECT VERSION(); SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'datadir';","mssql":"SELECT @@VERSION; SELECT SERVERPROPERTY('Collation') AS collation;","mongodb":"db.version(); db.serverStatus().host","redis":"INFO server","elasticsearch":"GET /"},"choices":[{"label":"Configuration initiale après l'installation","next":"firstconfig"},{"label":"Durcissement de la sécurité","next":"sol_security"},{"label":"Mise en place de la sauvegarde et de la durabilité","next":"durability"},{"label":"Mise en place de la supervision et des alertes","next":"sol_monitoring"}]},"firstconfig":{"kind":"step","title":"Quelle est la priorité de la configuration initiale ?","detail":"La configuration initiale se divise en deux grands sujets : le dimensionnement des ressources/paramètres (mémoire, connexions) et les fondations permanentes (jeu de caractères/collation, répertoire de données/organisation des fichiers). Ce sont les décisions les plus difficiles à modifier ensuite.","command":{"postgres":"SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','work_mem','max_connections','wal_level');","oracle":"SHOW PARAMETER sga_target; SHOW PARAMETER pga_aggregate_target; SHOW PARAMETER processes;","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW VARIABLES LIKE 'max_connections';","mssql":"SELECT name, value_in_use FROM sys.configurations WHERE name IN ('max server memory (MB)','max degree of parallelism');"},"choices":[{"label":"Dimensionnement mémoire / paramètres","next":"sol_sizing"},{"label":"Jeu de caractères, collation et organisation des fichiers","next":"sol_layout"}]},"durability":{"kind":"step","title":"Quelle partie de la durabilité mettez-vous en place ?","detail":"La durabilité repose sur deux piliers : une stratégie de sauvegarde vérifiée (première sauvegarde + test de restauration) et la continuité (mode archive/journal, réplication). Dire « j'ai une sauvegarde » sans l'avoir testée est l'idée fausse la plus répandue.","command":{"postgres":"SHOW archive_mode; SHOW wal_level;","oracle":"ARCHIVE LOG LIST; SELECT log_mode FROM v$database;","mysql":"SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'server_id';","mssql":"SELECT name, recovery_model_desc FROM sys.databases;"},"choices":[{"label":"Stratégie de sauvegarde + première sauvegarde + test de restauration","next":"sol_backup"},{"label":"Mode archive/journal + réplication","next":"sol_replication"}]},"sol_sizing":{"kind":"solution","title":"Configuration initiale : dimensionnement de la mémoire et des paramètres","detail":"Les valeurs par défaut sont prudentes pour la plupart des moteurs ; dimensionnez-les selon la RAM et le CPU du serveur, mais évitez le surengagement.","steps":["Répartissez la RAM totale : laissez une part à l'OS, puis définissez le buffer pool/la SGA comme cache principal.","Définissez la mémoire de session (work_mem/PGA) de façon prudente selon la concurrence attendue ; ne dépassez pas la RAM totale.","Réglez max_connections sur le besoin réel ; prévoyez un pooler de connexions pour une forte concurrence.","Passez en revue les paramètres essentiels propres au moteur (WAL/redo, checkpoint, parallélisme) et documentez les modifications."],"related":["show-config-param","connect-cli"]},"sol_layout":{"kind":"solution","title":"Configuration initiale : jeu de caractères, collation et organisation des fichiers","detail":"Le jeu de caractères/la collation et l'organisation du répertoire de données sont définis à l'installation ; les modifier ensuite exige généralement une réinstallation ou une migration.","steps":["Choisissez un jeu de caractères Unicode au niveau du serveur (par ex. UTF-8 / AL32UTF8) et définissez une collation cohérente.","Placez les répertoires de données, de journaux/WAL et de sauvegarde sur des disques/volumes distincts ; réduisez la contention d'E/S et les points de défaillance uniques.","Durcissez le système de fichiers et les permissions : seul l'utilisateur de la base doit pouvoir lire le répertoire de données.","Documentez les choix d'organisation et de jeu de caractères afin que les nœuds ajoutés plus tard s'y conforment."],"related":["show-config-param","connect-cli"]},"sol_security":{"kind":"solution","title":"Durcissement de la sécurité","detail":"Une nouvelle installation arrive avec des comptes par défaut, un accès réseau permissif et des ports ouverts. Réduire la surface d'attaque avant la mise en production est obligatoire.","steps":["Supprimez ou verrouillez les comptes par défaut/d'exemple ; désactivez les comptes d'administration inutilisés.","Imposez une politique de mots de passe robuste et changez tous les mots de passe par défaut.","Moindre privilège : n'accordez à chaque application que les privilèges nécessaires et évitez les rôles étendus.","Activez TLS, limitez le listener/port aux réseaux de confiance et activez la journalisation d'audit."],"related":["create-user-grant","change-password","enable-audit"]},"sol_backup":{"kind":"solution","title":"Durabilité : stratégie de sauvegarde et test de restauration","detail":"Une sauvegarde ne vaut que par sa capacité à être restaurée. Effectuez la première sauvegarde, mettez en place la continuité et réalisez toujours un test de restauration.","steps":["Définissez les objectifs de reprise (RPO/RTO) et mettez en place un planning de sauvegardes complètes + incrémentales.","Effectuez la première sauvegarde complète ; stockez les sauvegardes dans un emplacement distinct du serveur de base de données.","Testez la sauvegarde en la restaurant dans un environnement distinct ; une sauvegarde dont l'intégrité n'a pas été prouvée ne compte pas.","Planifiez la tâche de sauvegarde, envoyez sa sortie vers la supervision et vérifiez régulièrement l'état de la dernière sauvegarde."],"related":["logical-dump","restore-dump","last-backup-status"]},"sol_replication":{"kind":"solution","title":"Durabilité : mode archive/journal et réplication","detail":"Pour la restauration à un instant donné et la haute disponibilité, le mode archive/journal doit être activé et il doit exister au moins un réplica.","steps":["Activez le mode archive/journal (PG archive_mode/wal_level, Oracle ARCHIVELOG, MySQL binlog, MSSQL FULL recovery).","Mettez en place au moins un réplica (standby/replica) et terminez la synchronisation initiale.","Surveillez le retard de réplication et définissez une alerte de retard.","Validez le failover en réalisant un exercice contrôlé de failover/switchover."],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_monitoring":{"kind":"solution","title":"Mise en place de la supervision et des alertes","detail":"La dernière étape avant la production : voir les problèmes avant les utilisateurs. Mettez en place des contrôles de santé et des alertes avec les bons seuils.","steps":["Collectez les contrôles de santé de base : taux de succès du cache, sessions actives/en attente, requêtes les plus lentes.","Définissez des alertes de capacité : utilisation disque, nombre de connexions proche de la limite, retard de réplication.","Acheminez les alertes vers un canal (e-mail/chat) et calibrez les seuils sur la charge réelle.","Ajoutez aussi la réussite des sauvegardes et les journaux d'erreurs à la supervision pour détecter les défaillances silencieuses."],"related":["cache-hit-ratio","connection-count","free-space"]}}},"solver/corruption-recovery.json":{"id":"corruption-recovery","title":"Récupération après corruption de données","icon":"🧯","category":"Récupération","intro":"Un arbre de décision pas à pas pour la corruption de données (erreurs de checksum de bloc/page, tables illisibles, RDB/AOF corrompus) : confirmez d'abord l'étendue de la corruption, puis choisissez correctement entre récupération au niveau bloc, restauration complète/PITR depuis une sauvegarde et outils de réparation de dernier recours.","start":"root","nodes":{"root":{"kind":"step","title":"Quelle est l'étendue de la corruption ?","detail":"Lancez d'abord un contrôle d'intégrité pour déterminer jusqu'où la corruption s'est propagée. Seuls quelques blocs/pages sont-ils corrompus, un seul objet (table/index) est-il touché, ou la corruption est-elle généralisée ? Analysez avec les commandes ci-dessous pour déterminer l'étendue, puis choisissez la branche appropriée.","command":{"oracle":"RMAN> BACKUP VALIDATE CHECK LOGICAL DATABASE;  -- then: SELECT file#, block#, blocks, corruption_type FROM v$database_block_corruption;","postgres":"pg_amcheck --all --heapallindexed --progress   # single table: pg_amcheck -d mydb -t app_schema.my_table","mysql":"CHECK TABLE app_schema.my_table EXTENDED;   -- for InnoDB also: SHOW ENGINE INNODB STATUS\\G","mssql":"DBCC CHECKDB (N'mydb') WITH NO_INFOMSGS, ALL_ERRORMSGS;","mongodb":"db.my_table.validate({ full: true })","redis":"redis-check-rdb /var/lib/redis/dump.rdb   # AOF (7+, read-only check): redis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.manifest  (--fix modifies the files; only after taking a copy)","elasticsearch":"GET _cluster/health?level=shards   # for a corrupted shard: GET _cat/shards?v&h=index,shard,state,unassigned.reason"},"choices":[{"label":"Seuls quelques blocs / pages sont corrompus","next":"block_level"},{"label":"Un seul objet (table/index) est touché","next":"restore"},{"label":"Corruption généralisée / nombreux objets","next":"restore"},{"label":"Les outils de contrôle ne fonctionnent pas, la base ne s'ouvre pas","next":"last_resort"},{"label":"La corruption se répète (cause racine suspectée)","next":"hardware"}]},"block_level":{"kind":"solution","title":"Cause racine : corruption limitée de blocs/pages → récupération ciblée","detail":"Si seuls quelques blocs/pages sont corrompus, inutile de restaurer toute la base. Minimisez la perte de données en ne réparant que les parties corrompues depuis la sauvegarde/les journaux grâce à la fonctionnalité de récupération au niveau bloc/page du moteur.","steps":["Oracle : après avoir examiné la liste des blocs corrompus dans v$database_block_corruption (exécutez d'abord RMAN> VALIDATE DATABASE; si la liste n'est pas à jour), récupérez un seul bloc avec RMAN> RECOVER DATAFILE 5 BLOCK 1234; ou tous les blocs listés avec RMAN> RECOVER CORRUPTION LIST;.","SQL Server : restauration d'une seule page — RESTORE DATABASE [db] PAGE='5:1234' FROM DISK='D:\\backup\\db.bak' WITH NORECOVERY;  puis appliquez les sauvegardes de journal suivantes WITH NORECOVERY et la dernière WITH RECOVERY.","PostgreSQL : il n'existe pas de réparation automatique au niveau bloc ; restaurez partiellement la table concernée depuis une sauvegarde, ou activez temporairement zero_damaged_pages=on UNIQUEMENT pour extraire les données (les lignes corrompues sont perdues).","MySQL/InnoDB : il n'existe pas de réparation d'une seule page ; restaurez la table concernée depuis la dernière bonne sauvegarde et avancez par PITR si nécessaire.","Après la réparation, relancez la commande de contrôle (VALIDATE / DBCC CHECKDB / pg_amcheck) pour confirmer qu'il ne reste aucune corruption.","Vérifiez les données métier des blocs récupérés par échantillonnage côté application."],"related":["restore-dump","last-backup-status"],"playbook":"corruption"},"restore":{"kind":"solution","title":"Cause racine : corruption au niveau objet/généralisée → restauration complète ou PITR depuis une sauvegarde","detail":"Si la corruption s'est propagée à un ou plusieurs objets, la voie la plus sûre est de restaurer depuis une bonne sauvegarde et, si possible, d'avancer jusqu'au moment de la corruption (PITR). Cela évite la perte de données qu'entraîneraient les outils de réparation.","steps":["Déterminez l'heure approximative du début de la corruption (alert log / journal d'erreurs) afin de fixer la cible PITR juste avant.","Vérifiez la dernière BONNE sauvegarde complète et les journaux de transactions jusqu'à ce point (journaux archivés / WAL / binlog).","Oracle : RMAN> RESTORE DATABASE; RECOVER DATABASE UNTIL TIME \"TO_DATE('2026-07-04 09:59:00','YYYY-MM-DD HH24:MI:SS')\"; ALTER DATABASE OPEN RESETLOGS;","SQL Server : RESTORE DATABASE [db] FROM DISK='...' WITH NORECOVERY; RESTORE LOG [db] FROM DISK='...' WITH STOPAT='2026-07-04 09:59:00', RECOVERY;","PostgreSQL : restaurez la sauvegarde de base, définissez recovery_target_time='2026-07-04 09:59:00' et appliquez les WAL jusqu'à ce point.","MySQL/MariaDB : restaurez la sauvegarde complète, puis appliquez le binlog jusqu'à ce point avec mysqlbinlog --stop-datetime='2026-07-04 09:59:00'.","MongoDB : restaurez avec mongorestore depuis un bon réplica/snapshot ; Elasticsearch : restaurez les index corrompus avec POST _snapshot/my_repo/snap1/_restore.","Après l'ouverture, relancez le contrôle d'intégrité et validez les données par comptage de lignes/contrôles applicatifs."],"related":["restore-dump","logical-dump","last-backup-status"],"playbook":"corruption"},"last_resort":{"kind":"step","title":"Pas de sauvegarde / la base ne s'ouvre pas — passer au dernier recours ?","detail":"Les outils de réparation (REPAIR_ALLOW_DATA_LOSS, innodb_force_recovery, --fix) PEUVENT ENTRAÎNER UNE PERTE DE DONNÉES et rompre les contraintes d'intégrité. Ils ne sont qu'un dernier recours lorsqu'aucune sauvegarde exploitable n'existe et que la base ne peut pas être ouverte. Assurez-vous d'abord qu'il n'existe vraiment aucune sauvegarde exploitable.","choices":[{"label":"J'ai trouvé une bonne sauvegarde, je restaure","next":"restore"},{"label":"Aucune sauvegarde, un outil de réparation est la seule option","next":"repair"}]},"repair":{"kind":"solution","title":"Dernier recours : outils de réparation avec risque de perte de données","detail":"Ces outils écartent les lignes/pages corrompues pour rendre la base ouvrable. Ne les utilisez que s'il n'existe aucune sauvegarde et après avoir copié les fichiers existants. Objectif : l'ouvrir juste assez pour exporter les données, puis les transférer vers une copie saine.","steps":["Faites D'ABORD une copie brute des datafiles existants (les tentatives peuvent aggraver la situation).","SQL Server : DBCC CHECKDB (N'db', REPAIR_ALLOW_DATA_LOSS) WITH ALL_ERRORMSGS;  (la base doit être en mode SINGLE_USER ; cette commande peut entraîner une perte de données).","MySQL/InnoDB : démarrez avec innodb_force_recovery=1 sous [mysqld] dans my.cnf ; si elle ne s'ouvre pas, augmentez à 2, 3... (jusqu'à 6) ; dès qu'elle s'ouvre, exportez immédiatement les données avec mysqldump, puis importez-les dans une installation saine et supprimez le paramètre.","PostgreSQL : en dernier recours, pg_resetwal (risque élevé de perte de données) uniquement lorsque les WAL sont corrompus ; extrayez ensuite les données avec pg_dump et chargez-les dans un nouveau cluster.","Oracle : sans sauvegarde, DBMS_REPAIR / la récupération par événements s'évalue avec le support Oracle ; les blocs peuvent être ignorés avec 'skip corrupt' (perte de données).","MongoDB : ouvrez avec mongod --repair ou WiredTiger salvage, puis transférez les données vers un nouveau replica set.","Après la réparation, passez TOUJOURS à une installation saine, relancez le contrôle d'intégrité et signalez les données perdues."],"related":["logical-dump","restore-dump"],"playbook":"corruption"},"hardware":{"kind":"solution","title":"Cause racine : matériel/infrastructure → corrigez la source et activez les checksums","detail":"Si la corruption se répète, la cause réelle est très probablement le disque, le contrôleur, la mémoire (RAM) ou une couche de stockage instable. Récupérer les données ne suffit pas ; si vous ne corrigez pas la source, la corruption reviendra. Activez aussi la vérification par checksum/intégrité pour une détection précoce.","steps":["Vérifiez la santé des disques : valeurs SMART (smartctl -a /dev/sdX), journaux du contrôleur RAID et alertes du stockage.","Lancez un test mémoire (memtest86+ ou compteurs d'erreurs ECC) ; les erreurs sur un seul bit sont une cause classique de corruption.","Examinez les journaux d'erreurs de l'OS/du système de fichiers et du matériel (dmesg, Observateur d'événements, alertes de la baie de stockage).","PostgreSQL : créez un cluster avec checksums via initdb --data-checksums (ou pg_checksums --enable) afin que la corruption soit détectée lors de l'écriture sur disque.","Oracle : activez la vérification des blocs avec DB_BLOCK_CHECKSUM=FULL et DB_BLOCK_CHECKING=FULL ; ajoutez DB_LOST_WRITE_PROTECT=TYPICAL.","SQL Server : assurez-vous que la base utilise PAGE_VERIFY CHECKSUM (ALTER DATABASE [db] SET PAGE_VERIFY CHECKSUM).","MySQL/InnoDB : protégez-vous des écritures partielles avec innodb_checksum_algorithm=crc32 et, si nécessaire, innodb_doublewrite=ON.","Après avoir corrigé la source, restaurez la dernière bonne sauvegarde sur une copie saine et renforcez la supervision."],"related":["restore-dump","last-backup-status"],"playbook":"corruption"}}},"solver/failover-drill.json":{"id":"failover-drill","title":"Exercice de failover / transition de rôle planifiée","icon":"🔀","category":"Réplication","intro":"Un arbre de décision qui identifie le type de changement de rôle (switchover planifié, failover d'urgence ou exercice sans toucher à la production) et fournit la bonne séquence de commandes et les étapes de vérification pour chaque scénario. Objectif : être prêt à basculer sans perte de données en planifié, avec une perte minimale en urgence, et sans aucune interruption lors d'un exercice.","start":"root","nodes":{"root":{"kind":"step","title":"Que voulez-vous faire ?","detail":"Choisissez le contexte du changement de rôle. Effectuez-vous une transition planifiée alors que le primaire est sain (maintenance/migration), le primaire est-il réellement tombé et une transition d'urgence est-elle nécessaire, ou réalisez-vous un exercice/une validation de disponibilité sans toucher à la production ?","choices":[{"label":"Switchover planifié (primaire sain)","next":"planned"},{"label":"Failover d'urgence (primaire tombé)","next":"emergency_check"},{"label":"Exercice / test de disponibilité (sans toucher à la production)","next":"drill"}]},"planned":{"kind":"step","title":"Transition planifiée — êtes-vous prêt ?","detail":"L'objectif d'un switchover planifié est une perte de données NULLE. Pour cela, avant la transition, assurez-vous que le réplica a rattrapé le primaire (retard ~0) et qu'il est sain. Vérifiez le retard avec les commandes ci-dessous ; s'il est proche de zéro, passez aux étapes de transition.","command":{"oracle":"SELECT thread#, MAX(sequence#) FROM v$archived_log GROUP BY thread#;   -- DG: SELECT name, value FROM v$dataguard_stats WHERE name LIKE '%lag%';","postgres":"SELECT client_addr, state, replay_lag, write_lag FROM pg_stat_replication;","mysql":"SHOW REPLICA STATUS\\G   -- check Seconds_Behind_Source and Replica_SQL_Running","mssql":"SELECT ag.name, drs.synchronization_state_desc, drs.log_send_queue_size, drs.redo_queue_size FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_groups ag ON ag.group_id = drs.group_id;","mongodb":"rs.printSecondaryReplicationInfo()","redis":"redis-cli INFO replication   # check the difference between master_repl_offset and the slave offset","elasticsearch":"GET _cat/recovery?v&active_only=true"},"choices":[{"label":"Retard proche de zéro, prêt pour la transition","next":"sol_planned"},{"label":"Retard élevé, attendez d'abord la synchronisation","next":"drill"}]},"sol_planned":{"kind":"solution","title":"Switchover planifié (transition contrôlée sans perte de données)","detail":"Tant que le primaire et le standby sont sains et synchronisés, échangez les rôles de façon contrôlée. L'ordre compte : arrêtez les écritures, assurez-vous que les dernières modifications ont atteint le standby, puis transférez le rôle et redirigez l'application vers le nouveau primaire.","steps":["Annoncez la fenêtre de maintenance et suspendez l'application de façon contrôlée pour les lectures et écritures.","Oracle Data Guard (12c+) : SQL> ALTER DATABASE SWITCHOVER TO standby_db VERIFY; puis SQL> ALTER DATABASE SWITCHOVER TO standby_db;  (avec le Broker : DGMGRL> SWITCHOVER TO 'standby_db'; 11g et antérieures : COMMIT TO SWITCHOVER TO PHYSICAL STANDBY).","PostgreSQL : arrêtez proprement le primaire (pg_ctl stop -m fast), promouvez le standby avec SELECT pg_promote();, puis rattachez l'ancien primaire comme nouveau standby avec pg_rewind.","SQL Server AG : sur un réplica synchrone, ALTER AVAILABILITY GROUP [ag] FAILOVER;  (failover planifié sans perte de données ; SYNCHRONOUS_COMMIT est requis).","MongoDB : utilisez rs.stepDown(60) pour forcer le primaire actuel à se retirer pendant 60 s ; le secondaire le plus à jour est élu automatiquement et devient primaire.","Redis : sur le nouveau candidat master, REPLICAOF NO ONE; puis sur les autres nœuds REPLICAOF new_master port; (avec Sentinel, SENTINEL FAILOVER master_name).","Pointez la chaîne de connexion / le routage de l'application vers le nouveau primaire (VIP, nom de service ou chaîne de connexion).","Vérifiez : le nouveau primaire accepte les écritures, l'ancien primaire réplique en tant que standby avec un retard ~0, et l'application ne présente aucune erreur."],"related":["promote-failover","replication-status","list-replicas"],"playbook":"replication-lag"},"emergency_check":{"kind":"step","title":"Transition d'urgence — le primaire est-il vraiment tombé ?","detail":"Avant de forcer un failover, vérifiez que le primaire est réellement injoignable et évaluez le risque de split-brain. Si le primaire est en fait vivant (par ex. une coupure réseau passagère), une transition forcée aboutit à deux copies distinctes accessibles en écriture (split-brain). Comparez aussi le retard pour choisir le standby le plus à jour.","command":{"oracle":"-- try to reach the primary: tnsping primary_db; and on the standby: SELECT thread#, MAX(sequence#) FROM v$archived_log GROUP BY thread#;","postgres":"SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();   -- choose the highest LSN among the standbys","mysql":"SHOW REPLICA STATUS\\G   -- choose the candidate with the largest Exec_Source_Log_Pos","mssql":"SELECT drs.replica_id, drs.last_hardened_lsn FROM sys.dm_hadr_database_replica_states drs;","mongodb":"rs.status()   -- the member with the highest optime is the primary candidate","redis":"redis-cli INFO replication   # choose the one with the highest slave_repl_offset","elasticsearch":"GET _cat/nodes?v   # verify whether the master node is reachable"},"choices":[{"label":"Oui, le primaire est tombé — forcez la transition","next":"emergency"},{"label":"Incertain / le primaire peut revenir","next":"planned"}]},"emergency":{"kind":"solution","title":"Failover d'urgence (primaire tombé, transition avec risque de perte de données)","detail":"Le primaire est injoignable et vous ne pouvez pas attendre son retour. Vous devez forcer la promotion du standby ; avec une réplication asynchrone, une partie des dernières transactions peut être perdue. Votre priorité est de rétablir le service, puis d'identifier la fenêtre de perte et de réintégrer l'ancien primaire en toute sécurité.","steps":["Vérifiez que le primaire est vraiment tombé (isolez l'ancien primaire du réseau / arrêtez-le pour éviter le split-brain — fencing/STONITH).","Choisissez le standby le plus à jour : comparez retard/offset avant d'agir afin de promouvoir celui qui présente la moindre perte de données.","Oracle Data Guard : DGMGRL> FAILOVER TO 'standby_db';  (ou SQL> ALTER DATABASE ACTIVATE STANDBY DATABASE;).","PostgreSQL : promouvez immédiatement le standby avec SELECT pg_promote();; lorsque l'ancien primaire revient, ne l'OUVREZ PAS comme primaire sans pg_rewind (risque de split-brain).","SQL Server AG : ALTER AVAILABILITY GROUP [ag] FORCE_FAILOVER_ALLOW_DATA_LOSS;  (une perte de données est possible avec un réplica asynchrone).","MongoDB : s'il ne reste plus de majorité, reconfigurez les membres survivants avec rs.reconfig(cfg, {force:true}).","Redis : sans Sentinel, exécutez REPLICAOF NO ONE sur le nouveau candidat master ; rattachez-y les autres ; ne laissez jamais l'ancien master rejoindre automatiquement.","Redirigez l'application vers le nouveau primaire ; identifiez et signalez ensuite la fenêtre de données perdues (transactions perdues) et reconstruisez proprement l'ancien primaire comme nouveau standby."],"related":["promote-failover","replication-status","list-replicas"],"playbook":"db-down"},"drill":{"kind":"solution","title":"Exercice / test de disponibilité (valider sans toucher à la production)","detail":"Prouvez que vous êtes prêt pour un failover sans effectuer de transition réelle. Vérifiez la santé des réplicas, que le retard est acceptable et que l'application peut être redirigée, sans impact sur la production. En option, répétez un véritable switchover sur une copie isolée.","steps":["Mesurez le retard et vérifiez qu'il est sous le seuil (PG : pg_stat_replication.replay_lag ; MySQL : Seconds_Behind_Source ; Oracle : v$dataguard_stats ; Mongo : rs.printSecondaryReplicationInfo()).","Vérifiez la santé des réplicas : tous les standbys répliquent et le mode synchrone est actif sur les membres attendus (SQL Server : synchronization_state_desc='SYNCHRONIZED').","Test de lecture/rôle : exécutez une requête en lecture seule sur le standby et confirmez une réponse sans délai (PG hot standby, Oracle Active Data Guard, read replica).","Répétition du routage applicatif : dans un environnement de test, pointez la chaîne de connexion/le nom de service vers le nouveau primaire et vérifiez que l'application se connecte sans problème (ne touchez pas au trafic de production).","Répétez un véritable switchover de bout en bout sur une copie isolée et mesurez la durée de transition (RTO).","Répétez aussi les étapes de failback : reconstruction de l'ancien primaire avec pg_rewind / DG reinstate / AG resume.","Consignez constats, durées et lacunes ; mettez à jour le runbook et planifiez l'exercice périodiquement."],"related":["replication-status","list-replicas","promote-failover"],"playbook":"replication-lag"}}},"solver/security-incident.json":{"id":"security-incident","title":"Incident de sécurité / accès suspect","icon":"🚨","category":"Sécurité","intro":"Pour un accès suspect, une modification de privilèges inattendue ou une possible fuite de données, un arbre de décision qui mène, selon le symptôme, aux bonnes étapes de réponse (verrouiller le compte, révoquer les privilèges excessifs, couper les connexions, déterminer l'étendue) et au durcissement permanent après incident.","start":"root","nodes":{"root":{"kind":"step","title":"Qu'avez-vous constaté ?","detail":"Choisissez le type d'incident. Le symptôme est-il une connexion suspecte ou une rafale d'échecs de connexion, une modification de privilèges inattendue (GRANT/rôle), une possible fuite de données / un export anormal, ou l'incident est-il clos et vous procédez au durcissement permanent ? Choisissez la branche la plus appropriée selon votre première observation. Notez d'abord l'heure de l'incident et le compte/la ressource concernés.","choices":[{"label":"Connexion suspecte / rafale d'échecs de connexion","next":"identity"},{"label":"Modification de privilèges inattendue (GRANT/rôle)","next":"privilege"},{"label":"Fuite de données / export anormal","next":"data"},{"label":"Mesures permanentes après incident (durcissement)","next":"harden"}]},"identity":{"kind":"step","title":"Incident d'identité : quel symptôme ?","detail":"De nombreux échecs de connexion indiquent une attaque par force brute / credential stuffing. À l'inverse, une connexion réussie mais inattendue (depuis une IP/à une heure inhabituelle) indique que le compte a pu être compromis. Dans les deux cas, collectez d'abord les preuves, puis réagissez.","command":{"oracle":"SELECT username, os_username, userhost, timestamp, returncode FROM dba_audit_session WHERE returncode <> 0 ORDER BY timestamp DESC;","postgres":"-- failed logins in the log file: grep 'password authentication failed' postgresql.log | awk '{print $NF}' | sort | uniq -c | sort -rn","mysql":"SELECT * FROM performance_schema.host_cache ORDER BY COUNT_AUTHENTICATION_ERRORS DESC;   -- also 'Access denied' in the error log","mssql":"-- failed logins in the error log: EXEC xp_readerrorlog 0,1,N'Login failed';  also SELECT login_name, COUNT(*) FROM sys.dm_exec_sessions GROUP BY login_name;","mongodb":"-- audit log: grep 'authenticate' audit.json | grep '\"result\":18'  (18 = AuthenticationFailed)","redis":"ACL LOG   # shows failed authentication/command attempts","elasticsearch":"GET .security-audit-*/_search { \"query\": { \"match\": { \"event.action\": \"authentication_failed\" } } }"},"choices":[{"label":"Rafale d'échecs de connexion (force brute)","next":"id_bruteforce"},{"label":"Connexion réussie mais inattendue (compromission)","next":"id_compromised"}]},"id_bruteforce":{"kind":"solution","title":"Force brute → bloquer la source et protéger le compte","detail":"Lors d'une rafale d'échecs de connexion, l'accès n'a peut-être pas encore été obtenu ; l'objectif est de bloquer la source, de protéger le compte par verrouillage/délai et d'écarter toute intrusion réussie.","steps":["Identifiez les IP attaquantes (requêtes ci-dessus) et bloquez-les au niveau réseau : pare-feu / reject dans pg_hba.conf / ACL du listener / groupe de sécurité.","Verrouillez temporairement le compte ciblé : Oracle : ALTER USER target_user ACCOUNT LOCK;  PostgreSQL : ALTER ROLE target_user NOLOGIN;  SQL Server : ALTER LOGIN [target_user] DISABLE;  MySQL : ALTER USER 'target_user'@'%' ACCOUNT LOCK;  Redis : ACL SETUSER target_user off.","Appliquez une limite de tentatives de connexion / politique de verrouillage : Oracle : FAILED_LOGIN_ATTEMPTS dans le profil ; PostgreSQL : le module auth_delay (shared_preload_libraries ; il retarde les tentatives échouées au lieu de verrouiller) ; MySQL : FAILED_LOGIN_ATTEMPTS ... PASSWORD_LOCK_TIME.","Réinitialisez de toute façon le mot de passe du compte ciblé avec une valeur robuste (une tentative a pu réussir).","Vérifiez si une tentative a réussi : recherchez une connexion réussie depuis la même IP ; si c'est le cas, suivez aussi la branche compromission.","Consignez l'incident avec une chronologie et suivez l'étape de durcissement pour les mesures permanentes (« Mesures permanentes après incident » depuis la racine)."],"related":["change-password","list-users"],"playbook":"harden-sql"},"id_compromised":{"kind":"solution","title":"Compte compromis → arrêter, enquêter, renouveler les identifiants","detail":"Une connexion réussie inattendue indique que le compte a été compromis. Arrêtez d'abord le compte et ses sessions, examinez ce qu'il a fait, puis réinitialisez le mot de passe et restreignez la source d'accès.","steps":["Verrouillez immédiatement le compte (commandes LOCK/DISABLE ci-dessus) et terminez ses sessions actives : Oracle : ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;  PostgreSQL : SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE usename='app_user';  SQL Server : KILL <spid>;  MySQL : KILL <id>;  Redis : CLIENT KILL.","Examinez ce que le compte a fait dans ses sessions : les requêtes exécutées, les tables consultées et toute opération GRANT/CREATE dans la piste d'audit/les journaux.","Réinitialisez le mot de passe avec une valeur robuste et renouvelez tous les identifiants de service/d'application associés.","Recherchez tout nouveau compte ou toute modification de privilèges que l'attaquant a pu créer (suivez aussi la branche privilèges).","Restreignez la source d'accès : n'autorisez le compte à se connecter que depuis des IP connues (pg_hba.conf / ACL du listener / pare-feu).","Suivez l'étape de durcissement pour les mesures permanentes (« Mesures permanentes après incident » depuis la racine)."],"related":["change-password","list-users","show-privileges"],"playbook":"harden-sql"},"privilege":{"kind":"solution","title":"Élévation de privilèges → auditer et révoquer les privilèges excessifs","detail":"Un GRANT inattendu, une attribution de rôle ou un nouveau compte à privilèges élevés peut constituer la deuxième étape d'une compromission (persistance/élévation de privilèges). Auditez les modifications de privilèges récentes, révoquez celles qui ne sont pas légitimes et revenez au principe du moindre privilège.","steps":["Auditez les modifications de privilèges récentes : Oracle : SELECT * FROM dba_audit_trail WHERE action_name IN ('GRANT','REVOKE','CREATE USER') ORDER BY timestamp DESC;  SQL Server : événements GRANT dans la default trace / Server Audit et SELECT * FROM sys.server_permissions;  MySQL : entrées GRANT dans le journal d'audit et SELECT * FROM mysql.user;  MongoDB : db.getRoles({showPrivileges:true}).","Listez les privilèges actuels : Oracle : SELECT grantee, privilege FROM dba_sys_privs WHERE grantee='APP_USER';  PostgreSQL : SELECT * FROM information_schema.role_table_grants WHERE grantee='app_user';  MySQL : SHOW GRANTS FOR 'app_user'@'%';","Révoquez les privilèges excessifs illégitimes : Oracle : REVOKE DBA FROM app_user;  PostgreSQL : REVOKE ALL ON ... FROM app_user; ALTER ROLE app_user NOSUPERUSER;  SQL Server : ALTER SERVER ROLE [sysadmin] DROP MEMBER [app_user];  MySQL : REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'app_user'@'%';","Recherchez et supprimez les comptes cachés/de secours que l'attaquant a pu créer (enregistrements CREATE USER inattendus).","Examinez et restreignez les privilèges excessifs accordés à PUBLIC ou à des rôles étendus.","Vérifiez que les modifications correspondent à une demande de changement légitime ; traitez tout ce qui ne correspond pas comme faisant partie de l'incident et suivez l'étape de durcissement."],"related":["show-privileges","list-users","create-user-grant"],"playbook":"harden-sql"},"data":{"kind":"solution","title":"Fuite de données → couper l'accès et déterminer l'étendue","detail":"Des requêtes/exports anormalement volumineux ou des connexions externes inattendues indiquent une fuite de données. Coupez d'abord les connexions dangereuses actives, puis déterminez l'étendue avec la piste d'audit/les journaux : quelles données ont été consultées, par qui et quand.","steps":["Examinez les sources de connexion actives : PostgreSQL : SELECT pid, usename, client_addr, state, query FROM pg_stat_activity WHERE client_addr IS NOT NULL;  Oracle : SELECT sid, serial#, username, machine, program FROM v$session;  SQL Server : SELECT * FROM sys.dm_exec_connections;  MongoDB : db.currentOp(true);  Redis : CLIENT LIST.","Coupez immédiatement les connexions suspectes/externes et verrouillez le compte concerné (commandes KILL/LOCK).","Bloquez la source au niveau réseau : fermez le bloc d'IP suspect avec le pare-feu / pg_hba.conf / ACL du listener.","Examinez le journal d'audit : déterminez quelles tables ont fait l'objet de SELECT/EXPORT, le nombre de lignes et la fenêtre temporelle (Oracle unified_audit_trail ; SQL Server sys.fn_get_audit_file ; journaux pgaudit de PostgreSQL ; MongoDB auditLog).","Recherchez des traces d'export : grands jeux de résultats, COPY / SELECT ... INTO OUTFILE, mongoexport, schémas de lecture en masse.","Documentez l'étendue des données touchées (quelles tables, quels champs personnels/confidentiels) et le volume possible ; évaluez les obligations légales/de notification et suivez l'étape de durcissement."],"related":["show-privileges","list-users"],"playbook":"harden-sql"},"harden":{"kind":"solution","title":"Durcissement permanent après incident","detail":"Après la réponse d'urgence, appliquez des contrôles permanents pour éviter la récidive : activez l'audit, imposez le moindre privilège, restreignez l'accès réseau et consignez intégralement l'incident.","steps":["Activez l'audit : capturez les événements critiques (connexions, GRANT/REVOKE, accès aux tables sensibles) dans la piste d'audit ; pour les détails, suivez l'arbre « Mise en place de l'audit ».","Moindre privilège : n'accordez à chaque compte que les privilèges nécessaires, séparez les comptes partagés/de service, désactivez les comptes inutilisés.","Politique de mots de passe/d'identité : mots de passe robustes, renouvellement régulier, authentification multifacteur si possible et identité centralisée (LDAP/Kerberos).","Restriction réseau : n'ouvrez la base qu'aux serveurs d'application (pare-feu, groupe de sécurité, pg_hba.conf, ACL du listener) ; bloquez l'accès direct depuis Internet.","Chiffrement : activez TLS en transit et, si nécessaire, le chiffrement au repos.","Supervision/alertes : définissez des alertes sur les pics d'échecs de connexion, les nouveaux GRANT et les volumes de données anormaux.","Rapport d'incident : consignez la chronologie, les actifs touchés, les actions menées et les enseignements ; mettez à jour le runbook."],"related":["list-users","show-privileges","change-password"]}}},"solver/audit-setup.json":{"id":"audit-setup","title":"Mise en place de l'audit","icon":"🧾","category":"Sécurité","intro":"Un arbre de décision qui, selon ce que vous voulez auditer (connexions/sessions, DDL/modifications de schéma, accès aux données ou modifications de privilèges), active l'audit pour ce périmètre sur le bon moteur de base de données, puis fournit les étapes de lecture des enregistrements et de rétention/rotation.","start":"root","nodes":{"root":{"kind":"step","title":"Que voulez-vous auditer ?","detail":"Choisissez le périmètre d'audit. Tout auditer crée du bruit et des coûts ; limitez-le à vos besoins. Voulez-vous suivre les événements de connexion/session, les DDL/modifications de schéma, l'accès aux données (DML/SELECT) ou les modifications de privilèges (GRANT/REVOKE) ?","choices":[{"label":"Connexions / sessions (login, connect)","next":"login"},{"label":"DDL / modifications de schéma (CREATE/ALTER/DROP)","next":"ddl"},{"label":"Accès aux données (DML / SELECT)","next":"dml"},{"label":"Modifications de privilèges (GRANT / REVOKE)","next":"grants"},{"label":"Lecture des enregistrements d'audit et rétention / rotation","next":"retention"}]},"login":{"kind":"solution","title":"Audit des connexions / sessions","detail":"Enregistre les connexions réussies et échouées : qui s'est connecté, d'où et quand. C'est la base de la détection des accès non autorisés et des tentatives de force brute. Activez la politique correspondante pour chaque moteur, puis lisez et conservez les enregistrements.","steps":["Oracle (Unified Auditing) : CREATE AUDIT POLICY login_pol ACTIONS LOGON, LOGOFF;  AUDIT POLICY login_pol;  (pour les échecs : AUDIT POLICY login_pol WHENEVER NOT SUCCESSFUL;).","PostgreSQL : définissez log_connections=on et log_disconnections=on dans postgresql.conf et rechargez ; les connexions sont journalisées sans pgaudit.","MySQL/MariaDB : installez le plugin audit_log (INSTALL PLUGIN audit_log ...) et exécutez SET GLOBAL audit_log_policy='LOGINS'; (MariaDB : server_audit_events='CONNECT').","SQL Server : créez un Server Audit (CREATE SERVER AUDIT ...), ajoutez FAILED_LOGIN_GROUP et SUCCESSFUL_LOGIN_GROUP à une Server Audit Specification, et activez les deux (ENABLE).","MongoDB : démarrez avec --auditDestination file --auditFilter '{ atype: { $in: [\"authenticate\", \"logout\"] } }' (Enterprise/Percona).","Redis : suivez les tentatives d'authentification/de commande échouées avec ACL LOG (pour les détails, utilisez le journal ACL, pas 'notify').","Elasticsearch : définissez xpack.security.audit.enabled: true dans elasticsearch.yml et ajoutez authentication_success, authentication_failed à la liste include.","Pour lire et conserver/faire tourner les enregistrements, suivez l'étape « Lecture des enregistrements d'audit et rétention / rotation » depuis la racine."],"related":["list-users","change-password"]},"ddl":{"kind":"solution","title":"Audit des DDL / modifications de schéma","detail":"Enregistre les modifications de schéma telles que CREATE/ALTER/DROP. Sert à détecter les modifications d'objets non autorisées, les tables cachées/portes dérobées et la dérive de schéma.","steps":["Oracle : CREATE AUDIT POLICY ddl_pol ACTIONS CREATE TABLE, ALTER TABLE, DROP TABLE, CREATE USER, ALTER USER;  AUDIT POLICY ddl_pol;","PostgreSQL (pgaudit) : chargez-le avec shared_preload_libraries='pgaudit', puis ALTER SYSTEM SET pgaudit.log='ddl'; SELECT pg_reload_conf();","MySQL/MariaDB : avec audit_log, SET GLOBAL audit_log_policy='ALL'; puis filtrez les instructions DDL dans le journal (MariaDB : server_audit_events='QUERY_DDL').","SQL Server : ajoutez SCHEMA_OBJECT_CHANGE_GROUP (et DATABASE_OBJECT_CHANGE_GROUP) à une Database Audit Specification et activez-la (ENABLE).","MongoDB : --auditFilter '{ atype: { $in: [\"createCollection\",\"dropCollection\",\"createIndex\",\"dropIndex\",\"createDatabase\",\"dropDatabase\"] } }'.","Elasticsearch : ajoutez les événements liés à index_creation/index_deletion à la liste include de l'audit ; pour les modifications de schéma (mapping), journalisez les appels _mapping en tant que requêtes.","Pour lire et conserver/faire tourner les enregistrements, suivez l'étape « Lecture des enregistrements d'audit et rétention / rotation » depuis la racine."],"related":["show-privileges"]},"dml":{"kind":"solution","title":"Audit des accès aux données (DML / SELECT)","detail":"Enregistre les accès en lecture/écriture (SELECT/INSERT/UPDATE/DELETE) aux tables sensibles. Comme le volume peut être élevé, limitez l'audit aux objets sensibles. Indispensable pour la détection des fuites et la conformité (KVKK/RGPD).","steps":["Restreignez le périmètre : n'auditez que les tables/colonnes sensibles, pas toutes les données (pour maîtriser le bruit et les coûts).","Oracle : CREATE AUDIT POLICY dml_pol ACTIONS SELECT, INSERT, UPDATE, DELETE ON HR.SALARIES;  AUDIT POLICY dml_pol;","PostgreSQL (pgaudit) : pour une journalisation large, utilisez ALTER SYSTEM SET pgaudit.log='read,write'; ou, pour un audit ciblé au niveau objet, n'accordez au rôle d'audit des privilèges que sur les tables concernées et définissez pgaudit.role.","MySQL/MariaDB : ne journalisez que les tables/utilisateurs concernés avec un filtre audit_log (audit_log_filter / MariaDB server_audit_events='QUERY_DML').","SQL Server : ajoutez les actions SELECT, INSERT, UPDATE, DELETE pour des tables précises et un principal (par ex. public) à une Database Audit Specification et activez-la (ENABLE).","MongoDB : --auditFilter '{ atype: \"authCheck\", \"param.command\": { $in: [\"find\",\"insert\",\"update\",\"delete\"] }, \"param.ns\": \"db.sensitive\" }' (surveillez le volume).","Elasticsearch : ajoutez access_granted/access_denied à la liste include de l'audit et utilisez emit_request_body pour voir quel index une requête cible (prudence lors de la journalisation des corps pour les données sensibles).","Pour lire et conserver/faire tourner les enregistrements, suivez l'étape « Lecture des enregistrements d'audit et rétention / rotation » depuis la racine."],"related":["show-privileges","list-users"]},"grants":{"kind":"solution","title":"Audit des modifications de privilèges (GRANT / REVOKE)","detail":"Enregistre les modifications de privilèges et de rôles (GRANT/REVOKE, attribution de rôle, création d'utilisateur). C'est la base de la détection des élévations de privilèges et des comptes portes dérobées ; un audit généralement peu volumineux mais à forte valeur.","steps":["Oracle : CREATE AUDIT POLICY priv_pol ACTIONS GRANT, REVOKE, CREATE USER, ALTER USER, CREATE ROLE;  AUDIT POLICY priv_pol;","PostgreSQL (pgaudit) : ALTER SYSTEM SET pgaudit.log='role'; SELECT pg_reload_conf();  (capture les événements GRANT/REVOKE/CREATE ROLE).","MySQL/MariaDB : journalisez les instructions GRANT/REVOKE/CREATE USER avec audit_log (politique 'ALL' ou DDL) et conservez les instructions ACL dans le filtre.","SQL Server : ajoutez SERVER_PERMISSION_CHANGE_GROUP, SERVER_ROLE_MEMBER_CHANGE_GROUP et DATABASE_PERMISSION_CHANGE_GROUP à une Server Audit Specification et activez-la (ENABLE).","MongoDB : --auditFilter '{ atype: { $in: [\"grantRolesToUser\",\"revokeRolesFromUser\",\"createUser\",\"dropUser\",\"createRole\"] } }'.","Elasticsearch : pour les modifications de rôles/d'utilisateurs, journalisez les événements de requêtes liés à la sécurité (appels d'API realm/role) dans la piste d'audit.","Pour lire et conserver/faire tourner les enregistrements, suivez l'étape « Lecture des enregistrements d'audit et rétention / rotation » depuis la racine."],"related":["show-privileges","create-user-grant","list-users"]},"retention":{"kind":"solution","title":"Lecture des enregistrements d'audit et rétention / rotation","detail":"L'audit n'est utile que si les enregistrements sont lus et conservés. Définissez ici comment interroger les enregistrements, où ils sont conservés et la politique de rétention/rotation qui maîtrise leur croissance.","steps":["Lisez les enregistrements : Oracle : SELECT event_timestamp, dbusername, action_name, object_name, sql_text FROM unified_audit_trail ORDER BY event_timestamp DESC;  PostgreSQL : la sortie de pgaudit va dans le fichier journal du serveur (log_directory) ; filtrez les lignes 'AUDIT:' avec grep.","SQL Server : SELECT * FROM sys.fn_get_audit_file('D:\\audit\\*.sqlaudit', DEFAULT, DEFAULT);  MySQL : lisez le fichier audit_log (JSON/XML) ou le server_audit.log de MariaDB ; MongoDB : analysez audit.json ; Elasticsearch : interrogez les index .security-audit-*.","Définissez la durée de rétention (selon les exigences de conformité, par ex. 90 jours / 1 an) et inscrivez-la dans la politique.","Rotation/nettoyage : Oracle : archivez/purgez les anciens enregistrements avec DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP et CLEAN_AUDIT_TRAIL ; pour les journaux fichiers PostgreSQL/MySQL/MongoDB, effectuez la rotation par taille/durée avec logrotate.","Elasticsearch : définissez une politique ILM (rollover + delete) pour les index d'audit.","Envoyez les enregistrements d'audit vers un emplacement immuable (WORM) dont l'accès est séparé de celui des DBA (séparation des tâches) ; restreignez l'accès.","Surveillez la croissance du stockage : vérifiez périodiquement que l'audit ne remplit pas le disque et n'affecte pas excessivement les performances."],"related":["list-users","show-privileges"]}}},"solver/decide-ha.json":{"id":"decide-ha","title":"Décision : quelle solution HA/DR ?","icon":"🧭","category":"Guide de décision","intro":"Le choix d'une solution de haute disponibilité et de reprise après sinistre ne commence pas par la technologie mais par l'équilibre entre les exigences métier (RTO/RPO), le besoin de répartition géographique et le coût. En partant de votre tolérance à l'interruption et à la perte de données, cet arbre vous oriente vers la bonne approche parmi réplicas synchrones, standbys asynchrones, clusters à failover automatique et sauvegarde+PITR.","start":"root","nodes":{"root":{"kind":"step","title":"Quelle est votre tolérance à la perte de données (RPO) ?","detail":"Commencez par la contrainte la plus forte : en cas de sinistre, combien de secondes de données pouvez-vous vous permettre de perdre ? Si une perte de données nulle (RPO=0) est exigée, la réplication synchrone est obligatoire ; si quelques secondes/minutes sont acceptables, des solutions asynchrones et des approches moins coûteuses deviennent possibles. L'objectif de RPO conditionne généralement toutes les autres décisions.","choices":[{"label":"Perte de données nulle obligatoire (RPO = 0)","next":"rpo_zero"},{"label":"Quelques secondes/minutes de perte de données sont acceptables","next":"rpo_soft"}]},"rpo_zero":{"kind":"step","title":"Une reprise automatique et rapide (RTO faible) est-elle aussi requise ?","detail":"Vous voulez une perte de données nulle ; mais en combien de temps devez-vous être de nouveau opérationnel quand le primaire tombe ? Failover automatique en quelques secondes, ou une courte fenêtre d'intervention manuelle est-elle acceptable ? Cela fait la différence entre un simple réplica synchrone et un cluster entièrement automatisé.","choices":[{"label":"Failover automatique en quelques secondes requis","next":"sol_cluster"},{"label":"Une reprise manuelle/planifiée suffit ; la perte nulle prime","next":"sol_sync"}]},"rpo_soft":{"kind":"step","title":"Une autre région/zone géographique est-elle requise (DR) ?","detail":"Si une certaine perte de données est acceptable, la vraie question est le périmètre de résilience : devez-vous être protégé contre la perte d'un data center/d'une région entière, ou une protection contre la panne d'un seul nœud dans la même région suffit-elle ? Si la répartition géographique est requise, un standby asynchrone s'impose ; sinon, on évalue les options privilégiant le coût.","choices":[{"label":"Oui, une région distincte contre les sinistres géographiques est obligatoire","next":"sol_async"},{"label":"Non, budget et simplicité priment","next":"budget"}]},"budget":{"kind":"step","title":"Pouvez-vous accepter un temps de reprise long (RTO élevé) ?","detail":"Vous restez dans une seule région et tolérez une certaine perte de données. Visez-vous le coût le plus bas, ou voulez-vous un nœud standby qui reprend rapidement en cas de panne ? Si une reprise de plusieurs heures est acceptable, sauvegarde+PITR seul est la voie la plus économique ; sinon, il vous faut un warm standby.","choices":[{"label":"Oui, une reprise de plusieurs heures est acceptable (le moins cher)","next":"sol_backup"},{"label":"Non, un warm standby qui reprend en quelques minutes est requis","next":"sol_async"}]},"sol_sync":{"kind":"solution","title":"Recommandation : réplica synchrone (perte de données nulle)","detail":"Le primaire ne termine pas un commit tant qu'au moins un standby ne l'a pas acquitté ; ainsi, même si le primaire est perdu, aucune transaction acquittée n'est perdue (RPO=0). La solution idéale au sein d'une région sur un réseau à faible latence.","steps":["Pourquoi : un commit ne se termine pas tant que les transactions acquittées ne sont pas écrites sur le standby, donc la perte de données est mathématiquement nulle.","Quand : OLTP financier/critique, même région (zone de disponibilité identique/adjacente), avec un réseau à faible RTT.","Exemple : PostgreSQL synchronous_commit + synchronous_standby_names, MySQL Group Replication / semisync, Oracle Data Guard SYNC (Maximum Protection/Availability).","Attention : l'acquittement synchrone ajoute la latence réseau à chaque commit ; si le standby ralentit/se déconnecte et qu'il n'existe qu'une seule copie synchrone, le primaire peut aussi cesser d'accepter les écritures. Concevez avec au moins deux standbys ou un repli adapté.","Attention : ne répliquez pas de façon synchrone vers une région distante/à latence élevée ; les performances en écriture s'effondrent. Utilisez la réplication asynchrone pour le DR géographique."],"related":["replication-status","promote-failover"]},"sol_async":{"kind":"solution","title":"Recommandation : standby asynchrone (DR géographique)","detail":"Le standby applique les modifications en flux sans retarder les commits du primaire. C'est la méthode standard pour conserver une copie dans une autre région sans nuire aux performances en écriture ; en contrepartie, une petite fenêtre de perte de données (égale au retard de réplication) est acceptée.","steps":["Pourquoi : le primaire n'attend pas l'acquittement du standby, donc la réplication vers une région distante n'ajoute aucune latence d'écriture.","Quand : lorsqu'une reprise après sinistre au niveau région/data center est nécessaire et qu'une perte de données de l'ordre de quelques secondes est acceptable.","Exemple : PostgreSQL streaming replication (async), réplication asynchrone MySQL, Oracle Data Guard ASYNC (Maximum Performance).","Attention : lors d'un failover, vous pouvez perdre autant de données que le retard ; surveillez en continu le retard de réplication et mettez en place des alertes de seuil.","Attention : la reprise se fait généralement manuellement ou par une orchestration externe ; une procédure de failover claire et des exercices réguliers sont indispensables."],"related":["replication-status","promote-failover"]},"sol_cluster":{"kind":"solution","title":"Recommandation : cluster à failover automatique","detail":"Une solution de cluster combinant supervision de santé, élection de leader et reprise automatique ; elle vise à la fois un RTO faible (failover automatique en quelques secondes) et un RPO faible/nul avec une copie synchrone. La charge opérationnelle est plus élevée, mais l'intervention humaine est réduite au minimum.","steps":["Pourquoi : le gestionnaire de cluster détecte automatiquement une panne du primaire, promeut une copie saine et redirige le trafic ; aucune intervention manuelle en pleine nuit.","Quand : si un RTO faible et un RPO faible sont tous deux obligatoires et que l'équipe d'exploitation peut gérer la complexité supplémentaire.","Exemple : Patroni/etcd pour PostgreSQL, MySQL InnoDB Cluster (Group Replication + MySQL Router), SQL Server Always On Availability Groups, Oracle RAC + Data Guard avec Fast-Start Failover (le failover automatique requiert FSFO et un observer).","Attention : le risque de split-brain est réel ; ne déployez pas sans fencing/quorum adéquat (nombre impair de nœuds, stockage distribué fiable).","Attention : le cluster lui-même est une nouvelle surface de panne ; répétez régulièrement le failover et validez l'automatisation en conditions réelles."],"related":["promote-failover","replication-status"]},"sol_backup":{"kind":"solution","title":"Recommandation : sauvegarde + PITR (le plus économique)","detail":"Au lieu d'un standby en fonctionnement, seuls des sauvegardes régulières et la restauration à un instant donné (PITR) via les WAL/journaux archivés sont conservés. C'est la solution la moins coûteuse ; le prix à payer est une reprise qui peut prendre des heures (RTO élevé).","steps":["Pourquoi : aucun coût lié à un second nœud fonctionnant en permanence ; seuls le stockage des sauvegardes et les journaux archivés sont conservés.","Quand : systèmes non critiques à forte tolérance d'interruption ; environnements de dev/test ; lorsque budget et simplicité priment.","Exemple : PITR avec une sauvegarde physique + journaux archivés (PG base backup + WAL, Oracle RMAN + archivelog, MySQL xtrabackup + binlog).","Attention : le RTO est élevé — la durée de restauration + application des journaux croît avec la taille des données ; mesurez-la pour confirmer qu'elle tient dans le délai accepté.","Attention : une sauvegarde non testée n'est pas une sauvegarde ; effectuez régulièrement des exercices de restauration et la vérification des sauvegardes."],"related":["last-backup-status","logical-dump"]}}},"solver/decide-isolation.json":{"id":"decide-isolation","title":"Décision : quel niveau d'isolation ?","icon":"🧭","category":"Guide de décision","intro":"Le niveau d'isolation des transactions détermine l'équilibre entre concurrence et cohérence. Une isolation plus élevée signifie moins d'anomalies mais davantage de verrous/conflits et de nouvelles tentatives. Cet arbre vous aide à choisir entre Read Committed, Repeatable Read/Snapshot et Serializable selon les anomalies de lecture (dirty, non-repeatable, phantom) que vous pouvez tolérer.","start":"root","nodes":{"root":{"kind":"step","title":"Pouvez-vous tolérer les lectures sales (dirty reads) ?","detail":"Commencez par l'anomalie la plus élémentaire : une transaction doit-elle pouvoir lire des données qui ne sont pas encore validées (et peuvent être annulées) ? Presque aucune application sérieuse ne le souhaite ; dans la plupart des moteurs modernes, le comportement par défaut empêche déjà les lectures sales. On ne l'assouplit que pour des comptages approximatifs/analytiques et une vitesse maximale.","choices":[{"label":"Non, je dois lire uniquement des données validées","next":"committed"},{"label":"Oui, une lecture approximative suffit (rare)","next":"sol_read_uncommitted"}]},"committed":{"kind":"step","title":"Devez-vous voir le même résultat en relisant au sein de la même transaction ?","detail":"Les lectures sales étant exclues, la question suivante : si vous lisez deux fois la même ligne dans une transaction, est-ce un problème de voir une valeur différente parce que quelqu'un a validé entre-temps (non-repeatable read), ou de voir apparaître de nouvelles lignes (phantom) ? Une logique exigeant la cohérence d'un rapport/calcul nécessite généralement un snapshot cohérent.","choices":[{"label":"Non, chaque instruction peut voir les dernières données validées","next":"sol_read_committed"},{"label":"Oui, un snapshot cohérent est nécessaire pour toute la transaction","next":"consistent"}]},"consistent":{"kind":"step","title":"Les transactions concurrentes doivent-elles être logiquement entièrement ordonnées ?","detail":"Vous voulez des lectures cohérentes ; est-ce suffisant, ou les transactions concurrentes doivent-elles produire un résultat comme si elles s'étaient exécutées une par une, en séquence (serializable) ? Si vous pouvez tolérer des anomalies subtiles comme le write skew (par ex. deux transactions lisant des lignes différentes et écrivant de façon à s'invalider mutuellement), Snapshot suffit ; sinon, une sérialisation complète est requise.","choices":[{"label":"Non, un snapshot cohérent suffit","next":"sol_repeatable_read"},{"label":"Oui, aucune anomalie de concurrence n'est acceptable","next":"sol_serializable"}]},"sol_read_uncommitted":{"kind":"solution","title":"Recommandation : Read Uncommitted (cas particuliers uniquement)","detail":"L'isolation la plus faible ; elle autorise les lectures sales. Elle lit presque sans attendre les verrous, mais vous pouvez voir des données susceptibles d'être annulées. Déconseillée pour les applications généralistes.","steps":["Pourquoi : le lecteur n'attend pas le verrou de l'écrivain ; concurrence maximale mais aucune garantie de cohérence.","Quand : uniquement pour des métriques approximatives/compteurs de progression où l'exactitude importe peu.","Attention : dans PostgreSQL ce niveau se comporte comme Read Committed, et Oracle ne propose pas du tout Read Uncommitted ; les vraies lectures sales concernent surtout SQL Server, basé sur les verrous (NOLOCK).","Attention : « NOLOCK pour la vitesse » ne doit pas devenir une habitude ; cela comporte le risque de lire des lignes erronées/en double/manquantes."]},"sol_read_committed":{"kind":"solution","title":"Recommandation : Read Committed (OLTP par défaut)","detail":"Chaque instruction voit les dernières données validées à cet instant ; les lectures sales sont empêchées. C'est le niveau par défaut dans la plupart des moteurs et il offre le meilleur équilibre concurrence/cohérence pour les charges OLTP typiques.","steps":["Pourquoi : il empêche les lectures sales sans obliger la transaction à garder des verrous inutilement longtemps ; il offre une forte concurrence.","Quand : transactions OLTP courtes et ciblées ; le bon choix par défaut pour la grande majorité des applications web.","Exemple : Read Committed est le niveau par défaut dans PostgreSQL, SQL Server et Oracle (dans Oracle, la cohérence est assurée par un snapshot au niveau de l'instruction).","Attention : deux lectures dans la même transaction peuvent renvoyer des résultats différents (non-repeatable/phantom) ; n'y fondez pas une logique exigeant un reporting cohérent.","Attention : le niveau par défaut de MySQL/InnoDB est Repeatable Read ; tenez compte de cette différence de moteur lors d'une migration."]},"sol_repeatable_read":{"kind":"solution","title":"Recommandation : Repeatable Read / Snapshot (lectures cohérentes)","detail":"La transaction travaille sur un snapshot cohérent figé à son début (ou à sa première lecture) ; les lectures ne changent pas pendant la transaction. Avec MVCC, les lecteurs ne bloquent pas les écrivains et les écrivains ne bloquent pas les lecteurs.","steps":["Pourquoi : il fournit une vue figée pour toute la transaction ; il empêche les non-repeatable reads (et en pratique aussi les phantoms dans les moteurs MVCC), ce qui rend cohérente la logique de rapport/calcul à forte lecture.","Quand : rapports, rapprochements et calculs batch qui lisent plusieurs fois les mêmes données dans une transaction et exigent des résultats cohérents.","Exemple : MySQL/InnoDB Repeatable Read par défaut ; SQL Server SNAPSHOT (row versioning) ; PostgreSQL REPEATABLE READ ; Oracle SERIALIZABLE a en réalité une sémantique de snapshot.","Attention : il n'empêche pas les anomalies de sérialisation comme le write skew ; passez à Serializable si la règle métier l'exige.","Attention : le snapshot/row versioning peut faire croître tempdb ou l'undo/version store ; les transactions longues font gonfler les anciennes versions."]},"sol_serializable":{"kind":"solution","title":"Recommandation : Serializable (isolation complète)","detail":"L'isolation la plus élevée : les transactions concurrentes produisent un résultat comme si elles avaient été exécutées en série (une par une, en séquence). Elle empêche toutes les anomalies de lecture et le write skew ; le prix est davantage de conflits et la nécessité de nouvelles tentatives.","steps":["Pourquoi : il garantit que le résultat équivaut toujours à une exécution sérielle valide ; il élimine toutes les anomalies de concurrence.","Quand : logique portant des invariants critiques pour l'exactitude : contrôles de solde/stock, cas où une double réservation ne doit jamais se produire.","Exemple : PostgreSQL SSI (Serializable Snapshot Isolation), SQL Server SERIALIZABLE (verrous de plage) ; dans Oracle, la véritable sérialisation est gérée au niveau applicatif.","Attention : les échecs de sérialisation (par ex. 40001 dans PG) sont un comportement attendu ; une logique de nouvelle tentative qui réexécute automatiquement la transaction dans l'application est OBLIGATOIRE.","Attention : le débit peut chuter sous une charge très conflictuelle ; ne l'appliquez pas partout, mais seulement aux transactions où l'exactitude est critique."]}}},"solver/decide-sqlnosql.json":{"id":"decide-sqlnosql","title":"Décision : SQL ou NoSQL (quelle famille de bases) ?","icon":"🧭","category":"Guide de décision","intro":"Le choix de la bonne famille de bases de données dépend de la forme du modèle de données, de vos priorités de cohérence et d'échelle, et de la complexité de vos requêtes. Les généralisations comme « le NoSQL est plus moderne » sont trompeuses ; chaque famille est conçue pour des schémas d'accès précis. En partant de la structure des données, cet arbre vous oriente vers le bon outil parmi les moteurs relationnels, documents, clé-valeur et de recherche, et vous rappelle l'approche polyglotte si nécessaire.","start":"root","nodes":{"root":{"kind":"step","title":"Quelle est la nature des données et le schéma d'accès dominant ?","detail":"Commencez par la question la plus décisive : les données sont-elles des enregistrements structurés fortement liés, des documents au schéma flexible/variable, de simples recherches clé-valeur, ou un besoin particulier comme la recherche textuelle ? La forme des données et la façon de les interroger déterminent largement la famille.","choices":[{"label":"Relationnel, enregistrements structurés et requêtes complexes","next":"relational_check"},{"label":"Documents imbriqués au schéma flexible/variable","next":"sol_document"},{"label":"Clé-valeur simple, très faible latence/cache","next":"sol_keyvalue"},{"label":"Recherche plein texte / à facettes, analyse de logs","next":"sol_search"}]},"relational_check":{"kind":"step","title":"La priorité est-elle la cohérence forte (ACID) ou l'échelle horizontale ?","detail":"Les données semblent relationnelles ; mais quelle est votre priorité ? Les jointures multi-tables, l'intégrité transactionnelle et la cohérence forte (ACID) sont-elles essentielles, ou s'agit-il de faire monter horizontalement en charge un volume énorme qui ne tiendra pas sur un seul nœud ? Le modèle relationnel est le bon choix par défaut pour la plupart des charges ; il n'est mis à l'épreuve que lorsque l'échelle dépasse réellement un seul nœud.","choices":[{"label":"ACID, jointures et intégrité des requêtes complexes priment","next":"sol_relational"},{"label":"L'échelle avant tout, relations/jointures faibles","next":"sol_document"}]},"sol_relational":{"kind":"solution","title":"Recommandation : base de données relationnelle (SGBDR)","detail":"Le bon choix par défaut pour les charges à données structurées et fortement liées exigeant l'intégrité transactionnelle. Les garanties ACID, un moteur de requêtes SQL mature et les jointures lui permettent de répondre de façon fiable à des questions complexes.","steps":["Pourquoi : il offre des transactions ACID, l'intégrité des clés étrangères et une cohérence forte, ainsi qu'un planificateur mature qui optimise les requêtes complexes joignant plusieurs tables.","Quand : charges OLTP et de reporting où relations et cohérence sont critiques, comme la finance, les commandes/stocks, l'ERP/CRM.","Exemple : PostgreSQL, MySQL, Oracle, SQL Server ; pour les séries temporelles, des extensions comme PostgreSQL + TimescaleDB.","Attention : les modifications de schéma (migrations) exigent de la rigueur ; si elles sont fréquentes et imprévisibles, évaluez le modèle document.","Attention : si vous atteignez la limite d'écriture/de données d'un seul nœud, montez en charge avec des réplicas de lecture, le partitionnement et, si nécessaire, le sharding."],"related":["create-partition"]},"sol_document":{"kind":"solution","title":"Recommandation : base de données documents","detail":"Stocke sous leur forme naturelle (documents de type JSON) des données flexibles et imbriquées dont le schéma change fréquemment. Adaptée aux applications qui lisent/écrivent surtout via un seul document, ont besoin de peu de jointures et d'une échelle horizontale.","steps":["Pourquoi : le schéma est flexible ; les documents peuvent ajouter ou retirer des champs, récupérer des structures imbriquées en une seule lecture et se prêtent à l'échelle horizontale (sharding).","Quand : modèles de données centrés sur le document, à structure variable et évoluant rapidement, comme les catalogues, profils utilisateurs, contenus/CMS, enregistrements d'événements.","Exemple : MongoDB, Couchbase ; dans le monde relationnel, JSONB (PostgreSQL) offre une flexibilité similaire dans une certaine mesure.","Attention : les jointures multi-documents complexes et le reporting sont faibles ; si les relations dominent, le modèle relationnel convient mieux.","Attention : la responsabilité de la cohérence et de la validation du schéma passe en grande partie à l'application ; protégez la qualité des données dans le code."]},"sol_keyvalue":{"kind":"solution","title":"Recommandation : stockage clé-valeur / cache","detail":"Conçu pour les charges nécessitant des lectures/écritures très rapides par clé et aucune requête complexe. Il fonctionne généralement en mémoire et offre une latence de l'ordre de la microseconde/milliseconde ; idéal pour le cache, les sessions et les compteurs.","steps":["Pourquoi : latence extrêmement faible et débit élevé pour un accès simple par clé ; les structures en mémoire déchargent la base relationnelle.","Quand : couche de cache, stockage de sessions, compteurs de rate limiting, classements, files temporaires/pub-sub.","Exemple : Redis, Memcached ; Redis propose aussi des structures riches comme lists/sets/sorted sets et des options de persistance.","Attention : ce n'est généralement pas la source de vérité unique ; conservez les données sources dans une base durable et stockez ici les données dérivées/mises en cache.","Attention : la mémoire est limitée et coûteuse ; planifiez la politique d'éviction, les TTL et les paramètres de persistance/durabilité selon les besoins métier."]},"sol_search":{"kind":"solution","title":"Recommandation : moteur de recherche / d'analyse","detail":"Un moteur basé sur un index inversé, spécialisé dans la recherche plein texte, le classement par pertinence, le filtrage à facettes et l'analyse de logs/textes à fort volume. Il offre l'expérience de recherche que les requêtes LIKE relationnelles ne peuvent pas fournir.","steps":["Pourquoi : grâce à l'index inversé et au scoring, il réalise recherche plein texte, autocomplétion, facettes et agrégations avec de hautes performances.","Quand : recherche de produits/sur un site, analyse de logs et d'observabilité, applications nécessitant une recherche classée par pertinence.","Exemple : Elasticsearch/OpenSearch, Apache Solr ; PostgreSQL tsvector peut suffire pour une recherche plein texte de taille moyenne.","Attention : ce n'est généralement pas le système de référence principal ; conservez les données principales dans une base relationnelle/documents et alimentez l'index de recherche à partir de là (CDC/sync).","Attention : les garanties de cohérence forte en temps réel sont faibles (indexation en quasi temps réel) ; tenez compte de la latence d'indexation dans la conception."],"related":["setup-cdc-replication"]}}},"solver/decide-shard-replica.json":{"id":"decide-shard-replica","title":"Décision : sharding ou réplication (montée en charge) ?","icon":"🧭","category":"Guide de décision","intro":"La décision de montée en charge d'une base dépend de la nature du goulot (lectures ou écritures) et du fait que les données tiennent ou non sur un seul nœud. Une mauvaise stratégie signifie une complexité inutile ou un goulot non résolu. Cet arbre vous rappelle d'abord les options les moins chères et les plus simples (montée en charge verticale, cache), puis vous aide à choisir correctement entre réplicas de lecture et sharding.","start":"root","nodes":{"root":{"kind":"step","title":"Avez-vous d'abord essayé la voie la plus simple : montée en charge verticale et optimisation des requêtes ?","detail":"Avant de passer à une architecture distribuée, prenez le gain le moins coûteux : renforcer le matériel (CPU/RAM/IOPS), ajouter les index manquants et corriger les requêtes coûteuses résout la plupart des goulots. N'ajoutez de la complexité que lorsque ces leviers sont épuisés.","choices":[{"label":"Non / je n'ai pas encore atteint la limite","next":"sol_vertical"},{"label":"Oui, la montée en charge verticale et l'optimisation ne suffisent pas","next":"bottleneck"}]},"bottleneck":{"kind":"step","title":"Le goulot concerne-t-il les lectures, ou les écritures/le volume de données ?","detail":"Isolez la source du problème de montée en charge : de nombreuses requêtes de lecture saturent-elles un seul nœud (charge en lecture), ou le débit d'écriture ou la taille totale des données dépasse-t-il la capacité d'un seul nœud (charge en écriture/volume) ? Cette distinction est la différence fondamentale entre réplicas et sharding.","choices":[{"label":"Le trafic de lecture domine, les écritures tiennent sur un seul nœud","next":"read_path"},{"label":"Le débit d'écriture ou le volume de données dépasse un seul nœud","next":"sol_sharding"}]},"read_path":{"kind":"step","title":"Les lectures sont-elles répétitives/cachables, ou fraîches et variées ?","detail":"Affinez encore le goulot de lecture : les mêmes résultats sont-ils lus encore et encore (un cache est très efficace), ou les lectures demandent-elles des données variées, larges et actuelles (une capacité de lecture supplémentaire est nécessaire) ? Cela tranche entre une couche de cache et des réplicas de lecture.","choices":[{"label":"Les mêmes données sont lues de façon répétée, un léger décalage est acceptable","next":"sol_cache"},{"label":"Lectures variées/fraîches, une capacité de lecture brute est nécessaire","next":"sol_read_replica"}]},"sol_vertical":{"kind":"solution","title":"Recommandation : d'abord montée en charge verticale + optimisation des requêtes","detail":"La première voie, et la moins coûteuse, avant de passer à une architecture distribuée. Renforcer le matériel et corriger les requêtes/index résout la plupart des problèmes de montée en charge sans ajouter de complexité opérationnelle.","steps":["Pourquoi : complexité et risque minimaux ; en restant sur un seul nœud, votre modèle transactionnel/de cohérence ne change pas et vous n'avez pas à découper le code applicatif.","Quand : si vous n'avez pas encore atteint le plafond matériel d'un seul nœud, ou si le goulot provient en réalité de mauvaises requêtes/d'index manquants.","Étape : profilez les requêtes lentes, ajoutez les index manquants, corrigez les plans/statistiques ; augmentez ensuite CPU/RAM/IOPS.","Attention : la montée en charge verticale est limitée et son coût croît de façon exponentielle ; un seul nœud reste un point de défaillance unique (mettez en place un réplica séparément pour la HA).","Attention : planifiez à l'avance l'étape suivante (réplicas/sharding) à l'approche du plafond ; les migrations précipitées sont risquées."]},"sol_read_replica":{"kind":"solution","title":"Recommandation : réplicas de lecture (montée en charge des lectures)","detail":"Les écritures restent sur un seul nœud (le primaire) tandis que le trafic de lecture est réparti sur un ou plusieurs réplicas. C'est la méthode standard pour accroître la capacité de lecture brute et la résilience des charges à forte lecture.","steps":["Pourquoi : les requêtes de lecture sont réparties sur plusieurs copies ; le primaire ne traite que les écritures, et le débit de lecture croît de façon presque linéaire.","Quand : charges à fort ratio lecture/écriture ; séparer les requêtes de reporting et d'analyse du primaire ; obtenir en même temps un standby pour la HA.","Exemple : réplicas en streaming PostgreSQL, réplicas MySQL + routage par proxy, séparation lecture/écriture dans l'application (read-write split).","Attention : avec des réplicas asynchrones, le retard de réplication crée un problème de read-your-writes (vous ne pouvez pas lire immédiatement ce que vous venez d'écrire) ; acheminez les lectures critiques vers le primaire.","Attention : les réplicas ne résolvent pas un goulot d'écriture ; le nœud écrivain unique reste la limite — si les écritures sont aussi un goulot, le sharding est requis."],"related":["replication-status"]},"sol_cache":{"kind":"solution","title":"Recommandation : couche de cache","detail":"Conserver les résultats fréquemment relus dans une couche rapide en mémoire réduit considérablement la charge de la base. Avec le bon schéma, elle répond sans interroger la base, réduisant à la fois latence et charge.","steps":["Pourquoi : les lectures répétées obtiennent une réponse à la vitesse de la mémoire sans jamais atteindre la base ; c'est la méthode de montée en charge des lectures au meilleur rapport bénéfice/coût.","Quand : les mêmes données sont lues de nombreuses fois et un léger décalage est acceptable ; par ex. fiches produits, paramètres, données de référence.","Exemple : le schéma cache-aside avec Redis/Memcached ; l'application consulte d'abord le cache puis, en cas d'absence, la base.","Attention : l'invalidation du cache est difficile ; concevez explicitement le TTL et la stratégie de mise à jour/purge à l'écriture.","Attention : ne vous fiez pas à un cache obsolète pour les lectures exigeant une cohérence forte (argent/stock) ; servez ces lectures depuis la source."]},"sol_sharding":{"kind":"solution","title":"Recommandation : sharding (montée en charge des écritures/données)","detail":"Les données sont découpées selon une clé de shard et réparties sur plusieurs nœuds indépendants ; chaque nœud détient une partie des données et leurs écritures. C'est la méthode pour faire monter en charge le débit d'écriture et le volume total de données, mais elle ajoute une complexité importante.","steps":["Pourquoi : écritures et stockage sont répartis sur plusieurs nœuds ; les limites d'un écrivain unique et d'un nœud unique disparaissent et la montée en charge horizontale devient possible.","Quand : lorsque le débit d'écriture ou la taille des données ne tient plus sur un seul nœud (ni avec des réplicas de lecture) ; lorsqu'il ne reste aucune autre option.","Exemple : routage des shards dans la couche applicative, Vitess (MySQL), Citus (PostgreSQL), sharding intégré dans les bases documents.","Attention : le choix de la clé de shard est critique et difficile à inverser ; une mauvaise clé crée des points chauds et une répartition inégale. Choisissez une clé à forte cardinalité correspondant au schéma d'accès.","Attention : les jointures, transactions et contraintes d'unicité inter-shards deviennent difficiles ; la complexité applicative, le rééquilibrage (resharding) et la charge opérationnelle augmentent fortement."],"related":["create-partition"]}}},"solver/decide-backup.json":{"id":"decide-backup","title":"Décision : quelle stratégie de sauvegarde ?","icon":"🧭","category":"Guide de décision","intro":"Une stratégie de sauvegarde se choisit en équilibrant trois facteurs : la perte de données tolérable (RPO), la taille des données et la rapidité de reprise exigée (RTO). Aucune méthode ne convient à tous les scénarios. En partant de l'objectif de RPO, cet arbre vous oriente entre dumps logiques, sauvegardes physiques + PITR et snapshots, et vous rappelle que la règle 3-2-1 et la vérification ne sont pas négociables.","start":"root","nodes":{"root":{"kind":"step","title":"Quel est votre objectif de perte de données (RPO) ?","detail":"Commencez par la contrainte la plus décisive : en cas de sinistre, combien de minutes/heures de données pouvez-vous vous permettre de perdre ? Si une perte quasi nulle est exigée, l'archivage continu des journaux (PITR) est obligatoire ; si une tolérance de sauvegarde quotidienne/hebdomadaire est acceptable, des méthodes plus simples suffisent. Le RPO définit le cadre de la stratégie.","choices":[{"label":"Quasi nulle — je dois pouvoir revenir à n'importe quel instant","next":"sol_physical_pitr"},{"label":"Des sauvegardes quotidiennes/périodiques (quelques heures de perte) sont acceptables","next":"size"},{"label":"Rappelez-moi d'abord les bonnes pratiques générales (3-2-1 / vérification)","next":"sol_321"}]},"size":{"kind":"step","title":"Quelles sont la taille des données et les exigences de portabilité ?","detail":"Si des sauvegardes périodiques suffisent, la question suivante porte sur l'échelle et l'objectif : la base est-elle petite/moyenne et voulez-vous une sauvegarde portable vers un autre environnement/une autre version, ou les données sont-elles volumineuses et votre priorité est-elle de revenir en quelques minutes ? Cela tranche entre un dump logique et un snapshot.","choices":[{"label":"Petite/moyenne, portable et compatible entre versions","next":"sol_logical"},{"label":"Données volumineuses, reprise très rapide requise (RTO faible)","next":"sol_snapshot"}]},"sol_logical":{"kind":"solution","title":"Recommandation : dump logique","detail":"Exporte les données au niveau logique (définitions de schéma + lignes) ; la sortie est soit du SQL/texte (format plain de pg_dump, mysqldump), soit un dump binaire propre à l'outil (pg_dump -Fc, Oracle Data Pump). Une méthode flexible et portable pour les bases petites et moyennes, les migrations, les montées de version et la restauration de tables/objets choisis.","steps":["Pourquoi : la sortie est portable (et lisible au format texte) ; adaptée au passage entre versions/moteurs et à la restauration d'une seule table/d'un seul objet.","Quand : données petites/moyennes, montées de version/migrations, copies d'environnements de développement, besoin de restauration logique (au niveau ligne).","Exemple : PostgreSQL pg_dump/pg_dumpall, MySQL mysqldump, Oracle Data Pump (expdp/impdp ; produit un .dmp binaire, utilisez impdp SQLFILE= pour voir le DDL en texte).","Attention : comme une restauration retraite les données, la sauvegarde comme la restauration sont très lentes pour les grandes bases (RTO élevé).","Attention : seul, il ne fournit pas un RPO faible ; la restauration à un instant donné exige une sauvegarde physique + l'archivage des journaux. Utilisez les options de cohérence appropriées pour obtenir un snapshot cohérent."],"related":["logical-dump","last-backup-status"]},"sol_physical_pitr":{"kind":"solution","title":"Recommandation : sauvegarde physique + WAL/journaux archivés (PITR)","detail":"Vous effectuez régulièrement des sauvegardes physiques de base et archivez en continu les journaux de transactions (WAL/redo/binlog). Cela permet une Point-in-Time Recovery à n'importe quel instant et réduit la perte de données à presque zéro.","steps":["Pourquoi : grâce à l'archivage continu des journaux, vous pouvez avancer jusqu'à n'importe quelle transaction postérieure à la dernière sauvegarde de base et revenir au moment voulu (par ex. juste avant une commande erronée) — le RPO le plus faible.","Quand : bases de production volumineuses et critiques ; systèmes nécessitant un RPO faible et un RTO raisonnable.","Exemple : PostgreSQL base backup + archive WAL (pgBackRest/barman), Oracle RMAN + archivelog, sauvegarde physique MySQL + binlog (xtrabackup).","Attention : la mise en place et l'exploitation sont plus complexes ; surveillez en continu que les journaux archivés arrivent sans interruption ni trou — un seul trou rompt la chaîne PITR.","Attention : le stockage des journaux archivés croît en continu ; sans politique de rétention ni plan de stockage, le disque se remplit."],"related":["last-backup-status","logical-dump"]},"sol_snapshot":{"kind":"solution","title":"Recommandation : snapshot","detail":"Prend un snapshot au niveau bloc des données dans la couche de stockage ou de virtualisation. Sauvegarde et restauration sont très rapides, même pour de très grandes bases ; il dépend toutefois de l'infrastructure et exige une utilisation prudente.","steps":["Pourquoi : une copie au niveau bloc est créée presque instantanément et les restaurations sont très rapides ; il offre le RTO le plus faible pour de gros volumes.","Quand : des téraoctets de données, une reprise rapide prioritaire ; si la couche de stockage/cloud prend en charge les snapshots.","Exemple : snapshot SAN/LVM, snapshot de disque cloud (EBS, etc.), snapshot ZFS/système de fichiers ; déclenché de façon cohérente avec le moteur de base de données.","Attention : un snapshot dépend du système de stockage et réside généralement sur la même infrastructure ; seul, il ne satisfait pas la règle 3-2-1 — copiez-le hors site.","Attention : pour être cohérente du point de vue applicatif, la base doit être dans le mode approprié (fsfreeze/backup mode/hot backup) au moment du snapshot ; sinon, vous obtenez une sauvegarde 'crash-consistent'."],"related":["last-backup-status"]},"sol_321":{"kind":"solution","title":"Recommandation : règle 3-2-1 + hors site + vérification obligatoire","detail":"Quelle que soit la méthode choisie, ces principes doivent chapeauter la stratégie. La simple existence d'une sauvegarde ne suffit pas ; elle doit être sur un support différent, dans un lieu différent et testée régulièrement. C'est un cadre qui complète toutes les options ci-dessus.","steps":["3-2-1 : conservez au moins 3 copies, sur 2 supports/types différents, dont 1 copie hors site.","Pourquoi hors site : un incendie, une inondation, un ransomware ou la perte d'une région peuvent détruire d'un coup toutes les copies d'un même lieu ; au moins une copie doit être isolée/distante.","Vérification obligatoire : une sauvegarde non testée n'est pas une sauvegarde ; effectuez régulièrement de vrais exercices de restauration et mesurez le temps de reprise (RTO).","Immuabilité : contre les ransomwares, conservez une copie non supprimable/non modifiable (immutable/WORM) ou air-gapped.","Attention : surveillez automatiquement l'intégrité (checksum) et la restaurabilité des sauvegardes ; définissez des alertes pour les sauvegardes échouées/obsolètes. Chiffrez aussi les sauvegardes elles-mêmes."],"related":["last-backup-status"]}}},"solver/decide-index.json":{"id":"decide-index","title":"Décision : quel type d'index ?","icon":"🧭","category":"Guide de décision","intro":"Le bon type d'index dépend du schéma d'accès de la requête (égalité, plage ou recherche texte/JSON), de la cardinalité de la colonne et de l'équilibre lecture/écriture. Un mauvais index gaspille de l'espace et ralentit les écritures. Cet arbre part de la forme de la requête et vous oriente parmi les options B-tree, Hash, Bitmap, GIN/GiST, index partiel et index couvrant, et vous rappelle que l'ordre des colonnes est critique dans un index composite.","start":"root","nodes":{"root":{"kind":"step","title":"Comment votre requête utilise-t-elle cette colonne ?","detail":"Le schéma d'accès est le facteur le plus décisif dans le choix d'un index. Comparez-vous seulement la colonne avec '=', effectuez-vous des plages/tris (>, <, BETWEEN, ORDER BY), ou recherchez-vous du texte intégral, un champ JSON ou des données géométriques/tableaux ? Commencez ici ; ne choisissez pas de type d'index tant que le besoin n'est pas clair.","choices":[{"label":"Égalité et plage/tri mélangés (=, >, <, BETWEEN, ORDER BY)","next":"cardinality"},{"label":"Égalité seule (=), ni plage ni tri","next":"sol_hash"},{"label":"Recherche plein texte, champ JSON/JSONB, tableau ou géométrique","next":"sol_gin_gist"},{"label":"La requête cible toujours le même petit sous-ensemble de lignes (par ex. WHERE status='active')","next":"sol_partial"}]},"cardinality":{"kind":"step","title":"Quelle est la cardinalité de la colonne (nombre de valeurs distinctes) ?","detail":"Si une plage/un tri est nécessaire, le choix par défaut est le B-tree ; toutefois, pour des colonnes avec très peu de valeurs distinctes (faible cardinalité : sexe, statut, oui/non) dans un data warehouse à forte lecture, un index Bitmap peut être préférable. La colonne a-t-elle de nombreuses valeurs uniques, ou quelques valeurs répétées ?","choices":[{"label":"Forte cardinalité (nombreuses valeurs uniques) ou charge OLTP","next":"coverage"},{"label":"Faible cardinalité et forte lecture / data warehouse (DWH)","next":"sol_bitmap"}]},"coverage":{"kind":"step","title":"La requête ne lit-elle que les colonnes de l'index ?","detail":"Le schéma d'accès convient à un B-tree. Une dernière optimisation : si toutes les colonnes renvoyées et filtrées par la requête sont dans l'index, le moteur peut répondre à partir de l'index seul sans visiter la table (index-only / covering scan). Votre requête lit-elle quelques colonnes, ou a-t-elle besoin de la majeure partie de la ligne ?","choices":[{"label":"Oui, quelques colonnes ; réponse possible sans visiter la table","next":"sol_covering"},{"label":"Non, un accès standard suffit","next":"sol_btree"}]},"sol_btree":{"kind":"solution","title":"Recommandation : B-tree (par défaut)","detail":"Un B-tree est une structure arborescente équilibrée qui prend en charge de nombreux schémas d'accès : égalité, plage, tri (ORDER BY), correspondance de préfixe avec LIKE 'abc%' et MIN/MAX. C'est le type d'index par défaut de la plupart des moteurs relationnels et le bon point de départ en cas de doute.","steps":["Pourquoi : une seule structure sert efficacement l'égalité comme la plage/le tri ; c'est l'index le plus polyvalent.","Quand : requêtes OLTP, WHERE + plage + ORDER BY ensemble, clés primaires/uniques.","L'ORDRE des colonnes est critique dans un index composite : placez en premier la colonne la plus sélective utilisée en égalité et laissez la colonne de plage en dernier. Un index (a,b) aide un filtre sur 'a' ou 'a,b' ; il n'aide généralement pas un filtre sur 'b' seul (règle du préfixe le plus à gauche).","Exemple : l'index B-tree/B+tree par défaut de PostgreSQL/Oracle/MySQL(InnoDB)/MSSQL.","Attention : chaque index augmente le coût d'écriture (INSERT/UPDATE/DELETE) et l'espace disque ; ne conservez pas d'index inutiles ou en double."],"related":["slow-query"]},"sol_hash":{"kind":"solution","title":"Recommandation : index Hash (égalité uniquement)","detail":"Un index hash stocke le hachage de la valeur de la colonne et est très rapide uniquement pour les recherches d'égalité exacte ('='). Il ne prend pas en charge les recherches par plage, tri ou préfixe, son domaine d'usage est donc restreint.","steps":["Pourquoi : il offre un accès en temps constant moyen (O(1)) pour les recherches d'égalité ; il peut être avantageux pour de très grandes charges d'égalité tenant en mémoire.","Quand : la colonne n'est utilisée qu'avec '=' et aucune plage/aucun tri n'est jamais nécessaire.","Différences entre moteurs : PostgreSQL dispose d'index hash, mais en pratique le B-tree suffit et est plus flexible dans la plupart des cas ; MySQL(InnoDB) ne gère le hachage qu'en interne via l'adaptive hash index, tandis que le moteur MEMORY utilise explicitement le hash ; dans Oracle, il apparaît dans le contexte des clusters/hash clusters.","Attention : plage, BETWEEN, ORDER BY et LIKE 'abc%' ne sont pas pris en charge ; choisissez un B-tree dès que ces besoins apparaissent.","Attention : comme le B-tree par défaut suffit généralement, ne passez pas au hash sans mesurer."],"related":["slow-query"]},"sol_bitmap":{"kind":"solution","title":"Recommandation : index Bitmap (faible cardinalité / DWH)","detail":"Un index bitmap conserve un tableau de bits pour chaque valeur distincte et combine très rapidement plusieurs conditions à faible cardinalité par des AND/OR bit à bit. Il excelle dans les charges à forte lecture comme le data warehouse et le reporting.","steps":["Pourquoi : il occupe très peu d'espace sur les colonnes à peu de valeurs distinctes et combine efficacement plusieurs conditions (WHERE gender=.. AND region=..) par des opérations bitmap.","Quand : colonnes à faible cardinalité, requêtes en schéma en étoile, data warehouse/reporting, charges à forte lecture.","Différences entre moteurs : un point fort classique d'Oracle ; PostgreSQL n'a pas d'index bitmap persistant, mais le planificateur peut produire un 'bitmap heap scan' à l'exécution. MySQL/MSSQL n'ont pas d'index bitmap persistant à proprement parler.","Attention : ne l'utilisez pas sur des tables fréquemment mises à jour (OLTP) ; les verrous bitmap réduisent fortement la concurrence au niveau ligne.","Attention : l'avantage disparaît sur les colonnes à forte cardinalité ; préférez-y un B-tree."],"related":["slow-query"]},"sol_gin_gist":{"kind":"solution","title":"Recommandation : GIN / GiST (texte, JSON, tableau, géométrie)","detail":"PostgreSQL dispose de familles d'index spécialisées pour les types d'accès qu'un B-tree standard ne peut pas traiter. GIN pointe vers de nombreux composants d'une valeur (mots, clés JSONB, éléments de tableau) ; GiST convient aux requêtes géométriques/de plage/de plus proche voisin.","steps":["Pourquoi : pour ces types de données, les requêtes du type 'contient/chevauche/proche de' ne peuvent pas être servies efficacement par un B-tree ; GIN/GiST les accélèrent.","Quand GIN : recherche plein texte (tsvector), recherches de champs/clés JSONB (@>, ?), requêtes d'inclusion de tableaux ; forte lecture, peu d'écritures.","Quand GiST : données géométriques/géospatiales (PostGIS), types range, requêtes de plus proche voisin (KNN).","Différences entre moteurs : GIN/GiST sont propres à PostgreSQL. Les index FULLTEXT et SPATIAL (R-tree) de MySQL, Oracle Text et Spatial dans Oracle, et le Full-Text Index de MSSQL répondent à un besoin similaire.","Attention : GIN est coûteux en écriture et lent à construire (atténuable avec fastupdate) ; mesurez le coût sur les tables à forte écriture."],"related":["slow-query"]},"sol_partial":{"kind":"solution","title":"Recommandation : index partiel / filtré","detail":"Un index partiel n'indexe que les lignes satisfaisant une condition (WHERE ...) au lieu de toute la table. Si les requêtes ciblent toujours le même petit sous-ensemble, l'index devient beaucoup plus petit et rapide, et le coût d'écriture diminue.","steps":["Pourquoi : comme il n'indexe que les lignes pertinentes, l'espace et le coût de maintenance baissent ; le sous-ensemble 'chaud' fréquemment interrogé devient beaucoup plus rapide.","Quand : conditions stables et étroites comme WHERE status='active', deleted_at IS NULL, priority='high'.","Différences entre moteurs : PostgreSQL le prend en charge directement avec un 'partial index' (CREATE INDEX ... WHERE ...), MSSQL avec un 'filtered index'. Dans Oracle, des schémas d'index fonctionnels/conditionnels servent d'équivalent ; MySQL(InnoDB) ne le prend pas en charge directement.","Attention : la condition WHERE de l'index doit correspondre à celle de la requête ; sinon le planificateur ne peut pas utiliser l'index.","Attention : si le sous-ensemble grossit, l'avantage diminue ; assurez-vous que la condition est stable."],"related":["slow-query"]},"sol_covering":{"kind":"solution","title":"Recommandation : index couvrant (INCLUDE)","detail":"Un index couvrant contient toutes les colonnes dont la requête a besoin, ce qui permet au moteur de répondre à partir de l'index seul sans jamais visiter la table (heap) (index-only scan). Il apporte une accélération importante pour les requêtes fréquentes renvoyant peu de colonnes.","steps":["Pourquoi : l'accès à la table (heap fetch) est éliminé ; les E/S disque et la latence baissent nettement.","Quand : requêtes fréquentes aux colonnes WHERE + SELECT bien définies et au jeu de résultats restreint.","Différences entre moteurs : MSSQL et PostgreSQL ajoutent des colonnes non clés à la feuille de l'index avec 'INCLUDE' ; dans MySQL(InnoDB), les index secondaires contiennent déjà la clé primaire, la couverture s'obtient donc avec un index composite ; dans Oracle, l'accès index-only a lieu lorsque toutes les colonnes requises sont dans l'index.","Attention : des colonnes INCLUDE superflues/larges font gonfler l'index et ralentissent les écritures ; n'ajoutez que les colonnes réellement nécessaires.","Attention : dans PostgreSQL, un index-only scan exige une visibility map à jour ; un VACUUM régulier est indispensable."],"related":["slow-query"]}}},"solver/decide-partition.json":{"id":"decide-partition","title":"Décision : quelle stratégie de partitionnement ?","icon":"🧭","category":"Guide de décision","intro":"Le partitionnement découpe une grande table en plusieurs morceaux physiques sous un même nom logique, ce qui facilite la suppression des anciennes données et permet aux requêtes de ne consulter que les morceaux pertinents (partition pruning). Sur de petites tables, en revanche, il ne fait qu'ajouter de la complexité. Cet arbre vérifie d'abord si le partitionnement est vraiment nécessaire, puis vous oriente entre Range, List, Hash et Composite selon les schémas d'accès et de répartition.","start":"root","nodes":{"root":{"kind":"step","title":"La table est-elle vraiment assez grande pour nécessiter un partitionnement ?","detail":"Le partitionnement n'est pas une solution miracle de performance ; c'est un outil d'échelle et de maintenance (cycle de vie). Si la table n'est pas assez grande ou si les requêtes sont déjà performantes avec des index, le partitionnement n'apporte que complexité des plans et surcharge de gestion. Votre table atteint-elle des dizaines de millions de lignes ou plus/des centaines de Go, ou est-elle encore petite/moyenne ?","choices":[{"label":"Oui, elle est très grande et/ou les anciennes données doivent être supprimées/archivées en masse","next":"access"},{"label":"Non, table petite/moyenne, ou je ne suis pas sûr","next":"sol_no_partition"}]},"access":{"kind":"step","title":"Comment accédez-vous aux données et comment les découper ?","detail":"La clé de partition doit être la colonne sur laquelle filtrent la plupart des requêtes ; c'est seulement alors que le pruning s'applique. Quel est le schéma d'accès dominant ? Par date/plages séquentielles (30 derniers jours), par valeurs de catégorie/région précises, ou n'y a-t-il pas de filtre clair et vous voulez répartir la charge uniformément ?","choices":[{"label":"Plages séquentielles comme date/numéro de séquence (X derniers jours, par mois)","next":"sol_range"},{"label":"Ensembles de valeurs discrètes comme catégorie/région/tenant","next":"sol_list"},{"label":"Pas de plage/catégorie claire ; l'objectif est de répartir la charge uniformément","next":"sol_hash"},{"label":"Date et fort volume à la fois ; découpage sur deux dimensions nécessaire","next":"sol_composite"}]},"sol_no_partition":{"kind":"solution","title":"Recommandation : ne partitionnez pas (pas encore)","detail":"Le partitionnement n'apporte aucun bénéfice sur les tables petites/moyennes ; des index bien choisis et une maintenance normale suffisent. Un partitionnement prématuré complique les plans de requête, manque le pruning avec une mauvaise clé et ajoute une surcharge opérationnelle.","steps":["Pourquoi : sur une petite table, un parcours complet ou un accès par index est déjà rapide ; le partitionnement n'ajoute que gestion supplémentaire et complexité des plans.","Quand rester ainsi : moins de quelques millions de lignes, aucun besoin de supprimer en masse les anciennes données, requêtes performantes avec des index.","À la place : les bons index, une table d'archive si nécessaire, des statistiques/VACUUM/ANALYZE réguliers.","Attention : ne mettez pas en place le partitionnement trop tôt en supposant que 'la table va grossir' ; basculez lorsque la croissance est avérée et que la bonne clé est claire.","Différences entre moteurs : un principe général valable pour tous les moteurs ; le partitionnement déclaratif de PostgreSQL, PARTITION BY de MySQL et le partitionnement d'Oracle et de MSSQL peuvent facilement être ajoutés plus tard."],"related":[]},"sol_range":{"kind":"solution","title":"Recommandation : partitionnement Range","detail":"Le partitionnement Range découpe les lignes selon des plages séquentielles d'une colonne (par ex. plages de dates : une partition par mois/trimestre). C'est la stratégie la plus courante et la plus puissante pour les séries temporelles et les tables en croissance continue.","steps":["Pourquoi : lorsque les requêtes filtrent par date/plage, le moteur ne lit que les partitions concernées (partition pruning) ; les anciennes données sont supprimées instantanément en une seule commande (DROP de partition).","Quand : séries temporelles, logs, historique des commandes/transactions ; requêtes 'N derniers jours' et archivage temporel.","Clé de partition : choisissez la colonne date/séquentielle présente dans la clause WHERE de l'immense majorité des requêtes ; sinon le pruning ne fonctionne pas.","Exemple : partitionnement RANGE de PostgreSQL, partitionnement range/interval d'Oracle (nouvelles partitions automatiques), MySQL PARTITION BY RANGE (généralement RANGE COLUMNS pour les dates), partition function/scheme de MSSQL.","Attention : automatisez la création des nouvelles partitions pour les plages futures (interval Oracle ou tâche planifiée) ; une partition 'default/maxvalue' qui se remplit devient un point chaud."],"related":[]},"sol_list":{"kind":"solution","title":"Recommandation : partitionnement List","detail":"Le partitionnement List regroupe les lignes selon des valeurs discrètes et connues d'une colonne (par ex. region = 'TR','EU','US' ou ID de tenant). Lorsqu'il existe des catégories naturelles et finies, il rend l'accès et la gestion clairs.","steps":["Pourquoi : lorsque les requêtes filtrent sur des valeurs de catégorie précises, seule la partition concernée est lue ; la maintenance/suppression par catégorie devient plus simple.","Quand : ensembles de valeurs discrets et relativement stables comme région/pays, type de produit, statut, séparation multi-tenant.","Clé de partition : l'ensemble de valeurs doit être fini et prévisible ; si de nouvelles valeurs sont ajoutées fréquemment, envisagez Hash plutôt que List.","Exemple : partitionnement LIST de PostgreSQL, partitionnement list d'Oracle, MySQL PARTITION BY LIST, partition scheme de MSSQL avec des bornes adaptées.","Attention : conservez une partition 'default' pour les valeurs non définies/nouvelles ; sinon la ligne ne peut pas être insérée et une erreur survient."],"related":[]},"sol_hash":{"kind":"solution","title":"Recommandation : partitionnement Hash (répartition uniforme)","detail":"Le partitionnement Hash répartit les lignes de façon relativement uniforme sur un nombre prédéfini de partitions selon le hachage de la colonne clé. Il sert à éviter les points chauds et à équilibrer la charge en l'absence de plage/catégorie naturelle.","steps":["Pourquoi : les lignes sont réparties uniformément ; le point chaud d'écriture qui s'accumule sur une seule partition ou en fin de clé primaire est réduit.","Quand : fort volume d'écriture, pas de plage/catégorie naturelle, objectif d'égaliser E/S et taille (par ex. un id à forte cardinalité).","Clé de partition : choisissez une colonne à forte cardinalité et uniformément répartie (généralement la clé primaire/unique).","Exemple : partitionnement HASH de PostgreSQL, partitionnement hash d'Oracle, MySQL PARTITION BY HASH/KEY.","Attention : il n'offre aucun pruning pour les requêtes de plage (le hachage détruit l'ordre) ; les requêtes du type 'N derniers jours' parcourent toutes les partitions. Modifier ultérieurement le nombre de partitions exige une redistribution."],"related":[]},"sol_composite":{"kind":"solution","title":"Recommandation : partitionnement Composite (multi-niveaux)","detail":"Le partitionnement composite combine deux stratégies : il découpe d'abord au niveau supérieur, puis divise chaque partition en sous-partitions (par ex. Range-by-Hash ou Range-by-List). Il est utilisé lorsque le cycle de vie temporel et la répartition uniforme/séparation par catégorie sont nécessaires en même temps.","steps":["Pourquoi : lorsqu'une seule stratégie ne suffit pas, il répond aux deux besoins ensemble ; par exemple, découper par mois (archivage) et répartir par hash à l'intérieur du mois (prévention des points chauds).","Quand : très grandes séries temporelles + fortes écritures concurrentes, ou double schéma d'accès date + région/tenant.","Exemple : composite Oracle (range-hash, range-list, list-hash, etc.) ; dans PostgreSQL, il se construit manuellement en partitionnant à nouveau les partitions (partition of partition) ; sous-partitions MySQL (RANGE/LIST + HASH/KEY).","Attention : c'est l'option à la plus forte complexité de gestion et de schéma ; ne la choisissez que si les deux dimensions sont un besoin avéré.","Attention : un mauvais choix de clé supérieure/inférieure fait perdre le pruning ; vérifiez que les deux niveaux correspondent au schéma des requêtes."],"related":[]}}},"solver/decide-pooling.json":{"id":"decide-pooling","title":"Décision : comment mettre en place le pooling de connexions ?","icon":"🧭","category":"Guide de décision","intro":"Chaque connexion à la base a un coût en mémoire et en ordonnancement ; une croissance incontrôlée du nombre de connexions ralentit le serveur, voire le fait tomber. Un pool de connexions résout ce problème en réutilisant un petit nombre de connexions persistantes. Cet arbre guide les décisions sur l'emplacement du pool, le mode à utiliser (session/transaction/statement) et la bonne taille, et souligne pourquoi un pooler externe est obligatoire dans les environnements serverless.","start":"root","nodes":{"root":{"kind":"step","title":"Comment votre architecture applicative se connecte-t-elle ?","detail":"La décision de pooling commence par l'architecture. S'agit-il d'un seul processus applicatif de longue durée (serveur classique), de nombreux processus/instances indépendants (microservices, nombreux pods), ou de fonctions serverless/Lambda qui naissent et meurent à chaque requête ? Chaque cas exige un placement de pool différent.","choices":[{"label":"Un/quelques processus applicatifs de longue durée","next":"sol_app_pool"},{"label":"De nombreux processus/instances/pods se connectent à la même base","next":"middleware"},{"label":"Serverless / Lambda / fonctions de courte durée","next":"sol_serverless"},{"label":"Montrez-moi d'abord comment choisir la bonne taille de pool","next":"sol_size"}]},"middleware":{"kind":"step","title":"Comment choisir le mode du pooler intermédiaire ?","detail":"Avec de nombreux clients, un pooler intermédiaire (PgBouncer, ProxySQL) maintient un faible nombre de connexions sur la base. Mais le mode de pool dépend des fonctionnalités utilisées par l'application : dépendez-vous d'un état lié à la session (prepared statements, tables temporaires, SET, advisory locks, fonctionnalités de niveau session), ou tout se termine-t-il au sein d'une seule transaction ?","choices":[{"label":"Je dépends de l'état de session (SET, tables temporaires, prepared statements de session)","next":"sol_session_mode"},{"label":"Les transactions sont courtes et indépendantes ; je ne conserve aucun état de session","next":"sol_txn_mode"}]},"sol_app_pool":{"kind":"solution","title":"Recommandation : pool de connexions dans l'application","detail":"Pour un ou quelques processus de longue durée, une bibliothèque de pool intégrée à l'application (HikariCP, le pool intégré de database/sql en Go, le connection pooling de .NET) couvre la plupart des besoins. Les connexions sont ouvertes une fois et réutilisées sans cesse.","steps":["Pourquoi : le coût d'ouverture/fermeture des connexions disparaît ; un nombre de connexions limité et prévisible protège la base.","Quand : un monolithe classique ou quelques instances applicatives ; la demande totale de connexions est inférieure à la capacité de la base.","Exemple : Java HikariCP, pooling .NET SqlConnection/Npgsql, Node pg-pool, pool Python SQLAlchemy.","Pour le dimensionnement : déterminez la taille du pool selon les cœurs/disques (décision suivante) ; 'plus il y en a, mieux c'est' ne s'applique pas.","Attention : chaque instance applicative ouvre son propre pool ; nombre d'instances × taille du pool ne doit pas dépasser max_connections de la base. Si c'est le cas, passez à un pooler intermédiaire."],"related":["connection-storm"]},"sol_size":{"kind":"solution","title":"Recommandation : dimensionnez correctement le pool","detail":"Le pool ne doit être ni trop petit (les requêtes attendent) ni trop grand. Trop de connexions provoquent des changements de contexte CPU et une contention de verrous, et réduisent le débit. La taille n'est pas un nombre à augmenter indéfiniment, mais un petit nombre fondé sur les cœurs et les E/S.","steps":["Formule de départ : un point de départ courant est taille du pool ≈ (nombre de cœurs × 2) + nombre effectif de disques ; sur SSD/nombreux cœurs, environ 'cœurs × 2-4' est un bon début.","Pourquoi rester petit : le parallélisme réel de la base est borné par le nombre de cœurs ; les connexions supplémentaires ne font que s'accumuler en file et ralentissent tout par changements de contexte/contention de verrous.","Règle : avec des centaines de clients concurrents, la solution n'est pas d'agrandir le pool mais de faire tourner rapidement un petit pool (transactions courtes + mise en file).","Limite totale : (nombre d'instances applicatives × taille du pool) < max_connections de la base ; gardez une marge pour les connexions superuser et de maintenance.","Attention : partez de la formule mais mesurez toujours ; ajustez à l'aide des métriques de temps d'attente, de connexions actives et de profondeur de file."],"related":["connection-storm"]},"sol_session_mode":{"kind":"solution","title":"Recommandation : mode de pool session","detail":"En mode session, une connexion client est liée à une connexion serveur jusqu'à la fin de sa session. L'état de niveau session (SET, tables temporaires, advisory locks, prepared statements de session) est préservé, mais le partage des connexions est minimal.","steps":["Pourquoi : c'est le seul mode sûr pour les applications qui ont besoin d'un état préservé pendant toute la session ; le comportement est identique à une connexion directe à la base.","Quand : applications utilisant des variables de session SET, des tables temporaires, LISTEN/NOTIFY, des prepared statements de niveau session ou des advisory locks.","Exemple : PgBouncer pool_mode=session ; dans ProxySQL, routage séparé du trafic sensible à la session.","Attention : le partage des connexions est faible, le bénéfice diminue donc avec de nombreux clients ; si possible, visez une application sans état de session et passez au mode transaction.","Différences entre moteurs : PgBouncer pour PostgreSQL ; côté MySQL, ProxySQL gère un comportement similaire différemment, et les variables de session demandent de la vigilance."],"related":["connection-storm"]},"sol_txn_mode":{"kind":"solution","title":"Recommandation : mode de pool transaction","detail":"En mode transaction, une connexion serveur n'est prêtée au client que pendant la durée d'une transaction et retourne au pool à la fin de celle-ci. Ainsi, des milliers de clients peuvent travailler avec très peu de connexions réelles sur la base ; c'est le mode le plus courant à grande échelle.","steps":["Pourquoi : comme les connexions sont transmises par transaction, le partage est maximal ; vous servez de nombreux clients avec peu de connexions à la base.","Quand : transactions courtes et indépendantes ; architectures microservices/multi-pods qui ne conservent pas d'état de session (ou sont adaptées pour cela).","Exemple : PgBouncer pool_mode=transaction (la configuration la plus courante), comportement de multiplexage par défaut de ProxySQL.","Attention : les fonctionnalités couvrant la session sont cassées : les prepared statements de niveau session, les tables temporaires, SET et les advisory locks ne sont pas sûrs. Pour les prepared statements nommés au niveau protocole, vérifiez la compatibilité application/pooler.","Mode statement : le mode encore plus agressif (statement) transmet la connexion à chaque instruction ; il n'est envisagé que pour des scénarios très spécifiques en lecture seule sans transaction et est très restrictif."],"related":["connection-storm"]},"sol_serverless":{"kind":"solution","title":"Recommandation : un pooler externe est obligatoire en serverless","detail":"Les fonctions serverless/Lambda montent en charge rapidement et chaque instance ouvre sa propre connexion ; un pool dans l'application n'aide pas ici car les instances sont de courte durée et ne partagent rien. Lorsque des centaines/milliers d'instances apparaissent soudainement, la limite de connexions de la base est dépassée. La solution est un pool de connexions externe placé devant la base.","steps":["Pourquoi : dans des instances de fonctions de courte durée et non partagées, un pool applicatif ne peut pas persister ; une tempête de connexions survient lors du scale-out.","Quand : environnements qui créent une instance par requête, comme AWS Lambda, Cloud Functions, Azure Functions.","Exemple : PgBouncer/ProxySQL en tant que tier distinct, ou un pooler managé (par ex. AWS RDS Proxy, Data API/services de pooler externes pour PostgreSQL).","Mode : le mode de pool transaction est généralement préféré ; rendez le code de la fonction exempt d'état de session.","Attention : définissez aussi une limite de concurrence par fonction (reserved concurrency) ; dimensionnez ensemble le pooler et max_connections de la base, sinon le pooler devient lui-même le goulot."],"related":["connection-storm"]}}},"solver/decide-cache.json":{"id":"decide-cache","title":"Décision : quelle stratégie de cache ?","icon":"🧭","category":"Guide de décision","intro":"Un cache conserve les données fréquemment lues dans une couche rapide devant la base (par ex. Redis), réduisant la latence et la charge de la base. Mais toutes les données ne se prêtent pas au cache, et une mauvaise invalidation crée un risque de données obsolètes/incorrectes. Cet arbre vérifie d'abord si vous avez vraiment besoin d'un cache, puis vous aide à choisir entre cache-aside, read-through, write-through et write-behind selon le schéma lecture/écriture, et traite la prévention des stampedes.","start":"root","nodes":{"root":{"kind":"step","title":"À quoi ressemble votre charge : forte lecture ou forte écriture ?","detail":"Un cache est le plus rentable lorsque les mêmes données sont lues de nombreuses fois (forte lecture) et changent peu. Établissez d'abord le schéma global : les données sont-elles beaucoup lues et rarement modifiées, sont-elles mises à jour en permanence, ou ne savez-vous pas si ajouter un cache en vaut la peine ?","choices":[{"label":"Très forte lecture, données relativement peu modifiées","next":"read_pattern"},{"label":"Écritures également fortes ; les données écrites sont lues immédiatement/souvent","next":"write_pattern"},{"label":"Pas sûr ; ai-je vraiment besoin d'un cache ?","next":"sol_no_cache"}]},"read_pattern":{"kind":"step","title":"Qui doit être responsable du remplissage du cache ?","detail":"Dans un scénario à forte lecture, le choix clé est de savoir qui remplit le cache. Votre application doit-elle gérer le cache explicitement et se replier elle-même sur la base (grande flexibilité), ou la couche de cache doit-elle récupérer les données dans la base sous-jacente en cas d'absence et les renvoyer de façon transparente ?","choices":[{"label":"L'application le gère explicitement ; je veux flexibilité et contrôle","next":"sol_cache_aside"},{"label":"La couche de cache récupère depuis la base en cas d'absence (transparent)","next":"sol_read_through"}]},"write_pattern":{"kind":"step","title":"En écriture, la priorité est-elle la cohérence ou la vitesse d'écriture ?","detail":"Si les écritures sont fortes, vous devez décider du degré de cohérence des données écrites entre le cache et la base. Le cache et la base doivent-ils être mis à jour ensemble à chaque écriture pour garantir la cohérence (écritures un peu plus lentes), ou les écritures doivent-elles aller d'abord dans le cache et être appliquées à la base de façon asynchrone en arrière-plan (écritures très rapides mais risque de perte) ?","choices":[{"label":"La cohérence compte ; les lectures doivent toujours être à jour","next":"sol_write_through"},{"label":"La vitesse d'écriture est critique ; une cohérence légèrement différée et un risque de perte sont acceptables","next":"sol_write_behind"}]},"sol_no_cache":{"kind":"solution","title":"Recommandation : n'ajoutez pas de cache pour l'instant","detail":"Un cache n'est pas gratuit : il introduit un problème de cohérence (données obsolètes), une infrastructure supplémentaire et la complexité de l'invalidation. Si la requête est déjà rapide ou si les données changent à chaque requête, un cache n'apporte aucun bénéfice, seulement une surface de panne.","steps":["Pourquoi : un cache ajoute une charge de cohérence, d'invalidation et d'exploitation. Sans gain, c'est une perte nette.","Quand il n'est pas nécessaire : la requête est déjà rapide grâce à un index, les données diffèrent à chaque lecture/sont propres à l'utilisateur et ne sont pas réutilisées, ou les écritures dépassent largement les lectures.","À la place : d'abord l'optimisation des index/requêtes, une vue matérialisée ou le cache de buffers/plans de la base elle-même.","Attention : n'ajoutez pas de cache 'au cas où' ; n'en ajoutez pas sans goulot de lecture mesuré ni données réutilisées.","Ensuite : une fois un véritable point chaud de lecture prouvé, commencez par le cache-aside."],"related":["slow-query","high-cpu"]},"sol_cache_aside":{"kind":"solution","title":"Recommandation : cache-aside (lazy loading)","detail":"Avec le cache-aside, l'application consulte d'abord le cache ; si la donnée est absente (miss), elle la lit dans la base, l'écrit dans le cache et la renvoie. C'est le schéma le plus courant et le plus flexible ; le cache ne se remplit qu'avec les données réellement demandées.","steps":["Pourquoi : simple, flexible et résilient ; même si le cache tombe, l'application continue de fonctionner en se repliant sur la base. Seules les données demandées sont mises en cache.","Quand : accélération des lectures à usage général ; nombreuses lectures, écritures modérées ; l'usage classique avec Redis/Memcached.","Invalidation : en écriture, mettez à jour ou supprimez la clé concernée (write-invalidate) ; bornez l'obsolescence en attribuant à chaque clé un TTL raisonnable.","Exemple : Redis GET → si absent, requête à la base → SET key value EX <ttl>.","Attention : la première requête est toujours un miss (démarrage à froid). Si la même clé est demandée par de nombreux clients à la fois, un cache stampede peut survenir — voir la prévention des stampedes (élément suivant).","Stampede : lorsqu'une clé populaire expire, des milliers de requêtes frappent la base en même temps ; évitez-le avec un verrou/single-flight, un rafraîchissement anticipé ou des TTL avec jitter."],"related":["high-cpu","slow-query"]},"sol_read_through":{"kind":"solution","title":"Recommandation : cache read-through","detail":"Avec le read-through, l'application ne communique qu'avec le cache ; en cas d'absence, la couche de cache récupère elle-même les données dans la base et se remplit (via un provider/loader). Le code applicatif devient plus simple et la logique de remplissage est centralisée.","steps":["Pourquoi : la logique de remplissage est centralisée dans la couche de cache ; le code applicatif n'a plus à répéter 'gérer le miss partout'.","Quand : charges à forte lecture où vous pouvez utiliser une couche d'accès au cache cohérente (bibliothèque/service).","Exemple : une bibliothèque/un client de cache (par ex. un cache local avec un loader défini ou une couche d'accès devant Redis) charge depuis la base en cas d'absence.","Invalidation : les TTL et l'invalidation à l'écriture restent nécessaires ; le read-through seul ne garantit pas la fraîcheur.","Attention : très proche du cache-aside ; la différence est 'qui le remplit'. Le risque de stampede existe aussi ici ; appliquez les mêmes contre-mesures.","Note sur le moteur : Redis ne fait pas de read-through par lui-même ; c'est la couche applicative/bibliothèque qui fournit ce schéma."],"related":["slow-query"]},"sol_write_through":{"kind":"solution","title":"Recommandation : cache write-through","detail":"Avec le write-through, chaque écriture va à la fois dans le cache et dans la base (de façon synchrone) ; les deux sont mis à jour ensemble. Le cache n'est donc jamais en retard sur la base et les lectures voient toujours des données à jour.","steps":["Pourquoi : le cache et la base restent cohérents ; si les données écrites sont lues tout de suite, il n'y a aucun risque de lecture obsolète.","Quand : scénarios où les données écrites sont lues immédiatement et souvent, et où une cohérence de lecture forte est requise.","Exemple : en écriture, l'application met d'abord à jour la base, puis définit immédiatement la clé du cache sur la nouvelle valeur (ou la supprime).","Attention : chaque écriture va vers deux cibles, la latence d'écriture augmente donc ; des données qui ne seront jamais lues peuvent aussi être mises en cache inutilement. Les deux écritures ne sont pas atomiques (l'une réussit, l'autre échoue), ce qui peut créer une incohérence — en cas d'erreur, supprimez l'entrée du cache.","Piège de la double écriture : plutôt que d'écrire séparément la base et le cache, l'approche « écrire la base, supprimer (invalider) le cache » est généralement plus sûre."],"related":[]},"sol_write_behind":{"kind":"solution","title":"Recommandation : cache write-behind (write-back)","detail":"Avec le write-behind, l'écriture va d'abord dans le cache et rend immédiatement la main à l'utilisateur ; l'écriture dans la base a lieu en arrière-plan, de façon asynchrone (par lots/différée). La vitesse d'écriture est maximale, mais elle comporte des risques de durabilité et de cohérence.","steps":["Pourquoi : la latence d'écriture baisse fortement et les écritures peuvent être regroupées et transférées efficacement vers la base ; la base est soulagée sous une charge à forte écriture.","Quand : volumes d'écriture très élevés comme les compteurs, métriques ou flux d'activité, où une cohérence légèrement différée est acceptable.","Exemple : écriture dans Redis → une file/un worker en arrière-plan vide vers la base par lots à intervalles réguliers.","Attention : si le nœud de cache tombe avant d'écrire dans la base, les données sont PERDUES ; ne l'utilisez pas pour des données critiques exigeant la durabilité, ou protégez-le avec une file durable/l'AOF.","Attention : la base reste obsolète pendant un certain temps ; les autres systèmes qui lisent directement la base voient des données périmées. Acceptez sciemment le délai de cohérence et la fenêtre de perte."],"related":[]}}},"solver/decide-datatype.json":{"id":"decide-datatype","title":"Décision : choisir le bon type de données","icon":"🧭","category":"Guide de décision","intro":"Le choix d'un type de données est l'une des décisions les plus coûteuses à corriger ensuite : un mauvais type produit des résultats erronés (par ex. float pour de l'argent), de l'espace gaspillé et des requêtes lentes. Cet arbre vous oriente vers le bon type pour les champs numériques, texte, date/heure, clé (PK), semi-structurés (JSON) et catégories (ENUM), et met en évidence les pièges classiques comme l'argent et les jeux de caractères.","start":"root","nodes":{"root":{"kind":"step","title":"Quel type de données stockez-vous ?","detail":"Le bon type est déterminé par la nature des données. Dans quelle catégorie entre le champ : un nombre, du texte, une date/heure, la clé primaire de la table, des données flexibles/semi-structurées (JSON) ou un ensemble limité de catégories ?","choices":[{"label":"Valeur numérique (quantité, argent, mesure, compteur)","next":"numeric"},{"label":"Texte (nom, description, code)","next":"sol_text"},{"label":"Date et/ou heure","next":"sol_datetime"},{"label":"Je conçois une clé primaire (PK)","next":"sol_pk"},{"label":"Champs flexibles/imbriqués/variables (données semi-structurées)","next":"sol_json"},{"label":"Un ensemble limité et connu de catégories (statut, type, priorité)","next":"sol_enum"}]},"numeric":{"kind":"step","title":"Le nombre est-il un entier ou un décimal — et s'agit-il d'argent ?","detail":"Pour les numériques, la distinction la plus critique est l'exactitude. La valeur est-elle un entier (quantité, ID, compteur) ou un décimal ? Si décimal, s'agit-il d'ARGENT/d'un montant financier ? La virgule flottante binaire (FLOAT/DOUBLE) n'est jamais utilisée pour l'argent ni partout où l'exactitude est requise, car elle produit des erreurs d'arrondi.","choices":[{"label":"Entier (quantité, compteur, id)","next":"sol_integer"},{"label":"Argent ou décimal exact (prix, solde, taux)","next":"sol_decimal"}]},"sol_integer":{"kind":"solution","title":"Recommandation : entier (SMALLINT / INT / BIGINT)","detail":"Pour les entiers, choisissez le plus petit type couvrant la plage requise. Un type inutilement grand gaspille de l'espace et de la taille d'index ; mais pour les champs susceptibles de croître, comme les compteurs/PK, une largeur suffisante est indispensable pour éviter le débordement.","steps":["Pourquoi : la bonne largeur optimise à la fois le stockage et l'empreinte index/mémoire ; l'arithmétique est rapide.","Quand SMALLINT/INT : pour une plage limitée (âge, quantité, petit compteur), SMALLINT/INT suffit.","Quand BIGINT : choisissez BIGINT pour les ID/compteurs toujours croissants et les PK dont le nombre de lignes peut atteindre des milliards — le débordement d'INT est une cause sérieuse de pannes en production.","Différences entre moteurs : dans MySQL, INT UNSIGNED double la plage ; PostgreSQL n'a pas d'UNSIGNED, passez à BIGINT. Oracle NUMBER(p), MSSQL INT/BIGINT.","Attention : ne mettez pas du BIGINT partout 'au cas où ça grossirait' ; mais pour une PK croissante, BIGINT dès le départ est une assurance peu coûteuse."],"related":[]},"sol_decimal":{"kind":"solution","title":"Recommandation : NUMERIC / DECIMAL — JAMAIS FLOAT pour l'argent","detail":"Pour l'argent et les décimaux exacts, utilisez NUMERIC/DECIMAL à virgule fixe (exacts). FLOAT/DOUBLE sont à virgule flottante binaire et ne peuvent pas stocker exactement des valeurs comme 0.1 ; additions/multiplications produisent des dérives de centimes, des soldes erronés et des problèmes d'audit.","steps":["Pourquoi : NUMERIC/DECIMAL stockent les valeurs exactement en décimal ; l'arithmétique monétaire donne des résultats corrects sans erreurs d'arrondi.","Quand : tout champ exigeant l'exactitude, comme prix, solde, taxe, taux ou intérêt. Spécifiez une échelle appropriée : DECIMAL(precision, scale), par ex. DECIMAL(19,4) pour un montant.","Alternative : certains systèmes stockent l'argent dans la plus petite unité (centimes) en BIGINT ; cela garantit aussi une exactitude totale.","Différences entre moteurs : PostgreSQL NUMERIC, MySQL DECIMAL, Oracle NUMBER(p,s), MSSQL DECIMAL/MONEY (attention aux limites de MONEY).","Attention : FLOAT/DOUBLE sont réservés aux valeurs approximatives scientifiques/mesurées et aux cas où la performance prime sur l'exactitude — jamais pour l'argent."],"related":[]},"sol_text":{"kind":"solution","title":"Recommandation : texte — VARCHAR vs TEXT vs CHAR","detail":"Pour le texte, VARCHAR/TEXT s'utilisent pour une longueur variable et CHAR pour une longueur réellement fixe. Dans les moteurs modernes, la différence de stockage entre VARCHAR et TEXT est le plus souvent négligeable ; la vraie décision est d'imposer ou non une limite sémantique (contrainte de longueur).","steps":["VARCHAR(n) : si la longueur est variable et qu'il existe une limite supérieure raisonnable (par ex. e-mail, nom), utilisez VARCHAR(n) — la limite est un outil de validation des données.","TEXT : pour un texte libre long de longueur imprévisible (description, corps), utilisez TEXT.","CHAR(n) : à utiliser uniquement pour des longueurs réellement fixes (par ex. code pays 'TR', toujours 2 caractères) ; pour un texte court, CHAR complète avec des espaces de fin et est généralement inutile.","Différences entre moteurs : dans PostgreSQL, VARCHAR et TEXT ont les mêmes performances, la limite n'est qu'une contrainte. Dans MySQL, VARCHAR est stocké dans la ligne, TEXT peut être stocké séparément, et l'indexation a une limite de préfixe. Dans Oracle, utilisez VARCHAR2.","Charset/collation : pour les emoji et l'Unicode complet, utilisez toujours utf8mb4 dans MySQL (l'ancien 'utf8' est sur 3 octets et casse les emoji) ; choisissez la collation (par ex. utf8mb4_0900_ai_ci) selon les besoins de tri/comparaison. Dans PostgreSQL, typiquement une base UTF-8 + une collation appropriée."],"related":[]},"sol_datetime":{"kind":"solution","title":"Recommandation : date/heure — TIMESTAMP avec fuseau horaire","detail":"Si vous stockez un instant (heure d'un événement), choisissez un type tenant compte du fuseau horaire. Stocker sans fuseau horaire conduit à des calculs et comparaisons erronés entre régions/lors des changements d'heure.","steps":["Pourquoi : un type tenant compte du fuseau normalise l'instant en UTC et assure des comparaisons et conversions cohérentes ; indispensable pour les systèmes mondiaux.","Quand TIMESTAMPTZ : pour les instants d'événements/de transactions dans PostgreSQL, utilisez TIMESTAMP WITH TIME ZONE (timestamptz) ; stockez en UTC et convertissez dans le fuseau de l'utilisateur pour l'affichage.","Date ou heure seule : DATE pour un jour calendaire, TIME pour une heure indépendante du fuseau ; INTERVAL pour les durées (dans les moteurs qui le prennent en charge).","Différences entre moteurs : PostgreSQL timestamptz (stocke en réalité de l'UTC). Dans MySQL, TIMESTAMP est converti selon le fuseau horaire mais sa plage est limitée (2038) ; DATETIME n'est pas converti — imposez dans l'application une discipline de stockage en UTC. Oracle TIMESTAMP WITH TIME ZONE, MSSQL DATETIMEOFFSET.","Attention : stocker l'heure en texte (VARCHAR) ou en INT epoch casse les opérations de tri, de plage et de fuseau horaire ; utilisez un vrai type date/heure."],"related":[]},"sol_pk":{"kind":"solution","title":"Recommandation : clé primaire — BIGINT vs UUID","detail":"Il existe deux options principales pour une PK : un nombre croissant (BIGINT identity/sequence) ou un UUID. Une PK numérique est petite et favorable à l'index, mais prévisible et nécessite une génération centralisée ; un UUID peut être généré de façon distribuée mais est plus volumineux et son ordre aléatoire peut nuire à l'index.","steps":["BIGINT (croissant) : l'index le plus petit et le plus rapide ; les insertions séquentielles sont idéales pour un B-tree. Le choix par défaut pour une base unique/une génération centralisée ; BIGINT plutôt qu'INT dès le départ protège du débordement.","UUID : peut être généré sur les clients/systèmes distribués sans collision et ne révèle pas de numéro de séquence lorsque l'ID est exposé. Mais il occupe 16 octets (2×BIGINT).","L'ordre des UUID compte : les UUID entièrement aléatoires (v4) ne sont pas séquentiels et provoquent donc des insertions dispersées dans l'index (page splits, faible taux de succès du cache) ; les UUID ordonnés dans le temps (v7/ULID) éliminent largement ce problème.","Différences entre moteurs : PostgreSQL dispose d'un type uuid natif ; dans MySQL, stockez l'UUID en BINARY(16) (plutôt qu'en texte CHAR(36)) et générez-le de façon ordonnée si possible. MSSQL uniqueidentifier + NEWSEQUENTIALID, Oracle RAW(16).","Attention : stocker un UUID en texte VARCHAR/CHAR(36) double ou triple environ l'espace et la taille d'index ; stockez-le en binaire."],"related":[]},"sol_json":{"kind":"solution","title":"Recommandation : JSON / JSONB — avec modération","detail":"Le type JSON est puissant pour les champs à schéma flexible, imbriqués ou clairsemés ; mais la discipline relationnelle (contraintes, index, sûreté de typage) peut se perdre. Règle : placez les champs fixes et fréquemment interrogés dans des colonnes normales, ne gardez en JSON que la partie réellement variable.","steps":["Pourquoi : stocke de façon flexible des champs variables/optionnels dans une seule colonne ; des champs peuvent être ajoutés sans migration de schéma.","Quand : structures imprévisibles comme les payloads d'API externes, champs définis par l'utilisateur ou attributs de produits.","Différences entre moteurs : dans PostgreSQL, utilisez JSONB (binaire, indexable, requêtes @>/? avec GIN) — préférez-le au JSON simple. Le type JSON de MySQL est aussi binaire et s'interroge via une generated column + index. Oracle 21c+ dispose d'un type JSON/OSON ; MSSQL avant 2025 conserve le JSON en NVARCHAR et le traite avec des fonctions ; SQL Server 2025 et Azure SQL disposent d'un type json natif (binaire).","Attention : indexez les champs JSON fréquemment filtrés (PostgreSQL GIN, index sur generated column MySQL) ; sinon chaque requête parcourt tout le document.","Attention : ne mettez pas tout dans du JSON — l'intégrité relationnelle, les clés étrangères et le typage se perdent. Les champs nécessitant requêtes/contraintes doivent être de vraies colonnes."],"related":["decide-sqlnosql"]},"sol_enum":{"kind":"solution","title":"Recommandation : ENUM vs table de référence (lookup)","detail":"Il existe deux options pour des ensembles de catégories limités : le type ENUM du moteur, ou une table de référence + clé étrangère. ENUM est compact et simple, mais ajouter une valeur exige une modification de schéma ; une table de référence est flexible et peut porter des attributs supplémentaires (description, indicateur actif, ordre).","steps":["Quand ENUM : si les valeurs sont très stables et changent rarement (par ex. sexe, jour de la semaine) et qu'aucun attribut supplémentaire n'est nécessaire ; compact et lisible.","Quand une table de référence : si les valeurs peuvent évoluer, si des champs supplémentaires comme traductions/description/ordre/indicateur actif sont nécessaires, ou si des rapports font des jointures dessus — elle est plus flexible.","Différences entre moteurs : PostgreSQL et MySQL proposent un type ENUM natif (dans PostgreSQL, ajouter une valeur à un ENUM exige ALTER TYPE, et l'ordre suit l'ordre de définition). Oracle et MSSQL n'ont pas d'ENUM intégré ; utilisez une contrainte CHECK ou une table de référence.","Attention : ajouter/supprimer une valeur d'un ENUM est une migration de schéma et peut provoquer verrouillage/coût sur une grande table ; préférez une table de référence pour les ensembles changeant souvent.","Alternative : du texte libre + une contrainte CHECK constitue aussi un compromis pratique pour des ensembles petits et stables."],"related":[]}}},"solver/decide-capacity.json":{"id":"decide-capacity","title":"Décision : dimensionnement du serveur","icon":"🧭","category":"Guide de décision","intro":"Un dimensionnement correct part du profil de charge (OLTP, data warehouse, mixte) et exige de trouver le vrai goulot (CPU, E/S ou RAM). Règle d'or : le jeu de données fréquemment consulté (working set) doit tenir en RAM ; mesurez d'abord, puis montez en charge, et gardez une marge pour les pics de charge. Cet arbre vous aide à identifier la charge et à choisir la ressource et le sens de montée en charge (verticale/horizontale) selon le goulot.","start":"root","nodes":{"root":{"kind":"step","title":"Quel est votre profil de charge ?","detail":"Le dimensionnement commence par la charge, car les charges OLTP et analytiques exigent des profils de ressources totalement différents. Quel est le travail prédominant du système : de nombreuses petites transactions rapides (OLTP), du reporting/de l'analyse avec de grands parcours et agrégations (DWH), ou un mélange des deux ? Si vous n'êtes pas sûr, vous devez d'abord mesurer.","choices":[{"label":"OLTP — nombreuses petites transactions courtes (web/application)","next":"bottleneck"},{"label":"Analyse / data warehouse (DWH) — grands parcours et agrégations","next":"sol_dwh"},{"label":"Charge mixte, ou je ne suis pas sûr","next":"sol_measure"},{"label":"Je connais le goulot ; dois-je monter en charge verticalement ou horizontalement ?","next":"sol_scale"}]},"bottleneck":{"kind":"step","title":"Où se situe le goulot : RAM, E/S ou CPU ?","detail":"En OLTP, trouvez le vrai goulot au lieu d'ajouter des ressources à l'aveugle. Examinez vos métriques : les données fréquemment consultées ne tiennent-elles pas en RAM avec de fortes lectures disque (RAM/E/S), la latence/les IOPS disque sont-elles insuffisantes (E/S), ou les données sont-elles déjà en mémoire mais le CPU saturé (CPU) ? Augmenter la mauvaise ressource gaspille argent et temps.","choices":[{"label":"Les données fréquemment consultées ne tiennent pas en RAM ; lectures disque élevées","next":"sol_ram"},{"label":"Les données sont en mémoire, mais la latence/les IOPS disque sont insuffisantes (forte écriture)","next":"sol_io"},{"label":"Les données sont en mémoire et le disque va bien, mais le CPU est constamment saturé","next":"sol_cpu"}]},"sol_measure":{"kind":"solution","title":"Recommandation : mesurez d'abord, puis dimensionnez","detail":"Acheter du matériel sur des suppositions aboutit presque toujours à une mauvaise taille. Le profil de charge et le goulot ressortent des métriques réelles. Le dimensionnement n'est pas une estimation mais une itération guidée par la mesure.","steps":["Pourquoi : qu'il s'agisse d'OLTP ou de DWH, et que le goulot soit le CPU, les E/S ou la RAM, cela ne devient clair qu'en mesurant sous charge réelle ; deviner est une erreur coûteuse.","Mesurez : utilisation CPU et run queue, taux de succès mémoire/cache (buffer cache hit), IOPS et latence disque, connexions actives et événements d'attente.","Outils des moteurs : PostgreSQL pg_stat_* et événements d'attente, Oracle AWR/ASH, MySQL Performance Schema, MSSQL DMV et wait stats.","Approche : testez avec une charge représentative, trouvez la ressource la plus tendue, augmentez uniquement celle-ci et mesurez à nouveau. Modifiez une ressource à la fois pour isoler l'effet.","Ensuite : une fois le profil clair, passez à la décision correspondante (goulot OLTP, DWH ou montée en charge horizontale/verticale)."],"related":["high-cpu","memory-pressure","slow-query"]},"sol_ram":{"kind":"solution","title":"Recommandation : ajoutez de la RAM — le working set doit tenir en mémoire","detail":"La règle OLTP la plus critique : les données et index fréquemment consultés (le working set) doivent tenir en mémoire (buffer/cache). Sinon, chaque requête va sur disque, la latence explose et les E/S deviennent le goulot. La RAM est généralement l'investissement le plus rentable.","steps":["Pourquoi : lire en mémoire est des centaines à des milliers de fois plus rapide que sur disque ; quand le working set tient en RAM, le taux de succès du cache augmente et les E/S disque s'effondrent.","Quand : un faible taux de succès du cache, des événements d'attente 'disk read' fréquents, un jeu de données actif plus grand que la RAM disponible.","Comment : dimensionnez correctement la zone mémoire de la base — PostgreSQL shared_buffers + page cache de l'OS, MySQL innodb_buffer_pool_size (généralement la majeure partie de la RAM), Oracle SGA/buffer cache, MSSQL max server memory.","Objectif : idéalement, working set + index tiennent en mémoire ; si tout ne tient pas, au moins la partie 'chaude' la plus consultée doit tenir.","Attention : ne donnez pas toute la RAM physique au buffer pool ; laissez de la place pour l'OS, la mémoire par connexion et les autres processus, sinon le swap ralentit tout."],"related":["memory-pressure","slow-query"]},"sol_io":{"kind":"solution","title":"Recommandation : renforcez la couche E/S (IOPS/latence)","detail":"Même lorsque les données sont en mémoire, la vitesse du disque est déterminante sous une charge à forte écriture (commit, WAL/redo/binlog, checkpoint). Le problème ici n'est pas la capacité mais les IOPS et la latence. Une mauvaise classe de disque met le système à genoux malgré un CPU/une RAM rapides.","steps":["Pourquoi : pour la durabilité, chaque commit écrit le journal sur disque ; un disque à faibles IOPS/forte latence limite directement le débit transactionnel.","Quand : latence disque élevée, longues attentes 'log/WAL write', goulot pendant les checkpoints, OLTP intensif en écriture.","Comment : utilisez du NVMe/SSD ; déplacez le journal de transactions sur un disque rapide distinct des fichiers de données ; dans le cloud, choisissez une classe de disque avec suffisamment d'IOPS/de débit provisionnés (par ex. provisioned IOPS).","Objectif : fixez des objectifs d'IOPS et de latence — pas seulement les IOPS, la latence p99 compte aussi. Le RAID/cache d'écriture (BBU) peut réduire la latence d'écriture.","Attention : dans le cloud, le débit et les IOPS disque dépendent souvent aussi de la taille de l'instance ; la latence des disques réseau est supérieure à celle du NVMe local."],"related":["disk-full","slow-query"]},"sol_cpu":{"kind":"solution","title":"Recommandation : résolvez le goulot CPU (requêtes d'abord, puis cœurs)","detail":"Si le CPU est saturé alors que les données sont en mémoire et que le disque va bien, la cause racine est généralement des requêtes inefficaces (mauvais plans, index manquants, nombreuses petites requêtes) ou une concurrence excessive. Réduire la charge avant d'ajouter des cœurs est généralement moins cher et plus efficace.","steps":["Pourquoi : le CPU est surtout consommé par les parcours complets, les tris/hachages, la compilation des plans et les changements de contexte dus à trop de connexions. Les corriger coûte moins cher que des cœurs.","Optimisez d'abord : trouvez les requêtes les plus coûteuses (pg_stat_statements / AWR / Performance Schema), ajoutez les index manquants, réduisez les requêtes N+1, réduisez le pool de connexions.","Puis montez en charge : si les requêtes sont saines et le CPU toujours saturé, augmentez le nombre de cœurs (vertical) — surtout pour l'OLTP très concurrent.","Note sur le moteur : plus de connexions = plus de changements de contexte ; un petit pool à rotation rapide l'emporte souvent sur davantage de cœurs (voir la décision sur le pool de connexions).","Attention : si la licence est par cœur (Oracle/MSSQL), ajouter des cœurs coûte cher ; optimiser d'abord les requêtes réduit le coût."],"related":["high-cpu","slow-query"]},"sol_dwh":{"kind":"solution","title":"Recommandation : analyse/DWH — débit, parallélisme et échelle horizontale","detail":"Une charge de data warehouse se compose d'un petit nombre de requêtes lourdes qui parcourent et agrègent de grandes tables. La priorité ici n'est pas une faible latence mais un fort débit de parcours, le parallélisme et généralement une architecture évolutive horizontalement.","steps":["Pourquoi : les requêtes parcourent des téraoctets ; la bande passante disque/mémoire et les cœurs travaillant en parallèle sont déterminants, pas la latence des transactions individuelles.","Comment : stockage à fort débit d'E/S, beaucoup de RAM (pour les agrégations/hash joins), de nombreux cœurs et des requêtes parallèles ; un stockage en colonnes ou une architecture MPP est très efficace à grande échelle.","Échelle horizontale : pour un grand DWH, le distribué/MPP (par ex. moteurs analytiques shardés) monte généralement mieux en charge qu'une machine unique renforcée ; partitionnement + partition pruning réduisent les parcours.","Note sur le moteur : ne reprenez pas les réglages OLTP — pour le DWH, on préfère des zones work_mem/de tri plus grandes, un degré de parallélisme plus élevé et de gros blocs/readahead.","Attention : ne mélangez pas OLTP et DWH sur le même serveur ; les rapports lourds étouffent l'OLTP. Si possible, déplacez l'analyse vers un réplica/data warehouse distinct."],"related":["decide-shard-replica","decide-partition"]},"sol_scale":{"kind":"solution","title":"Principe général : vertical vs horizontal + marge + mesurer d'abord","detail":"La façon de monter en charge compte autant que la ressource choisie. Le vertical (une machine plus grosse) est simple mais a un plafond et un point de défaillance unique ; l'horizontal (plus de machines) passe à l'échelle mais ajoute de la complexité. Dans tous les cas, gardez une marge pour les pics de charge et validez la décision par des mesures.","steps":["Vertical (scale-up) : augmentez les ressources (CPU/RAM/E/S) d'une seule machine — l'option la plus simple et la première étape pour la plupart des OLTP ; mais il existe un plafond physique/de coût et un point de défaillance unique.","Horizontal (scale-out) : ajoutez des réplicas de lecture (pour la charge en lecture) ou du sharding (pour l'écriture/le volume de données) — une échelle quasi illimitée, mais avec une complexité de cohérence et d'exploitation.","Marge : ne planifiez pas les ressources à 100 % ; gardez ~30 % de marge au pic de charge normal pour absorber pics soudains, maintenance et croissance.","Mesurez d'abord : avant de monter en charge, confirmez le goulot par la mesure, ciblez la ressource la plus tendue et mesurez à nouveau après le changement.","Attention : la montée en charge horizontale ne résout pas tous les problèmes — les transactions et jointures distribuées sont coûteuses ; ne basculez qu'une fois la montée en charge verticale et l'optimisation épuisées."],"related":["decide-shard-replica","decide-ha"]}}},"solver/slow-write.json":{"id":"slow-write","title":"Écritures lentes (latence INSERT/UPDATE)","icon":"🐢","category":"Performances","intro":"Les opérations INSERT/UPDATE/DELETE sont plus lentes que prévu. Nous déterminons d'abord ce que l'écriture attend (commit/redo, verrous, disque ou surcoût par ligne), puis remontons pas à pas jusqu'à la cause racine.","start":"root","nodes":{"root":{"kind":"step","title":"Qu'attend l'écriture ?","detail":"Classez le goulot en examinant les événements d'attente : est-il dominé par la synchronisation commit/redo (WAL), les verrous, la latence d'écriture disque ou le surcoût par ligne des index/triggers ?","command":{"oracle":"SELECT event, total_waits, ROUND(time_waited/100,1) AS sec_waited\nFROM v$system_event\nWHERE event IN ('log file sync','log file parallel write','enq: TX - row lock contention','db file sequential read')\nORDER BY time_waited DESC;","postgres":"SELECT wait_event_type, wait_event, count(*) AS sessions\nFROM pg_stat_activity\nWHERE state='active'\nGROUP BY 1,2 ORDER BY 3 DESC;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_waits';\nSHOW GLOBAL STATUS LIKE 'Innodb_os_log_pending_fsyncs';\nSHOW ENGINE INNODB STATUS\\G","mssql":"SELECT TOP 10 wait_type, waiting_tasks_count, wait_time_ms\nFROM sys.dm_os_wait_stats\nWHERE wait_type IN ('WRITELOG','PAGEIOLATCH_EX','LCK_M_X','LCK_M_U')\nORDER BY wait_time_ms DESC;"},"choices":[{"label":"Attente sur commit / redo (sync WAL)","next":"commit"},{"label":"Attente sur un verrou","next":"lock"},{"label":"Surcoût par ligne suspecté (index/triggers)","next":"overhead"},{"label":"Latence d'écriture disque élevée","next":"sol_disk"}]},"commit":{"kind":"step","title":"Schéma de commit ou fsync ?","detail":"Un commit distinct par ligne (autocommit) déclenche la synchronisation redo/journal autant de fois qu'il y a de lignes. À l'inverse, si le nombre de commits est raisonnable mais que chaque commit attend le fsync, le problème est la latence du disque des journaux.","command":{"oracle":"SELECT name, value FROM v$sysstat WHERE name IN ('user commits','redo writes','redo size');\nSELECT ROUND(time_waited_micro/GREATEST(total_waits,1)/1000,2) AS ms_per_sync FROM v$system_event WHERE event='log file sync';","postgres":"SHOW synchronous_commit;\nSELECT wal_records, wal_fpi, wal_bytes, wal_buffers_full FROM pg_stat_wal; -- wal_sync/wal_sync_time PG14-17; PG18+: pg_stat_io (object = 'wal')","mysql":"SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';\nSHOW VARIABLES LIKE 'sync_binlog';\nSHOW GLOBAL STATUS LIKE 'Com_commit';","mssql":"SELECT io_stall_write_ms, num_of_writes, io_stall_write_ms/NULLIF(num_of_writes,0) AS ms_per_write\nFROM sys.dm_io_virtual_file_stats(DB_ID(), 2);"},"choices":[{"label":"Autocommit / un commit à chaque ligne","next":"sol_batch"},{"label":"Le commit attend la synchronisation du journal (fsync)","next":"sol_disk"}]},"lock":{"kind":"step","title":"Qui détient le verrou ?","detail":"Si l'écriture attend un verrou : une autre session détient peut-être la même ligne/le même index, ou de nombreuses sessions tentent d'écrire sur la même ligne 'chaude'.","command":{"oracle":"SELECT s.sid, s.serial#, s.username, s.blocking_session, s.event, s.seconds_in_wait\nFROM v$session s WHERE s.blocking_session IS NOT NULL;","postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event, left(query,60) AS query\nFROM pg_stat_activity\nWHERE cardinality(pg_blocking_pids(pid)) > 0;","mysql":"SELECT requesting_engine_transaction_id AS waiter, blocking_engine_transaction_id AS blocker,\n       object_name, lock_type, lock_mode\nFROM performance_schema.data_lock_waits w\nJOIN performance_schema.data_locks l ON l.engine_lock_id = w.blocking_engine_lock_id;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_time, wait_resource\nFROM sys.dm_exec_requests\nWHERE blocking_session_id <> 0;"},"choices":[{"label":"Une autre session détient le verrou","next":"sol_lock"},{"label":"De nombreuses sessions écrivent sur la même ligne chaude","next":"sol_hotrow"}]},"overhead":{"kind":"step","title":"D'où vient le surcoût par ligne ?","detail":"Chaque INSERT/UPDATE met aussi à jour tous les index de la table et déclenche les triggers/contraintes éventuels. Un nombre excessif d'index ou un trigger lourd ralentit fortement les écritures.","command":{"oracle":"SELECT index_name, uniqueness FROM user_indexes WHERE table_name='T';\nSELECT trigger_name, triggering_event FROM user_triggers WHERE table_name='T';","postgres":"SELECT indexname, indexdef FROM pg_indexes WHERE tablename='t';\nSELECT tgname FROM pg_trigger WHERE tgrelid='t'::regclass AND NOT tgisinternal;","mysql":"SHOW INDEX FROM t;\nSHOW TRIGGERS LIKE 't';","mssql":"SELECT name, type_desc FROM sys.indexes WHERE object_id=OBJECT_ID('dbo.t');\nSELECT name, is_disabled FROM sys.triggers WHERE parent_id=OBJECT_ID('dbo.t');"},"choices":[{"label":"La table a trop d'index / des index inutiles","next":"sol_index"},{"label":"Il y a un coût de trigger ou de contrainte lourd","next":"sol_trigger"}]},"sol_batch":{"kind":"solution","title":"Cause racine : autocommit / commit par ligne","detail":"Un commit distinct pour chaque ligne répète la synchronisation redo/journal autant de fois qu'il y a de lignes. En regroupant les opérations, vous pouvez réduire considérablement le nombre de commits (et donc de fsync).","steps":["Désactivez l'autocommit dans l'application et appliquez des commits par lots, en validant toutes les N lignes (par ex. 500-5000).","Utilisez des INSERT multi-lignes / des chemins de chargement en masse au lieu d'INSERT ligne par ligne (Postgres COPY, Oracle bulk insert, MySQL LOAD DATA, MSSQL BULK INSERT).","Utilisez des prepared statements pour réduire le coût des analyses répétées (hard parse).","Après le changement, vérifiez que l'attente 'log file sync' / WRITELOG a diminué."],"related":["active-sessions"]},"sol_disk":{"kind":"solution","title":"Cause racine : disque redo/journal lent (latence fsync)","detail":"Chaque commit attend que l'enregistrement redo/WAL soit écrit sur un stockage durable (fsync). Si le disque des journaux est lent (forte latence d'écriture, IOPS insuffisantes), chaque commit paie cette latence.","steps":["Déplacez les fichiers de journal/redo sur un disque dédié à faible latence (de préférence SSD NVMe) ; séparez-les physiquement des datafiles.","Mesurez la latence du stockage (statistiques d'E/S fichiers) ; si les ms par écriture sont élevées, revoyez le matériel/le cache d'écriture RAID.","Dans les environnements virtuels/cloud, augmentez les limites d'IOPS et de débit provisionnés.","Si les exigences de durabilité le permettent, envisagez les réglages de group commit / commit delay (par ex. Postgres commit_delay) ; mais évaluez le risque de perte de données."],"related":[]},"sol_lock":{"kind":"solution","title":"Cause racine : une autre session détient le verrou","detail":"L'écriture attend un verrou de ligne/d'index détenu par une autre session. Le bloqueur est généralement une transaction longue ou dont le commit a été oublié.","steps":["Identifiez la session bloquante et examinez ce qu'elle fait (un long batch, ou idle in transaction ?).","Si le bloqueur reste ouvert/long sans raison, corrigez le schéma commit/rollback dans l'application.","En urgence, terminez le bloqueur de façon contrôlée (kill session / pg_terminate_backend / KILL).","Gardez les transactions courtes ; n'attendez pas d'interaction utilisateur au sein d'une transaction."],"related":["blocking-sessions","current-locks"]},"sol_hotrow":{"kind":"solution","title":"Cause racine : contention sur une ligne chaude","detail":"De nombreuses sessions tentent d'écrire sur la même ligne (par ex. un compteur, un numéro de séquence, un solde) ; elles font toutes la queue pour le même verrou et les écritures sont sérialisées.","steps":["Pour le schéma du compteur chaud, répartissez la valeur en compartiments parallèles (sharded counter) et additionnez-les à la lecture.","Remplacez la génération de séquence/d'ID depuis une ligne unique centrale par un mécanisme sequence/identity.","Réduisez la fréquence des mises à jour : accumulez les totaux en cours dans l'application et écrivez-les périodiquement.","Réduisez la contention en standardisant l'ordre des opérations et en raccourcissant la durée des verrous."],"related":["current-locks"]},"sol_index":{"kind":"solution","title":"Cause racine : surcoût excessif des index","detail":"Chaque index de la table est mis à jour à chaque écriture. Des index nombreux ou inutilisés ralentissent directement les INSERT/UPDATE.","steps":["Identifiez les index inutilisés/redondants (statistiques d'utilisation) et supprimez-les en toute sécurité.","Simplifiez les index composites larges ; fusionnez les index qui répètent les mêmes colonnes de tête.","Pour les gros chargements en masse, envisagez de désactiver temporairement les index et de les reconstruire après le chargement.","Après le changement, mesurez à nouveau la latence d'écriture et confirmez l'amélioration."],"related":["slow-queries"]},"sol_trigger":{"kind":"solution","title":"Cause racine : trigger / contrainte lourd(e)","detail":"Un trigger déclenché à chaque écriture ou une contrainte en cours de validation (surtout un contrôle de clé étrangère sans index) ajoute un coût supplémentaire à chaque ligne.","steps":["Rendez asynchrone/basé sur une file le travail lourd du corps du trigger (par ex. écritures de journal synchrones).","Indexez les colonnes de clé étrangère référençantes (enfant) ; sinon chaque DELETE/UPDATE sur le parent parcourt intégralement la table enfant.","Si possible, passez les triggers de niveau ligne au niveau instruction.","Déplacez la logique de trigger inutile, ou devenue règle métier, vers la couche applicative."],"related":[]}}},"solver/io-bottleneck.json":{"id":"io-bottleneck","title":"Forte attente E/S / latence disque","icon":"💽","category":"Performances","intro":"Le système attend les E/S disque. Nous distinguons d'abord si la charge est dominée par les lectures ou les écritures, puis remontons aux causes : buffer cache insuffisant, parcours complets de tables, tempêtes de checkpoints, limites d'IOPS ou débordements vers l'espace temporaire.","start":"root","nodes":{"root":{"kind":"step","title":"Les E/S sont-elles dominées par les lectures ou les écritures ?","detail":"Utilisez les statistiques d'E/S fichiers pour déterminer si l'attente porte sur la lecture (données lues sur disque) ou sur l'écriture (écritures redo/checkpoint/données). Cela divise l'arbre en deux.","command":{"oracle":"SELECT function_name, small_read_reqs+large_read_reqs AS reads, small_write_reqs+large_write_reqs AS writes,\n       small_read_megabytes+large_read_megabytes+small_write_megabytes+large_write_megabytes AS mb\nFROM v$iostat_function ORDER BY mb DESC;","postgres":"SELECT sum(blks_read) AS reads, sum(blks_hit) AS hits FROM pg_stat_database;\nSELECT buffers_checkpoint, buffers_clean, buffers_backend FROM pg_stat_bgwriter;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_data_read';\nSHOW GLOBAL STATUS LIKE 'Innodb_data_written';\nSHOW GLOBAL STATUS LIKE 'Innodb_data_reads';","mssql":"SELECT DB_NAME(database_id) AS db, io_stall_read_ms, io_stall_write_ms, num_of_reads, num_of_writes\nFROM sys.dm_io_virtual_file_stats(NULL, NULL)\nORDER BY io_stall_read_ms + io_stall_write_ms DESC;"},"choices":[{"label":"Surtout des E/S en lecture","next":"read"},{"label":"Surtout des E/S en écriture","next":"write"}]},"read":{"kind":"step","title":"Pourquoi les E/S en lecture sont-elles élevées ?","detail":"Des lectures physiques excessives ont trois causes principales : le buffer cache est trop petit (le working set ne tient pas en RAM), les requêtes effectuent des parcours complets de tables, ou de grandes opérations de tri/hachage débordent vers l'espace temporaire.","command":{"oracle":"SELECT ROUND((1-(pr.value/(dbg.value+cg.value)))*100,2) AS buffer_hit_pct\nFROM v$sysstat pr, v$sysstat dbg, v$sysstat cg\nWHERE pr.name='physical reads' AND dbg.name='db block gets' AND cg.name='consistent gets';\nSELECT sql_id, disk_reads, executions FROM v$sql ORDER BY disk_reads DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT ROUND(sum(blks_hit)*100.0/nullif(sum(blks_hit)+sum(blks_read),0),2) AS cache_hit_pct FROM pg_stat_database;\nSELECT query, shared_blks_read, temp_blks_read FROM pg_stat_statements ORDER BY shared_blks_read DESC LIMIT 10;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';\nSHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';\nSHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';","mssql":"SELECT CAST(100.0 * a.cntr_value / NULLIF(b.cntr_value, 0) AS decimal(5,2)) AS buffer_cache_hit_ratio\nFROM sys.dm_os_performance_counters a\nJOIN sys.dm_os_performance_counters b ON b.object_name = a.object_name AND b.counter_name = 'Buffer cache hit ratio base'\nWHERE a.counter_name = 'Buffer cache hit ratio' AND a.object_name LIKE '%Buffer Manager%';"},"choices":[{"label":"Le taux de succès du buffer cache est faible, le working set ne tient pas","next":"sol_cache"},{"label":"Les requêtes effectuent des parcours complets de tables","next":"sol_scan"},{"label":"De grands tris/hachages débordent vers l'espace temporaire","next":"sol_spill"}]},"write":{"kind":"step","title":"Pourquoi les E/S en écriture sont-elles élevées ?","detail":"Un goulot côté écriture provient soit de checkpoints/flushes périodiques qui saturent brièvement le disque (tempête de checkpoints), soit d'un stockage manquant d'IOPS/de débit pour soutenir la charge.","command":{"oracle":"SELECT event, total_waits, ROUND(time_waited/100,1) AS sec\nFROM v$system_event\nWHERE event IN ('log file parallel write','db file parallel write','checkpoint completed')\nORDER BY time_waited DESC;","postgres":"-- PG17+:\nSELECT num_timed, num_requested, buffers_written FROM pg_stat_checkpointer;\n-- PG16 and earlier:\nSELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, maxwritten_clean\nFROM pg_stat_bgwriter;\nSHOW checkpoint_completion_target;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';\nSHOW GLOBAL STATUS LIKE 'Innodb_os_log_written';\nSHOW ENGINE INNODB STATUS\\G","mssql":"SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Checkpoint pages/sec';\nSELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Page writes/sec';"},"choices":[{"label":"Vagues soudaines de checkpoint/flush (tempête de checkpoints)","next":"sol_checkpoint"},{"label":"Le stockage ne suit pas une charge soutenue (limite d'IOPS)","next":"sol_iops"}]},"sol_cache":{"kind":"solution","title":"Cause racine : buffer cache insuffisant","detail":"Le jeu de données fréquemment consulté (working set) ne tient pas dans le buffer cache, si bien que les blocs sont constamment relus sur disque. Le taux de succès est faible et les lectures physiques élevées.","steps":["Augmentez la taille du buffer/cache : Oracle db_cache_size/SGA, Postgres shared_buffers, MySQL innodb_buffer_pool_size, MSSQL max server memory.","Comparez le taux de succès et les lectures physiques avant et après le changement.","Si la RAM est physiquement insuffisante, ajoutez de la mémoire au serveur ou réduisez le working set par archivage/partitionnement.","Si le vrai goulot provient d'un seul schéma de parcours, passez à la branche parcours de l'arbre du taux de succès du cache."],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"Cause racine : parcours complets de tables","detail":"Les requêtes parcourent de grandes tables de bout en bout faute d'index adapté ; cela provoque de nombreuses lectures physiques et pollue le cache avec des blocs inutiles.","steps":["Obtenez les plans (EXPLAIN) des requêtes générant le plus de lectures physiques et repérez les parcours complets.","Ajoutez des index adaptés sur les colonnes de filtre et de jointure ; vérifiez que le plan passe à un accès par index.","Pour les très grandes tables, réduisez les données parcourues avec un partitionnement par date/plage.","Ne sélectionnez que les colonnes nécessaires au lieu d'un 'SELECT *' superflu pour réduire le nombre de blocs lus."],"related":["slow-queries"]},"sol_spill":{"kind":"solution","title":"Cause racine : tris/hachages débordant vers l'espace temporaire","detail":"Les grandes opérations de tri, hash join ou group by ne tiennent pas en mémoire de travail et écrivent dans l'espace temporaire (temp/tempdb), générant de fortes E/S en lecture/écriture.","steps":["Augmentez le réglage de mémoire de travail : Oracle pga_aggregate_target, Postgres work_mem, MySQL sort_buffer_size/join_buffer_size, MSSQL memory grant.","Réduisez le volume de l'opération : appliquez les filtres tôt, supprimez les tris inutiles, améliorez l'ordre des jointures.","Évitez les débordements sur disque en ajoutant des index adaptés aux tris/regroupements fréquents.","Placez l'espace temporaire sur un disque rapide ; si le problème persiste, approfondissez avec l'arbre de pression undo/temp."],"related":["slow-queries"]},"sol_checkpoint":{"kind":"solution","title":"Cause racine : tempête de checkpoints","detail":"Les pages modifiées (dirty pages) s'accumulent longtemps puis sont écrites en masse sur disque à intervalles rapprochés ; ces vagues d'écriture soudaines saturent périodiquement les E/S.","steps":["Étalez les checkpoints dans le temps : dans Postgres, augmentez checkpoint_completion_target et équilibrez checkpoint_timeout.","Dans MySQL, réglez innodb_io_capacity / innodb_io_capacity_max selon le stockage et ajustez l'adaptive flushing.","Dans MSSQL, lissez les vagues soudaines avec les indirect checkpoints (target_recovery_time).","Dans Oracle, équilibrez la fréquence des checkpoints avec fast_start_mttr_target et agrandissez les fichiers redo log."],"related":[]},"sol_iops":{"kind":"solution","title":"Cause racine : IOPS / débit insuffisants","detail":"Le stockage manque d'IOPS ou de bande passante pour servir les requêtes de lecture+écriture qu'exige la charge ; la profondeur de file et la latence restent durablement élevées.","steps":["Mesurez la latence disque (ms par requête) et comparez-la à la limite d'IOPS du matériel/du cloud.","Passez à un stockage plus rapide (HDD→SSD/NVMe) ou augmentez le niveau d'IOPS/de débit provisionnés dans le cloud.","Réduisez la contention en répartissant données, redo/journaux et espace temporaire sur des disques différents.","Comme correctif durable, réduisez la demande totale d'E/S (indexation, archivage, optimisation des requêtes)."],"related":[]}}},"solver/undo-temp-pressure.json":{"id":"undo-temp-pressure","title":"Pression sur undo / TempDB / espace temporaire","icon":"🧯","category":"Capacité","intro":"L'espace undo/rollback, l'espace temporaire (temp) ou TempDB est sous pression. Nous déterminons où se situe la pression et remontons à la cause racine, comme un grand tri/hachage, une requête/transaction longue ou une fuite d'espace.","start":"root","nodes":{"root":{"kind":"step","title":"Où est la pression : undo, temp ou contention tempdb ?","detail":"Classez la source de pression en examinant l'utilisation de l'espace : est-ce le tablespace undo/rollback qui se remplit, l'espace temporaire (temp) général ou, dans MSSQL, une contention de latch d'allocation de pages TempDB ?","command":{"oracle":"SELECT tablespace_name, ROUND(used_percent,1) AS used_pct\nFROM dba_tablespace_usage_metrics\nWHERE tablespace_name LIKE '%UNDO%' OR tablespace_name LIKE 'TEMP%';","postgres":"SELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_written\nFROM pg_stat_database ORDER BY temp_bytes DESC;","mysql":"SELECT * FROM information_schema.INNODB_TEMP_TABLE_INFO;\nSHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';","mssql":"SELECT SUM(user_object_reserved_page_count)*8/1024 AS user_obj_mb,\n       SUM(internal_object_reserved_page_count)*8/1024 AS internal_obj_mb,\n       SUM(version_store_reserved_page_count)*8/1024 AS version_store_mb\nFROM sys.dm_db_file_space_usage;"},"choices":[{"label":"L'espace undo/rollback se remplit (ORA-01555 / ORA-30036)","next":"undo"},{"label":"L'espace temporaire (temp) se remplit (débordement de tri/hachage)","next":"temp"},{"label":"Contention de latch d'allocation TempDB (PFS/GAM/SGAM)","next":"tempdb"}]},"undo":{"kind":"step","title":"Qu'est-ce qui consomme l'undo ?","detail":"La pression undo est généralement de deux types : une REQUÊTE longue a besoin d'un ancien undo pour la lecture cohérente à son heure de début (ORA-01555), ou une TRANSACTION longue/ouverte génère tant d'undo qu'elle remplit l'espace et provoque ORA-30036.","command":{"oracle":"SELECT s.sid, s.username, t.used_ublk*8/1024 AS undo_mb, s.status, t.start_time\nFROM v$transaction t JOIN v$session s ON s.taddr=t.addr\nORDER BY undo_mb DESC;\nSELECT MAX(maxquerylen) AS longest_query_sec, MAX(tuned_undoretention) AS tuned_retention FROM v$undostat;","postgres":"SELECT pid, now()-xact_start AS xact_age, state, left(query,60) AS query\nFROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 10;"},"choices":[{"label":"Une requête longue a besoin d'un ancien undo (ORA-01555)","next":"sol_undo_query"},{"label":"Une transaction longue/ouverte accumule de l'undo (ORA-30036)","next":"sol_undo_tx"}]},"temp":{"kind":"step","title":"Qu'est-ce qui remplit l'espace temporaire ?","detail":"La pression temp provient soit d'une grande opération de tri/hachage débordant sur disque (normale mais énorme), soit d'une seule session qui retient le temp en permanence (curseur ouvert, fuite, requête sans fin).","command":{"oracle":"SELECT s.sid, s.username, u.tablespace, u.blocks*8/1024 AS temp_mb, u.sqladdr\nFROM v$tempseg_usage u JOIN v$session s ON s.saddr=u.session_addr\nORDER BY temp_mb DESC;","postgres":"SELECT query, temp_blks_written, temp_blks_read\nFROM pg_stat_statements ORDER BY temp_blks_written DESC LIMIT 10;","mysql":"SELECT event_name, current_number_of_bytes_used\nFROM performance_schema.memory_summary_global_by_event_name\nWHERE event_name LIKE '%temptable%' OR event_name LIKE '%sort%';","mssql":"SELECT session_id, request_id, SUM(internal_objects_alloc_page_count)*8/1024 AS internal_mb\nFROM sys.dm_db_task_space_usage GROUP BY session_id, request_id ORDER BY internal_mb DESC;"},"choices":[{"label":"Une grande opération de tri/hachage déborde sur disque","next":"sol_temp_sort"},{"label":"Une seule session retient le temp (fuite/curseur ouvert)","next":"sol_temp_leak"}]},"tempdb":{"kind":"step","title":"La contention TempDB concerne-t-elle l'allocation ou le volume ?","detail":"Deux problèmes distincts surviennent dans TempDB de MSSQL : une contention PAGELATCH sur les pages PFS/GAM/SGAM (nombreux petits objets temporaires), ou un grand tri/version store qui remplit TempDB en volume.","command":{"mssql":"SELECT session_id, wait_type, wait_duration_ms, resource_description\nFROM sys.dm_os_waiting_tasks\nWHERE wait_type LIKE 'PAGELATCH%' AND resource_description LIKE '2:%';\nSELECT COUNT(*) AS tempdb_data_files FROM sys.master_files WHERE database_id=2 AND type=0;"},"choices":[{"label":"Contention de latch PFS/GAM/SGAM","next":"sol_tempdb_latch"},{"label":"Un grand tri/version store remplit TempDB","next":"sol_temp_sort"}]},"sol_undo_query":{"kind":"solution","title":"Cause racine : requête longue + undo insuffisant (ORA-01555)","detail":"L'ancien undo dont la requête longue a besoin pour une lecture cohérente a été écrasé parce que la rétention a expiré ou que l'espace était insuffisant.","steps":["Portez undo_retention à une valeur supérieure à la durée de la requête la plus longue (v$undostat.maxquerylen).","Agrandissez le tablespace UNDO ou activez l'autoextend afin que la période de rétention soit garantie par l'espace.","Pour les rapports critiques, protégez l'ancien undo avec ALTER TABLESPACE undotbs RETENTION GUARANTEE (les DML peuvent échouer si l'espace se remplit ; prudence).","Pour une correction permanente, optimisez la requête longue ou déplacez-la vers un standby/réplica."],"related":["slow-queries"]},"sol_undo_tx":{"kind":"solution","title":"Cause racine : une transaction longue/ouverte a rempli l'undo (ORA-30036)","detail":"Un seul DML énorme ou une transaction ouverte au commit retardé a généré trop d'undo ; le tablespace UNDO n'a pas pu s'étendre et ORA-30036 a été levée.","steps":["Identifiez la transaction active détenant le plus d'undo (v$transaction.used_ublk) et trouvez son propriétaire.","Exécutez le DML énorme par tranches de date/plage avec des commits intermédiaires.","Corrigez côté application les sessions au commit oublié / idle-in-transaction ; terminez-les de façon contrôlée si nécessaire.","Ajoutez temporairement de l'espace au tablespace UNDO ; de façon permanente, planifiez la rétention et la taille de l'espace selon la charge."],"related":["active-sessions"]},"sol_temp_sort":{"kind":"solution","title":"Cause racine : un grand tri/hachage remplit l'espace temporaire","detail":"Les grandes opérations de tri/hachage/group by qui ne tiennent pas en mémoire débordent vers l'espace temporaire et peuvent le remplir.","steps":["Augmentez la mémoire de travail : Oracle pga_aggregate_target, Postgres work_mem, MySQL sort_buffer_size, MSSQL memory grant.","Améliorez la requête : supprimez les tris inutiles, appliquez les filtres tôt, ajoutez des index sur les colonnes de tri/regroupement.","Découpez les très gros traitements en tranches ; limitez la concurrence afin qu'ils ne retiennent pas tous le temp en même temps.","Agrandissez l'espace temporaire et placez-le sur un disque rapide ; configurez TempDB de MSSQL avec plusieurs fichiers selon le nombre de cœurs."],"related":["slow-queries"]},"sol_temp_leak":{"kind":"solution","title":"Cause racine : une session retient de l'espace temporaire (fuite)","detail":"Une seule session ne libère pas l'espace temporaire en raison de curseurs non fermés, d'une requête sans fin ou de tables temporaires ouvertes.","steps":["Identifiez la session qui retient le plus de temp et le SQL qu'elle exécute.","Assurez-vous que les curseurs/jeux de résultats et les tables temporaires sont correctement fermés dans l'application.","Terminez de façon contrôlée la session bloquée/excessivement longue et vérifiez que l'espace est restitué.","Définissez des tables temporaires globales de sorte qu'elles soient nettoyées automatiquement en fin de session/transaction."],"related":["active-sessions"]},"sol_tempdb_latch":{"kind":"solution","title":"Cause racine : contention de latch PFS/GAM/SGAM dans TempDB","detail":"Lorsque de nombreuses sessions créent simultanément de petits objets temporaires, une contention PAGELATCH apparaît sur les pages de cartes d'allocation de TempDB (PFS/GAM/SGAM).","steps":["Configurez un nombre de fichiers de données TempDB proportionnel au nombre de cœurs CPU, de tailles égales (par ex. 4-8 fichiers, tous avec la même taille/le même autogrowth).","Sur les versions récentes de SQL Server, activez les options qui réduisent la contention d'allocation (memory-optimized tempdb metadata).","Réduisez la création de tables temporaires : remplacez les tables #temp inutiles par des variables de table / une seule requête.","Après le changement, vérifiez que les temps d'attente PAGELATCH ont diminué."],"related":["current-locks"]}}},"solver/long-transaction.json":{"id":"long-transaction","title":"Transaction longue / ouverte","icon":"⏱️","category":"Verrous","intro":"Une transaction longue ou ouverte accumule verrous, undo et bloat. Nous trouvons la transaction fautive, déterminons son type (idle in transaction, long batch, impact) et la résolvons en toute sécurité.","start":"root","nodes":{"root":{"kind":"step","title":"Quelle est la plus ancienne transaction ouverte ?","detail":"Listez les transactions ouvertes par heure de début. La plus ancienne est généralement la source du problème. Vérifiez son état : s'exécute-t-elle activement, ou est-elle 'idle in transaction' en attente d'un commit ?","command":{"oracle":"SELECT s.sid, s.serial#, s.username, s.status, t.start_time, t.used_ublk*8/1024 AS undo_mb\nFROM v$transaction t JOIN v$session s ON s.taddr=t.addr\nORDER BY t.start_time;","postgres":"SELECT pid, state, now()-xact_start AS xact_age, now()-state_change AS since_change, left(query,60) AS query\nFROM pg_stat_activity\nWHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 20;","mysql":"SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND,trx_started,NOW()) AS secs, trx_mysql_thread_id, trx_rows_locked\nFROM information_schema.INNODB_TRX ORDER BY trx_started;","mssql":"SELECT s.session_id, s.login_name, t.transaction_begin_time,\n       DATEDIFF(second,t.transaction_begin_time,GETDATE()) AS secs\nFROM sys.dm_tran_active_transactions t\nJOIN sys.dm_tran_session_transactions st ON st.transaction_id=t.transaction_id\nJOIN sys.dm_exec_sessions s ON s.session_id=st.session_id\nORDER BY t.transaction_begin_time;"},"choices":[{"label":"idle in transaction (commit oublié, inactive)","next":"idle"},{"label":"Batch/DML actif mais très long","next":"batch"},{"label":"Accumulation de verrous/bloat/undo pendant que la transaction se poursuit","next":"impact"}]},"idle":{"kind":"step","title":"Pourquoi l'état idle in transaction survient-il ?","detail":"La session a ouvert une transaction mais reste inactive sans exécuter de requête ; autrement dit, le commit/rollback a été oublié. Cela retient inutilement verrous et undo. Une correction applicative suffit-elle, ou faut-il la terminer d'urgence ?","command":{"postgres":"SELECT pid, now()-state_change AS idle_age, left(query,80) AS last_query\nFROM pg_stat_activity WHERE state='idle in transaction' ORDER BY state_change;","mysql":"SELECT trx.trx_mysql_thread_id, trx.trx_started, p.time AS idle_secs, p.command\nFROM information_schema.INNODB_TRX trx\nJOIN information_schema.PROCESSLIST p ON p.id=trx.trx_mysql_thread_id\nWHERE p.command='Sleep' ORDER BY trx.trx_started;","oracle":"SELECT sid, serial#, status, last_call_et AS idle_secs\nFROM v$session WHERE status='INACTIVE' AND type='USER' AND last_call_et>300;","mssql":"SELECT s.session_id, s.status, DATEDIFF(second,s.last_request_end_time,GETDATE()) AS idle_secs\nFROM sys.dm_exec_sessions s\nWHERE s.session_id IN (SELECT session_id FROM sys.dm_tran_session_transactions)\n  AND s.status='sleeping';"},"choices":[{"label":"L'application/le pool de connexions ne valide pas (correction permanente)","next":"sol_idle"},{"label":"Je dois la terminer immédiatement","next":"sol_kill"}]},"batch":{"kind":"step","title":"Que fait la transaction longue active ?","detail":"La session s'exécute réellement (active). S'agit-il d'un seul DML/rapport énorme, ou d'un bloqueur qui fait attendre d'autres sessions et doit être coupé d'urgence ?","command":{"oracle":"SELECT s.sid, s.serial#, sq.sql_text, s.last_call_et AS running_secs\nFROM v$session s JOIN v$sql sq ON sq.sql_id=s.sql_id\nWHERE s.status='ACTIVE' AND s.type='USER' ORDER BY s.last_call_et DESC;","postgres":"SELECT pid, now()-query_start AS run_time, left(query,80) AS query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start LIMIT 10;","mysql":"SELECT id, time AS run_secs, state, LEFT(info,80) AS query\nFROM information_schema.PROCESSLIST WHERE command<>'Sleep' ORDER BY time DESC;","mssql":"SELECT r.session_id, r.status, r.total_elapsed_time/1000 AS secs, SUBSTRING(t.text,1,80) AS query\nFROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nORDER BY r.total_elapsed_time DESC;"},"choices":[{"label":"Un seul DML/rapport énorme ; il faut le découper","next":"sol_batch"},{"label":"Il fait attendre les autres ; terminez-la d'urgence","next":"sol_kill"}]},"impact":{"kind":"step","title":"Qu'accumule la transaction longue ?","detail":"Déterminez l'effet secondaire de la transaction longue : s'agit-il surtout d'une file de verrous faisant attendre d'autres sessions, ou d'un bloat dû à la conservation d'anciennes versions de lignes qui bloque le nettoyage (VACUUM/undo) ?","command":{"postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid))>0;\nSELECT max(age(backend_xmin)) AS max_xmin_age FROM pg_stat_activity WHERE backend_xmin IS NOT NULL;","oracle":"SELECT blocking_session, sid, serial#, seconds_in_wait, event\nFROM v$session WHERE blocking_session IS NOT NULL;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_time\nFROM sys.dm_exec_requests WHERE blocking_session_id<>0;"},"choices":[{"label":"Une file de verrous fait attendre d'autres sessions","next":"sol_lock_backlog"},{"label":"L'accumulation d'anciennes versions bloque le nettoyage (undo/bloat)","next":"sol_bloat"}]},"sol_idle":{"kind":"solution","title":"Cause racine : l'application ne valide pas (idle in transaction)","detail":"Le pool de connexions ou l'application ouvre une transaction puis garde la connexion inactive sans commit/rollback. Cela retient inutilement verrous et undo.","steps":["Dans le code applicatif, garantissez un commit/rollback sur chaque chemin où une transaction est ouverte ; n'attendez pas de saisie utilisateur ni de longue E/S au sein d'une transaction.","Coupez automatiquement les transactions inactives : Postgres idle_in_transaction_session_timeout, réglages de connexion/timeout MySQL, maxLifetime du pool applicatif.","Configurez le pool de connexions selon le principe 'autocommit activé, transactions courtes'.","Mettez en place une supervision : déclenchez une alerte pour les sessions idle-in-transaction longues."],"related":["active-sessions","blocking-sessions"]},"sol_kill":{"kind":"solution","title":"Solution : terminez la session fautive en toute sécurité","detail":"Si une session bloquante/longue fait attendre les autres, elle doit être terminée de façon contrôlée. La terminaison déclenche un rollback, qui peut nécessiter espace/temps ; prudence.","steps":["Vérifiez d'abord de quelle session il s'agit (quel utilisateur/application, quel traitement) pour ne pas couper un traitement critique.","Terminez : Oracle ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE; Postgres SELECT pg_terminate_backend(pid); MySQL KILL thread_id; MSSQL KILL session_id;","Si vous tuez une grosse transaction d'écriture, attendez la fin du rollback (annulation via undo/journal).","Après la terminaison, vérifiez que les verrous et les sessions en attente ont disparu ; corrigez durablement la cause racine (idle/batch)."],"related":["blocking-sessions"]},"sol_batch":{"kind":"solution","title":"Cause racine : un seul DML/rapport énorme","detail":"Un DML qui traite des millions de lignes en une seule transaction, ou un rapport très long, accumule verrous et undo/bloat pendant toute sa durée.","steps":["Découpez le gros DML en tranches par date/plage/clé et validez après chaque tranche.","Déplacez le rapport lourd hors des heures de pointe ou délestez-le vers un standby/réplica de lecture.","Optimisez la requête longue pour réduire sa durée totale (et donc le temps pendant lequel elle retient verrous/undo).","Rendez le traitement batch observable : journalisez sa progression et rendez-le reprenable là où il s'est arrêté."],"related":["slow-queries"]},"sol_lock_backlog":{"kind":"solution","title":"Cause racine : file d'attente de verrous","detail":"Du fait des verrous qu'elle détient, la transaction longue a créé une file d'attente derrière elle ; de nombreuses sessions attendent la même ressource.","steps":["Identifiez la session en tête de la chaîne de verrous (head blocker) ; c'est le seul nœud qui videra la file.","Raccourcissez le head blocker (commit/rollback) ou terminez-le de façon contrôlée ; observez la file se vider.","Pour réduire la durée des verrous, réduisez la taille des transactions et définissez un ordre d'accès cohérent.","Le cas échéant, passez à des approches nécessitant des verrous plus faibles (niveau ligne, niveau d'isolation adapté)."],"related":["blocking-sessions","current-locks"]},"sol_bloat":{"kind":"solution","title":"Cause racine : accumulation d'anciennes versions (undo/bloat)","detail":"La transaction longue empêche le nettoyage des anciennes versions de lignes antérieures à elle. Dans Postgres, cela se traduit par du bloat de tables/d'index, dans Oracle par une pression undo.","steps":["Trouvez la session détenant le plus ancien xmin/la plus ancienne transaction (Postgres backend_xmin, Oracle la plus ancienne transaction active).","Raccourcissez/terminez cette transaction afin que le nettoyage (VACUUM / recyclage de l'undo) puisse reprendre.","Dans Postgres, une fois la transaction terminée, exécutez VACUUM sur les tables gonflées (VACUUM FULL/pg_repack si nécessaire).","Adoptez durablement un schéma applicatif gardant les transactions courtes et rendez l'autovacuum plus agressif."],"related":[]}}},"solver/plan-regression.json":{"id":"plan-regression","title":"Plan de requête dégradé (soudainement lent)","icon":"📉","category":"Performances","intro":"Une requête rapide hier est soudainement lente aujourd'hui. En général, l'optimiseur est passé à un plan moins bon. Nous trouvons la cause racine parmi statistiques obsolètes, sensibilité aux paramètres, croissance du volume de données ou changement brutal de plan, et stabilisons le plan.","start":"root","nodes":{"root":{"kind":"step","title":"Le plan a-t-il vraiment changé, et quand ?","detail":"Existe-t-il plusieurs plans (plan_hash/plan_generation) pour la même requête, et quand le ralentissement a-t-il commencé ? Coïncide-t-il avec un déploiement, une montée de version, un changement de paramètre ou un gros chargement de données récent ?","command":{"oracle":"SELECT sql_id, plan_hash_value, executions, ROUND(elapsed_time/GREATEST(executions,1)/1e6,3) AS sec_per_exec, first_load_time\nFROM v$sql WHERE sql_id='&sqlid' ORDER BY first_load_time;","postgres":"EXPLAIN (ANALYZE, BUFFERS) SELECT /* your query */ 1;\n-- Trend: SELECT query, calls, mean_exec_time FROM pg_stat_statements WHERE query LIKE '%...%';","mysql":"EXPLAIN FORMAT=JSON SELECT /* your query */ 1;\nSELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1e9 AS avg_ms\nFROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE '%...%';","mssql":"SELECT qs.plan_generation_num, qs.execution_count, qs.total_worker_time/qs.execution_count AS avg_cpu_us, qs.creation_time\nFROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t\nWHERE t.text LIKE '%...%';"},"choices":[{"label":"Les statistiques sont peut-être obsolètes","next":"stats"},{"label":"La même requête se comporte différemment selon le paramètre (sniffing/peeking)","next":"sniff"},{"label":"Le volume de données a fortement augmenté","next":"sol_datagrowth"},{"label":"Le plan a changé brutalement après une montée de version/un flush","next":"sol_planflip"}]},"stats":{"kind":"step","title":"Les statistiques sont-elles à jour ?","detail":"L'optimiseur fonde ses décisions sur les statistiques. Si la date du dernier analyze est ancienne ou si le compteur de modifications est élevé, les estimations ont peut-être réellement dérivé, ce qui mène à un mauvais plan.","command":{"oracle":"SELECT table_name, num_rows, last_analyzed, stale_stats\nFROM dba_tab_statistics WHERE table_name='T';","postgres":"SELECT relname, n_live_tup, n_mod_since_analyze, last_analyze, last_autoanalyze\nFROM pg_stat_user_tables WHERE relname='t';","mysql":"SELECT table_name, update_time, table_rows FROM information_schema.tables WHERE table_name='t';\nSHOW INDEX FROM t;","mssql":"SELECT s.name, STATS_DATE(s.object_id,s.stats_id) AS last_updated, p.modification_counter, p.rows\nFROM sys.stats s CROSS APPLY sys.dm_db_stats_properties(s.object_id,s.stats_id) p\nWHERE s.object_id=OBJECT_ID('dbo.t');"},"choices":[{"label":"Statistiques obsolètes/absentes","next":"sol_stats"},{"label":"Les statistiques sont à jour mais le plan reste sensible aux paramètres","next":"sniff"}]},"sniff":{"kind":"step","title":"Y a-t-il une sensibilité aux paramètres ?","detail":"La requête choisit et met en cache un plan selon la valeur de paramètre de sa première exécution (parameter sniffing MSSQL / bind peeking Oracle). Ce plan peut être mauvais pour d'autres valeurs à distribution asymétrique.","command":{"oracle":"SELECT sql_id, child_number, plan_hash_value, is_bind_sensitive, is_bind_aware\nFROM v$sql WHERE sql_id='&sqlid';","mssql":"SELECT qs.query_hash, qs.execution_count, qs.total_worker_time/qs.execution_count AS avg_cpu_us\nFROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t\nWHERE t.text LIKE '%...%';","postgres":"-- For generic vs custom plan behavior:\nPREPARE p AS SELECT /* your query */ 1;\nEXPLAIN EXECUTE p;"},"choices":[{"label":"Oui, le plan alterne entre bon et mauvais selon le paramètre","next":"sol_sniff"},{"label":"Le bon plan est connu, je veux le figer définitivement","next":"sol_forceplan"}]},"sol_stats":{"kind":"solution","title":"Cause racine : statistiques obsolètes / absentes","detail":"Sur la base d'anciennes statistiques ne correspondant pas à la réalité, l'optimiseur a mal estimé le nombre de lignes et choisi un mauvais plan. C'est la cause la plus fréquente de régression de plan.","steps":["Collectez des statistiques fraîches pour les tables et index concernés : Oracle DBMS_STATS.GATHER_TABLE_STATS, Postgres ANALYZE, MySQL ANALYZE TABLE, MSSQL UPDATE STATISTICS ... WITH FULLSCAN.","Créez des histogrammes/statistiques étendues sur les colonnes à distribution asymétrique.","Après la collecte des statistiques, vérifiez le plan avec EXPLAIN et confirmez le retour au bon plan.","Pour éviter la récidive, gardez la collecte automatique des statistiques (autovacuum/auto update stats) activée avec des seuils adaptés."],"related":["slow-queries"]},"sol_sniff":{"kind":"solution","title":"Cause racine : sensibilité aux paramètres (sniffing / bind peeking)","detail":"Le plan en cache a été choisi pour la première valeur de paramètre reçue et est mauvais pour d'autres valeurs à distribution asymétrique. L'objectif est de garantir le choix d'un plan adapté à la valeur.","steps":["Dans MSSQL, définissez un comportement ciblé sur la requête critique avec OPTION (RECOMPILE) ou OPTIMIZE FOR ; préférez la recompilation pour des valeurs changeant fréquemment.","Dans Oracle, utilisez l'adaptive cursor sharing / le comportement bind-aware ; si nécessaire, découpez la requête par plage de valeurs.","Dans Postgres, observez le choix entre plan générique et plan personnalisé ; orientez-le avec plan_cache_mode si nécessaire.","Si un plan unique, permanent et exact est nécessaire, passez à la solution de figement du plan."],"related":["slow-queries"]},"sol_forceplan":{"kind":"solution","title":"Solution : figer le bon plan (baseline / force plan)","detail":"S'il existe un bon plan connu, le figer pour que l'optimiseur ne s'en écarte pas met fin durablement à la régression.","steps":["Oracle : créez une SQL Plan Baseline pour le bon plan (DBMS_SPM) ou acceptez-le via SQL Plan Management.","MSSQL : sélectionnez le bon plan dans Query Store et forcez-le avec sp_query_store_force_plan.","MySQL/Postgres : orientez le plan avec des hints de l'optimiseur (par ex. index hints) ou pg_hint_plan.","Surveillez les performances après le figement ; si le profil des données change fondamentalement, réévaluez la baseline."],"related":[]},"sol_datagrowth":{"kind":"solution","title":"Cause racine : le volume de données a augmenté, le plan n'est plus adapté","detail":"À mesure que la table grossit, un plan autrefois idéal (par ex. nested loop) passe désormais mal à l'échelle ; l'optimiseur a besoin de statistiques et de chemins d'accès reflétant correctement la croissance.","steps":["Collectez des statistiques à jour pour que l'optimiseur voie le nouveau nombre de lignes.","Ajoutez des index adaptés aux tables en croissance ; corrigez le chemin d'accès qui passe désormais mal à l'échelle (par ex. full scan/nested loop).","Sur les très grandes tables, limitez les données parcourues avec un partitionnement par date/plage.","Réécrivez la requête en fonction de la croissance (filtrage précoce, pagination, vues de synthèse/matérialisées)."],"related":["slow-queries"]},"sol_planflip":{"kind":"solution","title":"Cause racine : changement brutal de plan (montée de version / vidage du cache)","detail":"Le plan a changé brutalement après une montée de version, un changement de paramètre ou le vidage du cache de plans. Le comportement de l'optimiseur ou les conditions d'environnement ont évolué.","steps":["Identifiez le déclencheur du changement : montée de version/patch, paramètres de l'optimiseur, chargement de données ou redémarrage/vidage du cache.","Si un paramètre est en cause, annulez le changement et réévaluez-le de façon contrôlée.","Figez le bon plan connu (baseline / force plan) pour que l'optimiseur ne s'en écarte plus.","Pour les régressions après montée de version, revoyez les réglages de compatibilité/de disponibilité de l'optimiseur."],"related":[]}}},"solver/replica-resync.json":{"id":"replica-resync","title":"Réplica cassé / resynchronisation nécessaire","icon":"🔗","category":"Réplication","intro":"La synchronisation entre le réplica/standby et le primaire est rompue. Déterminez si la réplication s'est arrêtée, quel est son retard et si la chaîne de position/GTID est rompue ; puis choisissez la bonne voie entre ignorer une erreur, attendre ou reconstruire (reseed). Comprend un avertissement sur le split-brain.","start":"root","nodes":{"root":{"kind":"step","title":"Quel est l'état général de la réplication ?","detail":"Lisez d'abord la santé de la réplication : les threads d'application tournent-ils, quelle est l'ampleur du retard, y a-t-il une erreur ? Vérifiez aussi que les deux nœuds n'acceptent pas tous deux des écritures (split-brain).","command":{"mysql":"SHOW REPLICA STATUS\\G\n-- Check: Replica_IO_Running, Replica_SQL_Running, Seconds_Behind_Source, Last_Error, Retrieved/Executed_Gtid_Set","postgres":"-- On the primary:\nSELECT client_addr, state, sent_lsn, replay_lsn, pg_wal_lsn_diff(sent_lsn,replay_lsn) AS lag_bytes FROM pg_stat_replication;\n-- On the replica:\nSELECT status, last_msg_receipt_time, latest_end_lsn FROM pg_stat_wal_receiver;","oracle":"SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN ('transport lag','apply lag');\nSELECT dest_id, status, error, gap_status FROM v$archive_dest_status WHERE status<>'INACTIVE';","mssql":"SELECT ag.name AS ag, drs.synchronization_state_desc, drs.synchronization_health_desc,\n       drs.log_send_queue_size, drs.redo_queue_size\nFROM sys.dm_hadr_database_replica_states drs\nJOIN sys.availability_groups ag ON ag.group_id=drs.group_id;"},"choices":[{"label":"La réplication s'est arrêtée / échoue","next":"error"},{"label":"Elle tourne mais avec un fort retard","next":"lag"},{"label":"GTID/position rompu(e), incohérent — reconstruire","next":"sol_reseed"},{"label":"Les deux nœuds écrivent en tant que primaire (split-brain suspecté)","next":"sol_splitbrain"}]},"error":{"kind":"step","title":"L'erreur est-elle réparable, ou faut-il reconstruire ?","detail":"Examinez l'erreur d'application. Certaines erreurs (clé en double, ligne manquante) se résolvent en ignorant prudemment un seul événement ; mais en cas d'incohérence/corruption des données, la seule voie correcte est de reconstruire le réplica.","command":{"mysql":"SHOW REPLICA STATUS\\G\n-- Check: Last_SQL_Errno, Last_SQL_Error, Last_IO_Errno, Last_IO_Error","postgres":"-- Examine the replica logs (PostgreSQL log): recovery / WAL apply error messages.\nSELECT status, last_msg_receipt_time FROM pg_stat_wal_receiver;","mssql":"SELECT * FROM sys.dm_hadr_automatic_seeding;\nSELECT database_id, synchronization_state_desc, is_suspended, suspend_reason_desc\nFROM sys.dm_hadr_database_replica_states WHERE is_local=1;"},"choices":[{"label":"Une ou quelques erreurs d'application (ignorables)","next":"sol_skiperror"},{"label":"Incohérence / corruption irréparable","next":"sol_reseed"}]},"lag":{"kind":"step","title":"Le retard est-il temporaire ou persistant ?","detail":"Examinez l'évolution du retard dans le temps. Se résorbe-t-il (rattrapage) après un pic de charge temporaire, ou continue-t-il de croître si bien que le réplica ne rattrape jamais le primaire ?","command":{"mysql":"SHOW REPLICA STATUS\\G\n-- Measure Seconds_Behind_Source several times: is it decreasing or increasing?","postgres":"-- Measure repeatedly on the replica:\nSELECT now()-pg_last_xact_replay_timestamp() AS replay_lag;","oracle":"SELECT name, value, time_computed FROM v$dataguard_stats WHERE name='apply lag';","mssql":"SELECT drs.redo_queue_size, drs.redo_rate, drs.log_send_queue_size\nFROM sys.dm_hadr_database_replica_states drs WHERE drs.is_local=0;"},"choices":[{"label":"Pic de charge temporaire ; le retard se résorbe","next":"sol_lag_wait"},{"label":"Il continue de croître ; impossible de rattraper","next":"sol_lag_perm"}]},"sol_skiperror":{"kind":"solution","title":"Solution : ignorez l'erreur d'application avec prudence","detail":"Si la réplication s'est arrêtée sur une seule erreur d'application (par ex. une clé en double) et que vous êtes sûr que les données sont réellement cohérentes, vous pouvez ignorer cet événement et poursuivre le flux. C'est un dernier recours ; ignorer à l'aveugle crée une dérive silencieuse des données.","steps":["Comprenez d'abord pourquoi l'erreur s'est produite et vérifiez la cohérence des données en comparant la ligne concernée des deux côtés.","Sur MySQL sans GTID, ignorez un seul événement : STOP REPLICA; SET GLOBAL sql_replica_skip_counter=1; START REPLICA; (avant 8.0.26 : sql_slave_skip_counter ; avec prudence).","Avec GTID, pour ignorer la transaction en échec, marquez ce GTID avec une transaction vide, puis démarrez le réplica.","Après l'avoir ignorée, effectuez un contrôle de cohérence au niveau ligne (checksum) entre les deux côtés ; en cas de dérive, passez à un reseed."],"related":["replication-status"]},"sol_reseed":{"kind":"solution","title":"Solution : reconstruire le réplica (reseed / rebuild)","detail":"Si la chaîne de position/GTID est rompue, si les données ont dérivé ou en cas d'erreur irréparable, la seule voie fiable est de reconstruire le réplica à partir d'une copie fraîche prise sur le primaire.","steps":["Prenez une sauvegarde de base cohérente sur le primaire : MySQL xtrabackup/mysqldump ou CLONE, Postgres pg_basebackup, Oracle RMAN duplicate, MSSQL sauvegarde full+log.","Nettoyez l'ancien état sur le réplica et restaurez la sauvegarde (Postgres pg_basebackup -R, MSSQL RESTORE ... WITH NORECOVERY).","Démarrez la réplication à partir de la bonne position/du bon GTID : MySQL CHANGE REPLICATION SOURCE TO ...; START REPLICA; pour MSSQL, réajoutez la base à l'AG (automatic seeding).","Surveillez le rattrapage ; une fois le retard à zéro, vérifiez la cohérence des deux côtés avec un checksum."],"related":["replication-status"]},"sol_lag_wait":{"kind":"solution","title":"Cause racine : pic de charge temporaire (le retard se résorbe)","detail":"La réplication est saine ; le retard s'est creusé à cause d'une rafale d'écritures temporaire sur le primaire ou d'une fluctuation réseau, mais le réplica rattrape son retard.","steps":["Confirmez par plusieurs mesures que la tendance du retard est réellement à la baisse.","Jusqu'à la fin de la fenêtre de charge, n'acheminez si nécessaire vers le primaire que les lectures sensibles au retard.","En cas de récidive, étalez les rafales d'écriture (traitements batch, ETL) dans le temps ou déplacez-les hors des heures de pointe.","Ajoutez la latence réseau/d'application à la supervision afin de repérer un retard temporaire avant qu'il ne devienne persistant."],"related":["replication-status"]},"sol_lag_perm":{"kind":"solution","title":"Cause racine : le réplica ne suit pas le primaire","detail":"Le côté application (apply/redo) ne suit pas le rythme des modifications produites par le primaire ; le retard ne cesse de croître. En général : application mono-thread, matériel du réplica lent ou goulot réseau.","steps":["Trouvez le goulot de l'application : est-ce l'application mono-thread, le disque/CPU du réplica ou la bande passante réseau qui limite ?","Activez/augmentez l'application parallèle : plusieurs workers de réplica dans MySQL (replica_parallel_workers) ; revoyez les ressources d'application dans Postgres/Oracle.","Portez le matériel du réplica au moins au niveau du primaire (surtout les IOPS disque).","Réduisez les requêtes de lecture lourdes inutiles sur le réplica pour laisser des ressources à l'application ; s'il ne suit toujours pas, réévaluez après un reseed."],"related":["replication-status"]},"sol_splitbrain":{"kind":"solution","title":"AVERTISSEMENT : split-brain (deux primaires) — risque de perte de données","detail":"Si les deux nœuds acceptent des écritures dans le rôle de primaire, des modifications conflictuelles s'accumulent des deux côtés ; c'est la situation la plus dangereuse et elle peut entraîner une perte de données. Ramener les écritures sur un seul nœud est la priorité.","steps":["Regroupez immédiatement les écritures sur un seul nœud : acheminez strictement le trafic applicatif vers un seul primaire, passez l'autre en lecture seule/isolez-le.","Identifiez le nœud de référence (correct) : décidez quelles données sont considérées comme valides d'un point de vue métier.","Comparez les modifications conflictuelles (divergentes) des deux côtés ; réconciliez manuellement les modifications critiques du côté perdant.","Reconstruisez l'autre nœud par un reseed à partir du nœud de référence ; mettez ensuite en place un mécanisme de fencing/quorum empêchant la double primauté."],"related":["replication-status"]}}},"solver/cache-hit-low.json":{"id":"cache-hit-low","title":"Faible taux de succès du cache (buffer/cache hit)","icon":"🎯","category":"Performances","intro":"Le taux de succès du cache est faible ; les données sont constamment lues sur disque (ou depuis la source). Déterminez si le problème se situe dans le buffer/cache partagé de la base ou dans le cache Redis/applicatif, puis mesurez et corrigez.","start":"root","nodes":{"root":{"kind":"step","title":"Quel cache a un faible taux de succès ?","detail":"Mesurez le taux de succès et isolez le problème : le buffer/cache partagé de la base est-il faible (fortes lectures physiques), ou le cache Redis/applicatif subit-il de nombreux misses et évictions ?","command":{"oracle":"SELECT ROUND((1-(pr.value/(dbg.value+cg.value)))*100,2) AS buffer_hit_pct\nFROM v$sysstat pr, v$sysstat dbg, v$sysstat cg\nWHERE pr.name='physical reads' AND dbg.name='db block gets' AND cg.name='consistent gets';","postgres":"SELECT ROUND(sum(blks_hit)*100.0/nullif(sum(blks_hit)+sum(blks_read),0),2) AS cache_hit_pct\nFROM pg_stat_database;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';\nSHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';","mssql":"SELECT cntr_value AS buffer_cache_hit_ratio\nFROM sys.dm_os_performance_counters\nWHERE counter_name='Buffer cache hit ratio' AND object_name LIKE '%Buffer Manager%';","redis":"INFO stats\n# Check the keyspace_hits / keyspace_misses ratio and evicted_keys in INFO memory"},"choices":[{"label":"Le taux de succès du buffer/cache partagé de la base est faible","next":"dbcache"},{"label":"Nombreux misses / évictions dans le cache Redis/applicatif","next":"rediscache"}]},"dbcache":{"kind":"step","title":"Pourquoi lit-il sur disque : taille ou schéma de parcours ?","detail":"Un faible taux de succès du buffer a deux causes typiques : les données fréquemment consultées (working set) ne tiennent pas en mémoire (cache trop petit), ou de grands parcours complets de tables polluent le cache avec des blocs à usage unique et en chassent les pages utiles.","command":{"oracle":"SELECT component, current_size/1024/1024 AS mb FROM v$sga_dynamic_components WHERE component LIKE '%buffer cache%';\nSELECT sql_id, disk_reads, executions FROM v$sql ORDER BY disk_reads DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SHOW shared_buffers;\nSELECT pg_size_pretty(pg_database_size(current_database())) AS db_size;\nSELECT query, shared_blks_read FROM pg_stat_statements ORDER BY shared_blks_read DESC LIMIT 10;","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';\nSELECT ROUND(SUM(data_length+index_length)/1024/1024) AS data_mb FROM information_schema.tables;","mssql":"SELECT physical_memory_kb/1024 AS server_ram_mb FROM sys.dm_os_sys_info;\nSELECT COUNT(*)*8/1024 AS buffer_pool_mb FROM sys.dm_os_buffer_descriptors;"},"choices":[{"label":"Le working set ne tient pas en RAM (cache trop petit)","next":"sol_sizing"},{"label":"Les parcours complets de tables polluent le cache","next":"sol_scan"}]},"rediscache":{"kind":"step","title":"Pourquoi les misses Redis : éviction maxmemory ou gestion des TTL/clés ?","detail":"Si le taux de succès Redis est faible, soit la mémoire a atteint maxmemory et des clés sont évincées, soit la gestion des TTL/le schéma de mise en cache des clés est faible (clés froides, faible réutilisation).","command":{"redis":"INFO memory\n# used_memory, maxmemory, maxmemory_policy, evicted_keys\nCONFIG GET maxmemory-policy\nINFO keyspace\n# Key count and expires (keys with TTL) ratio per db"},"choices":[{"label":"maxmemory plein, éviction élevée","next":"sol_maxmem"},{"label":"Schéma TTL/clés faible (faible réutilisation)","next":"sol_ttl"}]},"sol_sizing":{"kind":"solution","title":"Cause racine : le cache est trop petit pour le working set","detail":"Les données fréquemment consultées dépassent le buffer/cache alloué, elles sont donc constamment évincées et relues sur disque. Taux de succès faible, lectures physiques élevées.","steps":["Agrandissez le cache pour couvrir le working set : Oracle db_cache_size/SGA, Postgres shared_buffers, MySQL innodb_buffer_pool_size, MSSQL max server memory.","Si la RAM physique est insuffisante, ajoutez de la mémoire au serveur ; c'est la principale ressource avec laquelle la base monte en charge.","Réduisez le working set : archivez/partitionnez les anciennes données, supprimez les colonnes/index inutiles.","Après le changement, comparez le taux de succès et les lectures physiques pour confirmer l'amélioration."],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"Cause racine : le schéma de parcours pollue le cache","detail":"De grands parcours complets de tables remplissent le cache de blocs utilisés une fois puis abandonnés, chassant les pages chaudes ; cela fait baisser le taux de succès.","steps":["Obtenez les plans d'exécution des requêtes générant le plus de lectures physiques et transformez les parcours complets en accès par index (ajoutez les index appropriés).","Ne sélectionnez que les colonnes nécessaires au lieu de 'SELECT *' ; réduisez le volume de blocs parcourus.","Sur les grandes tables, restreignez les parcours à la plage pertinente grâce au partitionnement.","Séparez les traitements analytiques/de parcours lourds du cache OLTP (réplica de lecture ou fenêtre horaire distincte)."],"related":["slow-queries","cache-hit-ratio"]},"sol_maxmem":{"kind":"solution","title":"Cause racine : éviction maxmemory de Redis","detail":"La mémoire Redis a atteint la limite maxmemory ; d'anciennes clés sont évincées pour faire de la place aux nouvelles (evicted_keys ne cesse d'augmenter). Des entrées encore nécessaires sont ainsi perdues et le taux de succès baisse.","steps":["Augmentez maxmemory pour qu'il corresponde au working set réel, ou réduisez le jeu de données.","Choisissez la politique d'éviction selon le schéma d'accès (par ex. LRU/LFU : allkeys-lfu est souvent efficace pour séparer le chaud du froid).","Éliminez les structures volumineuses/rarement utilisées ; réduisez les valeurs, supprimez les champs inutiles, utilisez des types de données adaptés.","Si le besoin est permanent, faites monter Redis en charge (plus de RAM ou cluster/sharding) et commencez à surveiller evicted_keys."],"related":[]},"sol_ttl":{"kind":"solution","title":"Cause racine : schéma de TTL / de mise en cache faible","detail":"Soit les clés n'ont aucun TTL et la mémoire se remplit de données froides, soit le schéma de mise en cache a une faible réutilisation et la plupart des requêtes finissent en misses.","steps":["Attribuez des TTL appropriés aux clés mises en cache pour que les données qui refroidissent expirent d'elles-mêmes.","Mettez en cache les données fréquemment lues (chaudes) ; ne mettez pas en cache les données rarement utilisées (évitez de remplir le cache d'entrées jamais sollicitées).","Dans le schéma cache-aside, implémentez correctement le remplissage après miss et utilisez un verrou/une déduplication des requêtes contre le 'thundering herd'.","Surveillez le ratio keyspace_hits/misses et affinez les TTL et la conception des clés selon les données."],"related":[]}}},"solver/arch-oltp.json":{"id":"arch-oltp","title":"Architecture de référence : application OLTP 3-tiers","icon":"🏛️","category":"Architecture de référence","intro":"Les applications métier classiques (commandes, paiements, stocks) exécutent un grand nombre de transactions courtes, mono-ligne et critiques pour la cohérence (OLTP). Cet arbre montre pas à pas, à partir de l'identification du profil de charge, quand introduire la base relationnelle principale, un pooler de connexions, des réplicas de lecture, une couche de cache, la sauvegarde + PITR et la supervision. L'objectif n'est pas de « tout ajouter d'emblée » mais d'ajouter le bon composant à mesure que chaque goulot est avéré.","start":"root","nodes":{"root":{"kind":"step","title":"Quel est votre profil de charge : mix transactionnel et exigences de cohérence ?","detail":"La première décision d'une architecture OLTP est de savoir si la charge est réellement transactionnelle (un mix court d'écritures et de lectures exigeant ACID). Les données sont-elles naturellement relationnelles et la cohérence critique (argent, stock, commandes), la charge est-elle surtout en lecture, ou la charge est-elle encore très faible ?","choices":[{"label":"Transactions courtes, cohérence forte (argent/stock/commandes)","next":"primary_db"},{"label":"Les lectures dominent largement, peu d'écritures, et le trafic augmente","next":"scale_reads"},{"label":"Charge très faible ; la configuration la plus simple suffit-elle ?","next":"sol_single_node"}]},"primary_db":{"kind":"step","title":"Avez-vous choisi le moteur de base principal, et l'échelle vous inquiète-t-elle ?","detail":"Un moteur relationnel est la bonne fondation pour le cœur transactionnel (PostgreSQL, MySQL/InnoDB, Oracle, SQL Server). Une fois le moteur choisi, la question suivante est la montée en charge : une seule instance suffit-elle pour l'instant, ou le nombre de connexions et la charge en lecture augmentent-ils ?","choices":[{"label":"Le moteur est choisi ; une seule instance suffit pour l'instant","next":"sol_single_node"},{"label":"Le nombre de connexions a explosé, les ressources s'épuisent","next":"sol_pooler"},{"label":"La charge en lecture augmente et sollicite l'instance d'écriture principale","next":"scale_reads"}]},"scale_reads":{"kind":"step","title":"Comment ferez-vous monter les lectures en charge : réplica ou cache ?","detail":"Si la charge en lecture sollicite l'instance principale, il existe deux options complémentaires. Les lectures tolérant des données légèrement en retard, comme les rapports/listes, peuvent être acheminées vers un réplica de lecture ; si les mêmes données 'chaudes' sont demandées sans cesse, un cache à latence de l'ordre de la microseconde (Redis) soulage la base.","choices":[{"label":"Nombreuses lectures de rapports/listes ; un léger retard est acceptable","next":"sol_read_replica"},{"label":"Les mêmes données populaires sont lues encore et encore","next":"sol_cache"}]},"sol_single_node":{"kind":"solution","title":"Recommandation : commencez avec un primaire unique + pool + sauvegarde","detail":"Gardez l'architecture simple au départ : une seule instance relationnelle principale, un pool de connexions devant elle et des sauvegardes régulières suffisent. N'ajoutez de la complexité que lorsqu'un goulot mesuré l'impose.","steps":["Base principale : un moteur relationnel conforme ACID comme PostgreSQL ou MySQL/InnoDB ; mettez correctement en place le schéma, les contraintes et les index.","Pool de connexions : mettez en place dès le départ un pool côté application (par ex. HikariCP) ou un PgBouncer côté serveur ; il évite les tempêtes de connexions.","Sauvegarde + PITR : sauvegardes complètes quotidiennes + archivage WAL/binlog pour la restauration à un instant donné ; stockez les sauvegardes sur un stockage distinct.","Attention : n'ajoutez pas réplicas/sharding/cache trop tôt ; vouloir résoudre un problème non mesuré ne fait qu'élargir la surface de panne.","Supervision : collectez dès le départ les métriques de latence, de taux d'erreur et de nombre de connexions ; ne manquez pas le signal de croissance."],"related":["decide-pooling","decide-backup"]},"sol_pooler":{"kind":"solution","title":"Recommandation : ajoutez un pooler de connexions","detail":"En OLTP, lorsque chaque instance applicative ouvre ses propres connexions, la base principale étouffe sous la charge mémoire et d'ordonnancement de milliers de connexions. Placer un pooler entre les deux partage un petit nombre de connexions réelles à la base entre de nombreuses requêtes applicatives.","steps":["PostgreSQL : PgBouncer (mode transaction pooling) ramène de nombreux clients à un petit nombre de connexions backend.","MySQL : ProxySQL fournit de la même façon le multiplexage des connexions et le routage.","Taille du pool : généralement proportionnelle au nombre de cœurs CPU ; quelques dizaines de connexions backend suffisent. 'Plus il y en a, mieux c'est' ne s'applique pas.","Attention : en transaction pooling, les fonctionnalités de niveau session (prepared statements, tables temporaires, variables de session) sont restreintes ; écrivez l'application en conséquence.","Ensuite : si le pool sature aussi, le problème n'est pas les connexions mais la durée des requêtes/transactions ; traquez les requêtes lentes et les transactions longues."],"related":["decide-pooling","connection-storm"]},"sol_read_replica":{"kind":"solution","title":"Recommandation : ajoutez un réplica de lecture","detail":"Les écritures restent sur le primaire tandis que le trafic en lecture seule (rapports, listes, recherche) est acheminé vers un ou plusieurs réplicas asynchrones alimentés par le primaire. Cela libère le primaire pour les écritures et les lectures critiques pour la cohérence.","steps":["Topologie : primaire -> un ou plusieurs réplicas asynchrones ; l'application sépare lectures/écritures et envoie les lectures au réplica.","Moteur : PostgreSQL streaming replication, réplicas MySQL ou réplicas de lecture managés.","Quand : pour les lectures tolérant un léger retard, comme rapports/analyses/listes ; lorsque vous voulez délester le CPU/les E/S du primaire.","Attention : le retard de réplication produit des lectures obsolètes ; servez depuis le primaire les flux qui doivent « lire immédiatement leur propre écriture » (read-your-writes).","Ensuite : si les réplicas ne suffisent pas non plus, envisagez un cache pour les données chaudes ou le sharding côté écriture."],"related":["replication-lag","decide-shard-replica"]},"sol_cache":{"kind":"solution","title":"Recommandation : ajoutez une couche de cache (Redis)","detail":"Si les mêmes données populaires (sessions, catalogue, profils, fragments de page) sont lues de façon répétée, placez un cache en mémoire devant la base et servez la plupart des lectures sans jamais atteindre la base. La latence baisse et la base principale est soulagée.","steps":["Moteur : le schéma cache-aside avec Redis (ou Memcached) ; l'application consulte d'abord le cache puis, en cas de miss, lit dans la base et écrit dans le cache.","TTL + invalidation : attribuez à chaque clé un TTL raisonnable ; en écriture, supprimez/mettez à jour la clé concernée pour qu'aucune donnée obsolète ne subsiste.","Quand : nombreuses lectures, données relativement peu modifiées et mêmes clés demandées de façon répétée.","Attention : cache stampede ; lorsqu'une clé populaire expire, des milliers de requêtes peuvent frapper la base en même temps. Utilisez un verrou/single-flight et des TTL avec jitter.","Attention : ne traitez pas le cache comme la « source de vérité » ; la vérité durable réside toujours dans la base, et l'application doit continuer à fonctionner en se repliant sur la base même si le cache tombe."],"related":["decide-cache","cache-hit-low"]}}},"solver/arch-dwh.json":{"id":"arch-dwh","title":"Architecture de référence : data warehouse / analyse","icon":"🏛️","category":"Architecture de référence","intro":"Les requêtes analytiques (reporting, agrégation, parcours larges) diffèrent fondamentalement des transactions OLTP : peu nombreuses mais très larges et longues. En partant de la décision de séparer l'analyse de l'OLTP, cet arbre parcourt pas à pas des composants comme la méthode de chargement (ETL vs ELT), le stockage en colonnes/columnstore, la modélisation en schéma en étoile, le partitionnement + la compression et le précalcul par vues matérialisées, tout en pesant le compromis entre concurrence et débit.","start":"root","nodes":{"root":{"kind":"step","title":"Votre charge analytique s'exécute-t-elle sur la même base que l'OLTP ?","detail":"L'erreur la plus courante consiste à exécuter des rapports lourds sur la base transactionnelle en production et à dégrader la latence OLTP. La première décision est la séparation : l'analyse s'exécute-t-elle encore sur l'instance OLTP, construisez-vous un environnement analytique distinct, ou décidez-vous comment y transférer les données ?","choices":[{"label":"Les rapports s'exécutent encore sur l'OLTP en production ; ils ralentissent l'OLTP","next":"sol_separate"},{"label":"Un entrepôt distinct existe ; comment charger les données ?","next":"load_method"},{"label":"Un entrepôt existe ; les requêtes restent lentes, comment modéliser ?","next":"model"}]},"load_method":{"kind":"step","title":"Où transformerez-vous : avant ou après le chargement ?","detail":"En transférant les données des sources vers l'entrepôt, ferez-vous le nettoyage/la transformation dans une couche de traitement distincte avant le chargement (ETL), ou chargerez-vous d'abord les données brutes dans l'entrepôt pour les transformer en SQL au sein du puissant moteur de l'entrepôt (ELT) ? La décision dépend de la puissance de traitement de l'entrepôt et du besoin de flexibilité du schéma.","choices":[{"label":"La transformation est complexe/externe ; faites-la avant le chargement","next":"sol_etl"},{"label":"L'entrepôt est puissant ; chargez en brut, transformez à l'intérieur en SQL","next":"sol_elt"}]},"model":{"kind":"step","title":"Le goulot des requêtes se situe-t-il dans l'organisation du stockage ou dans le modèle de données ?","detail":"Si l'analyse est lente, il existe deux leviers principaux. Les parcours larges lisent la ligne entière en stockage par lignes ; le stockage en colonnes ne lit que les colonnes nécessaires et se compresse bien. Côté modèle, une normalisation excessive multiplie les jointures qui ralentissent les requêtes ; un schéma en étoile (fait + dimension) simplifie les choses pour l'analyse.","choices":[{"label":"Les parcours d'agrégation larges sont lents ; peu de colonnes sont lues","next":"sol_columnar"},{"label":"Trop de jointures, schéma normalisé complexe","next":"sol_star"},{"label":"Le même rapport lourd produit le même résultat de façon répétée","next":"sol_matview"}]},"sol_separate":{"kind":"solution","title":"Recommandation : séparez l'analyse de l'OLTP","detail":"Les charges transactionnelles et analytiques s'étouffent mutuellement sur le même moteur : les longs rapports retiennent verrous/E/S et retardent les transactions courtes. Déplacer l'analyse vers une cible distincte (réplica de lecture ou entrepôt analytique séparé) soulage les deux côtés.","steps":["Première étape : acheminez les rapports légers vers un réplica de lecture OLTP et l'analyse lourde vers un moteur analytique distinct.","Moteur analytique : un moteur en colonnes/analytique (par ex. SQL Server avec columnstore, ClickHouse ou un entrepôt en colonnes) est bien plus rapide que le stockage par lignes sur les parcours larges.","Concurrence vs débit : l'OLTP est réglé pour de nombreuses petites transactions (forte concurrence), l'entrepôt pour quelques requêtes énormes (fort débit) ; vous ne pouvez pas optimiser les deux sur la même machine.","Attention : l'entrepôt n'est pas une « copie en temps réel de la production » ; ce sont des données chargées à intervalles, différées mais organisées pour l'analyse.","Ensuite : après la séparation, choisissez la méthode de chargement (ETL/ELT) et le modèle (schéma en étoile)."],"related":["decide-sqlnosql","slow-query"]},"sol_etl":{"kind":"solution","title":"Recommandation : ETL (transformer avant de charger)","detail":"En ETL, les données sont extraites des sources (Extract), nettoyées et transformées dans une couche de traitement distincte (Transform), puis seulement chargées dans l'entrepôt (Load). Seules des données propres et conformes au modèle entrent dans l'entrepôt.","steps":["Quand : la transformation est complexe, vous devez combiner plusieurs sources, ou vous voulez que les données brutes/sensibles n'entrent jamais dans l'entrepôt.","Flux : source (OLTP/fichier/API) → couche de transformation → chargement par lots dans l'entrepôt.","Avantage : garde le stockage de l'entrepôt propre ; les données sensibles peuvent être masquées avant le chargement.","Inconvénient : la transformation dépend de la puissance d'un système distinct ; elle ne peut pas profiter de la flexibilité SQL de l'entrepôt.","Attention : chargez dans une fenêtre batch (par ex. la nuit) ; planifiez pour éviter les conflits avec les requêtes lourdes pendant le chargement."],"related":["decide-capacity"]},"sol_elt":{"kind":"solution","title":"Recommandation : ELT (transformer dans l'entrepôt)","detail":"En ELT, les données brutes sont d'abord chargées dans l'entrepôt et la transformation s'effectue dans le puissant moteur SQL de l'entrepôt lui-même. Comme les entrepôts en colonnes modernes ont une forte puissance de traitement, vous déplacez les données une fois et les traitez sur place au lieu de porter la transformation vers les données.","steps":["Quand : le moteur de l'entrepôt est puissant et évolutif ; vous voulez aussi conserver les données brutes (audit/retraitement).","Flux : source → couche brute (staging) → vues SQL/de transformation → couche modélisée de faits/dimensions.","Avantage : un seul moteur, flexible ; comme les données brutes sont conservées, vous pouvez modifier la transformation ultérieurement et régénérer.","Inconvénient : des données brutes (potentiellement sensibles) entrent dans l'entrepôt ; contrôle d'accès et masquage sont obligatoires.","Attention : modélisez en couches (raw → staging → mart) ; écrivez des transformations idempotentes pour que les réexécutions soient sûres."],"related":["decide-capacity"]},"sol_columnar":{"kind":"solution","title":"Recommandation : stockage en colonnes / columnstore + compression","detail":"Les requêtes analytiques parcourent généralement quelques colonnes sur toutes les lignes. Le stockage en colonnes conserve les données colonne par colonne ; il ne lit que les colonnes nécessaires, se compresse très bien car les valeurs similaires sont voisines, et les E/S chutent considérablement.","steps":["Moteur/fonctionnalité : index columnstore de SQL Server, ClickHouse ou entrepôts en colonnes ; extensions en colonnes côté PostgreSQL.","Compression : l'organisation en colonnes offre une forte compression ; moins de disque, moins d'octets lus, des parcours plus rapides.","Partitionnement : partitionnez les grandes tables de faits par date/plage pour que le partition pruning ignore entièrement les partitions non pertinentes.","Attention : le stockage en colonnes est mauvais pour de nombreuses mises à jour mono-ligne/lectures ponctuelles ; n'y déplacez pas l'OLTP.","Concurrence vs débit : le stockage en colonnes excelle sur quelques requêtes de parcours énormes, pas sur des milliers de petites requêtes concurrentes."],"related":["decide-partition","io-bottleneck"]},"sol_star":{"kind":"solution","title":"Recommandation : schéma en étoile (fait + dimension)","detail":"Dans un modèle analytique, une table de faits contenant les mesures se trouve au centre, entourée de tables de dimensions descriptives. Par rapport à un schéma sur-normalisé, le nombre de jointures diminue, les requêtes deviennent plus simples et le moteur produit de meilleurs plans.","steps":["Structure : le fait (mesures + clés de dimensions) au centre ; les dimensions (temps, produit, client) forment une étoile autour.","Quand : les rapports joignent de nombreuses tables et le schéma normalisé croule sous les jointures.","Dénormalisation : une dénormalisation délibérée est normale en analyse ; la cohérence est assurée dans le processus de chargement (ETL/ELT).","Ensuite : précalculez les agrégations lourdes fréquentes avec des vues matérialisées.","Attention : ne convertissez pas l'OLTP en schéma en étoile ; ce modèle sert à l'analyse, pas aux écritures transactionnelles."],"related":["decide-index"]},"sol_matview":{"kind":"solution","title":"Recommandation : précalcul avec des vues matérialisées","detail":"Si la même agrégation lourde (chiffre d'affaires quotidien, synthèse mensuelle) s'exécute de façon répétée et produit le même résultat, stockez physiquement le résultat dans une vue matérialisée et faites lire aux rapports le résultat précalculé. Le calcul lourd s'exécute une fois et les requêtes répondent instantanément.","steps":["Quand : certaines requêtes de synthèse parcourent fréquemment et de façon répétée la même grande table.","Rafraîchissement : rafraîchissez la vue matérialisée selon un planning (par ex. batch nocturne ou incrémental) ; fixez la fréquence selon les besoins de fraîcheur.","Moteur : vue matérialisée PostgreSQL (REFRESH), vue matérialisée Oracle (y compris query rewrite), indexed view SQL Server.","Attention : la vue comporte une fenêtre de fraîcheur ; lorsque des données en temps réel sont requises, une requête en direct est obligatoire.","Ensuite : si le coût de rafraîchissement est élevé, envisagez un rafraîchissement incrémental ou par partition."],"related":["decide-partition"]}}},"solver/arch-microservice.json":{"id":"arch-microservice","title":"Architecture de référence : couche de données microservices","icon":"🏛️","category":"Architecture de référence","intro":"Dans les microservices, la décision la plus critique est la propriété des données : chaque service doit posséder ses données (database-per-service) et ne doit pas accéder directement aux tables d'un autre service. Cet arbre parcourt pas à pas l'anti-pattern de la base partagée, l'obtention de la cohérence inter-services sans transactions distribuées (saga + outbox), la synchronisation des données par CDC et les requêtes inter-services (API composition vs modèle de lecture CQRS). L'objectif est de garder des services réellement indépendants et déployables séparément.","start":"root","nodes":{"root":{"kind":"step","title":"Comment les services possèdent-ils les données : une base partagée ou une par service ?","detail":"L'isolation des données est la colonne vertébrale d'une architecture microservices. Les services utilisent-ils une seule base partagée (une dépendance cachée), chaque service a-t-il sa propre base (le bon schéma), ou le vrai défi est-il la cohérence/les requêtes inter-services ? Établissez d'abord l'état actuel.","choices":[{"label":"Tous les services utilisent une seule base partagée","next":"sol_db_per_service"},{"label":"Chaque service a sa propre base, mais les opérations exigent une cohérence inter-services","next":"consistency"},{"label":"Chaque service a sa propre base, mais des requêtes/rapports combinés sont nécessaires","next":"query"}]},"consistency":{"kind":"step","title":"Comment garderez-vous cohérent un workflow inter-services ?","detail":"Pour une opération couvrant plusieurs services (créer la commande → encaisser le paiement → décrémenter le stock), une transaction distribuée classique (2PC) est fragile et ne passe pas à l'échelle. À la place, découpez le flux en étapes transaction locale + événement (saga) et publiez les événements de façon fiable (outbox). Quel problème rencontrez-vous ?","choices":[{"label":"J'essaie de rendre atomique un workflow multi-services (2PC ?)","next":"sol_saga"},{"label":"Je n'arrive pas à rendre cohérentes l'écriture locale et la publication d'événement (double écriture)","next":"sol_outbox"}]},"query":{"kind":"step","title":"Comment servirez-vous une requête qui affiche des données provenant de plusieurs services ?","detail":"Une fois les données réparties entre services, vous ne pouvez plus construire un écran combiné avec une seule jointure. Il existe deux approches principales : appeler les API des services concernés au moment de la requête et fusionner les résultats (API composition), ou maintenir un modèle de lecture pré-joint alimenté par des événements (CQRS). La fréquence et la complexité des requêtes déterminent le choix.","choices":[{"label":"Jointure simple et peu fréquente ; appeler quelques services et fusionner suffit","next":"sol_api_composition"},{"label":"Requête combinée complexe/fréquente ; un modèle de lecture prêt à l'emploi est nécessaire","next":"sol_cqrs"}]},"sol_db_per_service":{"kind":"solution","title":"Recommandation : database-per-service (découper la base partagée)","detail":"Des services partageant une même base constituent une dépendance cachée et dangereuse : la modification de schéma d'un service casse les autres, la propriété des tables devient floue et le déploiement indépendant devient impossible. Chaque service doit posséder ses données.","steps":["Principe : chaque service ne lit/écrit que dans sa propre base ; il ne peut pas accéder directement aux tables d'un autre service, seulement demander des données via l'API de ce service.","Indépendance du moteur : chaque service peut choisir un moteur adapté à ses besoins (par ex. PostgreSQL pour les commandes, MongoDB pour le catalogue, Elasticsearch pour la recherche, Redis pour le panier).","Anti-pattern : la « base partagée » : tables communes, jointures croisées et schéma unique ; ce ne sont pas des microservices mais un monolithe distribué.","Décomposition : d'abord une séparation logique (schéma/utilisateur distinct), puis physique ; supprimez les jointures entre tables de services différents.","Attention : après le découpage, des problèmes de cohérence (saga/outbox) et de requêtes combinées (composition/CQRS) apparaissent ; c'est le coût naturel de cette transition."],"related":["decide-isolation","decide-sqlnosql"]},"sol_saga":{"kind":"solution","title":"Recommandation : schéma saga (éviter les transactions distribuées)","detail":"N'essayez pas de rendre atomique une opération couvrant plusieurs services avec une seule transaction distribuée (2PC) ; le 2PC retient des verrous et apporte une fragilité de point unique et une faible disponibilité. À la place, découpez le flux en étapes constituant chacune une transaction locale dans son propre service, et annulez-les par des étapes de compensation en cas d'échec.","steps":["Principe : modélisez le workflow comme une chaîne de transactions locales + événements ; chaque étape déclenche la suivante.","Compensation : si une étape échoue, exécutez des actions de compensation qui annulent les étapes précédentes (par ex. annuler la commande si le paiement échoue).","Chorégraphie vs orchestration : dans les flux simples, les services se déclenchent mutuellement par événements (chorégraphie) ; dans les flux complexes, un orchestrateur central gère les étapes.","Attention : une saga n'est pas atomique ; elle fournit une cohérence à terme (eventual consistency). Des états intermédiaires peuvent être visibles de l'extérieur ; concevez les règles métier en conséquence.","Attention : n'utilisez pas 2PC/XA dans des microservices à grande échelle ; le blocage et la durée des verrous paralyseront le système."],"related":["decide-isolation","long-transaction"]},"sol_outbox":{"kind":"solution","title":"Recommandation : transactional outbox (le piège de la double écriture)","detail":"Lorsqu'un service veut à la fois écrire dans sa propre base et publier un événement, faire les deux séparément est le piège de la « double écriture » : si l'une réussit et l'autre échoue, le système reste incohérent. La solution consiste à écrire l'événement dans une table outbox dans la MÊME transaction locale que les données métier, puis à le publier via un processus distinct.","steps":["Principe : l'enregistrement métier + l'enregistrement d'événement sont écrits dans la table outbox en une seule transaction locale ; soit les deux ont lieu, soit aucun (atomique).","Publication : un processus relais/CDC distinct lit les lignes de l'outbox, les publie vers un broker de messages (par ex. Kafka) et les marque.","Avec CDC : un outil CDC comme Debezium lit le journal de la base (WAL/binlog) et transfère de façon fiable les modifications de l'outbox vers Kafka.","Idempotence : les consommateurs peuvent recevoir le même événement plusieurs fois ; traitez de façon idempotente à l'aide de l'id d'événement (supposez une livraison at-least-once).","Attention : n'utilisez pas directement l'approche « écrire d'abord dans la base, puis publier dans Kafka » ; un crash au moment de la publication entraîne une perte d'événement."],"related":["replication-lag"]},"sol_api_composition":{"kind":"solution","title":"Recommandation : API composition (fusion au moment de la requête)","detail":"Lorsque des données combinées sont nécessaires, l'appelant (par ex. une gateway/BFF) appelle les API des services concernés et fusionne les résultats en mémoire. C'est la solution la plus simple pour des jointures simples couvrant peu de services ; aucune copie de données supplémentaire n'est conservée.","steps":["Principe : un composeur (API gateway/BFF) appelle service-A et service-B, fusionne le résultat et le renvoie.","Quand : la jointure est simple, couvre peu de services et la fréquence des requêtes est modérée ; vous ne voulez pas la charge de maintenance d'un modèle de lecture supplémentaire.","Avantage : aucun stockage de données ni synchronisation supplémentaire ; chaque service renvoie ses propres données à jour.","Attention : pour des jointures couvrant de nombreux services ou de grands jeux de données, la latence s'accumule et les jointures en mémoire sont coûteuses.","Ensuite : si la jointure devient complexe ou très fréquente, passez à un modèle de lecture CQRS."],"related":[]},"sol_cqrs":{"kind":"solution","title":"Recommandation : modèle de lecture CQRS (alimenté par des événements)","detail":"Pour des requêtes combinées complexes et fréquentes, conservez un modèle de lecture pré-joint et optimisé pour les requêtes (vue matérialisée/copie dénormalisée) alimenté par les événements émis par les services. Le côté écriture reste dans les services ; le côté lecture sert rapidement des données prêtes depuis un seul endroit.","steps":["Principe : les services publient les changements d'état sous forme d'événements ; une projection écoute ces événements et met à jour le modèle de lecture.","Choix du stockage : choisissez le stockage du modèle de lecture selon le type de requête : Elasticsearch pour la recherche, MongoDB pour des documents flexibles, Redis pour un accès rapide par clé, PostgreSQL pour les rapports relationnels.","Quand : la requête combinée est complexe/fréquente, une faible latence est requise et le coût de l'API composition a augmenté.","Attention : le modèle de lecture est cohérent à terme ; il est légèrement en retard jusqu'au traitement des événements.","Idempotence : la projection peut voir deux fois le même événement ; mettez à jour de façon idempotente avec l'id d'événement et suivez l'ordre/la version si nécessaire."],"related":["decide-cache"]}}},"solver/arch-eventdriven.json":{"id":"arch-eventdriven","title":"Architecture de référence : event-driven / CQRS","icon":"🏛️","category":"Architecture de référence","intro":"Dans une architecture event-driven, les services publient des événements dans un journal d'événements (par ex. Kafka) au lieu de s'appeler directement, et les consommateurs les traitent de façon asynchrone. En partant de la construction de la dorsale d'événements, cet arbre parcourt pas à pas l'event sourcing (dériver l'état des événements), la séparation commande/requête avec CQRS, la projection du modèle de lecture, l'idempotence, la tolérance à la cohérence à terme et l'illusion de la « livraison exactly-once ». L'objectif est un système faiblement couplé et évolutif dont les compromis de cohérence sont bien compris.","start":"root","nodes":{"root":{"kind":"step","title":"Quelle décision devez-vous prendre dans votre architecture event-driven ?","detail":"Le fondement d'un système event-driven est un journal d'événements durable qui relie les services par des événements asynchrones. Construisez-vous encore la dorsale, voulez-vous dériver l'état des événements (event sourcing), ou séparez-vous les chemins de lecture et d'écriture (CQRS) ?","choices":[{"label":"Je construis la dorsale d'événements qui relie les services","next":"sol_event_log"},{"label":"Je veux dériver l'état de l'historique des événements","next":"sol_event_sourcing"},{"label":"Je veux séparer les chemins de lecture et d'écriture (CQRS)","next":"cqrs_split"}]},"cqrs_split":{"kind":"step","title":"Comment construirez-vous le côté lecture en CQRS, et quelle est votre tolérance en matière de cohérence ?","detail":"En CQRS, les côtés commande (écriture) et requête (lecture) sont séparés ; les événements d'écriture mettent à jour la projection qui alimente le modèle de lecture. La question clé : pouvez-vous tolérer que le modèle de lecture soit en retard sur les écritures (cohérence à terme), ou attendez-vous que chaque événement soit traité exactement une fois ?","choices":[{"label":"J'alimenterai le modèle de lecture à partir des événements via une projection","next":"sol_read_model"},{"label":"Les lectures doivent être à jour ; le retard est inacceptable","next":"sol_consistency"},{"label":"Je veux que les événements soient traités « exactement une fois »","next":"sol_idempotency"}]},"sol_event_log":{"kind":"solution","title":"Recommandation : une dorsale de journal d'événements durable","detail":"Au centre d'une architecture event-driven se trouve un journal d'événements durable et ordonné dans lequel les producteurs écrivent et que les consommateurs lisent à leur propre rythme. Cette dorsale découple les services dans le temps et en échelle.","steps":["Moteur : Apache Kafka (ou une plateforme similaire basée sur un journal) : un journal d'événements partitionné, persistant et rejouable.","Partitions et ordre : l'ordre n'est garanti qu'au sein d'une partition ; préservez-le en acheminant les événements d'une même entité vers la même clé.","Indépendance des consommateurs : chaque groupe de consommateurs conserve son propre offset ; il peut relire/revenir en arrière sans affecter le producteur.","Rétention : conservez le journal assez longtemps pour que les nouveaux consommateurs et les retraitements puissent lire l'historique.","Attention : Kafka n'est pas une file mais un journal rejouable ; supposez une livraison at-least-once et écrivez des consommateurs idempotents."],"related":[]},"sol_event_sourcing":{"kind":"solution","title":"Recommandation : event sourcing (dériver l'état des événements)","detail":"En event sourcing, vous ne stockez pas l'état courant d'une entité mais tous les événements de modification (immuables) qui lui ont été appliqués ; l'état courant est dérivé en appliquant ces événements dans l'ordre. Cela fournit un historique d'audit complet et la possibilité de revenir à n'importe quel point du passé.","steps":["Principe : le flux d'événements est la source de vérité ; l'état est reconstruit en rejouant les événements depuis le début (ou un snapshot + les événements suivants).","Snapshots : pour les flux longs, prenez des snapshots périodiques afin d'éviter de rejouer depuis le début à chaque fois, et appliquez les événements ultérieurs par-dessus.","Avantage : une piste d'audit complète, une réponse à « comment en sommes-nous arrivés à cet état » dans le temps, et la possibilité de dériver différents modèles de lecture.","Attention : les schémas d'événements évoluent ; lire d'anciens événements exige du versioning/upcasting, car les événements sont conservés indéfiniment.","Attention : ce n'est pas nécessaire dans tous les scénarios ; si l'état courant suffit, le CRUD classique est plus simple. Généralement utilisé avec CQRS."],"related":[]},"sol_read_model":{"kind":"solution","title":"Recommandation : projection du modèle de lecture","detail":"En CQRS, le côté écriture produit des événements ; une projection écoute ces événements et met à jour un modèle de lecture optimisé pour les requêtes (vue dénormalisée). Les lectures sont servies depuis ce modèle prêt, entièrement séparé du chemin d'écriture, et les deux côtés montent en charge indépendamment.","steps":["Principe : événements → projection → stockage de lecture ; les requêtes de lecture n'interrogent que ce modèle dénormalisé.","Choix du stockage : selon le type de requête : Elasticsearch pour la recherche, MongoDB pour des documents flexibles, Redis pour un accès rapide, PostgreSQL pour le relationnel.","Reconstructibilité : comme le modèle de lecture est dérivé des événements, il peut être supprimé et reconstruit de zéro en relisant le journal.","Idempotence : la projection peut voir le même événement plusieurs fois ; mettez à jour de façon idempotente à l'aide de l'id/la version de l'événement.","Attention : le modèle de lecture est cohérent à terme ; il est en retard sur les écritures jusqu'au traitement de l'événement, concevez donc l'interface en conséquence."],"related":["decide-cache"]},"sol_consistency":{"kind":"solution","title":"Recommandation : concevez pour tolérer la cohérence à terme","detail":"Dans les systèmes event-driven/CQRS, le modèle de lecture est légèrement en retard sur les écritures (cohérence à terme). Plutôt que de chercher à « l'éliminer », concevez pour la tolérer ; isolez les quelques flux qui exigent une cohérence immédiate stricte.","steps":["Principe : la plupart des flux métier tolèrent une cohérence avec un court délai ; donnez à l'utilisateur un retour « demande reçue, traitement en cours ».","Read-your-writes : si les utilisateurs doivent voir immédiatement leurs propres écritures, servez cette lecture depuis le côté écriture/de façon synchrone.","Cœur exigeant une cohérence stricte : gardez le travail nécessitant une cohérence immédiate absolue, comme un solde monétaire, dans un seul service/une seule base avec une transaction ACID ; ne le distribuez pas par des événements.","Attention : ne masquez pas la cohérence à terme ; gérez explicitement les états intermédiaires/transitoires dans les écrans et les règles métier.","Attention : un objectif consistant à « rendre tout le système fortement cohérent » détruit les bénéfices de l'architecture event-driven (échelle, faible couplage)."],"related":["decide-ha","replication-lag"]},"sol_idempotency":{"kind":"solution","title":"Recommandation : idempotence (l'illusion de l'exactly-once)","detail":"En messagerie distribuée, la « livraison exactly-once » de bout en bout est en pratique une illusion ; les doublons de messages sont inévitables à cause du réseau et des nouvelles tentatives. Le bon objectif est un traitement idempotent : traiter deux fois le même événement doit donner le même résultat que le traiter une fois.","steps":["Principe : acceptez la livraison at-least-once ; rendez les doublons inoffensifs dans le consommateur.","Méthode : donnez à chaque événement un id unique ; enregistrez les id traités et ignorez-les s'ils reviennent (dédoublonnage), ou utilisez des opérations naturellement idempotentes (upsert, set).","Sémantique exactly-once : la fonctionnalité « exactly-once » de Kafka ne s'applique qu'à l'intérieur de la frontière transactionnelle propre à Kafka ; les effets de bord sur une base externe ne sont pas garantis sans idempotence.","Ordre : évitez les réordonnancements en acheminant les événements d'une même entité vers la même clé de partition.","Attention : l'hypothèse « j'ai configuré la livraison exactly-once, pas besoin de dédoublonnage » produit des incohérences en production ; traitez toujours de façon idempotente."],"related":[]}}},"solver/arch-multiregion.json":{"id":"arch-multiregion","title":"Architecture de référence : multi-région / globale","icon":"🏛️","category":"Architecture de référence","intro":"Lorsque les utilisateurs sont géographiquement répartis ou qu'une résilience à un sinistre régional est requise, les données se répartissent sur plusieurs régions. Cet arbre parcourt pas à pas la localité des lectures (un réplica de lecture dans chaque région), la topologie d'écriture (single-leader vs multi-leader vs quorum), la résidence des données/la conformité (RGPD), la résolution des conflits et les compromis entre RPO/RTO et latence. La vérité de base : en raison des lois de la physique, faible latence, cohérence forte et écritures multi-régions ne peuvent pas toutes être pleinement obtenues en même temps ; un compromis délibéré est obligatoire.","start":"root","nodes":{"root":{"kind":"step","title":"Pourquoi passer en multi-région : localité des lectures, écritures/résilience ou conformité ?","detail":"Une architecture multi-région est coûteuse et complexe ; clarifiez la motivation. Voulez-vous une faible latence de lecture pour les utilisateurs éloignés, des écritures inter-régions/une résilience aux sinistres, ou la vraie exigence est-elle que les données restent dans un pays précis (résidence légale) ?","choices":[{"label":"Lectures rapides pour les utilisateurs éloignés ; les écritures peuvent rester dans une région","next":"sol_read_locality"},{"label":"Écritures inter-régions / résilience à la perte d'une région requises","next":"write_topology"},{"label":"Les données doivent légalement rester dans un pays/une région précis(e)","next":"sol_data_residency"}]},"write_topology":{"kind":"step","title":"Quelle topologie d'écriture : single-leader, multi-leader ou quorum ?","detail":"Pour les écritures inter-régions, la décision clé est le modèle de leadership. Toutes les écritures vont-elles vers une seule région (simple, cohérent, mais écritures distantes lentes), chaque région peut-elle écrire (faible latence d'écriture mais conflits), ou une écriture est-elle acceptée avec l'acquittement d'une majorité de régions (quorum : cohérent mais avec une latence supplémentaire) ?","choices":[{"label":"Une région écrit, les autres lisent (simple et cohérent)","next":"sol_single_leader"},{"label":"Chaque région peut écrire localement (faible latence d'écriture)","next":"sol_multi_leader"},{"label":"Écriture avec acquittement majoritaire ; la cohérence prime","next":"sol_quorum"}]},"sol_read_locality":{"kind":"solution","title":"Recommandation : un réplica de lecture par région (localité des lectures)","detail":"Les écritures restent dans une seule région primaire tandis que vous placez dans chaque région d'utilisateurs un réplica de lecture alimenté par celle-ci. Les utilisateurs lisent depuis le réplica le plus proche et la latence de lecture baisse ; comme les écritures vont toujours vers un centre unique, le modèle de cohérence reste simple.","steps":["Topologie : une région primaire (écritures) → un réplica de lecture asynchrone dans chaque région d'utilisateurs.","Moteurs : PostgreSQL streaming replication, réplicas MySQL inter-régions ou réplicas de lecture globaux managés.","Routage : l'application achemine les lectures vers le réplica le plus proche et les écritures vers la région primaire (geo DNS / routeur intelligent).","Attention : le retard de réplication inter-régions provoque des lectures obsolètes ; faites lire depuis le primaire les flux nécessitant read-your-writes.","RPO/RTO : si la région primaire tombe, un réplica asynchrone peut être promu avec une certaine perte de données (RPO > 0) ; préparez le plan de failover à l'avance."],"related":["replication-lag","decide-shard-replica"]},"sol_single_leader":{"kind":"solution","title":"Recommandation : écritures single-leader","detail":"Toutes les écritures vont vers une seule région leader ; les autres régions sont des réplicas de lecture alimentés de façon asynchrone par ce leader. C'est le modèle le plus simple et le plus fortement cohérent ; il n'y a pas de conflits car il n'existe qu'un seul point d'écriture. Le prix est une latence d'écriture élevée depuis les régions distantes.","steps":["Principe : une seule autorité d'écriture → aucun conflit, modèle de cohérence simple.","Quand : le volume d'écriture peut être absorbé dans une région et la latence d'écriture distante est tolérable.","Failover : si la région leader tombe, un réplica est promu leader ; un failover automatique/contrôlé et une protection contre le split-brain sont obligatoires.","RPO/RTO : avec une réplication asynchrone, une petite quantité de données peut être perdue lors du failover (RPO) ; la réplication synchrone donne RPO=0 mais augmente la latence d'écriture.","Attention : les écritures des utilisateurs distants sont lentes ; si la localité des écritures est critique, examinez le multi-leader ou le quorum."],"related":["decide-ha","failover-drill"]},"sol_multi_leader":{"kind":"solution","title":"Recommandation : écritures multi-leader + résolution des conflits","detail":"Comme chaque région peut écrire localement, la latence d'écriture est faible et, même si une région est coupée, les autres continuent d'écrire. Le prix est lourd : deux régions peuvent mettre à jour différemment le même enregistrement, et vous devez concevoir la résolution des conflits.","steps":["Principe : chaque région est un leader ; les écritures sont propagées de façon asynchrone aux autres régions et fusionnées.","Résolution des conflits : last-write-wins (simple mais avec risque de perte de données), fusion propre à l'application, ou types de données sans conflit comme les CRDT.","Exemples de moteurs : configurations multi-master/multi-leader (par ex. EDB Postgres Distributed (BDR)/pgEdge pour PostgreSQL, Redis Enterprise Active-Active (CRDT), CouchDB). Cassandra est leaderless plutôt que multi-leader ; avec LOCAL_ONE/LOCAL_QUORUM, il permet des écritures locales dans chaque région pour un résultat similaire et résout les conflits par last-write-wins (horodatages).","Quand : la latence d'écriture locale et l'indépendance régionale comptent davantage que la cohérence forte.","Attention : concevez la résolution des conflits dès le départ ; l'hypothèse « il n'y aura pas de conflits » produit une perte de données silencieuse. Surveillez les collisions inter-régions dans la génération d'ID uniques."],"related":["decide-ha"]},"sol_quorum":{"kind":"solution","title":"Recommandation : écritures par quorum (acquittement majoritaire)","detail":"Une écriture est acceptée lorsqu'une majorité (quorum) de régions/réplicas l'acquitte ; lorsque les lectures vont aussi vers une majorité, la valeur la plus récente est garantie. C'est résilient à la perte d'une région et fortement cohérent ; le prix est la latence de chaque écriture devant atteindre une majorité.","steps":["Principe : acquittement majoritaire avec la règle W + R > N ; même si une région minoritaire tombe, le système continue d'écrire de façon cohérente.","Exemples de moteurs : bases distribuées fondées sur le consensus (par ex. systèmes utilisant Raft/Paxos), le niveau de cohérence QUORUM dans Cassandra.","Quand : la résilience aux sinistres régionaux et la cohérence forte sont toutes deux requises, et une latence d'écriture plus élevée est acceptable.","RPO/RTO : comme une majorité écrit de façon durable, RPO≈0 ; avec la reformation automatique de la majorité après la perte d'une région, le RTO peut être faible.","Attention : un quorum entre régions géographiquement éloignées ajoute des dizaines de ms à chaque écriture en raison de la latence liée à la vitesse de la lumière ; choisissez avec soin le nombre et l'emplacement des régions."],"related":["decide-ha","decide-shard-replica"]},"sol_data_residency":{"kind":"solution","title":"Recommandation : résidence des données / conformité (RGPD)","detail":"Certaines données (personnelles, de santé, financières) doivent légalement rester à l'intérieur des frontières d'un pays/d'une région précis(e) (par ex. RGPD). Dans ce cas, vous concevez l'architecture non pas selon le principe « copier partout » mais de sorte que les données soient ancrées géographiquement.","steps":["Principe : conservez les données personnelles/réglementées dans leur région d'origine ; restreignez ou interdisez la réplication hors de la région.","Partitionnement régional : acheminez les utilisateurs vers la région où ils sont enregistrés (geo-partitioning) ; chaque région détient les données de ses propres résidents.","Données globales vs locales : les données globales comme un catalogue peuvent être répliquées partout ; les données personnelles restent dans la région.","Attention : les sauvegardes et les réplicas de lecture déplacent aussi des données ; la région de stockage des sauvegardes et la région de DR entrent aussi dans le périmètre de conformité.","Attention : une décision du type « copions-le partout pour la faible latence » peut constituer une violation légale pour des données réglementées ; intégrez les contraintes de résidence dans l'architecture dès le départ."],"related":["security-incident","audit-setup"]}}},"solver/arch-highscale.json":{"id":"arch-highscale","title":"Architecture de référence : grande échelle / fort trafic","icon":"🏛️","category":"Architecture de référence","intro":"À mesure que le trafic augmente, il existe un bon ordre pour protéger la base : d'abord les leviers simples et peu coûteux, puis les complexes. Cet arbre parcourt pas à pas l'ordre montée en charge verticale → réplica de lecture → sharding, les schémas de cache (cache-aside, write-back), la mise en tampon avec une file + backpressure, le rate limiting, l'isolation de la base avec bulkheads/circuit breakers et la prévention des points chauds. Principe fondamental : gardez pour la fin les décisions difficiles à inverser comme le sharding, et épuisez d'abord les gains peu coûteux.","start":"root","nodes":{"root":{"kind":"step","title":"Où se situe le goulot : une seule instance est-elle saturée, les lectures ou les écritures sont-elles lourdes, ou le trafic est-il en rafales ?","detail":"Pour résoudre un problème de montée en charge au bon endroit, identifiez d'abord le goulot. Une seule instance est-elle saturée en CPU/mémoire, les lectures dominent-elles, les écritures atteignent-elles la limite, ou des pics de trafic soudains écrasent-ils la base ? Chaque cas désigne un composant différent.","choices":[{"label":"Une seule instance est saturée ; je n'ai pas encore essayé les leviers simples","next":"scale_path"},{"label":"Les lectures sont très lourdes et en croissance","next":"sol_cache"},{"label":"Des pics de trafic soudains / une charge en rafales écrasent la base","next":"protect"}]},"scale_path":{"kind":"step","title":"À quelle étape de montée en charge en êtes-vous : verticale, réplicas de lecture ou sharding ?","detail":"La montée en charge suit un bon ordre. La montée en charge verticale (matériel plus puissant) est la première étape la plus simple ; ensuite vous répartissez les lectures sur des réplicas ; ce n'est que lorsqu'un primaire unique ne peut plus absorber les écritures que vous passez au partitionnement horizontal (sharding). Où en êtes-vous ?","choices":[{"label":"Je n'ai pas encore renforcé le matériel / épuisé l'optimisation de base","next":"sol_vertical"},{"label":"Les écritures tiennent sur une instance, mais je dois répartir la charge de lecture","next":"sol_read_replica"},{"label":"Un primaire unique ne peut plus absorber la charge d'écriture","next":"sol_sharding"}]},"protect":{"kind":"step","title":"Comment protégerez-vous la base de la charge : mise en tampon, limitation ou isolation ?","detail":"Monter en charge ne consiste pas toujours à agrandir la base ; il s'agit souvent de la protéger de la surcharge. Allez-vous lisser les pics d'écriture soudains avec une file, limiter le débit des requêtes entrantes (rate limiting), ou empêcher qu'une seule dépendance fasse tomber tout le système (bulkhead/circuit breaker) ?","choices":[{"label":"Je veux lisser les pics d'écriture soudains","next":"sol_queue"},{"label":"Je veux limiter/partager équitablement le débit des requêtes entrantes","next":"sol_ratelimit"},{"label":"Je ne veux pas qu'une dépendance défaillante fasse tomber tout le système","next":"sol_bulkhead"}]},"sol_vertical":{"kind":"solution","title":"Recommandation : d'abord montée en charge verticale et optimisation de base","detail":"L'étape la moins coûteuse et la moins risquée est la montée en charge verticale : ajouter du CPU/de la RAM/des disques plus rapides et corriger les fondamentaux (index, pooling de connexions, paramètres mémoire). Épuisez ces gains avant des décisions complexes comme le sharding.","steps":["Première étape : renforcez le matériel (CPU/RAM/NVMe) et ajustez les paramètres mémoire du buffer/cache à la charge.","Gains peu coûteux : ajoutez les index manquants, corrigez les requêtes lourdes, placez un pooler de connexions (PgBouncer/ProxySQL) en amont ; la plupart des problèmes d'« échelle » sont en réalité des problèmes d'optimisation.","Quand cela suffit : si l'instance unique renforcée atteint la latence/le débit cibles, arrêtez-vous ; n'ajoutez pas davantage de complexité.","Attention : la montée en charge verticale a un plafond physique et de coût ; à son approche, passez aux réplicas de lecture et au cache.","Ensuite : si les lectures dominent, réplicas + cache ; si les écritures dominent et que le plafond est dépassé, sharding."],"related":["decide-capacity","high-cpu"]},"sol_read_replica":{"kind":"solution","title":"Recommandation : répartissez les lectures avec des réplicas de lecture","detail":"Les écritures restent sur un primaire unique tandis que le trafic en lecture seule est réparti sur un ou plusieurs réplicas. C'est bien plus simple que le sharding et cela résout la plupart des charges à forte lecture ; le primaire ne sert plus qu'aux écritures et aux lectures critiques pour la cohérence.","steps":["Topologie : primaire (écritures) → plusieurs réplicas de lecture asynchrones ; les lectures sont distribuées aux réplicas via un répartiteur de charge.","Moteurs : PostgreSQL streaming replication, réplicas MySQL, réplicas de lecture managés.","Quand : le ratio lecture/écriture penche fortement vers les lectures et les écritures peuvent encore être absorbées par une seule instance.","Attention : le retard de réplication provoque des lectures obsolètes ; servez depuis le primaire les lectures nécessitant read-your-writes.","Ensuite : si même les réplicas ne suffisent pas, ou si le vrai goulot est l'écriture, ajoutez un cache, puis examinez le sharding."],"related":["replication-lag","decide-shard-replica"]},"sol_sharding":{"kind":"solution","title":"Recommandation : sharding (partitionnement horizontal) en dernier recours","detail":"Lorsqu'un primaire unique ne peut pas absorber la charge d'écriture, vous découpez les données selon une clé en plusieurs morceaux indépendants (shards) ; chaque shard prend en charge une partie des écritures. C'est puissant mais difficile à inverser ; cela complique les requêtes et transactions inter-shards, c'est pourquoi on le garde pour la fin.","steps":["Clé de shard : choisissez une clé qui répartit uniformément charge et données et qui permet à la plupart des requêtes d'aboutir sur un seul shard (par ex. customer_id).","Méthode : partitionnement par hachage (répartition uniforme) ou par plage (requêtes de plage faciles mais risque de point chaud).","Exemples de moteurs : sharding MongoDB, Citus (PostgreSQL), Vitess (MySQL) ou sharding au niveau applicatif.","Attention : les jointures/transactions inter-shards sont coûteuses et complexes ; concevez le schéma pour que la plupart des transactions se terminent au sein d'un seul shard.","Attention : une mauvaise clé crée des points chauds (charge concentrée sur un shard) ; évitez les clés monotones comme les id/dates croissants, et n'oubliez pas que le resharding est difficile."],"related":["decide-shard-replica","decide-partition"]},"sol_cache":{"kind":"solution","title":"Recommandation : schémas de cache (cache-aside + write-back)","detail":"Avec un fort trafic à dominante lecture, vous placez un cache en mémoire (Redis) devant la base et servez la plupart des lectures sans qu'elles n'atteignent jamais la base. Pour les écritures à fort volume, le write-back met les écritures en tampon et les vide vers la base par lots, soulageant le primaire.","steps":["Cache-aside : l'application consulte d'abord Redis ; en cas de miss, elle lit dans la base et écrit dans le cache. Cela retire de la base la majeure partie de la charge de lecture.","Write-back (write-behind) : pour des volumes d'écriture très élevés comme les compteurs/métriques, écrivez d'abord dans le cache et videz vers la base par lots en arrière-plan.","TTL + invalidation : attribuez aux clés un TTL raisonnable ; en écriture, mettez à jour/supprimez la clé concernée pour qu'aucune donnée obsolète ne subsiste.","Attention : cache stampede : lorsqu'une clé populaire expire, des milliers de requêtes frappent la base en même temps ; utilisez un verrou/single-flight, un rafraîchissement anticipé et des TTL avec jitter.","Attention : avec le write-back, si le nœud de cache tombe avant d'écrire dans la base, les données sont perdues ; protégez les données critiques avec une file durable/l'AOF."],"related":["decide-cache","cache-hit-low"]},"sol_queue":{"kind":"solution","title":"Recommandation : mise en tampon avec une file (backpressure)","detail":"Pour éviter que les pics d'écriture soudains ne frappent directement la base, vous placez une file durable (par ex. Kafka/une file de messages) entre les deux et consommez les écritures au rythme que la base peut soutenir. La file absorbe le pic ; au lieu d'échouer de façon synchrone, le système fonctionne avec une latence maîtrisée (backpressure).","steps":["Principe : le producteur écrit l'événement dans la file ; le consommateur écrit dans la base à son rythme soutenable (éventuellement par lots).","Moteur : Kafka ou une file de messages ; elle agit comme un tampon qui isole la base du trafic de pointe.","Backpressure : si la file ne cesse de grossir, ralentissez/signalez le producteur ; n'autorisez pas une accumulation illimitée, sinon la latence explose.","Quand : le trafic d'écriture est très irrégulier et un traitement synchrone immédiat n'est pas obligatoire (un traitement asynchrone est acceptable).","Attention : une file introduit de l'asynchronisme ; le résultat est cohérent à terme. Rendez le consommateur idempotent (les messages peuvent être relivrés)."],"related":["connection-storm"]},"sol_ratelimit":{"kind":"solution","title":"Recommandation : rate limiting (limiter le débit des requêtes)","detail":"En limitant les requêtes entrantes par source (rate limiting), vous empêchez à la fois les abus et la consommation de toutes les ressources par un seul client. Plafonner la charge qui atteint la base rend le système prévisible et équitable.","steps":["Emplacement : appliquez la limitation en amont de la base (API gateway / périphérie de l'application) ; seules les requêtes acceptées doivent atteindre la base.","Méthode : débit par client ou par utilisateur avec token bucket / sliding window ; renvoyez 429 en cas de dépassement.","Partage équitable : utilisez des quotas pour éviter qu'un « voisin bruyant » n'avale toute la capacité.","Quand : le trafic peut exploser soudainement, ou il existe un risque d'abus/de clients automatisés.","Attention : le rate limiting est une mesure de protection, pas une montée en charge ; s'il existe un réel besoin de capacité, résolvez aussi le goulot réel (vertical/réplica/shard)."],"related":["connection-storm"]},"sol_bulkhead":{"kind":"solution","title":"Recommandation : bulkhead + circuit breaker (isolation)","detail":"Pour éviter qu'une dépendance (par ex. une base qui ralentit ou un service externe) ne fasse tomber tout le système, vous cloisonnez les ressources en compartiments (bulkheads) et coupez temporairement les appels vers une dépendance qui échoue sans cesse (circuit breaker). Ainsi, une panne partielle ne se propage pas à tout le système.","steps":["Bulkhead : allouez des pools de connexions/threads distincts pour différents types de travail ; un chemin lent ne doit pas épuiser tout le pool et affamer les autres.","Circuit breaker : lorsqu'une dépendance dépasse un seuil d'erreurs/de latence, « ouvrez » le circuit et échouez rapidement pendant un temps ; puis testez le rétablissement par des appels d'essai (half-open).","Timeouts : placez un timeout raisonnable sur chaque appel à la base/aux services ; les appels bloqués ne doivent pas verrouiller le pool.","Quand : dans un système à nombreuses dépendances, où le ralentissement d'un composant présente un risque de panne en cascade.","Attention : concevez une dégradation gracieuse pour le cas où le circuit est ouvert : renvoyez des données obsolètes du cache ou un résultat partiel au lieu de rejeter toute la requête."],"related":["connection-storm","high-cpu"]}}},"types/types-functions.json":{"schemaVersion":1,"sections":[{"id":"datatypes","title":"Types de données","icon":"🔤","rows":[{"id":"var-text","concept":"Texte de longueur variable","engines":{"oracle":"VARCHAR2(n)","postgres":"VARCHAR(n) / TEXT","mysql":"VARCHAR(n)","mssql":"NVARCHAR(n)","mongodb":"string","redis":"string (value)","elasticsearch":"text / keyword"},"note":"Dans ES, text sert à la recherche plein texte et keyword à la correspondance exacte/aux agrégations."},{"id":"fixed-text","concept":"Texte de longueur fixe","engines":{"oracle":"CHAR(n)","postgres":"CHAR(n)","mysql":"CHAR(n)","mssql":"NCHAR(n)","mongodb":"string","redis":"string","elasticsearch":"keyword"},"note":"Les systèmes documents/clé-valeur n'ont pas de notion de longueur fixe ; les valeurs sont stockées sous forme de chaînes."},{"id":"large-text","concept":"Texte volumineux (CLOB/TEXT)","engines":{"oracle":"CLOB","postgres":"TEXT","mysql":"LONGTEXT / TEXT","mssql":"NVARCHAR(MAX)","mongodb":"string","redis":"string (up to 512 MB)","elasticsearch":"text"}},{"id":"int32","concept":"Entier (32 bits)","engines":{"oracle":"NUMBER(10)","postgres":"INTEGER / INT4","mysql":"INT","mssql":"INT","mongodb":"int (NumberInt / int32)","redis":"string (interpreted as an integer)","elasticsearch":"integer"},"note":"Oracle n'a pas de type INT distinct ; on utilise NUMBER."},{"id":"int64","concept":"Grand entier (64 bits)","engines":{"oracle":"NUMBER(19)","postgres":"BIGINT / INT8","mysql":"BIGINT","mssql":"BIGINT","mongodb":"long (NumberLong / int64)","redis":"string (INCR 64-bit integer)","elasticsearch":"long"}},{"id":"decimal","concept":"Décimal/nombre exact (NUMERIC)","engines":{"oracle":"NUMBER(p,s)","postgres":"NUMERIC(p,s) / DECIMAL","mysql":"DECIMAL(p,s)","mssql":"DECIMAL(p,s) / NUMERIC","mongodb":"decimal (Decimal128)","redis":"none, stored as a string","elasticsearch":"scaled_float"},"note":"Pour des valeurs exactes comme l'argent, ces types sont préférés au float."},{"id":"float","concept":"Virgule flottante (FLOAT/DOUBLE)","engines":{"oracle":"BINARY_DOUBLE / FLOAT","postgres":"REAL / DOUBLE PRECISION","mysql":"FLOAT / DOUBLE","mssql":"FLOAT / REAL","mongodb":"double","redis":"none, string (INCRBYFLOAT)","elasticsearch":"float / double"}},{"id":"boolean","concept":"Logique (BOOLEAN)","engines":{"oracle":"NUMBER(1) / CHAR(1) (BOOLEAN in 23c)","postgres":"BOOLEAN","mysql":"TINYINT(1) / BOOL","mssql":"BIT","mongodb":"bool","redis":"none, 0/1 string or bit","elasticsearch":"boolean"},"note":"Un type de colonne BOOLEAN n'est arrivé dans Oracle SQL qu'avec la 23c ; auparavant, il est simulé avec NUMBER/CHAR."},{"id":"date","concept":"Date","engines":{"oracle":"DATE (includes time)","postgres":"DATE","mysql":"DATE","mssql":"DATE","mongodb":"Date (ISODate)","redis":"none (epoch string/number)","elasticsearch":"date (with format)"},"note":"Le type DATE d'Oracle contient à la fois la date et l'heure ; ce n'est pas une date seule."},{"id":"datetime","concept":"Date-heure (timestamp/datetime)","engines":{"oracle":"TIMESTAMP","postgres":"TIMESTAMP","mysql":"DATETIME / TIMESTAMP","mssql":"DATETIME2","mongodb":"Date (ISODate)","redis":"none (epoch string/number)","elasticsearch":"date"}},{"id":"datetime-tz","concept":"Date-heure avec fuseau horaire","engines":{"oracle":"TIMESTAMP WITH TIME ZONE","postgres":"TIMESTAMPTZ","mysql":"TIMESTAMP (stored as UTC)","mssql":"DATETIMEOFFSET","mongodb":"Date (stored as UTC)","redis":"none (epoch UTC)","elasticsearch":"date (UTC, time zone via format)"},"note":"Mongo et ES stockent les dates en UTC ; le fuseau horaire est appliqué à l'affichage."},{"id":"binary","concept":"Données binaires (BLOB/binary)","engines":{"oracle":"BLOB / RAW","postgres":"BYTEA","mysql":"BLOB / VARBINARY","mssql":"VARBINARY(MAX)","mongodb":"BinData (binary)","redis":"string (binary-safe)","elasticsearch":"binary (base64, not searchable)"},"note":"Les chaînes Redis sont binary-safe et peuvent contenir n'importe quelle séquence d'octets."},{"id":"uuid","concept":"UUID/GUID","engines":{"oracle":"RAW(16) / SYS_GUID()","postgres":"UUID","mysql":"BINARY(16) / CHAR(36)","mssql":"UNIQUEIDENTIFIER","mongodb":"UUID (BinData subtype 4) / ObjectId","redis":"none, stored as a string","elasticsearch":"keyword"},"note":"L'_id natif de Mongo est un ObjectId ; un UUID peut aussi être stocké en BinData."},{"id":"json","concept":"JSON","engines":{"oracle":"JSON (21c+) / CLOB","postgres":"JSON / JSONB","mysql":"JSON","mssql":"JSON (2025) / NVARCHAR","mongodb":"object (native BSON)","redis":"not native / RedisJSON module","elasticsearch":"object / nested / flattened"},"note":"Dans Postgres, JSONB est préféré pour l'interrogation/l'indexation ; MongoDB est orienté documents par nature."},{"id":"auto-increment","concept":"Auto-incrément (identity/serial)","engines":{"oracle":"GENERATED AS IDENTITY / SEQUENCE","postgres":"SERIAL / GENERATED ... IDENTITY","mysql":"AUTO_INCREMENT","mssql":"IDENTITY(1,1)","mongodb":"none (ObjectId or a counter pattern)","redis":"counter with INCR","elasticsearch":"none (automatic _id)"}},{"id":"enum","concept":"Enum/liste fixe","engines":{"oracle":"via a CHECK constraint","postgres":"ENUM (CREATE TYPE)","mysql":"ENUM(...)","mssql":"via a CHECK constraint","mongodb":"none (validation with JSON Schema enum)","redis":"none, stored as a string","elasticsearch":"keyword"}},{"id":"array","concept":"Tableau","engines":{"oracle":"VARRAY / nested table / collection","postgres":"ARRAY (e.g. INTEGER[])","mysql":"none (via JSON array)","mssql":"none (JSON / table variable)","mongodb":"array (native)","redis":"List / Set (native)","elasticsearch":"any field can be an array (native)"},"note":"Dans ES, chaque champ peut implicitement contenir un tableau ; il n'existe pas de type tableau distinct."},{"id":"ip-address","concept":"Adresse IP","engines":{"oracle":"VARCHAR2 / UTL_INADDR","postgres":"INET / CIDR","mysql":"VARBINARY(16) / INET6_ATON","mssql":"none, via VARCHAR","mongodb":"none, stored as a string","redis":"none, stored as a string","elasticsearch":"ip"},"note":"Les types IP natifs de Postgres et ES prennent en charge les requêtes par plage/sous-réseau."},{"id":"money","concept":"Argent","engines":{"oracle":"NUMBER(p,2)","postgres":"MONEY / NUMERIC(p,2)","mysql":"DECIMAL(p,2)","mssql":"MONEY / DECIMAL(p,2)","mongodb":"decimal (Decimal128)","redis":"none, an integer in minor units (cents) is recommended","elasticsearch":"scaled_float"},"note":"Pour éviter les erreurs d'arrondi, l'argent est stocké en DECIMAL/Decimal128 plutôt qu'en float."},{"id":"spatial","concept":"Géographique/spatial","engines":{"oracle":"SDO_GEOMETRY (Oracle Spatial)","postgres":"GEOMETRY / GEOGRAPHY (PostGIS)","mysql":"GEOMETRY / POINT","mssql":"GEOGRAPHY / GEOMETRY","mongodb":"GeoJSON object (2dsphere index)","redis":"Geo (GEOADD, sorted set based)","elasticsearch":"geo_point / geo_shape"}}]},{"id":"functions","title":"Fonctions courantes","icon":"🧩","rows":[{"id":"coalesce","concept":"Renvoyer une valeur par défaut si NULL (COALESCE/NVL)","engines":{"oracle":"NVL(x, y) / COALESCE","postgres":"COALESCE(x, y)","mysql":"COALESCE(x, y) / IFNULL","mssql":"COALESCE(x, y) / ISNULL","mongodb":"$ifNull: [x, y]","redis":"none (application side)","elasticsearch":"?.field / painless ternary"}},{"id":"nullif-replace","concept":"Remplacer si NULL (ISNULL/IFNULL)","engines":{"oracle":"NVL(x, y)","postgres":"COALESCE(x, y)","mysql":"IFNULL(x, y)","mssql":"ISNULL(x, y)","mongodb":"$ifNull: [x, y]","redis":"none","elasticsearch":"painless: doc[f].size()==0 ? y : x"}},{"id":"now","concept":"Date-heure courante (NOW/SYSDATE)","engines":{"oracle":"SYSDATE / SYSTIMESTAMP","postgres":"NOW() / CURRENT_TIMESTAMP","mysql":"NOW()","mssql":"GETDATE() / SYSDATETIME()","mongodb":"$$NOW / new Date()","redis":"TIME command","elasticsearch":"now (date math)"}},{"id":"current-date","concept":"Date courante","engines":{"oracle":"TRUNC(SYSDATE) / CURRENT_DATE","postgres":"CURRENT_DATE","mysql":"CURDATE()","mssql":"CAST(GETDATE() AS DATE)","mongodb":"$dateTrunc / new Date()","redis":"TIME (epoch)","elasticsearch":"now/d"}},{"id":"concat","concept":"Concaténer du texte (concat)","engines":{"oracle":"a || b / CONCAT","postgres":"a || b / CONCAT","mysql":"CONCAT(a, b)","mssql":"CONCAT(a, b) / a + b","mongodb":"$concat: [a, b]","redis":"APPEND","elasticsearch":"painless: a + b"},"note":"Dans MySQL, || signifie OR par défaut ; utilisez CONCAT pour la concaténation."},{"id":"substring","concept":"Sous-chaîne","engines":{"oracle":"SUBSTR(s, p, n)","postgres":"SUBSTRING(s, p, n)","mysql":"SUBSTRING(s, p, n)","mssql":"SUBSTRING(s, p, n)","mongodb":"$substrCP: [s, p, n]","redis":"GETRANGE key start end","elasticsearch":"painless: s.substring(p, e)"}},{"id":"length","concept":"Longueur de chaîne","engines":{"oracle":"LENGTH(s)","postgres":"LENGTH(s) / CHAR_LENGTH","mysql":"CHAR_LENGTH(s)","mssql":"LEN(s)","mongodb":"$strLenCP: s","redis":"STRLEN key","elasticsearch":"painless: s.length()"}},{"id":"upper-lower","concept":"Majuscules/minuscules","engines":{"oracle":"UPPER(s) / LOWER(s)","postgres":"UPPER(s) / LOWER(s)","mysql":"UPPER(s) / LOWER(s)","mssql":"UPPER(s) / LOWER(s)","mongodb":"$toUpper / $toLower","redis":"none (application side)","elasticsearch":"painless: s.toUpperCase() / lowercase analyzer"}},{"id":"trim","concept":"Supprimer les espaces (TRIM)","engines":{"oracle":"TRIM(s) / LTRIM / RTRIM","postgres":"TRIM(s)","mysql":"TRIM(s)","mssql":"TRIM(s)","mongodb":"$trim: { input: s }","redis":"none","elasticsearch":"painless: s.trim() / trim token filter"}},{"id":"replace","concept":"Remplacer du texte (REPLACE)","engines":{"oracle":"REPLACE(s, a, b)","postgres":"REPLACE(s, a, b)","mysql":"REPLACE(s, a, b)","mssql":"REPLACE(s, a, b)","mongodb":"$replaceAll / $replaceOne","redis":"SETRANGE (positional) / none","elasticsearch":"painless: s.replace(a, b)"}},{"id":"cast","concept":"Conversion de type (CAST/CONVERT)","engines":{"oracle":"CAST(x AS t) / TO_NUMBER","postgres":"CAST(x AS t) / x::t","mysql":"CAST(x AS t) / CONVERT","mssql":"CAST(x AS t) / CONVERT","mongodb":"$convert / $toInt, $toString","redis":"none (everything is a string)","elasticsearch":"painless: Integer.parseInt(...) / runtime field"}},{"id":"date-to-char","concept":"Formater une date en texte (TO_CHAR/FORMAT)","engines":{"oracle":"TO_CHAR(d, 'YYYY-MM-DD')","postgres":"TO_CHAR(d, 'YYYY-MM-DD')","mysql":"DATE_FORMAT(d, '%Y-%m-%d')","mssql":"FORMAT(d, 'yyyy-MM-dd') / CONVERT","mongodb":"$dateToString: { format, date }","redis":"none","elasticsearch":"date field format / painless SimpleDateFormat"}},{"id":"parse-date","concept":"Convertir du texte en date (TO_DATE/PARSE)","engines":{"oracle":"TO_DATE(s, 'YYYY-MM-DD')","postgres":"TO_DATE(s, 'YYYY-MM-DD')","mysql":"STR_TO_DATE(s, '%Y-%m-%d')","mssql":"CONVERT(date, s) / PARSE","mongodb":"$dateFromString: { dateString: s }","redis":"none","elasticsearch":"automatic parsing via date mapping"}},{"id":"date-add","concept":"Ajouter à une date (interval/DATEADD)","engines":{"oracle":"d + n / ADD_MONTHS / INTERVAL","postgres":"d + INTERVAL '1 day'","mysql":"DATE_ADD(d, INTERVAL 1 DAY)","mssql":"DATEADD(day, 1, d)","mongodb":"$dateAdd: { startDate, unit, amount }","redis":"none (add to the epoch)","elasticsearch":"date math: now+1d"}},{"id":"date-diff","concept":"Différence entre deux dates (DATEDIFF)","engines":{"oracle":"d1 - d2 (days) / MONTHS_BETWEEN","postgres":"d1 - d2 / AGE(d1, d2)","mysql":"DATEDIFF(d1, d2)","mssql":"DATEDIFF(day, d2, d1)","mongodb":"$dateDiff: { startDate, endDate, unit }","redis":"none (epoch difference)","elasticsearch":"painless: ChronoUnit.DAYS.between(...)"}},{"id":"extract","concept":"Extraire une partie de date (EXTRACT)","engines":{"oracle":"EXTRACT(YEAR FROM d)","postgres":"EXTRACT(YEAR FROM d) / DATE_PART","mysql":"EXTRACT(YEAR FROM d) / YEAR(d)","mssql":"DATEPART(year, d) / YEAR(d)","mongodb":"$year / $month / $dayOfMonth","redis":"none","elasticsearch":"painless: doc[f].value.year"}},{"id":"round","concept":"Arrondir (ROUND)","engines":{"oracle":"ROUND(x, n)","postgres":"ROUND(x, n)","mysql":"ROUND(x, n)","mssql":"ROUND(x, n)","mongodb":"$round: [x, n]","redis":"none","elasticsearch":"painless: Math.round(x)"}},{"id":"mod","concept":"Modulo (reste)","engines":{"oracle":"MOD(a, b)","postgres":"MOD(a, b) / a % b","mysql":"MOD(a, b) / a % b","mssql":"a % b","mongodb":"$mod: [a, b]","redis":"none","elasticsearch":"painless: a % b"}},{"id":"case","concept":"Conditionnel (CASE/DECODE)","engines":{"oracle":"CASE ... / DECODE(x, ...)","postgres":"CASE WHEN ... END","mysql":"CASE WHEN ... END / IF()","mssql":"CASE WHEN ... END / IIF()","mongodb":"$cond / $switch","redis":"none","elasticsearch":"painless: if/else"}},{"id":"greatest-least","concept":"Plus grand/plus petit (GREATEST/LEAST)","engines":{"oracle":"GREATEST(...) / LEAST(...)","postgres":"GREATEST(...) / LEAST(...)","mysql":"GREATEST(...) / LEAST(...)","mssql":"GREATEST(...) / LEAST(...) (2022+)","mongodb":"$max / $min (expression)","redis":"none","elasticsearch":"painless: Math.max / Math.min"}},{"id":"string-agg","concept":"Concaténer au sein d'un groupe (STRING_AGG/GROUP_CONCAT/LISTAGG)","engines":{"oracle":"LISTAGG(x, ',')","postgres":"STRING_AGG(x, ',')","mysql":"GROUP_CONCAT(x)","mssql":"STRING_AGG(x, ',')","mongodb":"$push + $reduce / $concatArrays","redis":"none","elasticsearch":"terms agg / scripted_metric"}},{"id":"row-number","concept":"Numéro de ligne (ROW_NUMBER)","engines":{"oracle":"ROW_NUMBER() OVER (...)","postgres":"ROW_NUMBER() OVER (...)","mysql":"ROW_NUMBER() OVER (...)","mssql":"ROW_NUMBER() OVER (...)","mongodb":"$setWindowFields: { $documentNumber }","redis":"none","elasticsearch":"none (top_hits / bucket_sort)"}},{"id":"limit","concept":"N premières lignes (LIMIT/TOP/ROWNUM)","engines":{"oracle":"FETCH FIRST n ROWS ONLY / ROWNUM","postgres":"LIMIT n","mysql":"LIMIT n","mssql":"TOP n / OFFSET-FETCH","mongodb":"$limit: n / .limit(n)","redis":"LRANGE key 0 n-1","elasticsearch":"size: n"}},{"id":"current-user","concept":"Utilisateur courant","engines":{"oracle":"USER / SYS_CONTEXT","postgres":"CURRENT_USER","mysql":"CURRENT_USER() / USER()","mssql":"CURRENT_USER / SUSER_SNAME()","mongodb":"connectionStatus / $$USER (none, application side)","redis":"ACL WHOAMI","elasticsearch":"_security/_authenticate API"}},{"id":"gen-uuid","concept":"Générer un UUID","engines":{"oracle":"SYS_GUID()","postgres":"gen_random_uuid()","mysql":"UUID()","mssql":"NEWID()","mongodb":"UUID() / ObjectId()","redis":"none (application side)","elasticsearch":"automatic _id / application side"}},{"id":"json-extract","concept":"Extraire un champ JSON","engines":{"oracle":"JSON_VALUE(j, '$.a')","postgres":"j->>'a' / j #>> '{a}'","mysql":"JSON_EXTRACT(j, '$.a') / j->>'$.a'","mssql":"JSON_VALUE(j, '$.a')","mongodb":"doc.a (native access)","redis":"JSON.GET key $.a (RedisJSON)","elasticsearch":"doc['a'] / field access (native)"}},{"id":"regexp","concept":"Correspondance d'expression régulière (REGEXP)","engines":{"oracle":"REGEXP_LIKE(s, p)","postgres":"s ~ p / REGEXP_MATCHES","mysql":"s REGEXP p / REGEXP_LIKE","mssql":"REGEXP_LIKE (2025+) / before: LIKE / CLR","mongodb":"$regex / $regexMatch","redis":"none (SCAN MATCH glob pattern)","elasticsearch":"regexp query"},"note":"SQL Server 2025 (et Azure SQL) a ajouté des fonctions regex natives comme REGEXP_LIKE, REGEXP_REPLACE et REGEXP_SUBSTR (REGEXP_LIKE exige le niveau de compatibilité 170) ; les versions antérieures nécessitent LIKE ou une fonction CLR."}]}]},"reference/params-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"params":[{"name":"sga_target","default":"0","recommended":"~60 % de la RAM sur un serveur dédié","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Définit la taille totale de la SGA pour l'Automatic Shared Memory Management (ASMM) ; répartit automatiquement les sous-pools.","tags":["memory","memory","sga"]},{"name":"sga_max_size","default":"Same as sga_target","recommended":"Égal ou légèrement supérieur à sga_target","unit":"bytes (K/M/G)","dynamic":"restart","desc":"Définit la limite supérieure jusqu'à laquelle la SGA peut croître à l'exécution ; l'augmenter exige un redémarrage de l'instance.","tags":["memory","memory","sga"]},{"name":"pga_aggregate_target","default":"10M or 20% of the SGA","recommended":"~20 % de la RAM","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Définit la mémoire PGA cible totale utilisée par tous les processus serveur ; influe sur les opérations de tri et de hachage.","tags":["memory","memory","pga","sort"]},{"name":"pga_aggregate_limit","default":"Greatest of: 2G, 200% of pga_aggregate_target, 3M x processes (not exceeding 120% of physical RAM minus SGA)","recommended":"La valeur par défaut suffit généralement","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Limite supérieure stricte de la mémoire PGA pour toute l'instance ; en cas de dépassement, les appels des sessions les plus consommatrices sont interrompus (SYS et processus d'arrière-plan exclus). Introduit dans Oracle 12c.","tags":["memory","memory","pga","limit"]},{"name":"memory_target","default":"0","recommended":"0 (une gestion séparée SGA/PGA est préférable)","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Regroupe SGA et PGA sous une cible unique avec l'Automatic Memory Management (AMM) ; déconseillé sur les grands systèmes.","tags":["memory","memory","amm"]},{"name":"db_cache_size","default":"0 (automatic)","recommended":"Laisser à 0 si sga_target est utilisé","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Définit la taille du buffer cache pour la taille de bloc par défaut ; conserve les blocs de données en mémoire.","tags":["memory","memory","cache","buffer"]},{"name":"shared_pool_size","default":"0 (automatic)","recommended":"Laisser à 0 si sga_target est utilisé","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Taille de la shared pool, qui contient les résultats de parsing SQL/PLSQL, le dictionary cache et le library cache.","tags":["memory","memory","shared","pool"]},{"name":"processes","default":"100 / 300 (depending on version)","recommended":"Augmenter selon le nombre de connexions simultanées attendu","unit":"integer","dynamic":"restart","desc":"Nombre maximal de processus du système d'exploitation pouvant se connecter à l'instance ; sa modification nécessite un redémarrage.","tags":["connection","connection","process","process"]},{"name":"sessions","default":"Derived from processes (1.5x+22)","recommended":"La valeur dérivée par défaut suffit généralement","unit":"integer","dynamic":"restart","desc":"Nombre maximal de sessions pouvant exister simultanément ; dérivé automatiquement de la valeur de processes.","tags":["connection","connection","session","session"]},{"name":"open_cursors","default":"50","recommended":"300-1000 selon les besoins de l'application","unit":"integer","dynamic":"dynamic","desc":"Définit le nombre maximal de curseurs qu'une session peut garder ouverts simultanément.","tags":["cursor","cursor","session","session"]},{"name":"cursor_sharing","default":"EXACT","recommended":"EXACT (utiliser des variables de liaison)","unit":"EXACT | FORCE","dynamic":"dynamic","desc":"Détermine si les instructions SQL contenant des littéraux sont partagées via des variables de liaison ; FORCE remplace les littéraux.","tags":["cursor","cursor","sql","parse"]},{"name":"optimizer_mode","default":"ALL_ROWS","recommended":"ALL_ROWS","unit":"ALL_ROWS | FIRST_ROWS_n","dynamic":"dynamic","desc":"Définit l'objectif de l'optimiseur basé sur les coûts : priorité au débit total ou au temps de réponse de la première ligne.","tags":["optimizer","optimize","plan","sql"]},{"name":"undo_retention","default":"900","recommended":"Selon la durée de la plus longue requête/du flashback","unit":"seconds","dynamic":"dynamic","desc":"Définit la durée de conservation, si possible, des données undo validées ; influe sur le risque d'ORA-01555.","tags":["undo","rollback","retention","flashback"]},{"name":"undo_tablespace","default":"UNDOTBS1","recommended":"Un tablespace undo dédié et correctement dimensionné","unit":"string (tablespace name)","dynamic":"dynamic","desc":"Définit le tablespace undo actif utilisé par la gestion automatique de l'undo ; modifiable en ligne.","tags":["undo","rollback","tablespace"]},{"name":"db_recovery_file_dest_size","default":"0","recommended":"Suffisant pour contenir archivelogs + sauvegardes + flashback logs","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Définit la taille disque maximale de la Fast Recovery Area (FRA) ; contient les journaux archivés et les sauvegardes.","tags":["recovery","recovery","fra","archive"]},{"name":"db_writer_processes","default":"1 or based on CPU count","recommended":"Environ CPU/8 (augmenter sous forte charge d'écriture)","unit":"integer","dynamic":"restart","desc":"Définit le nombre de processus database writer (DBWn) qui écrivent les blocs modifiés sur disque ; parallélise la charge d'écriture.","tags":["io","write","dbwr","writer"]},{"name":"parallel_max_servers","default":"parallel_threads_per_cpu x cpu_count x concurrent_parallel_users (1/2/4) x 5; capped at processes - 15","recommended":"Définir selon la charge de travail et le nombre de CPU","unit":"integer","dynamic":"dynamic","desc":"Limite le nombre maximal de serveurs d'exécution parallèle (PX) pouvant s'exécuter simultanément dans l'instance.","tags":["parallel","parallel","px","query"]},{"name":"statistics_level","default":"TYPICAL","recommended":"TYPICAL","unit":"BASIC | TYPICAL | ALL","dynamic":"dynamic","desc":"Définit le niveau d'AWR/ADDM et de la collecte automatique des statistiques ; BASIC désactive de nombreux diagnostics.","tags":["statistics","statistics","awr","monitoring"]},{"name":"deferred_segment_creation","default":"TRUE","recommended":"TRUE (économise de l'espace sur les tables vides)","unit":"TRUE | FALSE","dynamic":"dynamic","desc":"Diffère la création du premier segment d'une table/d'un index vide jusqu'à l'insertion de la première ligne ; économise de l'espace.","tags":["segment","deferred-segment","space","storage"]},{"name":"audit_trail","default":"NONE (DB in databases created with DBCA)","recommended":"Utiliser DB ou l'audit unifié","unit":"NONE | DB | DB,EXTENDED | OS | XML","dynamic":"restart","desc":"Détermine où et comment les enregistrements d'audit traditionnels sont écrits ; sa modification nécessite un redémarrage.","tags":["audit","audit","security","security"]},{"name":"compatible","default":"Installed version (e.g., 19.0.0)","recommended":"Relever une fois les fonctionnalités de la nouvelle version stabilisées","unit":"version string","dynamic":"restart","desc":"Détermine les fonctionnalités de version activées ; ne peut plus être abaissé une fois relevé et nécessite un redémarrage.","tags":["compatible","compatibility","version","version"]},{"name":"db_block_size","default":"8192","recommended":"8K (OLTP) ; défini uniquement à la création","unit":"bytes","dynamic":"restart","desc":"Définit la taille de bloc standard de la base ; elle est fixée uniquement à la création de la base et ne peut plus être modifiée ensuite.","tags":["block","block","storage","io"]},{"name":"filesystemio_options","default":"none (platform-dependent)","recommended":"SETALL (Direct + Async I/O)","unit":"none | asynch | directio | setall","dynamic":"restart","desc":"Contrôle l'utilisation des E/S asynchrones et directes pour les datafiles sur les systèmes de fichiers.","tags":["io","io","async","directio"]},{"name":"log_archive_max_processes","default":"4","recommended":"Augmenter selon le nombre de destinations d'archivage","unit":"integer","dynamic":"dynamic","desc":"Définit le nombre maximal de processus ARCn qui archivent les redo logs ; augmenté pour réduire le retard d'archivage.","tags":["archive","archive","redo","log"]}]},"reference/params-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"params":[{"name":"shared_buffers","default":"128MB","recommended":"~25 % de la RAM","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"Quantité de mémoire partagée que PostgreSQL alloue au cache des blocs de données ; sa modification nécessite un redémarrage.","tags":["memory","memory","cache","buffer"]},{"name":"effective_cache_size","default":"4GB","recommended":"~50-75 % de la RAM","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Indique au planificateur la taille totale estimée du cache OS + PostgreSQL ; influe sur l'utilisation des index.","tags":["memory","memory","planner","cache"]},{"name":"work_mem","default":"4MB","recommended":"16-64MB (attention au nombre de connexions)","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Mémoire utilisable par les opérations de tri et de hachage avant débordement sur disque ; allouée par nœud du plan de requête.","tags":["memory","memory","sort","query"]},{"name":"maintenance_work_mem","default":"64MB","recommended":"256MB-1GB","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Définit la mémoire maximale utilisable par les opérations de maintenance telles que VACUUM, CREATE INDEX et ALTER TABLE.","tags":["memory","memory","vacuum","maintenance"]},{"name":"max_connections","default":"100","recommended":"100-200 (utiliser un pooler au-delà)","unit":"integer","dynamic":"restart","desc":"Nombre maximal de connexions client simultanées acceptées ; sa modification nécessite un redémarrage.","tags":["connection","connection","limit"]},{"name":"wal_buffers","default":"-1 (1/32 of shared_buffers)","recommended":"16MB","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"Définit la taille du tampon en mémoire partagée qui contient les enregistrements WAL avant leur écriture sur disque. La valeur -1 (automatique) est bornée à au moins 64kB et au plus un segment WAL (généralement 16MB).","tags":["wal","memory","memory","buffer"]},{"name":"max_wal_size","default":"1GB","recommended":"4-16GB selon la charge de travail","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Définit la limite haute souple que le WAL peut atteindre avant le déclenchement d'un checkpoint automatique.","tags":["wal","checkpoint","disk"]},{"name":"min_wal_size","default":"80MB","recommended":"1-2GB","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Limite basse en dessous de laquelle les fichiers WAL sont conservés pour recyclage ; réduit les réallocations fréquentes.","tags":["wal","checkpoint","disk"]},{"name":"checkpoint_completion_target","default":"0.9","recommended":"0.9","unit":"ratio (0-1)","dynamic":"dynamic","desc":"Définit la fraction de l'intervalle de checkpoint sur laquelle les écritures du checkpoint sont réparties ; lisse les pics d'E/S. La valeur par défaut est passée à 0.9 en PG14 ; elle était de 0.5 auparavant.","tags":["checkpoint","io","wal"]},{"name":"checkpoint_timeout","default":"5min","recommended":"15-30min","unit":"time (s/min)","dynamic":"dynamic","desc":"Définit le délai maximal entre deux checkpoints automatiques ; l'augmenter réduit la charge d'écriture mais allonge la durée de récupération.","tags":["checkpoint","wal","recovery"]},{"name":"random_page_cost","default":"4.0","recommended":"1.1 pour SSD","unit":"decimal","dynamic":"dynamic","desc":"Définit le coût relatif d'un accès disque aléatoire pour le planificateur ; le réduire sur SSD favorise les parcours d'index.","tags":["planner","optimize","io","index"]},{"name":"effective_io_concurrency","default":"1","recommended":"200 pour SSD","unit":"integer","dynamic":"dynamic","desc":"Nombre estimé d'opérations d'E/S simultanées que le disque peut servir ; influe sur le prefetching des bitmap scans. La valeur par défaut est 1 en PG17 et antérieures, et 16 en PG18.","tags":["io","gc","ssd","prefetch"]},{"name":"default_statistics_target","default":"100","recommended":"100 (relever par colonne sur les grandes tables)","unit":"integer","dynamic":"dynamic","desc":"Définit le niveau de détail des statistiques collectées par ANALYZE ; le relever améliore la précision des plans et allonge la durée d'analyse.","tags":["statistics","statistics","analyze","planner"]},{"name":"autovacuum","default":"on","recommended":"on","unit":"on | off","dynamic":"dynamic","desc":"Active le processus de vacuum automatique qui supprime les dead tuples et collecte les statistiques ; le désactiver entraîne du bloat.","tags":["autovacuum","vacuum","maintenance","bloat"]},{"name":"autovacuum_vacuum_scale_factor","default":"0.2","recommended":"0.05-0.1 sur les grandes tables","unit":"ratio (0-1)","dynamic":"dynamic","desc":"Définit la fraction des lignes d'une table qui doit changer avant son vacuum ; la réduire nettoie plus souvent les grandes tables.","tags":["autovacuum","vacuum","bloat","maintenance"]},{"name":"autovacuum_max_workers","default":"3","recommended":"3-6 (relever sur les systèmes comportant de nombreuses tables)","unit":"integer","dynamic":"restart","desc":"Définit le nombre maximal de processus worker autovacuum pouvant s'exécuter simultanément ; en PG17 et antérieures, sa modification nécessite un redémarrage. En PG18, il peut être modifié par un reload dans la limite de autovacuum_worker_slots.","tags":["autovacuum","vacuum","worker","maintenance"]},{"name":"synchronous_commit","default":"on","recommended":"on (données critiques) ; off (performance d'abord)","unit":"on | off | local | remote_write | remote_apply","dynamic":"dynamic","desc":"Détermine si un commit attend l'écriture du WAL sur disque ; off comporte un risque de légère perte de données en cas de crash.","tags":["wal","commit","durability","performance"]},{"name":"wal_level","default":"replica","recommended":"replica (pour la réplication/PITR)","unit":"minimal | replica | logical","dynamic":"restart","desc":"Détermine la quantité d'informations écrites dans le WAL ; requis pour la réplication et la réplication logique, sa modification nécessite un redémarrage.","tags":["wal","replication","replication","pitr"]},{"name":"max_wal_senders","default":"10","recommended":"Selon le nombre de standbys + sauvegardes","unit":"integer","dynamic":"restart","desc":"Définit le nombre maximal de processus pouvant diffuser le WAL simultanément (pour les standbys et les sauvegardes).","tags":["wal","replication","replication","standby"]},{"name":"wal_keep_size","default":"0","recommended":"Suffisant pour couvrir le retard du standby","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Quantité minimale de WAL conservée dans pg_wal pour les standbys ; protège un standby en retard en l'absence de slot de réplication. Introduit en PG13 ; auparavant wal_keep_segments (nombre de segments) était utilisé.","tags":["wal","replication","replication","standby"]},{"name":"log_min_duration_statement","default":"-1 (disabled)","recommended":"1000ms pour capturer les requêtes lentes","unit":"milliseconds","dynamic":"dynamic","desc":"Journalise les instructions dont l'exécution dépasse la durée indiquée ; sert à détecter les requêtes lentes, -1 le désactive.","tags":["logging","log","slow-query","slow-query"]},{"name":"idle_in_transaction_session_timeout","default":"0 (disabled)","recommended":"60000ms pour éviter les verrous conservés","unit":"milliseconds","dynamic":"dynamic","desc":"Met fin aux sessions restées inactives dans une transaction ouverte pendant la durée indiquée ; réduit le risque de verrous et de bloat.","tags":["timeout","transaction","transaction","lock"]},{"name":"statement_timeout","default":"0 (disabled)","recommended":"Selon l'application (faible pour l'OLTP)","unit":"milliseconds","dynamic":"dynamic","desc":"Annule automatiquement les instructions dépassant la durée indiquée ; empêche les requêtes incontrôlées de consommer les ressources.","tags":["timeout","query","query","limit"]},{"name":"shared_preload_libraries","default":"'' (empty)","recommended":"pg_stat_statements (pour la supervision)","unit":"string (comma-separated list)","dynamic":"restart","desc":"Spécifie les bibliothèques partagées chargées au démarrage du serveur ; les extensions telles que pg_stat_statements sont configurées ici.","tags":["extension","extension","preload","monitoring"]},{"name":"max_parallel_workers_per_gather","default":"2","recommended":"2-4 (selon le nombre de CPU)","unit":"integer","dynamic":"dynamic","desc":"Définit le nombre maximal de workers parallèles qu'un seul nœud Gather/GatherMerge peut démarrer ; 0 désactive le parallélisme.","tags":["parallel","parallel","worker","query"]}]},"reference/params-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"params":[{"name":"innodb_buffer_pool_size","default":"128MB","recommended":"~70-80 % de la RAM sur un serveur dédié","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Principal pool mémoire dans lequel InnoDB met en cache les données et les index ; en 8.0 il est modifiable en ligne, mais les changements importants demandent de la prudence.","tags":["memory","memory sizing","innodb","buffer"]},{"name":"innodb_buffer_pool_instances","default":"1 (or 8 if size>1GB)","recommended":"8 pour les grands pools","unit":"integer","dynamic":"restart","desc":"Définit le nombre d'instances en lesquelles le buffer pool est divisé ; réduit la contention sur les mutex en concurrence, sa modification nécessite un redémarrage.","tags":["memory","memory sizing","innodb","concurrency"]},{"name":"innodb_redo_log_capacity","default":"100MB (8.0.30+)","recommended":"1-8GB (selon la charge d'écriture ; ~1 heure de redo en pic)","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Définit l'espace disque total occupé par le redo log InnoDB ; l'augmenter lisse la pression des checkpoints et les pics d'écriture. Introduit dans MySQL 8.0.30 et modifiable sans redémarrage via SET GLOBAL/SET PERSIST. Les anciens innodb_log_file_size (48MB par défaut) et innodb_log_files_in_group sont dépréciés depuis 8.0.30 ; avant 8.0.30, la taille du redo est définie par ces deux paramètres et nécessite un redémarrage.","tags":["innodb","redo","log","io"]},{"name":"innodb_flush_log_at_trx_commit","default":"1","recommended":"1 (ACID complet) ; 2 (performance)","unit":"0 | 1 | 2","dynamic":"dynamic","desc":"Contrôle si le redo log est écrit et synchronisé sur disque à chaque commit ; 1 garantit une durabilité complète, 2/0 comportent un risque de perte.","tags":["innodb","commit","durability","acid"]},{"name":"innodb_flush_method","default":"8.0: fsync (Linux) / 8.4: O_DIRECT (if supported); Windows: unbuffered","recommended":"O_DIRECT (Linux)","unit":"fsync | O_DIRECT | O_DIRECT_NO_FSYNC","dynamic":"restart","desc":"Définit la manière dont InnoDB vide les fichiers de données et de log sur disque ; O_DIRECT évite le double buffering, sa modification nécessite un redémarrage.","tags":["innodb","io","flush","gc"]},{"name":"innodb_io_capacity","default":"8.0: 200 / 8.4: 10000","recommended":"Selon les IOPS réelles du stockage (1000-2000 pour SSD en 8.0)","unit":"integer (IOPS)","dynamic":"dynamic","desc":"Définit la capacité IOPS estimée du disque utilisée par les tâches d'arrière-plan d'InnoDB (flush, merge). Dans MySQL 8.4, les valeurs par défaut ont été relevées à innodb_io_capacity=10000 et innodb_io_capacity_max=20000 (200/2000 en 8.0).","tags":["innodb","io","iops","ssd"]},{"name":"innodb_read_io_threads","default":"8.0: 4 / 8.4: half the number of logical CPUs (minimum 4)","recommended":"4-8 (selon la charge de lecture)","unit":"integer","dynamic":"restart","desc":"Définit le nombre de threads d'arrière-plan InnoDB traitant les requêtes d'E/S en lecture ; sa modification nécessite un redémarrage.","tags":["innodb","io","thread","read"]},{"name":"innodb_write_io_threads","default":"4","recommended":"4-8 (selon la charge d'écriture)","unit":"integer","dynamic":"restart","desc":"Définit le nombre de threads d'arrière-plan InnoDB traitant les requêtes d'E/S en écriture ; sa modification nécessite un redémarrage.","tags":["innodb","io","thread","write"]},{"name":"max_connections","default":"151","recommended":"200-500 selon la charge de travail","unit":"integer","dynamic":"dynamic","desc":"Définit le nombre maximal de connexions client simultanées autorisées ; modifiable en ligne.","tags":["connection","connections","limit"]},{"name":"thread_cache_size","default":"-1 (autosized)","recommended":"Automatique ; augmenter en cas de nombreuses connexions de courte durée","unit":"integer","dynamic":"dynamic","desc":"Définit le nombre de threads conservés en cache pour réutilisation ; réduit la création de threads pour les nouvelles connexions.","tags":["connection","connections","thread","cache"]},{"name":"table_open_cache","default":"4000","recommended":"4000-8000 selon le nombre de tables ouvertes","unit":"integer","dynamic":"dynamic","desc":"Définit la taille du cache des descripteurs de tables ouvertes pour l'ensemble des threads ; réduit le coût de réouverture des tables.","tags":["cache","table","table cache","open"]},{"name":"tmp_table_size","default":"16MB","recommended":"64-256MB","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Définit la taille maximale d'une table temporaire en mémoire avant sa conversion en table sur disque ; fonctionne conjointement avec max_heap_table_size.","tags":["memory","memory sizing","temp","temporary table"]},{"name":"max_heap_table_size","default":"16MB","recommended":"Identique à tmp_table_size","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Définit la taille maximale que peuvent atteindre les tables du moteur MEMORY et les tables temporaires en mémoire.","tags":["memory","memory sizing","temp","heap"]},{"name":"sort_buffer_size","default":"256KB","recommended":"256KB-2MB (attention au coût par connexion)","unit":"bytes (K/M)","dynamic":"dynamic","desc":"Tampon alloué par session pour les tris ORDER BY et GROUP BY ; une taille trop grande consomme de la mémoire.","tags":["memory","memory sizing","sort","buffer"]},{"name":"join_buffer_size","default":"256KB","recommended":"256KB-1MB (privilégier une solution par index)","unit":"bytes (K/M)","dynamic":"dynamic","desc":"Tampon alloué par session pour les jointures ne pouvant pas utiliser d'index ; la solution durable consiste à ajouter un index approprié.","tags":["memory","memory sizing","join","buffer"]},{"name":"sync_binlog","default":"1","recommended":"1 (sécurité totale) ; 100-1000 sous forte charge","unit":"integer","dynamic":"dynamic","desc":"Définit après combien de commits le binary log est synchronisé sur disque ; 1 est le plus sûr, des valeurs plus élevées sacrifient la durabilité au profit des performances.","tags":["binlog","replication","durability","commit"]},{"name":"binlog_expire_logs_seconds","default":"2592000 (30 days)","recommended":"Selon la fenêtre de sauvegarde/réplication","unit":"seconds","dynamic":"dynamic","desc":"Définit la durée de conservation des fichiers binary log avant leur purge automatique ; contrôle l'utilisation du disque.","tags":["binlog","log","retention","replication"]},{"name":"long_query_time","default":"10","recommended":"1 (capturer les requêtes lentes)","unit":"seconds","dynamic":"dynamic","desc":"Définit la durée qu'une requête doit dépasser pour être enregistrée dans le slow query log ; accepte des valeurs fractionnaires.","tags":["slow-query","slow-query-log","log","performance"]},{"name":"slow_query_log","default":"OFF","recommended":"ON (pour le diagnostic)","unit":"ON | OFF","dynamic":"dynamic","desc":"Contrôle si les requêtes dépassant long_query_time sont écrites dans le slow query log ; à activer pour l'analyse des performances.","tags":["slow-query","slow-query-log","log","monitoring"]},{"name":"wait_timeout","default":"28800 (8 hours)","recommended":"Selon le pool applicatif (par ex. 600)","unit":"seconds","dynamic":"dynamic","desc":"Définit la durée maximale d'inactivité d'une connexion non interactive avant sa fermeture ; nettoie les connexions inactives.","tags":["timeout","connection","connections","idle"]},{"name":"max_allowed_packet","default":"64MB","recommended":"64-256MB pour les gros BLOB/lignes","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Définit la taille maximale d'un paquet unique ou de toute ligne générée/intermédiaire ; à augmenter pour les transferts de gros BLOB.","tags":["packet","packet size","blob","network"]},{"name":"innodb_lock_wait_timeout","default":"50","recommended":"10-50 pour l'OLTP","unit":"seconds","dynamic":"dynamic","desc":"Définit la durée pendant laquelle une transaction InnoDB attend un verrou de ligne avant expiration.","tags":["innodb","lock","lock wait","timeout"]},{"name":"character_set_server","default":"utf8mb4","recommended":"utf8mb4","unit":"text (character set)","dynamic":"dynamic","desc":"Définit le jeu de caractères par défaut du serveur ; utf8mb4 offre une prise en charge complète d'Unicode (emoji compris).","tags":["charset","character-set","unicode","utf8mb4"]}]},"reference/params-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"params":[{"name":"max server memory (MB)","default":"2147483647","recommended":"RAM totale moins la part réservée à l'OS et aux autres services (~10-25 %)","unit":"MB","dynamic":"dynamic","desc":"Définit la quantité maximale de mémoire utilisable par le buffer pool de SQL Server et les autres consommateurs de mémoire. La valeur par défaut est en pratique illimitée ; si d'autres services tournent sur le serveur, elle doit être abaissée pour laisser suffisamment de mémoire au système d'exploitation. Défini avec sp_configure ; effectif immédiatement avec RECONFIGURE.","tags":["memory","memory limit","buffer pool"]},{"name":"min server memory (MB)","default":"0","recommended":"Sur les instances critiques, un plancher raisonnable par rapport à max","unit":"MB","dynamic":"dynamic","desc":"Quantité minimale de mémoire que SQL Server ne rend plus au système d'exploitation une fois allouée. Sert à empêcher SQL Server de libérer trop de mémoire sous pression mémoire. Défini avec sp_configure ; effectif avec RECONFIGURE.","tags":["memory","memory limit"]},{"name":"max degree of parallelism","default":"0","recommended":"Généralement 8 ou le nombre de cœurs physiques par nœud NUMA","unit":"count","dynamic":"dynamic","desc":"Limite le nombre maximal de processeurs (cœurs) qu'une requête peut utiliser en exécution parallèle. 0 signifie que tous les cœurs peuvent être utilisés ; il est limité pour éviter un parallélisme excessif sur les charges OLTP. Effectif immédiatement avec sp_configure + RECONFIGURE.","tags":["parallelism","parallel query","maxdop","cpu"]},{"name":"cost threshold for parallelism","default":"5","recommended":"25-50 sur du matériel moderne","unit":"cost","dynamic":"dynamic","desc":"Seuil de coût estimé minimal qu'un plan de requête doit atteindre pour être exécuté en parallèle. La valeur par défaut de 5 est très basse et entraîne la parallélisation inutile de petites requêtes ; la relever améliore le comportement du parallélisme sur l'ensemble du serveur. Effectif immédiatement avec sp_configure + RECONFIGURE.","tags":["parallelism","parallel query","cpu"]},{"name":"max worker threads","default":"0","recommended":"0 (laisser SQL Server le calculer)","unit":"count","dynamic":"dynamic","desc":"Définit le nombre maximal de worker threads que SQL Server peut créer pour traiter les requêtes client. 0 le fait calculer automatiquement selon le nombre de processeurs et l'architecture. C'est une option avancée ; appliquée avec sp_configure + RECONFIGURE sans redémarrage (sys.configurations.is_dynamic = 1).","tags":["threads","worker","cpu","concurrency"]},{"name":"optimize for ad hoc workloads","default":"0","recommended":"1 (activé)","unit":"0/1","dynamic":"dynamic","desc":"Pour les requêtes ad hoc exécutées une seule fois, met en cache un petit stub de plan compilé à la première exécution au lieu du plan complet. Empêche le plan cache de se remplir de plans à usage unique et améliore l'utilisation de la mémoire. Effectif immédiatement avec sp_configure + RECONFIGURE.","tags":["plan cache","memory","memory usage"]},{"name":"backup compression default","default":"0","recommended":"1 (activé)","unit":"0/1","dynamic":"dynamic","desc":"Rend les nouvelles sauvegardes compressées par défaut. La compression réduit nettement la taille et la durée des sauvegardes au prix d'un peu de CPU. Effectif immédiatement avec sp_configure + RECONFIGURE.","tags":["backup","backup size","compression","backup compression"]},{"name":"fill factor (%)","default":"0","recommended":"0 ou 90-100 (selon la charge de travail)","unit":"%","dynamic":"restart","desc":"Définit, en pourcentage, le taux de remplissage des pages de niveau feuille lors de la création ou de la reconstruction (REBUILD) d'un index. 0 et 100 remplissent entièrement les pages ; des valeurs plus faibles réduisent les page splits mais augmentent l'utilisation du disque et de la mémoire. La valeur par défaut au niveau serveur se modifie avec sp_configure mais ne prend effet qu'au redémarrage du service (is_dynamic = 0) ; l'option FILLFACTOR par index est utilisable immédiatement.","tags":["index","page split","storage","disk usage"]},{"name":"recovery interval (min)","default":"0","recommended":"0 (automatique)","unit":"minutes","dynamic":"dynamic","desc":"Définit la fréquence des checkpoints automatiques en fonction du temps de récupération maximal visé. 0 laisse SQL Server gérer automatiquement le temps de récupération cible par base. Effectif immédiatement avec sp_configure + RECONFIGURE.","tags":["checkpoint","recovery","crash recovery"]},{"name":"remote query timeout (s)","default":"600","recommended":"600 ou 0 (illimité), selon la charge de travail","unit":"seconds","dynamic":"dynamic","desc":"Durée pendant laquelle le serveur attend une réponse aux requêtes distantes (via un linked server) avant expiration. 0 signifie une attente illimitée. Effectif immédiatement avec sp_configure + RECONFIGURE.","tags":["timeout","remote","linked server"]},{"name":"network packet size (B)","default":"4096","recommended":"4096 (ne pas modifier sauf cas particuliers)","unit":"bytes","dynamic":"dynamic","desc":"Taille de paquet utilisée pour le transfert de données sur le réseau. La valeur par défaut est recommandée, sauf pour des opérations de masse particulières transférant de très gros volumes ; un mauvais réglage peut dégrader les performances. Effectif avec sp_configure + RECONFIGURE.","tags":["network","network packet","packet"]},{"name":"database compatibility level (ALTER DATABASE)","default":"Depends on server version","recommended":"Le dernier niveau testé","unit":"level","dynamic":"dynamic","desc":"Détermine avec quels comportements et quel modèle d'optimiseur de requêtes de version SQL Server une base fonctionne. Défini par base avec ALTER DATABASE ... SET COMPATIBILITY_LEVEL et effectif immédiatement ; influe sur des fonctionnalités de l'optimiseur telles que l'estimation de cardinalité.","tags":["database","compatibility level","optimizer","compatibility"]},{"name":"MAXDOP (database scoped configuration)","default":"0 (inherits the server setting)","recommended":"Une valeur adaptée à la charge de travail de la base","unit":"count","dynamic":"dynamic","desc":"Remplace au niveau de la base le paramètre max degree of parallelism défini pour le serveur. Défini avec ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP et effectif immédiatement ; permet un comportement de parallélisme différent selon les bases.","tags":["parallelism","parallel query","maxdop","database scoped"]},{"name":"tempdb file count (recommandation)","default":"SQL Server 2016+ setup: equal to the number of logical cores, at most 8 (1 before)","recommended":"Égal au nombre de cœurs logiques, au plus 8 (augmenter par paliers de 4 si nécessaire)","unit":"count","dynamic":"dynamic","desc":"Le nombre de fichiers de données tempdb est important pour réduire la contention sur les pages d'allocation. La recommandation générale est d'utiliser des fichiers de taille égale, jusqu'au nombre de cœurs (max 8). Un fichier ajouté avec ALTER DATABASE tempdb ADD FILE est utilisé immédiatement sans redémarrage ; toutefois, si les fichiers existants sont pleins, ramenez tous les fichiers à une taille égale pour équilibrer le remplissage proportionnel. La suppression d'un fichier tempdb (REMOVE FILE) n'est souvent possible qu'après un redémarrage, car le fichier ne se vide pas.","tags":["tempdb","storage","data files","contention"]},{"name":"Trace Flag 1117 / 1118 (remarque)","default":"Default behavior for tempdb in SQL 2016+","recommended":"Généralement inutile en SQL 2016+ ; activé sur les versions plus anciennes","unit":"flag","dynamic":"restart","desc":"TF 1117 force tous les fichiers d'un filegroup à croître ensemble ; TF 1118 impose l'allocation d'extents uniformes au lieu d'extents mixtes. Depuis SQL Server 2016, ces comportements sont par défaut pour tempdb, les flags sont donc généralement inutiles. Nécessite un redémarrage lorsqu'ils sont ajoutés comme flag de démarrage.","tags":["trace flag","tempdb","note"]},{"name":"Trace Flag 3226","default":"Off","recommended":"Activé (supprime les messages de sauvegarde réussie)","unit":"flag","dynamic":"dynamic","desc":"Empêche l'écriture des opérations de sauvegarde réussies dans le journal d'erreurs de SQL Server et le journal des événements Windows. Sur les systèmes avec des sauvegardes de log fréquentes, évite que les journaux se remplissent de messages inutiles (log spam). Peut être activé immédiatement à l'exécution avec DBCC TRACEON(3226, -1) ; pour le conserver après redémarrage, ajoutez aussi -T3226 comme paramètre de démarrage.","tags":["trace flag","backup","backup messages","log"]},{"name":"lock pages in memory (remarque sur la stratégie Windows)","default":"Off","recommended":"Accordé au compte de service dans des scénarios spécifiques","unit":"policy","dynamic":"restart","desc":"Stratégie de droits utilisateur Windows permettant au compte de service SQL Server d'empêcher le système d'exploitation de paginer ses pages mémoire sur disque. Peut réduire les fluctuations de performances sous pression mémoire mais doit être configurée avec soin. La modification de la stratégie nécessite un redémarrage du service.","tags":["memory","memory paging","windows","policy","note"]}]},"reference/params-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"params":[{"name":"storage.wiredTiger.engineConfig.cacheSizeGB","default":"(RAM - 1GB) * 0.5","recommended":"Conserver la valeur par défaut ou ~50 % de la RAM disponible","unit":"GB","dynamic":"restart","desc":"Définit, en gigaoctets, la quantité de mémoire allouée au cache interne du moteur de stockage WiredTiger. Par défaut, la moitié de (RAM totale - 1GB) est utilisée. Une définition manuelle n'est nécessaire que dans des cas particuliers et requiert un redémarrage de mongod.","tags":["memory","memory","cache","wiredtiger"]},{"name":"net.maxIncomingConnections","default":"1000000 (effectively limited by OS limits)","recommended":"Selon la charge de travail et l'ulimit du système d'exploitation","unit":"count","dynamic":"restart","desc":"Limite le nombre maximal de connexions entrantes simultanées qu'une instance mongod ou mongos accepte. Si la valeur dépasse la limite de descripteurs de fichiers/connexions du système d'exploitation (ulimit), elle est sans effet ; c'est l'OS qui détermine le plafond réel (visible comme current + available dans db.serverStatus().connections). La modification se fait dans le fichier de configuration et nécessite un redémarrage.","tags":["network","network","connections","connection"]},{"name":"operationProfiling.slowOpThresholdMs","default":"100","recommended":"100 ou moins selon la charge de travail","unit":"ms","dynamic":"dynamic","desc":"Définit, en millisecondes, le seuil de durée au-delà duquel une opération est considérée comme lente et enregistrée par le profiler et dans le log. Les opérations dépassant ce seuil sont enregistrées pour faciliter le diagnostic. Modifiable immédiatement à l'exécution avec db.setProfilingLevel().","tags":["profiling","slow query","slow query","monitoring"]},{"name":"replication.oplogSizeMB","default":"~5% of disk size (max 50GB)","recommended":"Suffisant pour couvrir la fenêtre de maintenance","unit":"MB","dynamic":"dynamic","desc":"Définit, en mégaoctets, la taille maximale de la collection oplog qui stocke les opérations sur les membres du replica set. Un oplog suffisamment grand permet aux membres en retard de rattraper leur retard sans resynchronisation complète. Modifiable immédiatement sur un replica set en fonctionnement avec la commande replSetResizeOplog.","tags":["replication","oplog","replica set"]},{"name":"security.authorization","default":"disabled","recommended":"enabled (contrôle d'accès basé sur les rôles)","unit":"enabled/disabled","dynamic":"restart","desc":"Active le contrôle d'accès basé sur les rôles (RBAC) ; une fois activé, les clients doivent s'authentifier et être autorisés. Doit toujours être activé en production. Défini dans le fichier de configuration et nécessite un redémarrage.","tags":["security","security","authorization","authorization","rbac"]},{"name":"net.compression.compressors","default":"snappy,zstd,zlib","recommended":"snappy,zstd (la valeur par défaut convient)","unit":"list","dynamic":"restart","desc":"Définit les algorithmes de compression, et leur ordre de préférence, utilisables pour le trafic réseau client et intra-cluster. Réduit l'utilisation de la bande passante réseau au prix d'un peu de CPU. Défini dans le fichier de configuration et nécessite un redémarrage.","tags":["network","network","compression","compression"]},{"name":"storage.wiredTiger.collectionConfig.blockCompressor","default":"snappy","recommended":"snappy (vitesse) ou zstd (forte compression)","unit":"algorithm","dynamic":"restart","desc":"Définit l'algorithme de compression de blocs utilisé par WiredTiger lors de l'écriture des données de collection sur disque. snappy privilégie la vitesse, tandis que zlib et zstd offrent des taux de compression plus élevés. S'applique uniquement aux collections créées après le réglage et nécessite un redémarrage.","tags":["storage","storage","compression","compression","wiredtiger"]},{"name":"storage.directoryPerDB","default":"false","recommended":"true si vous souhaitez séparer les disques","unit":"true/false","dynamic":"restart","desc":"Stocke les données de chaque base dans un sous-répertoire distinct, ce qui permet de placer différentes bases sur différents volumes disque. Ne peut être activé que sur un répertoire de données vide. La modification se fait dans le fichier de configuration et nécessite un redémarrage.","tags":["storage","storage","directory"]},{"name":"setParameter.cursorTimeoutMillis","default":"600000","recommended":"La valeur par défaut (10 minutes), selon la charge de travail","unit":"ms","dynamic":"dynamic","desc":"Définit, en millisecondes, le délai d'attente des curseurs inactifs avant leur fermeture automatique par le serveur. Empêche les curseurs abandonnés de consommer des ressources. Modifiable immédiatement à l'exécution avec db.adminCommand({setParameter:1}).","tags":["cursor","timeout","setparameter"]},{"name":"setParameter.transactionLifetimeLimitSeconds","default":"60","recommended":"La valeur par défaut, sauf besoin de transactions longues","unit":"seconds","dynamic":"dynamic","desc":"Définit, en secondes, la durée maximale pendant laquelle une transaction multi-documents peut rester ouverte avant son annulation automatique. Empêche les transactions longues de monopoliser les ressources et le cache WiredTiger. Modifiable immédiatement avec setParameter.","tags":["transaction","transaction","setparameter"]},{"name":"setParameter.maxTransactionLockRequestTimeoutMillis","default":"5","recommended":"La valeur par défaut, selon la contention sur les verrous","unit":"ms","dynamic":"dynamic","desc":"Définit, en millisecondes, la durée maximale pendant laquelle une transaction attend l'acquisition d'un verrou nécessaire avant expiration. Le réglage de l'attente sur verrou évite que les transactions restent bloquées trop longtemps. Modifiable immédiatement avec setParameter.","tags":["transaction","lock","lock","setparameter"]},{"name":"net.tls.mode","default":"disabled","recommended":"requireTLS (en production)","unit":"mode","dynamic":"restart","desc":"Détermine l'utilisation par le serveur des connexions chiffrées TLS/SSL. requireTLS impose le chiffrement de toutes les connexions ; preferTLS et allowTLS fournissent des modes intermédiaires pour les périodes de transition. S'utilise avec la configuration des certificats et nécessite un redémarrage.","tags":["security","security","tls","ssl","encryption"]},{"name":"storage.syncPeriodSecs","default":"60","recommended":"60 (ne pas modifier la valeur par défaut)","unit":"seconds","dynamic":"dynamic","desc":"Définit, en secondes, la fréquence des checkpoints WiredTiger, c'est-à-dire l'intervalle entre deux vidages des données en mémoire sur disque. La valeur par défaut convient à la plupart des charges et sa modification est généralement déconseillée. Défini dans le fichier de configuration ; à l'exécution, modifiable aussi sans redémarrage avec db.adminCommand({ setParameter: 1, syncdelay: <seconds> }).","tags":["storage","storage","checkpoint","sync"]},{"name":"replication.enableMajorityReadConcern","default":"true","recommended":"true (non modifiable en 5.0+)","unit":"true/false","dynamic":"restart","desc":"Permet d'utiliser le niveau de read concern majority ; ce niveau garantit la lecture de données écrites sur une majorité de membres et qui ne seront pas annulées par rollback. Depuis MongoDB 5.0, ce réglage n'est plus modifiable et vaut toujours true ; dans les versions antérieures (par ex. dans une architecture PSA), il pouvait être mis à false. Défini dans le fichier de configuration et nécessite un redémarrage.","tags":["replication","read concern","replica set"]},{"name":"processManagement.fork","default":"false","recommended":"false avec systemd, true pour un démon manuel","unit":"true/false","dynamic":"restart","desc":"Fait que le processus mongod se duplique (fork) et s'exécute en arrière-plan comme démon. Avec systemd ou un gestionnaire de services similaire, on laisse généralement false, car le gestionnaire de services prend en charge la gestion du processus. Défini dans le fichier de configuration et influe sur le comportement au démarrage.","tags":["process","process","daemon","fork"]}]},"reference/params-redis.json":{"engineId":"redis","engine":"Redis","order":6,"params":[{"name":"maxmemory","default":"0 (unlimited)","recommended":"~70-75 % de la RAM physique","unit":"bytes","dynamic":"dynamic","desc":"Définit la quantité maximale de mémoire que Redis peut utiliser pour les données. Une valeur de 0 signifie une utilisation illimitée et peut conduire à un manque de mémoire ; il est fortement recommandé de fixer une limite. Modifiable immédiatement avec CONFIG SET maxmemory.","tags":["memory","memory"]},{"name":"maxmemory-policy","default":"noeviction","recommended":"allkeys-lru pour un cache, sinon noeviction","unit":"policy","dynamic":"dynamic","desc":"Politique qui détermine quelles clés Redis évince, et comment, lorsque la limite maxmemory est atteinte. noeviction renvoie des erreurs d'écriture, tandis que allkeys-lru supprime les clés les moins récemment utilisées. Modifiable immédiatement avec CONFIG SET.","tags":["memory","memory","eviction","cache"]},{"name":"maxclients","default":"10000","recommended":"Selon la charge de travail et l'ulimit de l'OS","unit":"count","dynamic":"dynamic","desc":"Définit le nombre maximal de connexions client simultanées acceptées. La limite de descripteurs de fichiers du système d'exploitation peut plafonner cette valeur. Modifiable immédiatement avec CONFIG SET maxclients.","tags":["connections","connection","clients"]},{"name":"appendonly","default":"no","recommended":"yes si la persistance est requise","unit":"yes/no","dynamic":"dynamic","desc":"Active le mécanisme de persistance AOF (Append Only File) ; une fois activé, chaque opération d'écriture est ajoutée à un fichier journal. Présente un risque de perte de données moindre que les snapshots RDB. Activable immédiatement avec CONFIG SET appendonly yes.","tags":["persistence","persistence","aof"]},{"name":"appendfsync","default":"everysec","recommended":"everysec (équilibré)","unit":"policy","dynamic":"dynamic","desc":"Définit la fréquence de synchronisation du fichier AOF sur disque (fsync). always synchronise à chaque écriture, c'est le plus sûr mais lent ; everysec synchronise une fois par seconde, en équilibrant performances et durabilité ; no laisse la synchronisation au système d'exploitation. Modifiable immédiatement avec CONFIG SET.","tags":["persistence","persistence","aof","fsync"]},{"name":"save","default":"3600 1 300 100 60 10000","recommended":"Selon la charge de travail, ou désactivé (AOF uniquement)","unit":"rule","dynamic":"dynamic","desc":"Définit les conditions (un nombre donné de modifications dans un délai donné) dans lesquelles un snapshot RDB est enregistré sur disque. Une valeur vide désactive complètement les snapshots RDB. Modifiable immédiatement avec CONFIG SET save.","tags":["persistence","persistence","rdb","snapshot"]},{"name":"tcp-keepalive","default":"300","recommended":"300 (la valeur par défaut convient)","unit":"seconds","dynamic":"dynamic","desc":"Définit, en secondes, l'intervalle auquel Redis envoie des ACK TCP keepalive aux connexions client. Garantit la détection et la fermeture des connexions mortes et le nettoyage des connexions semi-ouvertes. Modifiable immédiatement avec CONFIG SET.","tags":["network","network","tcp","keepalive"]},{"name":"timeout","default":"0","recommended":"0 (ne jamais fermer les connexions) ou un délai d'inactivité raisonnable","unit":"seconds","dynamic":"dynamic","desc":"Définit, en secondes, la durée maximale d'inactivité d'une connexion client ; la connexion est fermée au-delà. Une valeur de 0 signifie que les clients peuvent rester inactifs indéfiniment. Modifiable immédiatement avec CONFIG SET timeout.","tags":["connections","connection","timeout","idle"]},{"name":"hz","default":"10","recommended":"10 (jusqu'à 100 sur les systèmes chargés)","unit":"Hz","dynamic":"dynamic","desc":"Définit combien de fois par seconde Redis exécute ses tâches d'arrière-plan (expiration des clés, vérification des timeouts client, etc.). Des valeurs plus élevées offrent un contrôle plus précis mais augmentent l'utilisation CPU. Modifiable immédiatement avec CONFIG SET hz.","tags":["performance","cpu","background"]},{"name":"activedefrag","default":"no","recommended":"yes si la fragmentation mémoire est élevée","unit":"yes/no","dynamic":"dynamic","desc":"Active la défragmentation mémoire active ; réorganise les données en cours de fonctionnement pour réduire la fragmentation mémoire. Utile sur les instances de longue durée fortement fragmentées. Activable immédiatement avec CONFIG SET activedefrag yes.","tags":["memory","memory","defragmentation"]},{"name":"maxmemory-samples","default":"5","recommended":"5 (peut être relevé pour une éviction plus précise)","unit":"count","dynamic":"dynamic","desc":"Définit le nombre de clés échantillonnées par les algorithmes d'éviction LRU et LFU lors du choix d'une clé. Des valeurs plus élevées se rapprochent du véritable comportement LRU/LFU mais consomment un peu plus de CPU. Modifiable immédiatement avec CONFIG SET.","tags":["memory","memory","eviction","lru"]},{"name":"io-threads","default":"1","recommended":"2-4 sur les serveurs multicœurs (selon le nombre de cœurs)","unit":"count","dynamic":"restart","desc":"Définit le nombre de threads d'E/S utilisés pour écrire sur les sockets client et y lire. Peut augmenter le débit sur les systèmes multicœurs à fort trafic réseau. Ce réglage ne se modifie que dans le fichier de configuration et nécessite un redémarrage.","tags":["performance","threads","io","cpu"]},{"name":"repl-backlog-size","default":"1mb","recommended":"Plus grand, selon le débit d'écriture et la durée des coupures","unit":"bytes","dynamic":"dynamic","desc":"Définit la taille du tampon de réplication conservé sur le primaire pour la resynchronisation partielle en cas de perte de connexion d'un réplica. Un tampon suffisamment grand permet au réplica de rattraper son retard après de courtes coupures sans synchronisation complète. Modifiable immédiatement avec CONFIG SET.","tags":["replication","replication","backlog"]},{"name":"repl-diskless-sync","default":"yes","recommended":"yes (sur les réseaux rapides)","unit":"yes/no","dynamic":"dynamic","desc":"Lors d'une synchronisation complète, envoie les données RDB aux réplicas directement par le socket sans les écrire sur disque. Accélère la synchronisation sur les systèmes limités par les E/S disque. Modifiable immédiatement avec CONFIG SET repl-diskless-sync.","tags":["replication","replication","diskless","sync"]},{"name":"requirepass","default":"(empty)","recommended":"Définir un mot de passe fort","unit":"password","dynamic":"dynamic","desc":"Définit le mot de passe avec lequel les clients doivent s'authentifier via AUTH avant d'exécuter des commandes. Un mot de passe fort doit être défini pour empêcher tout accès non autorisé à Redis. Modifiable immédiatement avec CONFIG SET requirepass.","tags":["security","security","authentication","password"]},{"name":"cluster-enabled","default":"no","recommended":"yes si une montée en charge horizontale est requise","unit":"yes/no","dynamic":"restart","desc":"Active le mode Redis Cluster ; une fois activé, les données sont réparties sur plusieurs nœuds via des hash slots. Sert à la montée en charge horizontale et au partitionnement automatique des données. Ce réglage ne se modifie que dans le fichier de configuration et nécessite un redémarrage.","tags":["cluster","cluster","scaling","scaling"]},{"name":"cluster-node-timeout","default":"15000","recommended":"15000 ou une valeur ajustée à la latence réseau","unit":"ms","dynamic":"dynamic","desc":"En mode cluster, définit en millisecondes la durée maximale pendant laquelle un nœud peut rester sans répondre avant d'être considéré comme injoignable (fail). Influe directement sur le comportement du failover et la stabilité du cluster. Modifiable immédiatement avec CONFIG SET.","tags":["cluster","cluster","timeout","failover"]},{"name":"aof-use-rdb-preamble","default":"yes","recommended":"yes (rechargement rapide)","unit":"yes/no","dynamic":"dynamic","desc":"Active le format de persistance hybride dans lequel une réécriture AOF écrit un snapshot RDB compact au début du fichier, suivi des commandes AOF incrémentales. Raccourcit le temps de chargement de l'AOF au redémarrage. Modifiable immédiatement avec CONFIG SET.","tags":["persistence","persistence","aof","rdb"]}]},"reference/params-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"params":[{"name":"-Xms / -Xmx (heap JVM)","default":"Automatic depending on version (half of RAM)","recommended":"50 % de la RAM physique, au plus ~30-31GB (Xms=Xmx)","unit":"GB","dynamic":"restart","desc":"Définit la taille initiale (-Xms) et maximale (-Xmx) du heap JVM ; les deux doivent être égales. Il est recommandé de ne pas dépasser la moitié de la RAM totale ni la limite de ~32GB au-delà de laquelle les compressed object pointers ne s'appliquent plus. Défini dans jvm.options et nécessite un redémarrage du nœud.","tags":["memory","memory","jvm","heap"]},{"name":"indices.memory.index_buffer_size","default":"10%","recommended":"10 % (peut être augmenté sous forte charge d'indexation)","unit":"% / bytes","dynamic":"restart","desc":"Définit la part du heap allouée au tampon mémoire qui contient les nouveaux documents pendant l'indexation. L'augmenter peut améliorer le débit d'indexation pour les charges à forte écriture. Défini par nœud dans elasticsearch.yml et nécessite un redémarrage.","tags":["memory","memory","indexing","buffer"]},{"name":"thread_pool.write.queue_size","default":"10000","recommended":"10000 (augmenter prudemment en cas de rejets de file)","unit":"count","dynamic":"restart","desc":"Définit la longueur de la file du thread pool pour les opérations d'écriture index, delete et bulk. Lorsque la file est pleine, les requêtes sont rejetées. Une valeur trop élevée peut provoquer une pression mémoire. Défini dans elasticsearch.yml et nécessite un redémarrage.","tags":["thread pool","write","queue","queue"]},{"name":"thread_pool.search.queue_size","default":"1000","recommended":"1000 (augmenter prudemment en cas de rejets de recherche)","unit":"count","dynamic":"restart","desc":"Définit la longueur de la file du thread pool pour les opérations de recherche. Lorsque les recherches simultanées dépassent la capacité du pool, les requêtes attendent dans cette file et sont rejetées si elle se remplit. Défini dans elasticsearch.yml et nécessite un redémarrage.","tags":["thread pool","search","search","queue"]},{"name":"indices.fielddata.cache.size","default":"unbounded","recommended":"Une limite de 20-40 % par exemple (protection mémoire)","unit":"% / bytes","dynamic":"restart","desc":"Définit la taille maximale du cache fielddata utilisé pour les tris et agrégations sur les champs text. La valeur par défaut étant illimitée, il est recommandé de fixer une limite pour éviter l'épuisement de la mémoire. Défini dans elasticsearch.yml et nécessite un redémarrage.","tags":["memory","memory","fielddata","cache"]},{"name":"index.number_of_shards","default":"1","recommended":"Selon le volume de données ; viser 10-50GB par shard","unit":"count","dynamic":"restart","desc":"Définit le nombre de shards primaires entre lesquels un index est découpé. C'est un réglage d'index statique (pas un redémarrage, mais fourni uniquement à la création de l'index) : il ne peut pas être modifié directement sur un index existant ; pour le changer, produisez un nouvel index avec un nouvel index + _reindex, ou avec les API _split (multiplier) / _shrink (réduire à un diviseur). Trop de shards augmentent la charge du cluster, trop peu limitent la montée en charge.","tags":["index","index","shard","scaling"]},{"name":"index.number_of_replicas","default":"1","recommended":"Au moins 1 en production (pour la haute disponibilité)","unit":"count","dynamic":"dynamic","desc":"Définit le nombre de shards réplicas conservés pour chaque shard primaire. Les réplicas assurent à la fois la haute disponibilité et les performances de lecture. Ce réglage peut être modifié instantanément sur un index actif avec l'API _settings.","tags":["index","index","replica","availability"]},{"name":"index.refresh_interval","default":"1s","recommended":"30s ou -1 (temporairement) sous forte indexation","unit":"duration","dynamic":"dynamic","desc":"Définit la fréquence de l'opération refresh qui rend les documents nouvellement indexés visibles à la recherche. Augmenter la valeur, ou la mettre à -1 pendant les chargements massifs, améliore nettement le débit d'indexation. Modifiable instantanément avec l'API _settings.","tags":["index","index","refresh","indexing"]},{"name":"indices.breaker.total.limit","default":"95%","recommended":"95 % (par défaut ; modifier avec prudence)","unit":"%","dynamic":"dynamic","desc":"Empêche le nœud de planter en OutOfMemory en fixant la part maximale du heap utilisable par l'ensemble des circuit breakers. Modifiable instantanément à l'échelle du cluster avec l'API _cluster/settings.","tags":["memory","memory","circuit breaker","stability"]},{"name":"cluster.routing.allocation.disk.watermark.low","default":"85%","recommended":"85 % (la valeur par défaut convient)","unit":"% / bytes","dynamic":"dynamic","desc":"Lorsque l'utilisation disque d'un nœud dépasse ce seuil, Elasticsearch cesse d'allouer de nouveaux shards à ce nœud (les shards existants restent). C'est le premier niveau d'alerte pour éviter un disque plein. Modifiable instantanément avec l'API _cluster/settings.","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.high","default":"90%","recommended":"90 % (la valeur par défaut convient)","unit":"% / bytes","dynamic":"dynamic","desc":"Lorsque l'utilisation disque d'un nœud dépasse ce seuil, Elasticsearch commence à déplacer des shards de ce nœud vers d'autres nœuds. Soulage les nœuds dont les disques se remplissent. Modifiable instantanément avec l'API _cluster/settings.","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.flood_stage","default":"95%","recommended":"95 % (la valeur par défaut convient)","unit":"% / bytes","dynamic":"dynamic","desc":"Lorsque l'utilisation disque dépasse ce seuil critique, les index ayant un shard sur le nœud concerné passent en mode lecture seule (read-only-allow-delete) et les écritures sont bloquées. Évite la corruption de données avant que le disque soit complètement plein ; en 7.4+, le blocage est levé automatiquement une fois l'utilisation disque repassée sous le high watermark. En 8.x, sur les gros disques, c'est max_headroom (100GB libres par défaut) qui est déterminant plutôt que le pourcentage. Modifiable instantanément avec l'API _cluster/settings.","tags":["disk","allocation","watermark","read-only"]},{"name":"index.translog.durability","default":"request","recommended":"request (durabilité) ou async (performance)","unit":"mode","dynamic":"dynamic","desc":"Définit quand le journal de transactions (translog) est synchronisé (fsync) sur disque. request synchronise après chaque requête et minimise la perte de données ; async synchronise périodiquement et améliore les performances, mais comporte un risque de perte de données en cas de crash. Modifiable instantanément avec l'API _settings.","tags":["translog","durability","durability","indexing"]},{"name":"indices.recovery.max_bytes_per_sec","default":"40mb","recommended":"Peut être augmenté selon la capacité réseau et disque","unit":"bytes/s","dynamic":"dynamic","desc":"Définit le débit maximal de transfert de données par nœud pendant la récupération des shards. Augmenter la valeur accélère la récupération mais peut consommer les ressources réseau et disque de la charge normale. Modifiable instantanément avec l'API _cluster/settings.","tags":["recovery","recovery","throttle","network"]},{"name":"action.destructive_requires_name","default":"true","recommended":"true (bloque les opérations destructrices avec wildcard)","unit":"true/false","dynamic":"dynamic","desc":"Bloque les opérations destructrices telles que la suppression d'index avec un wildcard (*) ou _all, et impose d'indiquer explicitement le nom de l'index. Réglage de sécurité important pour éviter la suppression accidentelle de tous les index. Modifiable instantanément avec l'API _cluster/settings.","tags":["security","security","destructive","safety"]},{"name":"discovery.seed_hosts","default":"(empty / 127.0.0.1)","recommended":"Adresses des nœuds master-eligible du cluster","unit":"list","dynamic":"restart","desc":"Définit la liste des adresses de seed hosts (nœuds master-eligible) qu'un nœud contacte pour rejoindre le cluster. Nécessaire à la découverte du cluster et pour que les nœuds se trouvent. Défini dans elasticsearch.yml et nécessite un redémarrage.","tags":["discovery","cluster","cluster","network"]}]},"reference/waits-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"waits":[{"name":"log file sync","kind":"wait","meaning":"La session a émis un COMMIT (ou ROLLBACK) et attend que le processus LGWR écrive les enregistrements redo dans les fichiers redo log en ligne et en accuse réception.","causes":"E/S redo log lentes, commits petits et très fréquents, processus LGWR surchargé, LGWR retardé par un manque de CPU, redo logs sur des disques lents.","action":"Réduisez la fréquence des commits (commits par lots), placez les fichiers redo log sur des disques rapides/à faible latence (SSD), supprimez les commits inutiles, n'envisagez COMMIT NOWAIT/BATCH que pour les traitements où une perte est tolérable (ATTENTION : cela réduit la durabilité ; des transactions apparemment validées peuvent être perdues lors d'un crash), et mesurez la latence d'E/S du log writer (log file parallel write).","tags":["commit","redo","io","lgwr"]},{"name":"db file sequential read","kind":"wait","meaning":"La session effectue une lecture physique mono-bloc ; elle attend que le bloc soit amené du disque dans le buffer cache, généralement lors d'un accès par index ou de la lecture d'un bloc de table par ROWID.","causes":"Parcours d'index, plans d'accès inefficaces, buffer cache trop petit, sous-système disque lent, blocs absents du cache en raison de lectures physiques élevées.","action":"Optimisez le SQL pour réduire les lectures inutiles, ajoutez des index adaptés, augmentez la taille du buffer cache, améliorez le sous-système d'E/S et identifiez les requêtes les plus consommatrices en lecture avec AWR/ASH.","tags":["io","index","single-block","read"]},{"name":"db file scattered read","kind":"wait","meaning":"La session effectue une lecture physique multi-blocs ; elle attend la lecture de plusieurs blocs depuis le disque, généralement lors d'un full table scan ou d'un fast full index scan.","causes":"Full table scans, index absents ou inutilisés, plans de requête inefficaces, parcours de grandes tables, statistiques de l'optimiseur incorrectes.","action":"Évitez les full scans inutiles en ajoutant des index, examinez les plans de requête, rafraîchissez les statistiques, vérifiez le réglage db_file_multiblock_read_count et envisagez de paralléliser les full scans réellement nécessaires.","tags":["io","full-scan","multi-block","read"]},{"name":"direct path read","kind":"wait","meaning":"La session lit les blocs directement dans la PGA en contournant le buffer cache ; généralement observé lors de requêtes parallèles, de parcours de grandes tables ou de lectures de LOB.","causes":"Full table scans parallèles, décision de direct path read en série (adaptive direct read), parcours de grandes tables, lectures du tablespace temporaire (temp), accès aux LOB.","action":"Réduisez le besoin de grands parcours par l'optimisation des requêtes, évaluez la taille de la PGA, optimisez les opérations de tri/hachage si l'utilisation du temp est élevée et augmentez la bande passante d'E/S.","tags":["io","direct-path","parallel","read"]},{"name":"direct path write","kind":"wait","meaning":"La session écrit les blocs directement sur disque sans passer par le buffer cache ; généralement observé lors d'insertions direct-path, de débordements de tri ou d'écritures de LOB.","causes":"Opérations INSERT direct-path (APPEND), débordements de tri/hachage vers le tablespace temporaire, DML parallèle, écritures de LOB, disques temp ou de données lents.","action":"Augmentez la PGA pour les opérations de tri/hachage afin de réduire les débordements vers le temp, placez le tablespace temp sur des disques rapides, examinez les chargements direct-path inutiles et améliorez les performances d'E/S.","tags":["io","direct-path","temp","write"]},{"name":"buffer busy waits","kind":"wait","meaning":"La session veut accéder à un bloc du buffer cache, mais ce bloc est utilisé par une autre session dans un mode incompatible ; elle attend donc sa libération.","causes":"Accès concurrents intensifs au même bloc (hot block), freelists/ASSM insuffisants, contention sur le bloc de droite d'un index séquentiel (right-hand index growth), segments à forte intensité DML.","action":"Répartissez les hot blocks (partitionnement par hachage, reverse key index), utilisez ASSM, augmentez les freelists/freelist groups et améliorez le cache et la répartition des séquences sur les tables générant des clés séquentielles.","tags":["concurrency","buffer","hot-block","contention"]},{"name":"read by other session","kind":"wait","meaning":"Une autre session est déjà en train de lire depuis le disque le bloc dont cette session a besoin ; cette session attend la fin de cette lecture du même bloc.","causes":"Demandes de lecture physique simultanées sur les mêmes hot blocks, buffer cache trop petit, E/S lentes, nombreuses sessions accédant en même temps à des blocs d'index ou de table populaires.","action":"Augmentez le buffer cache pour réduire les lectures physiques, identifiez les segments chauds et améliorez leurs plans d'accès, réduisez la latence d'E/S et éliminez les full scans inutiles.","tags":["io","concurrency","read","hot-block"]},{"name":"latch: cache buffers chains","kind":"wait","meaning":"La session attend d'acquérir le latch qui protège une hash chain du buffer cache ; une sérialisation se produit lors de la localisation des blocs dans le buffer cache.","causes":"SQL inefficace générant des E/S logiques excessives, accès concurrents intensifs aux hot blocks, accès répétés aux mêmes blocs via des nested loops, blocs racine/branche d'index très sollicités.","action":"Optimisez le SQL pour réduire les lectures logiques, éliminez les accès nested loop inutiles, répartissez les hot blocks, et trouvez et corrigez avec AWR/ASH les requêtes produisant des buffer gets excessifs.","tags":["latch","concurrency","logical-io","buffer"]},{"name":"cursor: pin S wait on X","kind":"wait","meaning":"La session veut épingler un curseur avec un mutex en mode partagé (S), mais attend car une autre session détient ce même curseur en mode exclusif (X).","causes":"Taux de hard/soft parse élevé, SQL populaire parsé très fréquemment, requêtes avec littéraux n'utilisant pas de variables de liaison, prolifération de child cursors, nombreuses sessions parsant le même SQL simultanément.","action":"Réduisez la charge de parsing en utilisant des variables de liaison, mettez les curseurs en cache dans l'application (session_cached_cursors), réduisez les re-parsings inutiles et corrigez le SQL à version count élevé.","tags":["mutex","parse","cursor","concurrency"]},{"name":"library cache lock","kind":"wait","meaning":"La session attend d'acquérir un verrou sur le handle d'un objet du library cache (package, vue, définition de table) ; généralement observé lors d'accès/modifications concurrents de la définition de l'objet.","causes":"DDL sur des objets en cours d'utilisation, packages/vues recompilés en permanence, objets invalides, connexions et parsing concurrents intensifs, modifications de privilèges (grant).","action":"Exécutez les DDL en heures creuses, évitez les compilations inutiles, corrigez les objets invalides et réduisez les boucles excessives de connexion/parsing côté application.","tags":["library-cache","concurrency","ddl","parse"]},{"name":"library cache: mutex X","kind":"wait","meaning":"La session attend d'acquérir un mutex pour protéger un objet du library cache en mode exclusif (X) ; généralement observé en cas de parsing intensif et de contention sur les handles d'objets.","causes":"Parsing excessif (hard/soft), grande variété de SQL avec littéraux sans binds, nombre élevé de child cursors, invalidations d'objets fréquentes, nombreuses sessions accédant simultanément au même objet.","action":"Utilisez des variables de liaison et le cache de curseurs pour réduire le parsing, standardisez les littéraux SQL, corrigez les problèmes de version count élevé et améliorez le comportement de connexion de l'application.","tags":["mutex","library-cache","parse","concurrency"]},{"name":"enq: TX - row lock contention","kind":"wait","meaning":"La session veut modifier une ligne verrouillée par une autre transaction et attend que cette transaction fasse commit/rollback et libère le verrou de ligne.","causes":"UPDATE/DELETE concurrents des mêmes lignes, transactions longues qui ne font pas de commit, workflow applicatif mal conçu, verrous de ligne indirects via des clés uniques/étrangères, sessions bloquées.","action":"Gardez les transactions courtes et validez rapidement, repensez le schéma d'accès en contention, imposez un accès ordonné avec SELECT FOR UPDATE si nécessaire, et trouvez et résolvez la session bloquante avec ASH.","tags":["lock","transaction","row-lock","contention"]},{"name":"enq: TX - allocate ITL entry","kind":"wait","meaning":"La session a besoin d'un slot ITL (Interested Transaction List) dans un bloc pour y travailler, mais doit attendre car il ne reste aucun slot ITL libre dans le bloc.","causes":"Réglage INITRANS faible, petite taille de bloc, nombreuses transactions simultanées dans le même bloc, blocs densément remplis subissant un DML intensif.","action":"Augmentez INITRANS pour la table/l'index, relevez PCTFREE pour placer moins de lignes par bloc, répartissez les hot blocks et reconstruisez l'objet si nécessaire.","tags":["lock","transaction","itl","block"]},{"name":"gc buffer busy acquire","kind":"wait","meaning":"Dans un environnement RAC, la session veut obtenir un bloc via le global cache, mais attend car une autre session de la même instance a déjà lancé une demande globale pour ce même bloc.","causes":"Partage de hot blocks entre instances en RAC, performances d'interconnect insuffisantes, contention à droite sur un index séquentiel, charge mal partitionnée, trafic global cache intensif.","action":"Partitionnez judicieusement la charge entre les instances (affinité applicative/services), répartissez les segments chauds (reverse key/partition), améliorez la latence de l'interconnect et analysez la contention gc avec la section RAC d'AWR et les vues GV$.","tags":["rac","global-cache","concurrency","interconnect"]},{"name":"gc cr block busy","kind":"wait","meaning":"Dans un environnement RAC, la session a demandé à une autre instance un bloc en lecture cohérente (CR) et attend que cette instance prépare le bloc et l'envoie via l'interconnect.","causes":"Partage de hot blocks entre instances, interconnect lent ou saturé, trafic redo/commit intensif, charge mal partitionnée, demandes CR global cache excessives.","action":"Améliorez l'affinité de la charge, optimisez la bande passante et la latence de l'interconnect, réduisez les hot blocks, examinez les schémas de commit et de DML, et optimisez RAC.","tags":["rac","global-cache","cr","interconnect"]},{"name":"log file switch (checkpoint incomplete)","kind":"wait","meaning":"La session veut basculer vers un nouveau redo log, mais le checkpoint du fichier de log suivant n'est pas encore terminé ; LGWR ne peut donc pas réutiliser ce log et toute génération de redo s'arrête.","causes":"Fichiers redo log trop petits ou trop peu nombreux, E/S DBWR/checkpoint lentes, débit de génération de redo élevé, nombre insuffisant de groupes de log.","action":"Agrandissez les fichiers redo log et ajoutez des groupes, améliorez les performances d'E/S DBWR/checkpoint, apportez des optimisations applicatives réduisant la génération de redo et surveillez la fréquence des log switches.","tags":["redo","checkpoint","log-switch","io"]},{"name":"free buffer waits","kind":"wait","meaning":"La session cherche un buffer propre/libre dans le buffer cache pour lire un nouveau bloc, mais attend un buffer libre car DBWR n'écrit pas assez vite les dirty buffers sur disque.","causes":"E/S d'écriture DBWR lentes, buffer cache trop petit, génération rapide de dirty buffers par un DML intensif, vitesse de checkpoint insuffisante, sous-système disque saturé.","action":"Accélérez les E/S d'écriture des datafiles, augmentez la taille du buffer cache, évaluez le nombre de processus DBWR (db_writer_processes) et examinez l'intensité DML et les réglages de checkpoint.","tags":["buffer","dbwr","io","checkpoint"]},{"name":"Buffer Cache Hit Ratio","kind":"metric","meaning":"Proportion des blocs demandés servis depuis le buffer cache sans accès disque ; indique quel pourcentage des lectures logiques n'a pas nécessité de lecture physique.","causes":"Un ratio faible est généralement dû à un buffer cache trop petit, à des full scans inefficaces, à un cache froid ou à des lectures physiques élevées. Pris isolément, il peut être trompeur ; un ratio élevé peut aussi masquer un mauvais SQL.","action":"Ne visez pas le ratio en lui-même ; évaluez-le avec les événements d'attente et les tendances des lectures physiques. Optimisez le SQL inefficace, réduisez les lectures logiques inutiles et dimensionnez le cache selon la charge.","tags":["metric","buffer-cache","ratio","memory"]},{"name":"Average Active Sessions","kind":"metric","meaning":"Nombre moyen de sessions actives simultanément (en exécution sur CPU ou en attente d'un événement) sur un intervalle ; résume la charge globale et l'intensité des goulets d'étranglement de la base.","causes":"Un AAS élevé peut être dû à une saturation CPU, à des attentes d'E/S, à une contention de verrous/latches ou à une hausse de la concurrence. Des valeurs proches ou supérieures au nombre de cœurs CPU signalent un goulet d'étranglement de ressources.","action":"Comparez l'AAS au nombre de cœurs CPU, trouvez la wait class dominante (CPU/IO/concurrency) avec ASH, optimisez le SQL et les sessions générant le plus de charge, et résolvez le problème de ressources ou de concurrence.","tags":["metric","load","ash","sessions"]}]},"reference/waits-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"waits":[{"name":"LWLock:WALWrite","kind":"wait","meaning":"La session attend le verrou léger nécessaire à l'écriture des buffers WAL sur disque ; généralement causé par la sérialisation des écritures WAL lors du commit.","causes":"E/S disque WAL lentes, commits petits/très fréquents, charge d'écriture élevée, bande passante d'écriture WAL insuffisante, nombreux backends écrivant du WAL en même temps.","action":"Placez le WAL sur un disque rapide/à faible latence, regroupez les commits (évaluez synchronous_commit, commit_delay), augmentez wal_buffers, réduisez les opérations d'écriture inutiles.","tags":["wal","lwlock","commit","io"]},{"name":"LWLock:BufferContent","kind":"wait","meaning":"La session attend le verrou léger d'un buffer du shared buffer pool qui protège le contenu de la page ; indique une contention due à des accès concurrents à la page.","causes":"Lectures/écritures concurrentes intensives sur la même page (hot page), contention sur le bord droit des index séquentiels, petites tables subissant de nombreuses mises à jour, concurrence élevée.","action":"Répartissez les pages chaudes (réglage fillfactor, partitionnement), réduisez la contention sur les clés séquentielles, restructurez les lignes fortement mises à jour, examinez le schéma d'accès concurrent.","tags":["lwlock","buffer","concurrency","contention"]},{"name":"Lock:transactionid","kind":"wait","meaning":"La session attend qu'une autre transaction, qui a verrouillé la ligne qu'elle veut modifier, se termine (commit/rollback) ; l'attente de verrou porte sur l'ID de la transaction cible.","causes":"UPDATE/DELETE concurrents sur les mêmes lignes, transactions longues qui ne font pas de commit, sessions idle-in-transaction, workflow en contention mal conçu.","action":"Gardez les transactions courtes et validez rapidement, limitez les sessions idle-in-transaction (idle_in_transaction_session_timeout), repensez le schéma d'accès en contention, trouvez la requête bloquante avec pg_stat_activity.","tags":["lock","transaction","row-lock","contention"]},{"name":"Lock:tuple","kind":"wait","meaning":"La session attend d'acquérir un verrou sur un tuple (ligne) précis ; observé lorsque plusieurs sessions font la queue pour mettre à jour/verrouiller la même ligne.","causes":"SELECT FOR UPDATE/UPDATE concurrents sur la même ligne, contention sur une hot row (par ex. lignes de compteur), verrous de ligne conservés longtemps, concurrence élevée.","action":"Répartissez ou mettez en file les mises à jour de hot rows, raccourcissez la durée des transactions, repensez les schémas en contention comme les compteurs (par ex. mises à jour par lots), envisagez FOR UPDATE SKIP LOCKED.","tags":["lock","tuple","row-lock","contention"]},{"name":"Lock:relation","kind":"wait","meaning":"La session attend d'acquérir un verrou de niveau table sur une table/un index (relation) ; généralement observé en cas de contention entre des modes de verrou incompatibles de DDL et de DML.","causes":"DDL telles que ALTER/DROP/TRUNCATE pendant l'exécution de requêtes, VACUUM FULL nécessitant un verrou agressif, accès concurrents à la même table dans des modes incompatibles, requêtes longues.","action":"Exécutez les DDL en période calme avec un lock_timeout court, utilisez l'autovacuum régulier plutôt que VACUUM FULL, surveillez les requêtes longues, examinez les opérations qui sérialisent l'accès à la table.","tags":["lock","relation","ddl","table-lock"]},{"name":"IO:DataFileRead","kind":"wait","meaning":"La session attend la lecture d'une page de fichier de données depuis le disque vers les shared buffers ; c'est l'attente de lecture physique qui survient lorsque la page demandée n'est pas en cache.","causes":"shared_buffers trop petit, plans de requête inefficaces et grands parcours, cache froid, sous-système disque lent, utilisation insuffisante des index.","action":"Ajustez shared_buffers et le page cache de l'OS à la charge, corrigez les requêtes inefficaces et les index manquants, améliorez les performances d'E/S, définissez correctement effective_cache_size.","tags":["io","read","buffer","disk"]},{"name":"IO:WALSync","kind":"wait","meaning":"La session attend que le WAL soit écrit durablement sur disque (fsync/synchronisation) ; pour la durabilité du commit, elle attend le vidage physique du WAL.","causes":"Latence fsync du stockage élevée, commits très fréquents, disque WAL peu performant, disque sans cache write-back, charge d'écriture élevée.","action":"Placez le disque WAL sur un stockage rapide à faible latence fsync, regroupez les commits (commit_delay/commit_siblings), évaluez synchronous_commit au regard des exigences métier, améliorez la couche de stockage.","tags":["wal","io","fsync","commit"]},{"name":"IO:DataFileWrite","kind":"wait","meaning":"La session attend l'écriture d'une page de données modifiée sur disque ; généralement observé lorsque le backend écrit lui-même pour trouver une page propre dans le buffer pool.","causes":"E/S d'écriture lentes sur le disque de données, engorgement des checkpoints, background writer insuffisant, buffer pool trop petit, backend écrivant lui-même en raison d'une charge d'écriture élevée.","action":"Lissez les checkpoints (checkpoint_completion_target, max_wal_size), réglez le background writer (bgwriter_lru_maxpages), améliorez les performances d'écriture disque, revoyez shared_buffers.","tags":["io","write","checkpoint","bgwriter"]},{"name":"Client:ClientRead","kind":"wait","meaning":"Le backend attend la prochaine commande/les prochaines données du client ; observé lorsque le serveur est inactif et attend que le client envoie des données.","causes":"Réseau lent, sessions idle-in-transaction, application cliente envoyant/recevant les données lentement, grands jeux de résultats consommés lentement, problèmes de pool de connexions.","action":"Examinez la latence côté application et réseau, limitez les sessions idle-in-transaction, consommez les résultats efficacement (par ex. curseur/lots), améliorez le pool de connexions. Cette attente n'est généralement pas un goulet d'étranglement de la base.","tags":["client","network","idle","application"]},{"name":"IPC:ProcArrayGroupUpdate","kind":"wait","meaning":"La session attend que le backend leader du groupe marque en groupe la fin de sa transaction dans le ProcArray ; il s'agit de la sérialisation des mises à jour du ProcArray lors du commit.","causes":"Taux de commits/transactions très élevé, excès de transactions courtes simultanées, nombre de connexions élevé, accès intensifs aux snapshots/au ProcArray.","action":"Maîtrisez le taux de transactions et le nombre de connexions simultanées (pool de connexions), réduisez les transactions courtes inutiles, limitez le nombre de connexions en cas de concurrence très élevée, équilibrez la charge.","tags":["ipc","commit","procarray","concurrency"]},{"name":"LWLock:LockManager","kind":"wait","meaning":"La session attend le verrou léger du lock manager qui protège la table des verrous lourds ; causé par la sérialisation des opérations d'acquisition/libération de verrous.","causes":"Requêtes touchant de nombreuses tables/partitions (dépassement de la limite des verrous fast-path), concurrence élevée, tables sur-partitionnées, charge acquérant/libérant de nombreux verrous.","action":"Réduisez le nombre de partitions/tables touchées par requête (partition pruning), revoyez le sur-partitionnement, maîtrisez la concurrence avec un pool de connexions, simplifiez les opérations prenant des verrous inutiles.","tags":["lwlock","lock-manager","concurrency","partitioning"]},{"name":"Timeout:VacuumDelay","kind":"wait","meaning":"Le processus vacuum/autovacuum est volontairement mis en pause en raison du cost-based vacuum delay ; attente intentionnelle pour limiter la charge d'E/S.","causes":"Bridage dû aux réglages vacuum_cost_delay/autovacuum_vacuum_cost_delay, cost_limit faible, vacuum nettoyant de nombreuses tables, réglages d'autovacuum peu agressifs.","action":"Si le vacuum est trop lent et que le bloat/les dead tuples s'accumulent, augmentez cost_limit et réduisez cost_delay, ajustez le nombre de workers autovacuum et les seuils, en équilibrant avec la capacité d'E/S.","tags":["vacuum","timeout","throttle","maintenance"]},{"name":"taux de succès du cache","kind":"metric","meaning":"Proportion des demandes de blocs servies depuis les shared buffers sans accès disque ; calculée à partir de blks_hit et blks_read dans pg_stat_database.","causes":"Un ratio faible indique généralement des shared_buffers trop petits, des grands parcours inefficaces, un cache froid ou une mémoire insuffisante. Le page cache de l'OS servant aussi des lectures, le ratio seul peut être trompeur.","action":"Interprétez le ratio avec les événements d'attente et les tendances d'E/S, ajustez shared_buffers et effective_cache_size, corrigez les requêtes inefficaces ; s'il reste durablement faible, examinez la mémoire et les plans de requête.","tags":["metric","cache","ratio","memory"]},{"name":"retard de réplication","kind":"metric","meaning":"Retard d'un serveur standby/réplica dans l'application du WAL produit par le primaire ; mesuré en octets (différence de LSN) ou en secondes de délai.","causes":"Réseau lent, forte charge de requêtes sur le standby (notamment conflits de replay), E/S disque lentes sur le standby, débit d'écriture élevé sur le primaire, longues requêtes de lecture retardant la récupération.","action":"Surveillez le write/flush/replay lag avec pg_stat_replication, améliorez les performances réseau et d'E/S du standby, gérez les longues requêtes en conflit (hot_standby_feedback/max_standby_streaming_delay), réévaluez le besoin de réplication synchrone.","tags":["metric","replication","lag","standby"]}]},"reference/waits-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"waits":[{"name":"wait/synch/mutex/innodb/buf_pool_LRU_list_mutex","kind":"wait","meaning":"Le thread attend le mutex qui protège la liste LRU du buffer pool InnoDB ; indique une sérialisation lors des lectures/évictions de pages. En 8.0, l'ancien buf_pool_mutex unique a été scindé ; les autres instruments associés sont buf_pool_free_list_mutex, buf_pool_flush_state_mutex et buf_pool_zip_mutex.","causes":"Accès intensifs au buffer pool sous forte concurrence, instance de buffer pool unique, lectures/évictions de pages fréquentes, buffer pool sous-dimensionné.","action":"Scindez le mutex en augmentant innodb_buffer_pool_instances, dimensionnez innodb_buffer_pool_size selon le working set, optimisez les requêtes pour réduire les lectures physiques et maîtrisez la concurrence.","tags":["innodb","mutex","buffer-pool","concurrency"]},{"name":"wait/io/file/innodb/innodb_data_file","kind":"wait","meaning":"Le thread attend la fin d'E/S physiques (lecture/écriture) sur les fichiers de données InnoDB (ibd/ibdata).","causes":"Sous-système disque lent, nombreuses lectures physiques dues à un buffer pool trop petit, parcours de grandes tables, pression de checkpoint/flush, capacité d'E/S insuffisante.","action":"Agrandissez le buffer pool, corrigez les requêtes inefficaces et les index manquants, passez à un stockage rapide (SSD/NVMe), réglez innodb_io_capacity selon le stockage et revoyez le comportement de flush.","tags":["innodb","io","datafile","disk"]},{"name":"wait/lock/table/sql/handler","kind":"wait","meaning":"Le thread attend d'acquérir le verrou de handler de niveau table (table lock) dans la couche SQL ; observé lors d'accès concurrents à une table dans des modes incompatibles.","causes":"Opérations nécessitant un verrou de table, moteurs de stockage à verrouillage au niveau table comme MyISAM, instructions LOCK TABLES, contention DDL contre DML, requêtes longues.","action":"Utilisez InnoDB avec le verrouillage au niveau ligne, réduisez l'usage de LOCK TABLES, exécutez les DDL en période de faible activité, optimisez les longues requêtes et revoyez les schémas qui sérialisent l'accès à la table.","tags":["lock","table-lock","sql-layer","contention"]},{"name":"wait/synch/mutex/sql/LOCK_open","kind":"wait","meaning":"Le thread attend le mutex LOCK_open, qui protège les définitions de tables ouvertes/le dictionnaire d'objets ; il s'agit de la sérialisation des opérations d'ouverture/fermeture de tables.","causes":"Accès permanents à de nombreuses tables différentes, table_open_cache/table_definition_cache trop petits, ouvertures et fermetures de tables fréquentes, concurrence élevée, trop de tables/partitions.","action":"Augmentez table_open_cache et table_open_cache_instances, ajustez table_definition_cache, revoyez un nombre excessif de tables/partitions et réduisez les ouvertures de tables inutilement fréquentes.","tags":["mutex","table-cache","sql-layer","concurrency"]},{"name":"attentes de verrous de ligne","kind":"wait","meaning":"Une transaction attend la libération d'un verrou de ligne InnoDB détenu par une autre transaction ; indique une contention de verrous au niveau ligne.","causes":"UPDATE/DELETE concurrents sur les mêmes lignes, transactions longues qui ne font pas de commit, plages de verrouillage étendues (gap locks) dues à des index manquants, contention sur des hot rows, cycles d'interblocage.","action":"Gardez les transactions courtes et validez rapidement, réduisez la plage de lignes verrouillées en ajoutant un index adapté, repensez les schémas en contention, surveillez les interblocages (SHOW ENGINE INNODB STATUS) et revoyez le niveau d'isolation.","tags":["innodb","row-lock","transaction","contention"]},{"name":"attentes sur le log","kind":"wait","meaning":"Le thread attend que de l'espace se libère dans le redo log buffer InnoDB ou que le redo log soit écrit sur disque ; généralement observé sous pression de flush du log buffer/de checkpoint.","causes":"Redo log trop petit (8.0.30+ innodb_redo_log_capacity ; auparavant innodb_log_file_size × innodb_log_files_in_group), disque de log lent, débit d'écriture élevé, flushs fréquents (innodb_flush_log_at_trx_commit=1 avec des commits fréquents), log buffer insuffisant.","action":"Augmentez la capacité redo (8.0.30+ innodb_redo_log_capacity, dynamique ; auparavant innodb_log_file_size, nécessite un redémarrage) et innodb_log_buffer_size, placez le redo log sur un disque rapide, revoyez le schéma de commit et évaluez le réglage de flush au regard des exigences de durabilité.","tags":["innodb","redo","log","io"]},{"name":"wait/io/file/innodb/innodb_log_file","kind":"wait","meaning":"Le thread attend la fin d'E/S physiques (écriture et fsync) sur les fichiers redo log InnoDB ; il attend que le redo atteigne le disque pour la durabilité du commit.","causes":"Latence fsync du stockage élevée, commits très fréquents avec innodb_flush_log_at_trx_commit=1, disque de log peu performant, charge d'écriture élevée.","action":"Placez le disque du redo log sur un stockage rapide à faible latence, regroupez les commits lorsque c'est possible, évaluez innodb_flush_log_at_trx_commit au regard des exigences métier et améliorez la couche de stockage.","tags":["innodb","redo","fsync","commit"]},{"name":"wait/synch/mutex/innodb/log_writer_mutex","kind":"wait","meaning":"Le thread attend le mutex qui protège les structures du thread log writer du redo log InnoDB. Le sous-système redo a été repensé en 8.0 ; à la place de l'ancien log_sys_mutex unique, il existe des instruments distincts tels que log_writer_mutex, log_flusher_mutex et log_checkpointer_mutex.","causes":"Débit d'écriture/de commits très élevé, génération de redo intensive, contention sur les threads log writer/flusher, disque de log lent, log buffer trop petit.","action":"Équilibrez la charge d'écriture et la fréquence des commits, envisagez des traitements par lots réduisant la génération de redo, ajustez le log buffer et tirez parti des améliorations de scalabilité du redo log de votre version de MySQL.","tags":["innodb","mutex","redo","concurrency"]},{"name":"Innodb_buffer_pool_wait_free","kind":"metric","meaning":"Nombre de fois où un thread n'a trouvé aucune page libre dans le buffer pool et a dû attendre le flush de pages modifiées pour en obtenir une ; une hausse continue indique une pression sur le buffer pool.","causes":"Buffer pool trop petit, E/S de flush/d'écriture lentes, innodb_io_capacity faible, génération rapide de dirty pages sous forte charge d'écriture, retard de checkpoint.","action":"Augmentez innodb_buffer_pool_size, réglez innodb_io_capacity/io_capacity_max selon le stockage, améliorez les performances d'E/S en écriture et revoyez le ratio de dirty pages (innodb_max_dirty_pages_pct).","tags":["metric","innodb","buffer-pool","flush"]},{"name":"Threads_running","kind":"metric","meaning":"Nombre de threads non inactifs exécutant activement des requêtes à cet instant ; indique l'exécution concurrente instantanée sur le serveur et l'intensité probable de la contention.","causes":"Pic soudain de requêtes, accumulation de requêtes lentes, threads bloqués par une contention de verrous/mutex, saturation CPU, effet thundering herd.","action":"Lors d'un pic de Threads_running, trouvez les requêtes lentes/bloquées (SHOW PROCESSLIST), optimisez les requêtes, limitez la concurrence avec un pool de connexions et un thread pool, et supprimez la source de contention.","tags":["metric","concurrency","threads","load"]},{"name":"Innodb_row_lock_time_avg","kind":"metric","meaning":"Temps moyen (millisecondes) passé à attendre l'acquisition d'un verrou de ligne ; indique la gravité de la contention des verrous au niveau ligne.","causes":"Accès concurrents intensifs aux mêmes lignes, transactions longues, plages de verrouillage étendues dues à des index manquants, attentes approchant l'interblocage/le lock wait timeout.","action":"Raccourcissez la durée des transactions, réduisez la portée des verrous avec des index adaptés, repensez le schéma d'accès en contention et revoyez innodb_lock_wait_timeout et le niveau d'isolation.","tags":["metric","innodb","row-lock","contention"]},{"name":"history list length","kind":"metric","meaning":"Nombre de transactions du undo log InnoDB pas encore purgées (conservant d'anciennes versions de lignes) ; indique l'accumulation des anciennes versions conservées pour le MVCC.","causes":"Transactions ouvertes de longue durée (en particulier longues transactions de lecture), purge thread lent, débit DML élevé, transactions non validées/inactives.","action":"Identifiez et terminez les transactions longues et inactives, ajustez le nombre de purge threads (innodb_purge_threads) et gardez les transactions courtes dans l'application ; un HLL élevé entraîne des problèmes de performances et de bloat.","tags":["metric","innodb","undo","mvcc"]},{"name":"Innodb_data_reads","kind":"metric","meaning":"Nombre total de demandes de lecture physique effectuées par InnoDB sur disque ; indique la charge de lecture que le buffer pool n'a pas pu servir et qui est allée au disque.","causes":"Buffer pool trop petit, cache froid, requêtes inefficaces et grands parcours, index manquants, working set plus grand que la mémoire.","action":"Augmentez la taille du buffer pool, corrigez les requêtes inefficaces et les index manquants, surveillez la tendance des lectures physiques avec le hit ratio du buffer pool et améliorez les performances d'E/S.","tags":["metric","innodb","io","read"]},{"name":"Innodb_buffer_pool_read_requests","kind":"metric","meaning":"Nombre total de demandes de lecture logique servies depuis le buffer pool InnoDB ; utilisé avec Innodb_buffer_pool_reads (nombre de pages absentes du buffer pool et lues sur disque) pour calculer le hit ratio du buffer pool.","causes":"Une valeur élevée indique une activité de requêtes intense. Si le rapport entre Innodb_buffer_pool_reads et cette valeur est élevé (hit ratio faible), il y a un manque de mémoire ou des accès inefficaces.","action":"Surveillez le hit ratio du buffer pool (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) ; s'il est faible, agrandissez le buffer pool, optimisez les requêtes et corrigez les requêtes inefficaces générant des lectures logiques excessives.","tags":["metric","innodb","buffer-pool","logical-io"]}]},"reference/waits-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"waits":[{"name":"CXPACKET","kind":"wait","meaning":"Attente survenant pendant la synchronisation des threads lors de l'exécution parallèle d'une requête ; observée tant que la répartition/l'appariement du travail entre opérateurs du plan parallèle est en suspens.","causes":"Degré de parallélisme élevé, répartition inégale du travail (skew), MAXDOP illimité/élevé (0) et cost threshold for parallelism faible (5 par défaut), grands parcours parallèles dus à des index manquants, plans inefficaces.","action":"Ajustez cost threshold for parallelism et MAXDOP à la charge, corrigez les index et statistiques manquants, et ne considérez pas CXPACKET comme un problème en soi ; isolez l'attente réelle à l'aide de CXCONSUMER et ciblez les plans parallèles inefficaces.","tags":["parallelism","cxpacket","query","cpu"]},{"name":"CXCONSUMER","kind":"wait","meaning":"Dans un plan parallèle, le thread consommateur attend l'arrivée des données des threads producteurs ; généralement une attente de parallélisme sans gravité.","causes":"Thread consommateur en attente lors d'une exécution parallèle normale, flux de données inégal, retards côté producteur, plans parallèles inefficaces.","action":"CXCONSUMER est le plus souvent bénin et ne nécessite aucune action en soi ; pour trouver le vrai goulet d'étranglement, concentrez-vous sur les types d'attente sous-jacents côté producteur (IO, LCK, etc.) et sur l'efficacité du plan.","tags":["parallelism","cxconsumer","query","benign"]},{"name":"PAGEIOLATCH_SH","kind":"wait","meaning":"Le thread attend un latch protégeant une page de données en mode partagé (SH) pendant sa lecture du disque vers le buffer pool ; attente d'E/S physique en lecture.","causes":"E/S disque lentes, mémoire insuffisante (buffer pool trop petit), grands parcours et index manquants, cache froid, forte charge de lectures physiques.","action":"Évaluez la mémoire (max server memory) et le buffer pool, corrigez les requêtes inefficaces et les index manquants, améliorez la latence d'E/S du stockage, et identifiez et optimisez les requêtes qui lisent le plus.","tags":["io","pageiolatch","read","buffer-pool"]},{"name":"PAGEIOLATCH_EX","kind":"wait","meaning":"Le thread attend un latch protégeant en mode exclusif (EX) une page de données en cours de lecture depuis le disque ; il attend généralement l'E/S physique d'une page qui sera lue pour être modifiée.","causes":"E/S disque lentes, charge d'écriture intensive, mémoire insuffisante, pression du checkpoint/lazy writer, opérations effectuant de grosses modifications de données.","action":"Améliorez les performances d'écriture/lecture du stockage, augmentez la mémoire du buffer pool, rendez les opérations à forte écriture efficaces et par lots, et séparez et mesurez les E/S de tempdb et des disques de données.","tags":["io","pageiolatch","write","buffer-pool"]},{"name":"PAGELATCH_UP","kind":"wait","meaning":"Le thread attend un latch protégeant en mode update (UP) une page déjà en mémoire ; il ne s'agit pas d'E/S physiques mais d'une contention d'accès concurrents à une page en mémoire.","causes":"Contention sur des pages chaudes en mémoire, en particulier sur les pages système de tempdb (PFS/GAM/SGAM), création intensive et concurrente d'objets tempdb, contention d'insertion sur la dernière page (hot page).","action":"Répartissez tempdb sur des fichiers de données de taille égale correspondant au nombre de cœurs, réduisez la contention sur tempdb, répartissez les schémas de pages chaudes et éliminez l'utilisation inutile de tempdb (spills, etc.).","tags":["pagelatch","tempdb","concurrency","contention"]},{"name":"PAGELATCH_EX","kind":"wait","meaning":"Le thread attend un latch protégeant en mode exclusif (EX) une page en mémoire ; généralement observé avec la contention d'insertion sur la dernière page des tables à clés croissantes (séquentielles).","causes":"Contention d'insertion sur la dernière page des index à clés séquentielles/croissantes, contention sur les pages système de tempdb, écritures concurrentes intensives sur des pages chaudes, taux d'insertion élevé.","action":"Pour la contention sur la dernière page, utilisez OPTIMIZE_FOR_SEQUENTIAL_KEY = ON (SQL Server 2019+) ou une distribution des clés (hash/GUID/reverse index), répartissez tempdb sur plusieurs fichiers et repensez le schéma d'insertion intensif.","tags":["pagelatch","last-page-insert","concurrency","contention"]},{"name":"LCK_M_X","kind":"wait","meaning":"Le thread attend d'acquérir un verrou exclusif sur une ressource (ligne/page/table) ; il attend généralement la libération d'autres verrous pour modifier des données (UPDATE/DELETE/INSERT).","causes":"Transactions longues, modifications concurrentes des mêmes ressources, escalade de verrous, portée de verrouillage étendue due à des index manquants, sessions bloquantes.","action":"Gardez les transactions courtes et validez rapidement, réduisez la portée des verrous avec des index appropriés, trouvez la requête bloquante (sys.dm_exec_requests/blocking), revoyez l'escalade de verrous et le niveau d'isolation, et envisagez RCSI si nécessaire.","tags":["lock","exclusive","blocking","transaction"]},{"name":"LCK_M_U","kind":"wait","meaning":"Le thread attend d'acquérir un verrou update sur une ressource ; pendant qu'un UPDATE parcourt les données, lisant une ligne et se préparant à la modifier, il attend la libération d'autres verrous.","causes":"Mises à jour concurrentes des mêmes lignes, transactions longues, nombreuses lignes parcourues et verrouillées en raison d'index manquants, sessions bloquantes, contention de verrous.","action":"Ajoutez des index appropriés pour les instructions UPDATE afin de réduire le nombre de lignes parcourues/verrouillées, raccourcissez la durée des transactions, analysez la chaîne de blocage et évaluez les options de niveau d'isolation et de RCSI.","tags":["lock","update-lock","blocking","transaction"]},{"name":"WRITELOG","kind":"wait","meaning":"Le thread attend l'écriture des enregistrements du journal des transactions sur disque (log flush) ; il attend généralement au commit que le log devienne durable.","causes":"E/S lentes sur le disque de log, commits très fréquents/petits, charge d'écriture élevée, contention entre le disque de log et le disque de données, effet de la réplication Always On synchrone.","action":"Placez le journal des transactions sur un disque rapide à faible latence, regroupez les commits, réduisez les commits inutilement fréquents, envisagez la delayed durability (si approprié) et mesurez la latence d'E/S du log.","tags":["log","writelog","commit","io"]},{"name":"ASYNC_NETWORK_IO","kind":"wait","meaning":"SQL Server est prêt à envoyer les résultats mais attend côté réseau/client parce que le client ne consomme pas les données assez vite ; généralement un goulet d'étranglement côté application.","causes":"Application cliente traitant les résultats lentement (consommation ligne par ligne), grands jeux de résultats, réseau lent, traitement RBAR dans l'application, ressources client insuffisantes.","action":"Consommez les résultats rapidement et en masse dans l'application, réduisez les jeux de résultats inutilement volumineux (uniquement les colonnes/lignes nécessaires) et vérifiez la latence réseau. Il s'agit généralement d'un problème client/réseau, et non de SQL Server.","tags":["network","client","application","async"]},{"name":"SOS_SCHEDULER_YIELD","kind":"wait","meaning":"Attente survenant lorsqu'un thread épuise sa tranche de temps CPU (quantum) et cède volontairement le scheduler ; indique le plus souvent une pression CPU ou un traitement intensif en CPU.","causes":"Requêtes intensives liées au CPU, concurrence élevée et saturation CPU, contention de spinlocks, requêtes inefficaces effectuant de nombreuses lectures logiques.","action":"Trouvez et optimisez les requêtes inefficaces consommatrices de CPU (index manquant, mauvais plan), réduisez les lectures logiques, évaluez la capacité CPU et la concurrence et, si l'attente reste élevée, recherchez le goulet d'étranglement matériel/de plan.","tags":["cpu","scheduler","cpu-bound","yield"]},{"name":"RESOURCE_SEMAPHORE","kind":"wait","meaning":"La requête attend l'octroi de mémoire (memory grant) nécessaire à son exécution ; l'exécution ne peut pas démarrer tant que suffisamment de mémoire de requête n'est pas libre.","causes":"Requêtes demandant trop de mémoire (grands tris/hachages), estimations de cardinalité erronées (surévaluées), mémoire serveur insuffisante, nombreuses requêtes gourmandes en mémoire simultanées, statistiques obsolètes.","action":"Mettez à jour les statistiques pour corriger les estimations de memory grant excessives, optimisez les requêtes nécessitant de grands tris/hachages (index, filtre), revoyez les réglages Resource Governor et max server memory, et limitez la charge simultanée gourmande en mémoire.","tags":["memory","memory-grant","query","resource"]},{"name":"THREADPOOL","kind":"wait","meaning":"Tâches attendant un thread libre du thread pool car aucun worker thread n'est disponible pour exécuter une nouvelle tâche ; type d'attente critique signalant un grave épuisement des worker threads.","causes":"Threads retenus par un blocage prolongé, nombre de connexions simultanées très élevé, max worker threads insuffisant, parallélisme intensif, interblocages/longues chaînes de verrous.","action":"Résolvez d'urgence la chaîne de blocage, gérez les connexions simultanées avec un pool de connexions, éliminez les verrous détenus longtemps, revoyez le réglage max worker threads ; connectez-vous via la DAC pour trouver la cause racine.","tags":["threadpool","worker-thread","blocking","critical"]},{"name":"HADR_SYNC_COMMIT","kind":"wait","meaning":"Sur un réplica synchrone Always On, le primaire attend que l'enregistrement de log soit durci (hardened) sur le réplica secondaire avant de valider la transaction ; latence du commit synchrone.","causes":"Réseau lent/à forte latence vers le réplica secondaire, E/S lentes sur le disque de log du secondaire, secondaire surchargé, réplica géographiquement éloigné, débit d'écriture élevé.","action":"Améliorez la latence et la bande passante réseau entre réplicas, augmentez les performances du disque de log du secondaire, équilibrez la charge du secondaire et, si la latence est critique, revoyez la topologie de réplicas synchrones/asynchrones.","tags":["alwayson","replication","sync-commit","hadr"]},{"name":"Page Life Expectancy","kind":"metric","meaning":"Métrique indiquant le nombre moyen de secondes pendant lesquelles une page de données reste dans le buffer pool (sans être évincée de la mémoire) ; indicateur important de pression mémoire.","causes":"Un PLE faible ou en chute rapide signale une pression mémoire, des grands parcours inefficaces, un working set plus grand que la mémoire ou une mémoire insuffisante. Les nœuds NUMA doivent aussi être évalués individuellement.","action":"Observez la tendance du PLE et ses chutes brutales plutôt qu'un seuil fixe, évaluez la mémoire du buffer pool (max server memory), optimisez les requêtes inefficaces fortement consommatrices de mémoire et surveillez le PLE par nœud NUMA.","tags":["metric","memory","buffer-pool","ple"]},{"name":"Taux de succès du buffer cache","kind":"metric","meaning":"Proportion des demandes de pages de données satisfaites depuis le buffer cache sans accès disque ; indique le pourcentage de pages lues depuis la mémoire.","causes":"Sur les systèmes modernes, ce ratio est généralement très élevé (grâce au read-ahead), et une valeur faible indique une pression mémoire importante. Un ratio élevé ne garantit pas à lui seul de bonnes performances.","action":"N'interprétez pas cette métrique isolément mais avec le PLE, les attentes PAGEIOLATCH et les tendances de lectures physiques ; en cas de signes de pression mémoire, augmentez la mémoire du buffer pool et corrigez les requêtes inefficaces.","tags":["metric","memory","buffer-pool","ratio"]}]},"reference/waits-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"waits":[{"name":"% du cache WiredTiger utilisé","kind":"metric","meaning":"Part du cache interne alloué au moteur de stockage WiredTiger qui est utilisée ; indique la pression liée au maintien des données en mémoire (le seuil cible par défaut est d'environ 80 %).","causes":"Working set plus grand que le cache, cache sous-dimensionné, grands parcours, lectures/écritures intensives, nombreuses transactions ouvertes.","action":"Évaluez la taille du cache WiredTiger (storage.wiredTiger.engineConfig.cacheSizeGB) et la RAM, ajoutez des index qui réduisent le working set, et surveillez la pression d'éviction et la latence à mesure que l'utilisation approche la cible de 80 % et le seuil d'éviction de 95 %.","tags":["metric","wiredtiger","cache","memory"]},{"name":"% dirty du cache WiredTiger","kind":"metric","meaning":"Pourcentage des données du cache WiredTiger pas encore écrites sur disque (dirty) ; indique la pression d'écriture et la charge de checkpoint/d'éviction (le seuil de dirty eviction est généralement d'environ 20 %).","causes":"Charge d'écriture élevée, E/S disque lentes, écritures volumineuses/longues, pression de checkpoint, cache incapable de suivre le débit d'écriture.","action":"Si le pourcentage de dirty approche le seuil d'éviction (20 %), améliorez les performances d'écriture disque, équilibrez la charge d'écriture, évaluez la taille du cache et revoyez les écritures longues et l'intervalle de checkpoint.","tags":["metric","wiredtiger","cache","write"]},{"name":"tickets disponibles (lecture/écriture)","kind":"metric","meaning":"Nombre restant de tickets de concurrence que WiredTiger accorde aux opérations de lecture et d'écriture simultanées ; s'il tombe à zéro, les nouvelles opérations font la queue pour obtenir un ticket.","causes":"Opérations lentes conservant longtemps les tickets, goulet d'étranglement d'E/S disque, verrouillage/contention, pics de charge soudains, tickets épuisés par des requêtes longues.","action":"Si les tickets disponibles approchent zéro et qu'une file se forme, optimisez les requêtes lentes (ajoutez des index), améliorez les E/S disque, équilibrez la charge ; l'épuisement des tickets est généralement le symptôme d'un goulet d'étranglement d'E/S/de contention plus profond.","tags":["metric","wiredtiger","concurrency","tickets"]},{"name":"file d'attente du verrou global","kind":"metric","meaning":"Longueur de la file des opérations de lecture (readers) et d'écriture (writers) en attente d'un verrou global/de base/de collection ; indique le nombre d'opérations en attente sous contention de verrous.","causes":"Verrous détenus longtemps, contention d'écriture intensive, opérations longues, opérations DDL et bref verrou exclusif de collection pris au début/à la fin d'une construction d'index (builds hybrides 4.2+ ; les builds foreground n'existent plus), verrous conservés longtemps en raison d'E/S lentes.","action":"Si la file reste pleine, trouvez et optimisez les opérations longues (currentOp), planifiez les constructions d'index et autres DDL en heures creuses, améliorez les E/S et revoyez les schémas d'écriture en contention.","tags":["metric","lock","queue","concurrency"]},{"name":"retard de réplication (secondaire)","kind":"metric","meaning":"Retard (en secondes) d'un membre secondary dans l'application de l'oplog du primaire ; indique le décalage des données entre les membres du replica set.","causes":"Réseau lent, E/S disque lentes sur le secondaire, forte charge de lecture sur le secondaire, débit d'écriture élevé sur le primaire, matériel du secondary insuffisant, opérations longues.","action":"Surveillez le retard avec rs.printSecondaryReplicationInfo, améliorez les performances réseau et d'E/S du secondary, équilibrez sa charge, maintenez une taille d'oplog suffisante et revoyez l'exigence de write concern.","tags":["metric","replication","lag","secondary"]},{"name":"fenêtre de l'oplog","kind":"metric","meaning":"Intervalle de temps (heures) entre les opérations les plus anciennes et les plus récentes conservées dans l'oplog ; indique combien de temps un secondaire peut prendre du retard sans perdre la synchronisation.","causes":"Taille d'oplog réduite, débit d'écriture soudainement élevé, secondaires hors ligne depuis longtemps, charge de réplication accrue.","action":"Si la fenêtre de l'oplog s'est réduite, augmentez la taille de l'oplog, maintenez le retard des secondaires sous la fenêtre et revoyez la charge d'écriture et les besoins de sauvegarde/synchronisation ; si la fenêtre est dépassée, une resynchronisation complète est nécessaire.","tags":["metric","replication","oplog","window"]},{"name":"défauts de page","kind":"metric","meaning":"Nombre de défauts de page (en particulier hard faults) pour lesquels le système d'exploitation doit aller chercher sur disque des pages de données absentes de la mémoire ; indique un débordement des données de la mémoire vers le disque.","causes":"Working set plus grand que la RAM, mémoire insuffisante, grands parcours, pression sur le cache, consommation mémoire d'autres processus.","action":"Évaluez la mémoire du serveur/le working set, réduisez les données parcourues en ajoutant des index, équilibrez le cache WiredTiger et la mémoire de l'OS, et surveillez la hausse des hard page faults avec la latence d'E/S.","tags":["metric","memory","page-fault","io"]},{"name":"connexions actuelles","kind":"metric","meaning":"Nombre de connexions client actuellement ouvertes sur le serveur ; indique l'utilisation du pool de connexions et sa proximité avec le maximum.","causes":"Pools de connexions mal configurés, fuites de connexions, hausse soudaine du nombre de clients, connexions conservées longtemps, connexions excessives depuis des microservices.","action":"Comparez le nombre de connexions à la limite du serveur (maxIncomingConnections), dimensionnez correctement le pool de connexions côté application, corrigez les fuites de connexions et surveillez les pics soudains.","tags":["metric","connections","pool","capacity"]},{"name":"ratio scanAndOrder / COLLSCAN","kind":"metric","meaning":"Proportion des requêtes qui parcourent entièrement une collection sans index (COLLSCAN) ou trient en mémoire (scanAndOrder) ; indicateur d'inefficacité des index.","causes":"Index manquants ou inadaptés, clés de tri ne correspondant pas à la requête, requêtes larges non filtrées, mauvais schémas de requêtes.","action":"Examinez les requêtes lentes et leurs plans (explain), ajoutez des index composés correspondant aux schémas de requête et de tri, corrigez les requêtes produisant COLLSCAN et scanAndOrder, et surveillez le ratio documents examinés/renvoyés.","tags":["metric","index","collscan","query"]},{"name":"opérations en file d'attente","kind":"metric","meaning":"Nombre d'opérations de lecture et d'écriture mises en file (ne pouvant pas encore s'exécuter activement) car elles attendent un verrou ou une ressource ; indique que le système ne parvient pas à répondre à la demande.","causes":"Contention de verrous, épuisement des tickets, goulet d'étranglement d'E/S, requêtes longues, pics de charge soudains, disques lents.","action":"Si la file grandit, inspectez et optimisez les opérations actives et longues (currentOp), améliorez les index et les E/S, équilibrez la charge, et trouvez la cause racine à l'aide des métriques de tickets et de contention de verrous.","tags":["metric","queue","concurrency","load"]}]},"reference/waits-redis.json":{"engineId":"redis","engine":"Redis","order":6,"waits":[{"name":"taux de succès du keyspace","kind":"metric","meaning":"Proportion des recherches de clés trouvées en mémoire, calculée à partir de keyspace_hits et keyspace_misses ; indique l'efficacité de Redis en tant que cache.","causes":"Un ratio faible indique une éviction due à une mémoire insuffisante, des TTL courts, un cache froid, de nombreuses recherches de clés uniques jamais mises en cache ou une mauvaise conception des clés.","action":"Surveillez le hit ratio sous la forme hits/(hits+misses) ; s'il est faible, revoyez la mémoire/la politique d'éviction et les TTL, préchauffez les données fréquemment consultées et améliorez la conception des clés et la stratégie de cache.","tags":["metric","cache","hit-ratio","keyspace"]},{"name":"evicted_keys","kind":"metric","meaning":"Nombre de clés retirées (supprimées) de la mémoire par la politique d'éviction lorsque la limite maxmemory est atteinte ; indicateur direct de pression mémoire.","causes":"Dépassement de la limite maxmemory, mémoire insuffisante, accumulation de clés sans TTL, volume de données supérieur à la mémoire, politique d'éviction inadaptée.","action":"En cas d'éviction en hausse constante, ajoutez de la mémoire ou réduisez le volume de données, choisissez maxmemory-policy (par ex. allkeys-lru/volatile-lru) selon la charge et revoyez l'utilisation des TTL et les big keys.","tags":["metric","memory","eviction","maxmemory"]},{"name":"blocked_clients","kind":"metric","meaning":"Nombre de clients en attente d'un événement/de données avec des commandes bloquantes telles que BLPOP, BRPOP, WAIT et XREAD BLOCK ; normal dans les schémas de consommateurs de files, mais une hausse anormale est un signal d'alerte.","causes":"Consommateurs utilisant des commandes bloquantes sur listes/streams, aucune donnée en provenance des producteurs, attente de l'acquittement des réplicas avec WAIT, déséquilibre consommateurs-producteurs.","action":"Comparez le nombre de clients bloqués au modèle de consommateurs attendu ; en cas de hausse anormale, examinez le côté producteur et les acquittements de réplication, et équilibrez le rythme de consommation de la file.","tags":["metric","clients","blocking","queue"]},{"name":"mem_fragmentation_ratio","kind":"metric","meaning":"Rapport entre la mémoire physique allouée à Redis par le système d'exploitation (RSS) et la mémoire réellement utilisée par Redis ; indique une fragmentation mémoire ou du swap.","causes":"Des valeurs nettement supérieures à 1 indiquent une fragmentation mémoire, tandis que des valeurs inférieures à 1 indiquent que Redis a été swappé (sur disque). Les clés de taille variable et les cycles suppression/insertion augmentent la fragmentation.","action":"En cas de forte fragmentation, envisagez d'activer activedefrag ou de redémarrer ; si la valeur est inférieure à 1, examinez le swap et ajoutez de la mémoire ; revoyez le comportement de l'allocateur (jemalloc) et les schémas de big keys.","tags":["metric","memory","fragmentation","swap"]},{"name":"instantaneous_ops_per_sec","kind":"metric","meaning":"Nombre de commandes que Redis traite par seconde à l'instant présent (débit instantané) ; indique l'intensité de la charge et les variations soudaines de trafic.","causes":"Pics de trafic soudains, charge de traitements batch, schémas d'accès client inefficaces, absence de pipelining au profit de commandes individuelles, rafales intensives de lectures/écritures.","action":"Surveillez le débit avec la latence et le CPU, corrélez les chutes/pics soudains avec le trafic et les commandes lentes, améliorez l'efficacité avec le pipelining/le batching et planifiez la capacité.","tags":["metric","throughput","ops","load"]},{"name":"rdb_last_bgsave_status","kind":"metric","meaning":"Statut de réussite du dernier enregistrement RDB en arrière-plan (BGSAVE) ; indique si le snapshot de persistance a été pris avec succès.","causes":"Un statut d'échec (err) peut être causé par un disque plein/un espace insuffisant, une mémoire insuffisante pour le fork, une erreur d'E/S disque, des problèmes de permissions ou une charge excessive.","action":"Si le statut est 'err', vérifiez l'espace disque, la mémoire nécessaire au fork (réglage overcommit) et l'état du disque, et examinez les logs ; un BGSAVE en échec crée un risque de perte de données, revoyez donc la configuration de persistance.","tags":["metric","persistence","rdb","bgsave"]},{"name":"master_link_status","kind":"metric","meaning":"Statut (up/down) du lien de réplication d'un réplica vers le master ; indique si la connexion entre réplica et master est saine.","causes":"Un statut 'down' peut résulter d'une coupure réseau, d'un master injoignable, d'une erreur de synchronisation de la réplication, d'un problème d'authentification ou d'une surcharge du master.","action":"Si le statut est 'down', vérifiez la connectivité réseau, l'accessibilité du master et les réglages d'auth, examinez les logs du réplica, surveillez le retard de master_repl_offset et vérifiez que la synchronisation est terminée.","tags":["metric","replication","master-link","availability"]},{"name":"latence (slowlog)","kind":"metric","meaning":"Latence des commandes lentes dépassant un seuil défini, détectée via le slowlog et le latency monitoring ; dans Redis, monothread, elle indique le risque qu'une commande bloque les autres.","causes":"Commandes à forte complexité temporelle (KEYS, grands SORT, SMEMBERS/HGETALL sur de grandes collections), big keys, scripts Lua lents, pauses de fork/persistance, swap.","action":"Trouvez les commandes lentes avec les commandes SLOWLOG et LATENCY, éliminez les commandes O(N) (SCAN au lieu de KEYS) et les big keys, optimisez les scripts et surveillez les pauses causées par fork/AOF/RDB.","tags":["metric","latency","slowlog","performance"]},{"name":"expired_keys","kind":"metric","meaning":"Nombre total de clés supprimées à l'expiration de leur TTL ; indicateur du comportement d'expiration basé sur les TTL et de la récupération de mémoire.","causes":"L'expiration des clés ayant un TTL est normale. Des variations soudaines peuvent indiquer un changement de politique de TTL, une expiration massive de clés ou des schémas de suppression inattendus.","action":"Distinguez expired et evicted (expired est un comportement TTL sain, evicted une pression mémoire), surveillez l'impact des vagues d'expiration soudaines sur les cache misses et la charge, et étalez la distribution des TTL.","tags":["metric","ttl","expiration","keyspace"]},{"name":"connected_clients vs maxclients","kind":"metric","meaning":"Proximité du nombre actuel de clients connectés avec la limite maxclients configurée ; indique le risque d'épuisement de la capacité de connexion.","causes":"Fuites de connexions, pool de connexions mal configuré, hausse soudaine du nombre de clients, connexions inactives conservées longtemps, nombreuses connexions de microservices.","action":"Comparez les clients connectés à maxclients ; à l'approche de la limite, dimensionnez correctement le pool de connexions, corrigez les fuites de connexions, définissez un timeout pour les connexions inactives et augmentez maxclients si nécessaire.","tags":["metric","connections","maxclients","capacity"]}]},"reference/waits-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"waits":[{"name":"% du heap JVM utilisé","kind":"metric","meaning":"Pourcentage du heap JVM d'un nœud en cours d'utilisation ; un heap élevé et durablement plein signifie une pression mémoire et un risque de GC intensif.","causes":"Mémoire fielddata/d'agrégation excessive, trop de shards, forte charge bulk/de requêtes, mappings volumineux, heap sous-dimensionné, schémas de fuite mémoire.","action":"Surveillez l'utilisation du heap (intervenez en particulier si elle reste au-dessus de 75 % après le GC), réduisez le nombre de shards, limitez les agrégations lourdes et l'utilisation de fielddata, et gardez le heap à au plus la moitié de la RAM et sous 32GB.","tags":["metric","jvm","heap","memory"]},{"name":"durée/fréquence du GC","kind":"metric","meaning":"Durée et fréquence du garbage collection JVM ; des pauses GC longues/fréquentes rendent le nœud non réactif et dégradent les performances.","causes":"Forte pression sur le heap, accumulation importante dans l'old generation (old gen), agrégations/fielddata lourdes, heap sous-dimensionné, trop de shards, grandes requêtes de longue durée.","action":"Éliminez la pression sur le heap à l'origine des longues pauses (en particulier old GC), optimisez les requêtes gourmandes en mémoire, réduisez le nombre de shards ; un GC fréquent et long est un signe avant-coureur d'instabilité du nœud.","tags":["metric","jvm","gc","pause"]},{"name":"rejets thread_pool write","kind":"metric","meaning":"Nombre de requêtes d'indexation rejetées car la file du thread pool write (indexation/bulk) était pleine ; indique que le nœud ne peut pas absorber la charge d'écriture.","causes":"Débit bulk/d'indexation excessif, E/S disque lentes, nombreuses petites requêtes au lieu de bulk, capacité du nœud insuffisante, pression de refresh.","action":"Envoyez les requêtes d'indexation en bulks de taille appropriée, limitez le débit d'écriture à la capacité du nœud (backoff), améliorez les E/S disque, augmentez refresh_interval, augmentez la capacité.","tags":["metric","thread-pool","indexing","rejection"]},{"name":"rejets thread_pool search","kind":"metric","meaning":"Nombre de requêtes de recherche rejetées car la file du thread pool search était pleine ; indique que le nœud ne peut pas absorber la charge de requêtes.","causes":"Requêtes simultanées excessives, agrégations lourdes/lentes, trop de shards sollicités, requêtes inefficaces, capacité nœud/CPU insuffisante.","action":"Limitez la concurrence des requêtes côté client, optimisez les requêtes et agrégations lourdes, réduisez le fan-out inutile sur les shards (routing/filtrage), ajoutez des nœuds et augmentez le nombre de shards réplicas (les recherches sont aussi servies par les shards réplicas), augmentez la capacité.","tags":["metric","thread-pool","search","rejection"]},{"name":"latence d'indexation/de recherche","kind":"metric","meaning":"Latence moyenne des opérations d'indexation et de recherche ; dérivée des métriques index_time_in_millis et query_time_in_millis, elle reflète le temps de réponse perçu par les utilisateurs.","causes":"E/S disque lentes, requêtes/agrégations lourdes, pauses GC, nombre de shards élevé, pression de refresh/merge, ressources insuffisantes.","action":"Trouvez et optimisez les requêtes lentes avec le slow log, ajustez bulk et refresh_interval pour l'indexation, améliorez les performances disque et heap, et interprétez la latence avec les rejets du thread pool et le GC.","tags":["metric","latency","indexing","search"]},{"name":"durée de refresh/flush","kind":"metric","meaning":"Temps passé en refresh, qui rend les nouveaux documents interrogeables, et en flush, qui persiste le translog dans Lucene ; indique le surcoût de l'indexation.","causes":"Refreshs trop fréquents (refresh_interval faible), E/S disque lentes, indexation intensive, translog volumineux, nombreux petits segments.","action":"Pendant une indexation intensive, augmentez refresh_interval (par ex. 30s) ou désactivez-le temporairement, améliorez les E/S disque, ajustez la taille des bulks, revoyez le comportement de merge des segments.","tags":["metric","refresh","flush","indexing"]},{"name":"shards non assignés","kind":"metric","meaning":"Nombre de shards assignés à aucun nœud ; un primaire non assigné signifie des données inaccessibles (état red), un réplica non assigné signifie un risque de durabilité/de capacité.","causes":"Panne/départ d'un nœud, disk watermark dépassé, réglage de réplicas trop élevé pour le nombre de nœuds, règles/filtres d'allocation, retards pendant la récupération des shards.","action":"Trouvez la raison avec l'API cluster allocation explain, rétablissez les nœuds en échec, corrigez les problèmes d'espace disque/de watermark, revoyez le nombre de réplicas et les règles d'allocation ; traitez en priorité l'état red.","tags":["metric","shards","allocation","cluster-health"]},{"name":"% de disk watermark","kind":"metric","meaning":"Utilisation disque du nœud par rapport aux seuils low/high/flood-stage (85 %/90 %/95 % par défaut) qui influent sur le placement des shards Elasticsearch.","causes":"Disque qui se remplit, croissance des données, stockage insuffisant, répartition déséquilibrée des shards, anciens index non nettoyés.","action":"Au low watermark, aucun nouveau shard n'est assigné au nœud ; au high watermark, les shards sont déplacés vers d'autres nœuds ; au flood-stage, les index passent en lecture seule. Libérez de l'espace disque (supprimez les anciens index/utilisez ILM), augmentez le stockage, équilibrez la répartition des shards et surveillez les seuils de watermark.","tags":["metric","disk","watermark","storage"]},{"name":"évictions du cache fielddata/query","kind":"metric","meaning":"Nombre d'entrées évincées du cache fielddata et du cache query (filter) en raison des limites mémoire ; indique une inefficacité du cache et une pression mémoire.","causes":"Utilisation élevée de fielddata (tris/agrégations sur des champs text), limites de cache faibles, filtres de requête très variés, pression mémoire, filtres jamais réutilisés.","action":"Utilisez keyword/doc_values plutôt que fielddata sur les champs text, limitez les agrégations fielddata lourdes, revoyez la taille des caches ; des évictions fréquentes doivent être évaluées avec la pression sur le heap.","tags":["metric","cache","fielddata","eviction"]},{"name":"déclenchements de circuit breaker","kind":"metric","meaning":"Nombre de fois où les circuit breakers (parent/fielddata/request breaker) ont prévu qu'une requête dépasserait la mémoire et l'ont rejetée ; mécanisme de sécurité protégeant le nœud contre l'OOM.","causes":"Agrégations/requêtes demandant trop de mémoire, utilisation élevée de fielddata, grosses requêtes bulk, pression générale sur le heap, très grands jeux de résultats.","action":"Considérez les déclenchements de circuit breaker comme un signe de pression sur le heap, optimisez les requêtes gourmandes en mémoire, réduisez l'utilisation de fielddata/d'agrégations, diminuez la taille des bulks et revoyez le heap et le nombre de shards.","tags":["metric","circuit-breaker","memory","oom"]},{"name":"tâches en attente","kind":"metric","meaning":"File des tâches de mise à jour du cluster state en attente de traitement par le nœud master ; indique que le master ne suit pas la charge de gestion du cluster.","causes":"Grand nombre d'index/de shards, modifications fréquentes de mappings/settings, ressources du nœud master insuffisantes, cluster state volumineux, problèmes réseau/d'élection du master.","action":"Si la file reste pleine, réduisez le nombre de shards/d'index, limitez les mises à jour fréquentes de dynamic mapping, utilisez des nœuds master dédiés séparés et revoyez la taille du cluster state et les ressources du master.","tags":["metric","master","cluster-state","queue"]}]},"reference/alerts-oracle.json":{"engineId":"oracle","engine":"Oracle Database","order":1,"alerts":[{"metric":"Taux de succès du buffer cache","warn":"< 95%","crit":"< 90%","tool":"V$SYSSTAT / V$BUFFER_POOL_STATISTICS","note":"Le buffer cache hit ratio indique le pourcentage de blocs de données lus en mémoire sans accès disque. Des valeurs faibles signalent une hausse des lectures physiques et une SGA sous-dimensionnée. Pris isolément, il peut être trompeur ; évaluez-le toujours avec les lectures physiques du rapport AWR.","tags":["memory","cache","sga"]},{"metric":"Utilisation des tablespaces (%used)","warn":"> 85%","crit":"> 95%","tool":"DBA_TABLESPACE_USAGE_METRICS","note":"Si l'utilisation d'un tablespace atteint 100 %, aucun nouvel extent ne peut être alloué et les opérations s'arrêtent avec des erreurs telles que ORA-01653. Même avec l'autoextend activé, surveillez la limite du disque. La vue DBA_TABLESPACE_USAGE_METRICS fournit le pourcentage correct pour les fichiers autoextensibles comme pour ceux de taille fixe.","tags":["disk","tablespace","capacity"]},{"metric":"Utilisation de la mémoire PGA","warn":"> 90 % de pga_aggregate_target","crit":"> 100 % (over-alloc)","tool":"V$PGASTAT / V$PGA_TARGET_ADVICE","note":"Lorsque la cible PGA est dépassée, les opérations de tri et de hachage débordent sur disque (temp) et les requêtes ralentissent. Un over allocation count en hausse constante montre que la cible est insuffisante. Revoyez le réglage pga_aggregate_target par rapport à la mémoire du serveur.","tags":["memory","pga","temp"]},{"metric":"Utilisation du tablespace TEMP","warn":"> 80%","crit":"> 95%","tool":"V$TEMPSEG_USAGE / V$SORT_SEGMENT / DBA_TEMP_FREE_SPACE","note":"L'espace TEMP est utilisé pour les grands tris, les hash joins et les opérations sur les tables temporaires globales. Lorsqu'il est plein, ORA-01652 est levée et les requêtes échouent. Les remplissages soudains proviennent généralement d'une seule requête avec un mauvais plan ou triant trop de données.","tags":["temp","disk","sort"]},{"metric":"Average Active Sessions (AAS)","warn":"> nombre de cœurs CPU","crit":"> 2x le nombre de cœurs CPU","tool":"V$ACTIVE_SESSION_HISTORY / OEM","note":"Average Active Sessions résume la charge actuelle de la base en un seul nombre. Si la valeur dépasse le nombre de cœurs CPU, les sessions font la queue pour le CPU ou sur des événements d'attente. Si elle reste à un multiple du nombre de cœurs, une analyse des goulets d'étranglement est nécessaire.","tags":["cpu","load","session"]},{"metric":"Apply lag Data Guard","warn":"> 60 s","crit":"> 300 s","tool":"V$DATAGUARD_STATS (apply lag)","note":"L'apply lag est le retard de la base standby sur le primaire. Un apply lag élevé augmente la quantité de redo à appliquer lors d'un failover et allonge donc le temps de récupération (RTO) ; si le redo a atteint le standby, aucune donnée n'est perdue. La perte de données potentielle (RPO) est déterminée par le transport lag. Examinez-le avec le transport lag pour déterminer si c'est le réseau ou l'application du redo qui est lent.","tags":["replication","dataguard","lag"]},{"metric":"Utilisation de la Fast Recovery Area (FRA)","warn":"> 85%","crit":"> 95%","tool":"V$RECOVERY_FILE_DEST / V$RECOVERY_AREA_USAGE","note":"Lorsque la FRA est pleine, la génération des archivelogs s'arrête et toute la base se fige. C'est l'une des causes d'indisponibilité les plus fréquentes en production. Vérifiez régulièrement les politiques de suppression RMAN et la valeur de db_recovery_file_dest_size.","tags":["disk","fra","archivelog"]},{"metric":"Nombre de sessions bloquantes","warn":">= 1 (> 30 s)","crit":">= 1 (> 120 s)","tool":"V$LOCK / V$SESSION (blocking_session)","note":"Lorsqu'un verrou détenu par une session fait attendre d'autres sessions, un ralentissement en cascade se produit dans toute l'application. Un blocage prolongé provient généralement de transactions non validées ou de bugs applicatifs. Trouvez la session racine de la chaîne de blocage et terminez-la si nécessaire.","tags":["lock","blocking","concurrency"]},{"metric":"Nombre d'objets invalides","warn":"> 0","crit":"> 50","tool":"DBA_OBJECTS (status='INVALID')","note":"Des packages, vues ou triggers invalides peuvent provoquer des erreurs inattendues à l'exécution. Ils apparaissent généralement après une modification DDL. Corrigez-les en les recompilant avec UTL_RECOMP.RECOMP_PARALLEL ou utlrp.sql, et recherchez la cause racine des objets durablement invalides.","tags":["object","compile","maintenance"]},{"metric":"Requête la plus longue","warn":"> 5 min","crit":"> 30 min","tool":"V$SQL / V$SESSION_LONGOPS","note":"Une seule requête longue peut monopoliser les ressources et ralentir aussi les autres opérations. Hors traitements batch, les requêtes durant plusieurs minutes sur les systèmes OLTP proviennent généralement d'un index manquant ou d'un mauvais plan. Examinez le plan et le nombre réel de lignes avec un rapport SQL Monitor.","tags":["query","performance","sql"]},{"metric":"Erreurs ORA dans l'alert log","warn":">= 1 (non critique)","crit":"ORA-00600 / ORA-07445 / ORA-04031","tool":"alert log / V$DIAG_ALERT_EXT","note":"L'alert log est le journal de santé de la base. ORA-00600 et ORA-07445 sont des erreurs internes pouvant indiquer une corruption de données, tandis que ORA-04031 indique un épuisement de la mémoire de la shared pool. Si ces erreurs apparaissent, analysez-les immédiatement et transmettez-les au support Oracle si nécessaire.","tags":["error","alertlog","monitoring"]},{"metric":"Fréquence des switches de redo log","warn":"> 6 / heure","crit":"> 12 / heure","tool":"V$LOG_HISTORY","note":"Des switches de redo log très fréquents indiquent des fichiers de log sous-dimensionnés et une pression de checkpoint. Idéalement, un switch toutes les 15-20 minutes est attendu aux heures de pointe. Des switches trop fréquents augmentent la charge d'IO et d'archivage ; envisagez d'agrandir les groupes de redo log.","tags":["redo","io","checkpoint"]},{"metric":"Âge de la dernière sauvegarde RMAN réussie","warn":"> 24 heures","crit":"> 48 heures","tool":"V$RMAN_BACKUP_JOB_DETAILS","note":"L'âge de la sauvegarde détermine le point le plus récent auquel vous pouvez restaurer et reflète directement le risque de perte de données. Si des sauvegardes en échec ou ignorées passent inaperçues, aucun retour en arrière n'est possible en cas de sinistre. Surveillez aussi séparément les sauvegardes d'archivelogs pour garantir la capacité de restauration à un instant donné.","tags":["backup","rman","recovery"]},{"metric":"Latence d'IO disque (db file sequential read)","warn":"> 10 ms","crit":"> 20 ms","tool":"V$SYSTEM_EVENT / AWR","note":"Le temps d'attente moyen des lectures mono-bloc indique les performances de la couche de stockage. Sur les systèmes flash/SSD modernes, cette valeur doit être de quelques millisecondes. Une latence durablement élevée indique un goulet d'étranglement du stockage ou des lectures physiques excessives ; évaluez ensemble la SGA et la couche de stockage.","tags":["io","disk","latency"]}]},"reference/alerts-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"alerts":[{"metric":"Cache hit ratio","warn":"< 99%","crit":"< 95%","tool":"pg_stat_database / postgres_exporter","note":"Le cache hit ratio est le pourcentage de blocs servis uniquement depuis shared_buffers ; les lectures servies par le cache du système d'exploitation sont aussi comptées comme blks_read, si bien que le ratio peut faire paraître les lectures disque réelles pires qu'elles ne le sont. Sur les systèmes PostgreSQL OLTP sains, ce ratio doit dépasser 99 %. Des valeurs faibles indiquent une mémoire insuffisante ou un cache froid ; il se calcule à partir de blks_hit et blks_read.","tags":["memory","cache"]},{"metric":"Nombre de connexions (connections vs max_connections)","warn":"> 80%","crit":"> 90%","tool":"pg_stat_activity / max_connections","note":"Des connexions actives approchant la limite max_connections entraînent des échecs de nouvelles connexions. Chaque connexion consommant de la mémoire, un nombre élevé crée aussi une pression mémoire. Il est recommandé de limiter le nombre de backends avec un connection pooler tel que PgBouncer.","tags":["connection","limit","pool"]},{"metric":"Retard de réplication","warn":"> 10 MB","crit":"> 100 MB","tool":"pg_stat_replication (replay_lag / lsn diff)","note":"Le retard de réplication est la quantité de WAL, en octets, dont le standby est en retard sur le primaire. Un retard élevé signifie des données obsolètes sur les réplicas en lecture et un risque de perte de données lors d'un failover. Surveillez la différence de LSN en octets avec la durée replay_lag.","tags":["replication","lag","standby"]},{"metric":"Âge de la plus ancienne transaction","warn":"> 5 min","crit":"> 30 min","tool":"pg_stat_activity (xact_start)","note":"Les transactions ouvertes de longue durée empêchent VACUUM de supprimer les dead tuples et entraînent du bloat de tables. Les sessions à l'état 'idle in transaction' conservent aussi des verrous inutilement. Identifiez ces sessions et limitez-les avec idle_in_transaction_session_timeout.","tags":["query","vacuum","transaction"]},{"metric":"Interblocages","warn":"> 1 / min","crit":"> 5 / min","tool":"pg_stat_database (deadlocks)","note":"Un interblocage se produit lorsque deux transactions attendent mutuellement leurs verrous ; PostgreSQL le résout en annulant l'une d'elles. Des interblocages occasionnels peuvent survenir, mais des interblocages fréquents indiquent un ordre de verrouillage incohérent dans l'application. Activez log_lock_waits pour voir quelles requêtes entrent en conflit.","tags":["lock","deadlock","concurrency"]},{"metric":"Utilisation des fichiers temporaires","warn":"> 1 GB / heure","crit":"> 10 GB / heure","tool":"pg_stat_database (temp_bytes)","note":"Des fichiers temporaires sont créés lorsque les opérations de tri et de hachage ne tenant pas dans work_mem débordent sur disque. Une utilisation élevée du temp entraîne à la fois lenteur et pression disque. Pour les requêtes qui débordent fréquemment, envisagez d'augmenter work_mem ou d'optimiser la requête.","tags":["temp","disk","work_mem"]},{"metric":"Retard de l'autovacuum (dead tuples)","warn":"> 10 % de dead tuples","crit":"> 20 % de dead tuples","tool":"pg_stat_user_tables (n_dead_tup)","note":"Un ratio de dead tuples en hausse montre que l'autovacuum ne suit pas et que le bloat des tables augmente. Les tables gonflées occupent de l'espace et ralentissent les parcours. Sur les tables très sollicitées, réduisez autovacuum_vacuum_scale_factor pour que le nettoyage soit plus fréquent.","tags":["vacuum","bloat","maintenance"]},{"metric":"Utilisation disque (répertoire de données)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"PostgreSQL n'offre aucun mécanisme d'autoprotection avant que le disque soit plein. Lorsque le disque de données est plein, les écritures échouent avec 'could not extend file' / 53100 ; si le disque du WAL (pg_wal) est plein, le serveur s'arrête en PANIC. Le WAL et les fichiers temporaires peuvent provoquer une croissance soudaine ; une intervention précoce est essentielle.","tags":["disk","capacity"]},{"metric":"Rétention du WAL (retard des replication slots)","warn":"> 1 GB","crit":"> 10 GB","tool":"pg_replication_slots (restart_lsn)","note":"Un replication slot inutilisé ou en retard empêche la suppression des fichiers WAL et fait gonfler le répertoire pg_wal. Cela peut aboutir à un disque plein. Identifiez et supprimez les slots morts, et fixez une limite haute avec max_slot_wal_keep_size.","tags":["wal","slot","disk"]},{"metric":"Fréquence des checkpoints (checkpoints demandés)","warn":"> 10 % de checkpoints demandés","crit":"> 30 % de checkpoints demandés","tool":"PG16 et antérieures : pg_stat_bgwriter (checkpoints_req) / PG17+ : pg_stat_checkpointer (num_requested)","note":"Lorsque la proportion de checkpoints demandés par rapport aux checkpoints planifiés augmente, max_wal_size est trop petit. Des checkpoints fréquents provoquent des pics d'IO et une dégradation des performances. Revoyez les réglages max_wal_size et checkpoint_completion_target pour répartir la charge.","tags":["checkpoint","wal","io"]},{"metric":"Âge de la dernière sauvegarde réussie","warn":"> 24 heures","crit":"> 48 heures","tool":"pgBackRest / barman info","note":"L'âge de la sauvegarde détermine le dernier point auquel vous pouvez revenir et exprime directement votre RPO. Une chaîne de sauvegardes ininterrompue et une archive WAL sont nécessaires pour la restauration à un instant donné. Il ne suffit pas de prendre des sauvegardes ; leur validité doit aussi être vérifiée par des tests de restauration réguliers.","tags":["backup","recovery","rpo"]},{"metric":"Latence d'IO disque","warn":"> 10 ms","crit":"> 20 ms","tool":"node_exporter / iostat","note":"La latence de lecture/écriture du stockage influe directement sur la durée des requêtes. Sur les systèmes SSD modernes, cette valeur doit être de quelques millisecondes. Une latence durablement élevée indique un goulet d'étranglement disque ou des lectures physiques excessives ; évaluez ensemble shared_buffers et la couche de stockage.","tags":["io","disk","latency"]},{"metric":"Transaction ID wraparound (âge)","warn":"> 1 milliard","crit":"> 1,5 milliard","tool":"pg_database (age(datfrozenxid))","note":"Si l'âge des transaction ID approche 2 milliards, PostgreSQL arrête toutes les écritures pour éviter une perte de données (protection contre le wraparound). C'est l'une des défaillances PostgreSQL les plus dangereuses. Surveillez l'âge de datfrozenxid et assurez-vous que l'autovacuum ne prend pas de retard sur le travail de freeze.","tags":["wraparound","vacuum","critical"]}]},"reference/alerts-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"alerts":[{"metric":"Taux de succès du buffer pool InnoDB","warn":"< 99%","crit":"< 95%","tool":"SHOW ENGINE INNODB STATUS / mysqld_exporter","note":"Le buffer pool hit ratio est le pourcentage de pages de données et d'index lues en mémoire sans accès disque. Des valeurs faibles indiquent que innodb_buffer_pool_size est trop petit pour le working set. Sur un serveur dédié, on alloue généralement ~70-80 % de la mémoire au buffer pool (moins sur un serveur partagé).","tags":["memory","cache","innodb"]},{"metric":"Nombre de connexions (Threads_connected vs max_connections)","warn":"> 80%","crit":"> 90%","tool":"SHOW STATUS / max_connections","note":"À mesure que le nombre de connexions approche la limite max_connections, les nouvelles connexions sont rejetées avec 'Too many connections'. Max_used_connections indique le pic historique. Maîtrisez les connexions simultanées avec un pool de connexions côté application.","tags":["connections","limit","thread"]},{"metric":"Retard du réplica (Seconds_Behind_Source)","warn":"> 30 s","crit":"> 300 s","tool":"SHOW REPLICA STATUS","note":"Seconds_Behind_Source indique, en secondes, le retard du réplica sur la source. Un retard élevé signifie des données obsolètes sur les réplicas en lecture et un risque lors d'un failover. Avec une application monothread, la valeur peut être trompeuse pendant les grosses transactions ; confirmez-la avec l'écart de GTID.","tags":["replication","lag","replica"]},{"metric":"Temps d'attente des verrous de ligne InnoDB","warn":"moy. > 1 s","crit":"moy. > 5 s","tool":"SHOW STATUS (Innodb_row_lock_time_avg)","note":"Le temps moyen d'attente des verrous de ligne indique à quel point les transactions se font attendre mutuellement. Des valeurs élevées signalent des mises à jour fortement conflictuelles ou des transactions longues. Examinez-le avec Innodb_row_lock_waits et trouvez les requêtes en conflit dans performance_schema.","tags":["lock","blocking","innodb"]},{"metric":"History list length (undo)","warn":"> 100000","crit":"> 1000000","tool":"SHOW ENGINE INNODB STATUS","note":"La history list length est le nombre d'enregistrements undo non purgés et augmente en raison des transactions ouvertes de longue durée. Une valeur élevée montre que la purge prend du retard et que les performances de lecture se dégradent à cause du MVCC. Trouvez et terminez les transactions longues pour que la liste puisse diminuer.","tags":["undo","transaction","mvcc"]},{"metric":"Connexions en échec (Aborted_connects)","warn":"> 5 / min","crit":"> 20 / min","tool":"SHOW STATUS (Aborted_connects)","note":"Aborted_connects compte les connexions qui échouent lors de l'authentification ou du handshake. Des pics soudains peuvent indiquer des mots de passe erronés, des problèmes réseau ou une possible tentative d'attaque. Examinez-le avec Aborted_clients pour déterminer où la connexion est interrompue.","tags":["connections","error","security"]},{"metric":"Tables temporaires écrites sur disque","warn":"ratio > 25 %","crit":"ratio > 50 %","tool":"SHOW STATUS (Created_tmp_disk_tables)","note":"La proportion de tables temporaires en mémoire débordant sur disque montre que les requêtes dépassent la limite tmp_table_size. Les tables temporaires sur disque ralentissent nettement les requêtes. Optimisez les requêtes comportant GROUP BY et des colonnes BLOB/TEXT et envisagez d'augmenter tmp_table_size et max_heap_table_size.","tags":["temp","disk","query"]},{"metric":"Requêtes lentes","warn":"> 5 / min","crit":"> 20 / min","tool":"slow query log / statut Slow_queries","note":"Les requêtes dépassant le seuil long_query_time sont écrites dans le slow query log et constituent la principale source de problèmes de performances. Des requêtes lentes fréquentes signalent des index manquants ou des requêtes mal écrites. Analysez le log avec pt-query-digest et traitez en priorité les requêtes les plus coûteuses.","tags":["query","performance","slowlog"]},{"metric":"Utilisation CPU","warn":"> 80%","crit":"> 90%","tool":"node_exporter / top","note":"Une utilisation CPU durablement élevée peut provenir de requêtes lourdes, d'index manquants ou de connexions trop nombreuses. Les pics brefs sont normaux, mais une saturation prolongée provoque la mise en file des requêtes. Identifiez les requêtes les plus consommatrices de CPU avec performance_schema ou le slow log.","tags":["cpu","load"]},{"metric":"Utilisation disque (répertoire de données)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"Un répertoire de données plein empêche MySQL d'écrire et provoque des erreurs de tables. La croissance des fichiers binlog, des undo tablespaces et du tablespace temporaire (ibtmp1) peut remplir soudainement le disque ; le redo log a une taille fixe (8.0.30+ #innodb_redo, innodb_redo_log_capacity). Assurez-vous que les anciens binlogs sont purgés via binlog_expire_logs_seconds.","tags":["disk","capacity","binlog"]},{"metric":"Requête la plus longue","warn":"> 60 s","crit":"> 300 s","tool":"SHOW PROCESSLIST / information_schema.PROCESSLIST","note":"Une seule requête longue peut affecter d'autres transactions en conservant des verrous et en consommant des ressources. Sur les systèmes OLTP, les requêtes durant plusieurs minutes proviennent généralement d'un mauvais plan. Examinez le plan avec EXPLAIN et, si nécessaire, tuez la requête et optimisez-la.","tags":["query","performance"]},{"metric":"Âge de la dernière sauvegarde réussie","warn":"> 24 heures","crit":"> 48 heures","tool":"xtrabackup / log des jobs mysqldump","note":"L'âge de la sauvegarde détermine le dernier point auquel vous pouvez revenir, et donc le RPO. Combinée aux sauvegardes des binlogs, la restauration à un instant donné devient possible. Prendre des sauvegardes ne suffit pas ; vérifiez leur validité par des tests de restauration réguliers.","tags":["backup","recovery","rpo"]},{"metric":"Latence d'IO disque","warn":"> 10 ms","crit":"> 20 ms","tool":"iostat / node_exporter","note":"La latence du stockage se répercute directement sur la durée des requêtes et est critique en particulier pour les charges qui ne tiennent pas dans le buffer pool. Quelques millisecondes sont attendues sur les systèmes SSD modernes. Une latence élevée indique un goulet d'étranglement disque ou des lectures physiques excessives ; revoyez aussi la taille du buffer pool.","tags":["io","disk","latency"]}]},"reference/alerts-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"alerts":[{"metric":"Page Life Expectancy (PLE)","warn":"< 900 s","crit":"< 300 s","tool":"sys.dm_os_performance_counters","note":"Page Life Expectancy indique, en secondes, la durée estimée pendant laquelle une page de données peut rester en mémoire avant d'être évincée du buffer pool. Des valeurs faibles signalent une pression mémoire et des pages vidées rapidement du cache. Surveillez-le séparément par nœud NUMA et considérez les chutes brutales comme un manque de mémoire.","tags":["memory","cache","ple"]},{"metric":"Taux de succès du buffer cache","warn":"< 98%","crit":"< 95%","tool":"sys.dm_os_performance_counters","note":"Le buffer cache hit ratio est le pourcentage de pages lues en mémoire sans accès disque. Dans SQL Server, cette valeur est généralement très élevée ; évaluez-la avec le PLE. Comme elle peut être trompeuse isolément, utilisez le PLE comme métrique principale pour analyser un goulet d'étranglement mémoire.","tags":["memory","cache"]},{"metric":"Processus bloqués","warn":">= 1 (> 5 s)","crit":">= 1 (> 30 s)","tool":"sys.dm_exec_requests (blocking_session_id)","note":"Lorsqu'un verrou détenu par une session fait attendre les autres, une chaîne de blocage se forme et entraîne un ralentissement de toute l'application. Un blocage prolongé provient généralement de transactions non validées ou d'un niveau d'isolation inadapté. Identifiez le head blocker avec le blocked process report.","tags":["lock","blocking","concurrency"]},{"metric":"Utilisation du journal des transactions (%used)","warn":"> 75%","crit":"> 90%","tool":"sys.dm_db_log_space_usage / DBCC SQLPERF(LOGSPACE)","note":"Lorsque le journal des transactions est plein, la base ne peut plus écrire et s'arrête avec l'erreur 9002. En mode de récupération FULL, le log continue de croître si les sauvegardes de log ne sont pas effectuées. Surveillez la fréquence des sauvegardes de log et le nombre de VLF pour vous assurer que le log est tronqué et réutilisé.","tags":["log","disk","recovery"]},{"metric":"Utilisation de tempdb (%used)","warn":"> 80%","crit":"> 95%","tool":"sys.dm_db_file_space_usage","note":"tempdb est partagée pour les tables temporaires, les tris, le version store et les opérations de hachage. Lorsqu'elle est pleine, les requêtes échouent sur tout le serveur. Une croissance soudaine provient généralement d'une grosse requête ou d'une transaction snapshot de longue durée ; surveillez séparément la taille du version store.","tags":["tempdb","disk","sort"]},{"metric":"Requête la plus longue","warn":"> 60 s","crit":"> 300 s","tool":"sys.dm_exec_requests / Query Store","note":"Une seule requête longue peut affecter d'autres traitements en conservant des verrous et en consommant des ressources. Sur les systèmes OLTP, les requêtes durant plusieurs minutes proviennent généralement d'un index manquant, du parameter sniffing ou d'un mauvais plan. Analysez les régressions de plan avec Query Store.","tags":["query","performance"]},{"metric":"Redo queue de l'Availability Group","warn":"> 1 MB","crit":"> 100 MB","tool":"sys.dm_hadr_database_replica_states","note":"La redo queue indique la quantité de log du réplica secondaire encore en attente d'application. Une redo queue élevée allonge le temps de récupération après un failover (RTO) et signifie des données obsolètes sur les réplicas lisibles. Examinez-la avec redo_rate pour prévoir quand le retard sera résorbé.","tags":["replication","ag","lag"]},{"metric":"Taux d'interblocages","warn":"> 1 / min","crit":"> 5 / min","tool":"XEvent system_health / compteurs de performance","note":"Un interblocage se produit lorsque deux transactions attendent mutuellement leurs verrous ; SQL Server le résout en choisissant l'une d'elles comme victime. Des interblocages occasionnels peuvent survenir, mais des interblocages fréquents indiquent un ordre de verrouillage incohérent. Extrayez le deadlock graph de la session extended events system_health et corrigez les requêtes en conflit.","tags":["lock","deadlock","concurrency"]},{"metric":"Utilisation des worker threads","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_schedulers / max worker threads","note":"Lorsque le pool de worker threads est épuisé, les nouvelles requêtes font la queue sur l'attente THREADPOOL et le serveur peut cesser de répondre. Un blocage prolongé ou des connexions simultanées excessives en sont généralement la cause. Réduisez la pression sur les threads en résolvant les chaînes de blocage.","tags":["cpu","thread","load"]},{"metric":"Utilisation CPU","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_ring_buffers / Performance Monitor","note":"Un CPU durablement élevé peut provenir de requêtes lourdes, d'index manquants ou de mauvais plans. Les pics brefs sont normaux, mais une saturation prolongée provoque la mise en file des requêtes. Identifiez les requêtes les plus consommatrices de CPU avec sys.dm_exec_query_stats.","tags":["cpu","load"]},{"metric":"Âge de la dernière sauvegarde réussie","warn":"> 24 heures (full)","crit":"> 48 heures (full)","tool":"msdb.dbo.backupset","note":"L'âge de la sauvegarde détermine le dernier point auquel vous pouvez revenir et votre RPO. En mode de récupération FULL, la restauration à un instant donné est possible avec les sauvegardes de log. Surveillez la chaîne des sauvegardes full, différentielles et de log pour garantir la restaurabilité, et effectuez des tests de restauration réguliers.","tags":["backup","recovery","rpo"]},{"metric":"Latence d'IO disque (lecture/écriture moy.)","warn":"> 15 ms","crit":"> 30 ms","tool":"sys.dm_io_virtual_file_stats","note":"La latence moyenne de lecture/écriture par fichier indique les performances du stockage. Environ 15 ms sont attendues pour les fichiers de données, et des valeurs plus faibles pour les fichiers de log. Une latence durablement élevée indique un goulet d'étranglement du stockage ; identifiez les fichiers les plus lents avec sys.dm_io_virtual_file_stats.","tags":["io","disk","latency"]}]},"reference/alerts-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"alerts":[{"metric":"Ratio dirty du cache WiredTiger","warn":"> 5%","crit":"> 20%","tool":"db.serverStatus().wiredTiger.cache","note":"Le ratio dirty du cache est le pourcentage de pages modifiées du cache WiredTiger pas encore écrites sur disque. Au-delà de 20 %, il y a pression d'éviction et goulet d'étranglement en écriture ; MongoDB oblige les threads applicatifs à effectuer l'éviction. Évaluez ensemble la taille du cache et le débit d'écriture disque.","tags":["memory","cache","wiredtiger"]},{"metric":"Utilisation du cache WiredTiger (used %)","warn":"> 80%","crit":"> 95%","tool":"db.serverStatus().wiredTiger.cache","note":"Lorsque l'utilisation du cache dépasse la cible d'éviction de 80 %, WiredTiger commence à évincer des pages de manière agressive et les performances chutent. Si le working set ne tient pas dans le cache, les lectures physiques augmentent. Revoyez le réglage wiredTigerCacheSizeGB et la mémoire du serveur.","tags":["memory","cache","wiredtiger"]},{"metric":"Tickets disponibles (read/write tickets)","warn":"available/totalTickets < 20 % ou queueLength > 0 (durablement)","crit":"available = 0 et queueLength ne cesse d'augmenter","tool":"db.serverStatus().wiredTiger.concurrentTransactions","note":"WiredTiger limite les opérations de lecture et d'écriture simultanées par un système de tickets. Avant 7.0, la valeur par défaut était fixe à 128 tickets ; en 7.0+, le nombre de tickets est ajusté dynamiquement (storageEngineConcurrencyAdjustmentAlgorithm: throughputProbing), surveillez donc le ratio available/totalTickets et queueLength plutôt que le nombre absolu. L'épuisement des tickets et la mise en file des opérations signalent un goulet d'étranglement sérieux ; il est généralement causé par des requêtes lentes ou une saturation des E/S disque.","tags":["concurrency","ticket","bottleneck"]},{"metric":"Retard de réplication","warn":"> 10 s","crit":"> 60 s","tool":"rs.printSecondaryReplicationInfo()","note":"Le retard de réplication indique, en secondes, le retard d'un membre secondary sur le primaire. Un retard élevé signifie des données obsolètes selon la read preference, et un risque de perte de données lors d'un failover. Il est généralement causé par des goulets d'étranglement d'E/S disque ou réseau sur le secondaire, ou par des opérations longues.","tags":["replication","lag","replicaset"]},{"metric":"Utilisation des connexions (connections %)","warn":"> 80%","crit":"> 90%","tool":"db.serverStatus().connections","note":"Des connexions actives approchant la limite haute configurée entraînent le rejet des nouvelles connexions. Chaque connexion consommant de la mémoire et un thread, un nombre élevé crée aussi une pression sur les ressources. Évitez les tempêtes de connexions en dimensionnant correctement le pool de connexions côté application.","tags":["connection","limit","pool"]},{"metric":"Taux de défauts de page","warn":"> 100 / s","crit":"> 1000 / s","tool":"db.serverStatus() / extra_info page_faults","note":"Un défaut de page se produit lorsque des données ne sont pas en mémoire et doivent être lues sur disque. Un taux élevé montre que le working set ne tient pas en mémoire et que le système est limité par le disque. Des valeurs durablement élevées nécessitent une augmentation de la mémoire ou une optimisation des requêtes/index.","tags":["memory","disk","pagefault"]},{"metric":"Opérations en file d'attente","warn":"> 10","crit":"> 50","tool":"db.serverStatus().globalLock.currentQueue","note":"globalLock currentQueue indique le nombre de lectures/écritures mises en file en attente d'un verrou ou d'une ressource. Une file durablement pleine indique que le serveur ne suit pas la charge entrante. Examinez-la avec l'épuisement des tickets et les E/S disque pour déterminer l'origine du goulet d'étranglement.","tags":["concurrency","queue","lock"]},{"metric":"Fenêtre de l'oplog","warn":"< 24 heures","crit":"< 6 heures","tool":"rs.printReplicationInfo()","note":"La fenêtre de l'oplog est l'intervalle de temps couvert par l'oplog et détermine combien de temps un secondaire peut rester déconnecté tout en pouvant se resynchroniser. Si la fenêtre se réduit, un membre qui prend du retard nécessite une resynchronisation complète. La fenêtre raccourcit à mesure que la charge d'écriture augmente ; agrandissez l'oplog en conséquence.","tags":["oplog","replication","recovery"]},{"metric":"Requête la plus longue","warn":"> 30 s","crit":"> 120 s","tool":"db.currentOp() / slow query log","note":"Les requêtes longues affectent les autres opérations en consommant des ressources et en conservant des verrous. Elles proviennent généralement de requêtes effectuant un parcours de collection (COLLSCAN) en raison d'un index manquant. Trouvez les opérations lentes avec le profiler et currentOp et ajoutez des index adaptés.","tags":["query","performance","index"]},{"metric":"Utilisation CPU","warn":"> 80%","crit":"> 90%","tool":"node_exporter / mongostat","note":"Un CPU durablement élevé peut provenir de requêtes sans index, d'opérations d'agrégation lourdes ou de connexions excessives. Les pics brefs sont normaux, mais une saturation prolongée provoque la mise en file des opérations. Utilisez le profiler pour identifier et optimiser les requêtes les plus consommatrices de CPU.","tags":["cpu","load"]},{"metric":"Utilisation disque (dbPath)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"Un répertoire de données plein empêche MongoDB d'écrire et peut entraîner une corruption. Bien que la compression WiredTiger aide, le journal et les collections en croissance peuvent remplir rapidement le disque. Planifiez la capacité et purgez automatiquement les données inutiles avec des index TTL.","tags":["disk","capacity"]},{"metric":"Âge de la dernière sauvegarde réussie","warn":"> 24 heures","crit":"> 48 heures","tool":"mongodump / Ops Manager / enregistrement de snapshot","note":"L'âge de la sauvegarde détermine le dernier point auquel vous pouvez revenir et votre RPO. Combinée à l'oplog, la restauration à un instant donné devient possible. Prendre des sauvegardes ne suffit pas ; validez-les par des tests de restauration réguliers et assurez-vous que des snapshots cohérents sont pris dans les clusters shardés.","tags":["backup","recovery","rpo"]}]},"reference/alerts-redis.json":{"engineId":"redis","engine":"Redis","order":6,"alerts":[{"metric":"Utilisation mémoire (used_memory vs maxmemory)","warn":"> 80%","crit":"> 95%","tool":"INFO memory / redis_exporter","note":"Le rapport entre used_memory et maxmemory indique la part de la mémoire de Redis utilisée. Lorsque la limite est atteinte, des clés sont évincées ou les écritures rejetées selon maxmemory-policy. Suivez la croissance de la mémoire pour éviter les OOM inattendus et les vagues d'éviction.","tags":["memory","maxmemory","capacity"]},{"metric":"Keyspace hit ratio","warn":"< 90%","crit":"< 80%","tool":"INFO stats (keyspace_hits/misses)","note":"Le hit ratio est le pourcentage de clés demandées trouvées dans le cache ; il se calcule à partir de keyspace_hits et keyspace_misses. Un ratio faible signale une utilisation inefficace du cache, des TTL inadaptés ou un cache froid. Pour les charges de cache, ce ratio doit être aussi élevé que possible.","tags":["cache","hit","performance"]},{"metric":"Clés évincées (evicted_keys)","warn":"> 1 / s","crit":"> 100 / s","tool":"INFO stats (evicted_keys)","note":"evicted_keys est le nombre de clés supprimées selon maxmemory-policy parce que la mémoire est pleine. Une éviction continue montre que la mémoire est insuffisante et que des données importantes peuvent être perdues. Réduisez l'éviction en augmentant la mémoire ou en revoyant la stratégie de TTL ou le modèle de données.","tags":["memory","eviction","maxmemory"]},{"metric":"Clients bloqués (blocked_clients)","warn":"> 50","crit":"> 200","tool":"INFO clients (blocked_clients)","note":"blocked_clients est le nombre de clients en attente sur des commandes bloquantes telles que BLPOP/BRPOP/WAIT. Des valeurs supérieures à la normale signalent des consommateurs qui ne suivent pas ou un arriéré croissant dans la file. Pour les charges basées sur des files, vérifiez l'équilibre producteurs/consommateurs.","tags":["client","blocking","queue"]},{"metric":"Ratio de fragmentation mémoire (mem_fragmentation_ratio)","warn":"> 1.5","crit":"> 2.0","tool":"INFO memory","note":"Le ratio de fragmentation est la mémoire obtenue du système d'exploitation divisée par la mémoire utilisée par Redis. Au-dessus de 1.5, il indique une fragmentation excessive ; en dessous de 1.0, il indique que la mémoire déborde dans le swap, ce qui est très dangereux. Vous pouvez réduire la fragmentation en activant activedefrag ou en redémarrant.","tags":["memory","fragmentation"]},{"metric":"Statut du lien master (master_link_status)","warn":"down (< 10 s)","crit":"down (> 30 s)","tool":"INFO replication (master_link_status)","note":"master_link_status indique l'état (up/down) de la connexion du réplica au master. 'down' signifie que la réplication est rompue et que le réplica sert des données obsolètes. Les déconnexions longues nécessitent une resynchronisation complète ; vérifiez immédiatement le réseau et l'état du master.","tags":["replication","master","connection"]},{"metric":"Retard de réplication (différence de master_repl_offset)","warn":"> 1 MB","crit":"> 10 MB","tool":"INFO replication (offset diff)","note":"La différence entre les offsets du master et du réplica indique, en octets, le retard du réplica. Une différence importante signifie un risque de perte de données lors d'un failover et des incohérences sur les réplicas en lecture. Examinez la latence réseau et la charge du réplica pour vous assurer que l'écart se résorbe.","tags":["replication","lag","offset"]},{"metric":"Statut du dernier bgsave RDB (rdb_last_bgsave_status)","warn":"err (dernière tentative)","crit":"err (> 24 heures)","tool":"INFO persistence","note":"rdb_last_bgsave_status indique si le dernier snapshot en arrière-plan a réussi (ok). Un bgsave en échec signifie que la persistance ne fonctionne pas et qu'il existe un risque de perte de données au redémarrage. Cela provient généralement d'un disque plein ou d'une mémoire insuffisante pour le fork.","tags":["backup","persistence","rdb"]},{"metric":"Utilisation des connexions (connected_clients)","warn":"> 80 % de maxclients","crit":"> 90 % de maxclients","tool":"INFO clients / maxclients","note":"Un connected_clients approchant la limite maxclients entraîne le rejet des nouvelles connexions. Des fuites de connexions ou un pool mal configuré peuvent provoquer des pics soudains. Configurez correctement le pool de connexions côté application et fermez les connexions inactives.","tags":["connection","limit","client"]},{"metric":"Latence instantanée des commandes","warn":"> 1 ms","crit":"> 10 ms","tool":"redis-cli --latency / LATENCY DOCTOR","note":"Redis étant monothread, la latence des commandes est généralement de l'ordre de la microseconde. Une latence en hausse peut provenir de commandes lentes (KEYS, grands SORT), d'un blocage pendant le fork ou de l'utilisation du swap. Trouvez la source avec le slowlog et les commandes LATENCY.","tags":["latency","performance","slowlog"]},{"metric":"Utilisation CPU","warn":"> 80 % (un cœur)","crit":"> 90 % (un cœur)","tool":"top / redis_exporter","note":"Le thread principal de Redis utilisant un seul cœur, la saturation de ce cœur ralentit tout le serveur. Un CPU élevé peut provenir de commandes coûteuses, de connexions trop nombreuses ou de scripts Lua. Trouvez les commandes coûteuses avec SLOWLOG et, si nécessaire, répartissez la charge sur plusieurs instances.","tags":["cpu","load"]},{"metric":"Connexions rejetées (rejected_connections)","warn":"> 0","crit":"> 10 / min","tool":"INFO stats (rejected_connections)","note":"rejected_connections est le nombre de connexions rejetées parce que la limite maxclients a été atteinte. Une valeur supérieure à zéro signifie que des clients ne sont pas servis et que des erreurs se produisent dans l'application. Examinez immédiatement la valeur de maxclients et les fuites de connexions.","tags":["connection","error","limit"]}]},"reference/alerts-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"alerts":[{"metric":"Utilisation du heap JVM","warn":"> 75%","crit":"> 85%","tool":"_nodes/stats/jvm / elasticsearch_exporter","note":"L'utilisation du heap JVM indique la pression mémoire du nœud ; des valeurs durablement supérieures à 75 % signifient des GC fréquents et un possible OutOfMemory. Si le heap dépasse 85 %, le nœud peut cesser de répondre ou quitter le cluster. Dimensionnez le heap à environ la moitié de la mémoire du serveur, sans dépasser 30-31 GB.","tags":["memory","jvm","heap"]},{"metric":"Nombre de shards non assignés (unassigned shards)","warn":">= 1 (réplica)","crit":">= 1 (primaire)","tool":"_cluster/health / _cat/shards","note":"Des shards non assignés signifient qu'une partie des données est inaccessible ou non répliquée. Un shard primaire non assigné signifie un risque de perte de données ; un réplica non assigné signifie une perte de redondance, et le cluster passe en yellow/red. Trouvez la raison avec _cluster/allocation/explain.","tags":["shard","cluster","health"]},{"metric":"Utilisation par rapport aux disk watermarks","warn":"> 85 % (low)","crit":"> 90 % (high) / > 95 % (flood_stage)","tool":"_nodes/stats/fs / _cat/allocation","note":"Elasticsearch agit de manière graduelle selon l'utilisation disque : au low watermark de 85 %, il cesse d'allouer de nouveaux shards au nœud ; au high watermark de 90 %, il déplace les shards existants vers d'autres nœuds ; et au flood_stage de 95 %, il pose un blocage read_only_allow_delete sur les index. En 8.x, sur les gros disques, les valeurs max_headroom (low 200GB / high 150GB / flood 100GB libres) priment sur les pourcentages. En 7.4+, le blocage est levé automatiquement une fois l'utilisation disque repassée sous le high watermark ; libérez néanmoins de l'espace disque avant que les watermarks soient dépassés.","tags":["disk","watermark","capacity"]},{"metric":"Opérations rejetées du thread pool (rejected)","warn":"> 0 (write/search)","crit":"> 100 / min","tool":"_cat/thread_pool / _nodes/stats/thread_pool","note":"Lorsqu'une file de thread pool est pleine, les requêtes entrantes sont rejetées ; un rejet dans le pool write signifie une indexation perdue, dans le pool search une requête en échec. Des rejets persistants indiquent que le nœud ne suit pas la charge entrante. Équilibrez la charge en réduisant la taille des bulks et la concurrence.","tags":["threadpool","rejected","load"]},{"metric":"Durée des pauses GC (old gen GC pause)","warn":"> 1 s","crit":"> 5 s","tool":"Logs GC / _nodes/stats/jvm","note":"Pendant les longues pauses GC, le nœud ne peut pas répondre et peut perdre sa connexion au master, ce qui déclenche une réallocation des shards. Des GC d'old generation fréquents et longs sont un symptôme de pression sur le heap. Revoyez la taille du heap et l'utilisation gourmande en mémoire des agrégations et de fielddata.","tags":["jvm","gc","pause"]},{"metric":"Latence d'indexation","warn":"> 50 ms","crit":"> 200 ms","tool":"_nodes/stats/indices (index_time/index_total)","note":"Le temps moyen d'indexation indique les performances d'écriture des documents ; il se calcule en divisant index_time_in_millis par index_total. Des valeurs en hausse indiquent un goulet d'étranglement d'IO disque, une chaîne d'analyse lourde ou une pression de refresh. Optimisez la taille des bulks et le réglage refresh_interval.","tags":["indexing","latency","performance"]},{"metric":"Latence de recherche","warn":"> 100 ms","crit":"> 500 ms","tool":"_nodes/stats/indices (query_time/query_total)","note":"Le temps moyen des requêtes reflète directement les performances de recherche perçues par les utilisateurs ; il se calcule en divisant query_time_in_millis par query_total. Des valeurs en hausse peuvent provenir d'agrégations lourdes, d'un trop grand nombre de shards ou d'un cache froid. Activez le slow log pour trouver les requêtes coûteuses.","tags":["search","latency","performance"]},{"metric":"Tâches en attente","warn":"> 10","crit":"> 100","tool":"_cluster/pending_tasks","note":"Les tâches en attente sont des modifications du cluster state que le nœud master n'a pas encore appliquées (mapping, allocation des shards, etc.). Une accumulation constante indique que le master est surchargé ou que le cluster state est devenu trop volumineux. Soulagez le master en réduisant un nombre excessif de shards.","tags":["cluster","master","queue"]},{"metric":"Déclenchements de circuit breaker","warn":"> 0 (tripped)","crit":"> 10 / min","tool":"_nodes/stats/breakers","note":"Les circuit breakers annulent une requête pour empêcher une opération de consommer trop de mémoire et de mener le nœud à un OutOfMemory. Des déclenchements fréquents indiquent une pression mémoire due à des agrégations lourdes ou à de grosses requêtes. Surveillez le compteur tripped et allégez les requêtes problématiques.","tags":["memory","breaker","jvm"]},{"metric":"État de santé du cluster (cluster status)","warn":"yellow","crit":"red","tool":"_cluster/health","note":"L'état du cluster peut être green (tous les shards assignés), yellow (réplicas manquants) ou red (primaires manquants, données inaccessibles). Red exige une action immédiate car une partie des données est perdue ou inaccessible. Yellow indique une perte de redondance et ne doit pas être ignoré.","tags":["cluster","health","shard"]},{"metric":"Utilisation CPU","warn":"> 80%","crit":"> 90%","tool":"_nodes/stats/os / node_exporter","note":"Un CPU durablement élevé peut provenir de requêtes lourdes, d'une indexation excessive ou d'opérations de merge. Les pics brefs sont normaux, mais une saturation prolongée provoque la mise en file des requêtes et de l'indexation. Identifiez les opérations les plus consommatrices de CPU avec l'API hot threads (_nodes/hot_threads).","tags":["cpu","load"]},{"metric":"Âge du dernier snapshot réussi","warn":"> 24 heures","crit":"> 48 heures","tool":"_snapshot/_status / SLM","note":"L'âge du snapshot détermine le dernier point auquel vous pouvez revenir et votre RPO. Des sauvegardes automatiques et régulières doivent être assurées avec Snapshot Lifecycle Management (SLM). Si des snapshots en échec ou ignorés passent inaperçus, les données ne peuvent pas être récupérées en cas de sinistre ; vérifiez régulièrement l'état des snapshots.","tags":["backup","snapshot","rpo"]}]},"cloud/cloud-ops-1.json":{"category":"Connexion, sauvegarde et réplica","categoryId":"cloud-core","order":1,"providers":[{"id":"aws-rds","name":"AWS RDS","color":"#B85C00"},{"id":"aws-aurora","name":"Aurora","color":"#7B3FE4"},{"id":"gcp-cloudsql","name":"Cloud SQL","color":"#C5372C"},{"id":"azure-sql","name":"Azure SQL","color":"#0067B8"},{"id":"mongodb-atlas","name":"Atlas","color":"#00843D"}],"concepts":[{"id":"list-instances","title":"Lister les instances / bases","aliases":["list","instances","list all"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'","note":"Liste les identifiants de toutes les instances RDS. Ajoutez --region pour cibler une région.","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --query 'DBClusters[].DBClusterIdentifier'","note":"Liste les identifiants des clusters Aurora ; consultez le champ DBClusterMembers pour voir les membres du cluster.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances list","note":"Liste toutes les instances Cloud SQL du projet. Ajoutez --project pour cibler un projet.","danger":"safe"},"azure-sql":{"command":"az sql db list -g <rg> -s <server>","note":"Liste les bases du groupe de ressources (-g) et du serveur logique (-s) indiqués. Utilisez 'az sql server list' pour lister les serveurs.","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters list","note":"Liste tous les clusters du projet Atlas. Ajoutez --projectId pour cibler un projet.","danger":"safe"}}},{"id":"connect","title":"Se connecter à une instance (trouver l'endpoint + se connecter)","aliases":["connect","endpoint","connection string","connect to"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --db-instance-identifier <id> --query 'DBInstances[0].Endpoint.Address'","note":"Renvoie l'adresse de connexion (host) de l'instance. Obtenez le port depuis Endpoint.Port et connectez-vous avec le client psql/mysql.","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --db-cluster-identifier <id> --query 'DBClusters[0].Endpoint'","note":"Renvoie le writer endpoint du cluster. Utilisez le champ ReaderEndpoint pour un accès en lecture seule.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances describe <instance> --format='value(ipAddresses[].ipAddress)'","note":"Renvoie la ou les adresses IP de l'instance. Cloud SQL Auth Proxy est recommandé pour des connexions sécurisées.","danger":"safe"},"azure-sql":{"command":"az sql db show-connection-string -s <server> -n <db> -c ado.net","note":"Génère la chaîne de connexion pour le type de client indiqué (-c : ado.net, jdbc, odbc, php, etc.) ; vous complétez l'utilisateur/le mot de passe.","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters connectionStrings describe <clusterName>","note":"Renvoie les chaînes de connexion standard et SRV du cluster. Une entrée dans l'IP access list et un utilisateur de base de données sont requis.","danger":"safe"}}},{"id":"take-snapshot","title":"Effectuer une sauvegarde / un snapshot manuel","aliases":["snapshot","backup","backup"],"providers":{"aws-rds":{"command":"aws rds create-db-snapshot --db-instance-identifier <id> --db-snapshot-identifier <snap-id>","note":"Prend un snapshot manuel de l'instance (conservé jusqu'à sa suppression manuelle). Une brève suspension des E/S peut se produire.","danger":"caution"},"aws-aurora":{"command":"aws rds create-db-cluster-snapshot --db-cluster-identifier <id> --db-cluster-snapshot-identifier <snap-id>","note":"Prend un snapshot manuel du cluster Aurora. Les sauvegardes Aurora étant continues, il s'agit d'une copie figée.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql backups create --instance=<instance>","note":"Crée une sauvegarde à la demande de l'instance. Un bref impact sur les performances est possible pendant l'opération.","danger":"caution"},"azure-sql":{"command":"az sql db export -s <server> -n <db> -u <user> -p <pwd> --admin-user <adm> --admin-password <pwd> --storage-key <key> --storage-key-type StorageAccessKey --storage-uri https://<acct>.blob.core.windows.net/<container>/<file>.bacpac","note":"Azure SQL est sauvegardé automatiquement ; pour une sauvegarde manuelle portable, cette commande exporte la base au format BACPAC. La restauration à un instant donné est une fonctionnalité distincte.","danger":"caution"},"mongodb-atlas":{"command":"atlas backups snapshots create <clusterName>","note":"Lance un snapshot de sauvegarde à la demande du cluster. Cloud Backup doit être activé.","danger":"caution"}}},{"id":"restore-pitr","title":"Restaurer à un instant donné (point-in-time restore)","aliases":["pitr","restore","point-in-time","roll back"],"providers":{"aws-rds":{"command":"aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier <src> --target-db-instance-identifier <new> --restore-time 2026-07-03T10:00:00Z","note":"Crée une NOUVELLE instance à partir de l'instance source à l'instant indiqué ; elle n'écrase pas l'instance existante. Utilisez --use-latest-restorable-time pour restaurer au point le plus récent.","danger":"caution"},"aws-aurora":{"command":"aws rds restore-db-cluster-to-point-in-time --source-db-cluster-identifier <src> --db-cluster-identifier <new> --restore-to-time 2026-07-03T10:00:00Z","note":"Restaure le cluster Aurora à l'instant indiqué sous la forme d'un nouveau cluster. Vous devez ensuite ajouter des instances avec create-db-instance.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances clone <src> <new> --point-in-time '2026-07-03T10:00:00Z'","note":"Crée un clone de l'instance source à l'instant indiqué sous la forme d'une nouvelle instance. La journalisation binaire/WAL doit être activée sur la source.","danger":"caution"},"azure-sql":{"command":"az sql db restore -g <rg> -s <server> -n <db> --dest-name <new-db> --time '2026-07-03T10:00:00'","note":"Restaure la base à l'instant indiqué sous la forme d'une NOUVELLE base ; la base source est préservée. L'heure est en UTC.","danger":"caution"},"mongodb-atlas":{"command":"atlas backups restores start pointInTime --clusterName <cluster> --pointInTimeUTCMillis <ms> --targetClusterName <target> --targetProjectId <projId>","note":"Lance une restauration à l'heure UTC indiquée via Continuous Cloud Backup. Le cluster cible est écrasé ; procédez avec prudence.","danger":"danger"}}},{"id":"read-replica","title":"Créer un réplica en lecture","aliases":["replica","read replica","read replica"],"providers":{"aws-rds":{"command":"aws rds create-db-instance-read-replica --db-instance-identifier <replica-id> --source-db-instance-identifier <src>","note":"Crée un réplica en lecture asynchrone à partir de l'instance source. Utilisez --source-region pour une copie inter-régions.","danger":"caution"},"aws-aurora":{"command":"aws rds create-db-instance --db-instance-identifier <reader-id> --db-cluster-identifier <cluster> --engine aurora-postgresql --db-instance-class db.r6g.large","note":"Dans Aurora, un reader est une instance ajoutée au cluster ; il partage le stockage du cluster. Définissez la valeur engine en fonction du cluster (aurora-mysql / aurora-postgresql).","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <replica> --master-instance-name=<src> --region=<region>","note":"Crée un réplica en lecture à partir de l'instance primaire (master). La journalisation binaire doit être activée sur la source.","danger":"caution"},"azure-sql":{"command":"az sql db replica create -g <rg> -s <server> -n <db> --partner-server <target-server>","note":"Crée sur un autre serveur un secondaire lisible géo-répliqué actif de la base. Le serveur cible doit exister.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <clusterName> --file cluster.json","note":"Les clusters Atlas sont déjà des replica sets répliqués. Pour une capacité de lecture supplémentaire, ajoutez des nœuds readOnly ou analytics sous replicationSpecs et mettez à jour.","danger":"caution"}}},{"id":"failover","title":"Failover","aliases":["failover","fail over","switch to standby"],"providers":{"aws-rds":{"command":"aws rds reboot-db-instance --db-instance-identifier <id> --force-failover","note":"Sur une instance Multi-AZ, force un redémarrage qui déclenche un failover vers le standby. Prévoyez une brève interruption.","danger":"danger"},"aws-aurora":{"command":"aws rds failover-db-cluster --db-cluster-identifier <cluster> --target-db-instance-identifier <reader>","note":"Promeut un reader du cluster Aurora en nouveau writer. Si aucune cible n'est indiquée, un réplica adapté est choisi ; prévoyez une brève interruption.","danger":"danger"},"gcp-cloudsql":{"command":"gcloud sql instances failover <instance>","note":"Bascule une instance HA (haute disponibilité) vers la zone de standby. Fonctionne uniquement sur les instances avec HA activée ; une interruption se produit.","danger":"danger"},"azure-sql":{"command":"az sql failover-group set-primary -g <rg> -s <secondary-server> -n <fog-name>","note":"Transfère le rôle primaire au serveur secondaire dans un auto-failover group (failover planifié). Peut être forcé avec --allow-data-loss, au risque d'une perte de données.","danger":"danger"},"mongodb-atlas":{"command":"atlas clusters failover <clusterName>","note":"Atlas effectue le failover automatiquement ; cette commande déclenche un failover manuel à des fins de test. Le nœud primaire change et l'application doit se reconnecter.","danger":"danger"}}}]},"cloud/cloud-ops-2.json":{"category":"Configuration, scaling et supervision","categoryId":"cloud-config","order":2,"providers":[{"id":"aws-rds","name":"AWS RDS","color":"#B85C00"},{"id":"aws-aurora","name":"Aurora","color":"#7B3FE4"},{"id":"gcp-cloudsql","name":"Cloud SQL","color":"#C5372C"},{"id":"azure-sql","name":"Azure SQL","color":"#0067B8"},{"id":"mongodb-atlas","name":"Atlas","color":"#00843D"}],"concepts":[{"id":"scale-instance","title":"Modifier la taille de l'instance (scaling vertical)","aliases":["scale","size","resize","scale up","vertical"],"providers":{"aws-rds":{"command":"aws rds modify-db-instance --db-instance-identifier <id> --db-instance-class db.r6g.xlarge --apply-immediately","note":"--apply-immediately applique la modification maintenant et redémarre l'instance (brève interruption). Sans ce flag, la modification est appliquée lors de la prochaine fenêtre de maintenance. Sur les instances Multi-AZ, l'interruption est réduite à peu près à la durée du failover.","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-instance --db-instance-identifier <writer-or-reader-id> --db-instance-class db.r6g.2xlarge --apply-immediately","note":"Dans Aurora, chaque instance (writer et readers) est dimensionnée séparément ; c'est la taille de l'instance qui change, pas celle du cluster. Redimensionnez d'abord les readers, puis effectuez un failover pour remplacer le writer afin de minimiser l'interruption. Avec Serverless v2, le scaling se fait via les ACU min/max.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --tier db-custom-4-16384","note":"Un type de machine personnalisé s'indique sous la forme db-custom-CPU-MEMORY (exemple : 4 vCPU, 16384 MB de RAM). La modification redémarre l'instance, ce qui provoque une brève interruption. Des tiers prédéfinis (par ex. db-n1-standard-4) peuvent aussi être utilisés.","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --service-objective S3","note":"Dans le modèle DTU, utilisez un service objective tel que S3/P2 ; dans le modèle vCore, utilisez --edition GeneralPurpose --family Gen5 --capacity 4. Le scaling s'effectue en ligne ; une brève coupure de connexion de quelques secondes peut survenir vers la fin de l'opération.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --tier M30","note":"Atlas redimensionne les nœuds du replica set de manière progressive (rolling), sans interruption. Nécessite le flag --projectId ou un projet par défaut. En cas de réduction, surveillez les limites de disque et de mémoire.","danger":"caution"}}},{"id":"parameter-group","title":"Modifier les paramètres (parameter group / flags / server config)","aliases":["parameter","parameter group","flags","config","setting"],"providers":{"aws-rds":{"command":"aws rds modify-db-parameter-group --db-parameter-group-name <pg> --parameters \"ParameterName=max_connections,ParameterValue=500,ApplyMethod=pending-reboot\"","note":"Utilisez ApplyMethod=immediate pour les paramètres dynamiques et pending-reboot pour les paramètres statiques. Le parameter group doit être attaché à l'instance ; les modifications de paramètres statiques prennent effet après un redémarrage. Utilisez create-db-parameter-group pour créer un groupe.","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-cluster-parameter-group --db-cluster-parameter-group-name <cpg> --parameters \"ParameterName=innodb_lock_wait_timeout,ParameterValue=100,ApplyMethod=immediate\"","note":"Dans Aurora, les paramètres valables pour tout le cluster sont gérés avec le DB cluster parameter group et les paramètres propres à une instance avec le DB parameter group (modify-db-parameter-group). Les paramètres statiques nécessitent un redémarrage sur tout le cluster.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --database-flags max_connections=500,log_min_duration_statement=1000","note":"--database-flags définit TOUS les flags en une fois ; les flags définis auparavant qui ne figurent pas dans la liste sont réinitialisés, indiquez-les donc tous ensemble. Certains flags nécessitent un redémarrage de l'instance. Affichez les flags actuels avec 'gcloud sql instances describe <id>'.","danger":"caution"},"azure-sql":{"command":"az sql db configure -g <rg> -s <server> -n <db> ...   # DB-level settings via T-SQL: ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 4;","note":"Dans Azure SQL, au lieu de la configuration serveur classique, les réglages s'appliquent surtout en T-SQL : ALTER DATABASE SCOPED CONFIGURATION ... ; sp_configure au niveau serveur est limité. Pour les options d'elastic pool/de base, utilisez les flags de 'az sql db update'.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters advancedSettings update <name> --oplogMinRetentionHours 24 --oplogSizeMB 20480","note":"Dans Atlas, les arguments bas niveau du processus MongoDB ne peuvent être définis que de façon limitée via 'advancedSettings' (oplog, recherche plein texte, profondeur d'index, etc.). La plupart des paramètres serveur sont gérés par Atlas et ne peuvent pas être modifiés directement.","danger":"caution"}}},{"id":"monitoring","title":"Afficher les métriques et les performances","aliases":["monitoring","metric","observability","performance","insights"],"providers":{"aws-rds":{"command":"aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name CPUUtilization --dimensions Name=DBInstanceIdentifier,Value=<id> --start-time 2026-07-05T00:00:00Z --end-time 2026-07-05T01:00:00Z --period 300 --statistics Average","note":"Les métriques de base (CPU, connexions, IOPS, latence) se trouvent dans le namespace CloudWatch AWS/RDS. Pour une analyse au niveau des requêtes, activez Performance Insights : 'aws pi get-resource-metrics --service-type RDS ...' ou utilisez le tableau de bord Performance Insights dans la console.","danger":"safe"},"aws-aurora":{"command":"aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name AuroraReplicaLag --dimensions Name=DBInstanceIdentifier,Value=<reader-id> --start-time 2026-07-05T00:00:00Z --end-time 2026-07-05T01:00:00Z --period 60 --statistics Maximum","note":"Les métriques propres à Aurora (AuroraReplicaLag, VolumeReadIOPs, CommitLatency, buffer cache hit) se trouvent aussi dans le namespace AWS/RDS. Performance Insights est recommandé pour Aurora et fournit l'analyse des top SQL et des événements d'attente.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud monitoring time-series list --filter='metric.type=\"cloudsql.googleapis.com/database/cpu/utilization\" AND resource.labels.database_id=\"<project>:<id>\"' --interval-start-time=2026-07-05T00:00:00Z --interval-end-time=2026-07-05T01:00:00Z","note":"Les métriques Cloud SQL se trouvent dans Cloud Monitoring sous cloudsql.googleapis.com/... Pour une analyse au niveau des requêtes, activez Cloud SQL Query Insights (console ou API) ; il affiche les requêtes les plus lentes et les événements d'attente.","danger":"safe"},"azure-sql":{"command":"az monitor metrics list --resource <db-resource-id> --metric \"cpu_percent\" --interval PT5M --start-time 2026-07-05T00:00:00Z --end-time 2026-07-05T01:00:00Z","note":"Des métriques telles que cpu_percent, dtu_consumption_percent, storage_percent et connection_successful proviennent d'Azure Monitor. Pour les performances des requêtes, utilisez les tableaux de bord Query Performance Insight et Intelligent Insights (Azure SQL Analytics).","danger":"safe"},"mongodb-atlas":{"command":"atlas metrics processes <host>:<port> --granularity PT5M --period P1D --type SYSTEM_CPU_USER,QUERY_TARGETING_SCANNED_OBJECTS_PER_RETURNED","note":"Les mesures au niveau des processus s'obtiennent avec 'atlas metrics processes', celles des disques avec 'atlas metrics disks'. Pour les recommandations sur les requêtes lentes, utilisez Performance Advisor : 'atlas performanceAdvisor slowQueryLogs list' et 'atlas performanceAdvisor suggestedIndexes list'.","danger":"safe"}}},{"id":"maintenance-window","title":"Fenêtre de maintenance et gestion des mises à jour","aliases":["maintenance","maintenance window","window","update","patch"],"providers":{"aws-rds":{"command":"aws rds modify-db-instance --db-instance-identifier <id> --preferred-maintenance-window Sun:03:00-Sun:04:00   # apply pending maintenance now: aws rds apply-pending-maintenance-action --resource-identifier <arn> --apply-action system-update --opt-in-type immediate","note":"La fenêtre est en UTC, au format ddd:hh24:mi-ddd:hh24:mi, et doit durer au moins 30 minutes. Listez les maintenances en attente avec 'aws rds describe-pending-maintenance-actions' et appliquez-les immédiatement ou lors de la prochaine fenêtre avec apply-pending-maintenance-action.","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-cluster --db-cluster-identifier <cluster> --preferred-maintenance-window Sun:03:00-Sun:04:00","note":"Dans Aurora, la fenêtre de maintenance au niveau du cluster se définit avec modify-db-cluster et celle au niveau de l'instance avec modify-db-instance. Les mises à jour moteur/OS en attente se gèrent avec apply-pending-maintenance-action ; les montées de version du moteur peuvent nécessiter une interruption.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --maintenance-window-day SUN --maintenance-window-hour 3 --maintenance-release-channel production","note":"Définit le jour (MON..SUN), l'heure (0-23, heure locale de l'instance/UTC) et le canal de publication (production ou preview). Avec les flags --deny-maintenance-period-*, vous pouvez aussi définir une période pendant laquelle aucune maintenance n'est effectuée.","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --maint-config-id SQL_Default   # or custom: /subscriptions/<sub>/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_<Region>_DB_1","note":"La fenêtre de maintenance Azure SQL se gère en attribuant une Maintenance Configuration prédéfinie (fenêtres en soirée en semaine/le week-end au lieu de la valeur par défaut). Listez les configurations avec 'az maintenance public-configuration list'.","danger":"caution"},"mongodb-atlas":{"command":"atlas maintenanceWindows update --dayOfWeek 1 --hourOfDay 3 --projectId <projectId>   # defer: atlas maintenanceWindows defer","note":"dayOfWeek 1=dimanche ... 7=samedi, hourOfDay 0-23 (UTC). Utilisez 'atlas maintenanceWindows defer' pour reporter d'une semaine une maintenance en attente, et 'atlas maintenanceWindows start' pour la lancer immédiatement.","danger":"caution"}}},{"id":"encryption","title":"Afficher/configurer l'état du chiffrement (at-rest)","aliases":["encryption","encryption at rest","at-rest","kms","tde","cmek"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --db-instance-identifier <id> --query 'DBInstances[0].[StorageEncrypted,KmsKeyId]'   # at creation: aws rds create-db-instance ... --storage-encrypted --kms-key-id <kms-arn>","note":"Le chiffrement at-rest de RDS ne peut être activé qu'à la CRÉATION de l'instance ; pour l'activer sur une instance existante non chiffrée, faites une copie chiffrée d'un snapshot et restaurez à partir de celle-ci. La clé est gérée par AWS KMS.","danger":"caution"},"aws-aurora":{"command":"aws rds describe-db-clusters --db-cluster-identifier <cluster> --query 'DBClusters[0].[StorageEncrypted,KmsKeyId]'   # at creation: aws rds create-db-cluster ... --storage-encrypted --kms-key-id <kms-arn>","note":"Dans Aurora, le chiffrement se définit au niveau du cluster et uniquement à la création. Pour chiffrer un cluster existant non chiffré, copiez un snapshot en le chiffrant et restaurez-le. Toutes les instances et sauvegardes utilisent la même clé KMS.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <id> --disk-encryption-key projects/<project>/locations/<region>/keyRings/<ring>/cryptoKeys/<key>   # to view: gcloud sql instances describe <id> --format='value(diskEncryptionConfiguration.kmsKeyName)'","note":"Cloud SQL est chiffré par défaut avec une clé gérée par Google ; une clé gérée par le client (CMEK) ne peut être attribuée qu'à la création de l'instance avec --disk-encryption-key. Le compte de service Cloud SQL doit disposer de l'autorisation encrypt/decrypt sur la clé.","danger":"caution"},"azure-sql":{"command":"az sql db tde set -g <rg> -s <server> -d <db> --status Enabled   # or T-SQL: ALTER DATABASE <db> SET ENCRYPTION ON;   # status: az sql db tde show -g <rg> -s <server> -d <db>","note":"Dans Azure SQL, le chiffrement at-rest est assuré par Transparent Data Encryption (TDE), activé par défaut sur les nouvelles bases. Pour utiliser votre propre clé, configurez 'az sql server tde-key set' (Azure Key Vault / BYOK) au niveau du serveur.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters describe <name> --output json | jq '.encryptionAtRestProvider'   # enable BYOK: atlas encryptionAtRest enable AWS --awsKmsMasterKeyID <keyId> --awsKmsRegion <region> --awsKmsRoleID <roleId>","note":"Dans Atlas, les données sont chiffrées at-rest par défaut. En complément, l'envelope encryption avec une clé client (BYOK) via AWS KMS / Azure Key Vault / GCP KMS s'active pour tout le projet avec 'atlas encryptionAtRest' et s'applique au cluster.","danger":"caution"}}},{"id":"storage-autoscale","title":"Augmenter le stockage / activer l'autoscaling","aliases":["storage","disk space","autoscale","grow","disk"],"providers":{"aws-rds":{"command":"aws rds modify-db-instance --db-instance-identifier <id> --allocated-storage 200 --max-allocated-storage 1000 --apply-immediately","note":"--allocated-storage définit la taille actuelle ; --max-allocated-storage définit la limite haute de l'autoscaling du stockage. L'augmentation du stockage se fait en ligne ; toutefois, vous devez attendre au moins 6 heures entre deux modifications du stockage, et le stockage ne peut pas être réduit.","danger":"caution"},"aws-aurora":{"command":"# Aurora storage is automatic; no manual setting required\naws rds describe-db-clusters --db-cluster-identifier <cluster> --query 'DBClusters[0].AllocatedStorage'","note":"Le stockage Aurora évolue de façon entièrement automatique (par paliers de 10 GB, jusqu'à 128 TB selon le moteur) et croît avec l'utilisation ; --allocated-storage / --max-allocated-storage ne se définissent pas. Vous surveillez seulement l'utilisation ; aucune gestion de taille n'est nécessaire.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --storage-auto-increase --storage-size 200GB   # upper limit: --storage-auto-increase-limit 1000","note":"--storage-auto-increase active la croissance automatique lorsque le disque est presque plein ; --storage-auto-increase-limit définit la limite haute (0 = illimité). Le stockage peut seulement être augmenté, il NE PEUT PAS ÊTRE RÉDUIT ; choisissez avec soin la taille initiale et la limite.","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --max-size 500GB","note":"Dans Azure SQL, la taille maximale de la base se définit avec --max-size ; la base croît automatiquement dans l'espace alloué. La taille maximale dépend du service tier/de la limite vCore choisis. Dans le tier Hyperscale, le stockage évolue automatiquement et presque sans limite.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --diskSizeGB 200   # for autoscaling, diskGBEnabled=true via extended configuration (advancedSettings / API)","note":"La taille du disque s'augmente avec --diskSizeGB. Dans Atlas, l'autoscaling du stockage (Auto-scale storage) est activé par défaut sur les clusters M10 et supérieurs ; le disque croît automatiquement lorsqu'il est plein à 90 %. L'activation/la désactivation se gère dans la configuration du cluster (autoScaling.diskGBEnabled).","danger":"caution"}}}]},"guide/best-practices.json":{"topics":[{"id":"indexing-strategy","title":"Stratégie d'indexation","category":"Performances","summary":"Les index accélèrent les requêtes de lecture mais ajoutent un coût d'écriture et de stockage ; ajoutez-les en fonction du besoin et de mesures.","points":["Ciblez les colonnes des clauses WHERE, JOIN et ORDER BY ; placez la colonne la plus sélective en premier dans un index composite (règle du préfixe le plus à gauche).","Utilisez des covering indexes : incluez dans l'index toutes les colonnes nécessaires à la requête pour éliminer l'accès à la table (lookup).","Identifiez et supprimez les index inutilisés et redondants (doublons ou préfixes d'un autre index) ; chaque index ralentit INSERT/UPDATE/DELETE.","Évitez les index B-tree isolés sur des colonnes à faible cardinalité (par ex. sexe, booléen) ; utilisez un index partiel/filtré si nécessaire.","Vérifiez avec EXPLAIN/EXPLAIN ANALYZE que l'index est réellement utilisé ; une fonction ou une conversion de type désactive l'index (comme dans WHERE UPPER(name))."],"level":"junior"},{"id":"normalization","title":"Normalisation vs dénormalisation","category":"Conception","summary":"La normalisation garantit l'intégrité des données et l'absence de redondance ; la dénormalisation est un compromis délibéré en faveur des performances de lecture.","points":["Visez la 3NF par défaut dans les systèmes OLTP ; évitez la redondance des données et les anomalies de mise à jour.","Ne dénormalisez qu'en présence d'un goulet d'étranglement de performances mesuré, et justifiez-le.","Garantissez la cohérence des données dénormalisées avec des triggers, des vues matérialisées ou la couche applicative.","Pour les charges de reporting/d'analyse, envisagez un schéma dénormalisé séparé (schéma en étoile) ou un réplica en lecture.","La dénormalisation prématurée est une dette technique ; essayez d'abord de résoudre le problème avec le bon index et la bonne requête."],"level":"senior"},{"id":"data-types","title":"Choisir le bon type de données","category":"Conception","summary":"Le type de données correct et le plus petit possible améliore directement le stockage, la taille des index et les performances.","points":["Choisissez le plus petit type répondant au besoin : SMALLINT pour un âge, DECIMAL/NUMERIC pour l'argent (jamais FLOAT).","Stockez toujours les données temporelles avec un fuseau horaire (TIMESTAMPTZ / UTC) ; évitez de stocker l'heure locale.","Utilisez un ENUM ou une table de référence pour les ensembles de valeurs fixes et limités ; contraignez au lieu d'autoriser du texte libre.","Pour le texte de longueur variable, dimensionnez selon le besoin réel ou utilisez TEXT au lieu d'un VARCHAR(255) inutilement grand.","Si vous utilisez un UUID comme PK, surveillez la fragmentation des index ; envisagez des UUID séquentiels (v7) ou une identité BIGINT.","Choisissez délibérément la sémantique de NULL ; rendez explicite dans la conception la différence entre 'inconnu' et 'vide'."],"level":"junior"},{"id":"naming-conventions","title":"Conventions de nommage","category":"Conception","summary":"Un nommage cohérent facilite la lisibilité, la maintenance et la communication au sein de l'équipe ; fixez la norme dès le départ.","points":["Choisissez une convention et appliquez-la partout : snake_case est répandu et robuste face aux problèmes de sensibilité à la casse.","Tranchez entre singulier et pluriel pour les noms de tables ; préférez des noms de colonnes explicites et non abrégés.","Nommez les clés étrangères de façon à rendre la source évidente (par ex. customer_id) et donnez aux index/contraintes des préfixes standard (fk_, ix_, uq_).","Évitez d'utiliser des mots réservés (order, user, group) comme noms de tables/colonnes.","Rendez les colonnes booléennes explicites avec les préfixes is_/has_ ; ajoutez des suffixes cohérents tels que _at, _date aux colonnes de date."],"level":"junior"},{"id":"partitioning-strategy","title":"Stratégie de partitionnement","category":"Performances","summary":"Le partitionnement découpe les grandes tables en morceaux gérables ; il améliore la maintenance, la purge et les performances des requêtes.","points":["Choisissez la clé de partitionnement selon les schémas de requête et d'archivage ; le partitionnement RANGE par date est le plus courant pour les séries temporelles.","Vérifiez que le partition pruning fonctionne : les requêtes doivent inclure la clé de partitionnement dans la clause WHERE, sinon toutes les partitions sont parcourues.","Au lieu de supprimer les anciennes données, purgez-les instantanément et quasiment sans verrou avec DROP/DETACH de partition.","Un nombre excessif de partitions ajoute un coût de planification et de métadonnées ; choisissez une granularité raisonnable (mensuelle/hebdomadaire).","Mettez en place un job de maintenance qui crée automatiquement les partitions futures ; évitez les erreurs dues à des écritures sans partition correspondante."],"level":"senior"},{"id":"connection-pooling","title":"Pool de connexions","category":"Exploitation","summary":"Un pool de connexions élimine le coût élevé d'établissement des connexions et protège la base contre les afflux de connexions.","points":["N'ouvrez pas une nouvelle connexion par requête ; utilisez un pool dans l'application ou une couche intermédiaire (PgBouncer, HikariCP).","Dimensionnez le pool selon les cœurs CPU et la charge ; un pool surdimensionné étouffe la base (un petit pool est généralement préférable).","Définissez délibérément la durée de vie des connexions et les timeouts d'inactivité et d'acquisition ; activez la détection des fuites.","Planifiez le pool en fonction de la limite max_connections de la base ; tenez compte du total sur plusieurs instances applicatives.","Utilisez des transactions courtes pour que les connexions reviennent rapidement au pool ; n'attendez pas d'E/S externes dans une transaction."],"level":"senior"},{"id":"transaction-design","title":"Conception des transactions (courtes et atomiques)","category":"Performances","summary":"Les transactions doivent être courtes, atomiques et couvrir une seule unité logique de travail ; cela minimise la durée des verrous.","points":["Gardez les transactions aussi courtes que possible ; n'y englobez pas d'interaction utilisateur, d'appels d'API externes ni de longs calculs.","Regroupez dans une même transaction uniquement les opérations qui doivent réussir ou échouer ensemble.","Mettez toujours à jour les lignes dans le même ordre pour réduire les conflits de verrous (la base de la prévention des interblocages).","Évitez les verrous inutilement larges ; ne placez pas d'opérations en lecture seule dans des blocs de transaction inutiles.","Définissez un chemin de rollback clair en cas d'erreur ; ne laissez derrière vous ni commits partiels ni transactions ouvertes.","Validez les longues opérations de masse par lots ; une seule transaction géante provoque un gonflement du WAL/de l'undo."],"level":"senior"},{"id":"isolation-levels","title":"Choisir un niveau d'isolation","category":"Performances","summary":"Le niveau d'isolation fixe l'équilibre entre cohérence et concurrence ; choisissez-le délibérément selon les règles métier.","points":["Connaissez les types d'anomalies : dirty read, non-repeatable read, phantom read ; comprenez quel niveau empêche lesquelles.","Read Committed suffit pour la plupart des charges OLTP ; ne montez plus haut que si c'est vraiment nécessaire.","Utilisez Serializable ou un verrouillage explicite (SELECT ... FOR UPDATE) pour les opérations critiques sur l'argent/les stocks.","Sous Serializable, ajoutez une logique de réessai automatique pour les échecs de sérialisation.","Connaissez la différence de comportement entre moteurs MVCC et moteurs à verrous ; le même niveau peut offrir des garanties différentes selon le moteur."],"level":"expert"},{"id":"deadlock-prevention","title":"Prévention des interblocages","category":"Performances","summary":"Un interblocage se produit lorsque des transactions attendent mutuellement leurs ressources ; il peut être largement évité dès la conception.","points":["Accédez toujours aux ressources/lignes dans un ordre cohérent et identique ; les différences d'ordre sont la source la plus fréquente d'interblocages.","Gardez les transactions courtes et réduisez le nombre de verrous ; évitez les mises à jour inutilement larges.","Si nécessaire, rendez les conflits prévisibles par un verrouillage anticipé (SELECT ... FOR UPDATE).","Interceptez les erreurs d'interblocage dans l'application et réessayez automatiquement la transaction ; éliminer complètement les interblocages est souvent impossible.","Examinez régulièrement les logs/graphes d'interblocage ; éliminez les schémas récurrents en modifiant les index ou l'ordre d'accès."],"level":"expert"},{"id":"sharding-strategy","title":"Stratégie de sharding / scaling horizontal","category":"Conception","summary":"Le sharding dépasse les limites d'un serveur unique en répartissant les données sur plusieurs nœuds ; comme il ajoute une complexité importante, il doit être un dernier recours.","points":["Épuisez d'abord le scaling vertical, l'indexation, le cache et les réplicas en lecture ; ne shardez que si c'est vraiment nécessaire.","Choisissez la shard key avec soin : elle doit limiter les requêtes cross-shard et assurer une répartition uniforme, sans créer de hotspots.","Utilisez le consistent hashing ou des shards virtuels pour le rééquilibrage (resharding) ; anticipez la croissance dès le départ.","Les JOIN cross-shard et les transactions distribuées sont coûteux ; concevez le schéma pour les minimiser.","Résolvez la génération d'identifiants globalement uniques (ID séquentiels de type snowflake, etc.) et la couche de routage vers les shards."],"level":"expert"},{"id":"backup-strategy","title":"Stratégie de sauvegarde (3-2-1)","category":"Sauvegarde et HA","summary":"Une stratégie de sauvegarde solide est la dernière ligne de défense contre les sinistres ; une sauvegarde ne compte pas tant qu'elle n'a pas été testée.","points":["Appliquez la règle 3-2-1 : 3 copies, 2 supports différents, 1 copie hors site (offsite/autre région).","Combinez sauvegardes full + incrémentales et archivage WAL/binlog pour disposer de la restauration à un instant donné (PITR).","Testez régulièrement les sauvegardes en les restaurant RÉELLEMENT ; une sauvegarde non testée n'est pas une sauvegarde.","Chiffrez les sauvegardes au repos et en transit ; restreignez l'accès et fixez la durée de rétention par une politique.","Conservez une copie immuable ou isolée (air-gapped) contre les ransomwares."],"level":"junior"},{"id":"hadr-design","title":"Conception HA/DR et RPO/RTO","category":"Sauvegarde et HA","summary":"La conception de la haute disponibilité et de la reprise après sinistre commence par la définition des objectifs métier sous forme de métriques RPO et RTO.","points":["Définissez les objectifs RPO (perte de données acceptable) et RTO (durée d'interruption acceptable) avec le métier.","La réplication synchrone offre un RPO quasi nul mais ajoute de la latence ; l'asynchrone est performante mais comporte un risque de perte de données.","Mettez en place un failover automatique et vérifiez qu'il fonctionne réellement par des exercices de failover réguliers.","Utilisez un mécanisme de quorum/witness pour éviter le split-brain ; ne permettez jamais aux deux nœuds de devenir master.","Placez l'environnement DR dans une autre région physique ; soyez résilient à la panne d'une seule région/d'un seul data center."],"level":"expert"},{"id":"capacity-planning","title":"Planification de capacité et croissance","category":"Exploitation","summary":"La planification de capacité évite les interruptions surprises en provisionnant les ressources en avance sur la tendance de croissance.","points":["Mesurez dans le temps le rythme de croissance des données, les IOPS, le CPU, la mémoire et l'utilisation des connexions, et dégagez-en des tendances.","Définissez des alertes de seuil précoces sur l'utilisation disque (par ex. 75 %/85 %) ; agissez avant que l'espace soit épuisé.","Planifiez la croissance à partir de prévisions ; tenez compte des pics de charge et des variations saisonnières.","Revoyez régulièrement le nombre de connexions, le bloat des tables/index et les besoins d'archivage.","Décidez de la voie de scaling (vertical, réplicas, partitionnement/sharding) avant d'atteindre le goulet d'étranglement."],"level":"senior"},{"id":"migration-strategy","title":"Stratégie de migration (interruption minimale)","category":"Exploitation","summary":"Les migrations de schéma et de données doivent suivre un plan réversible qui minimise l'interruption, en ligne lorsque c'est possible.","points":["Concevez chaque migration pour qu'elle soit réversible et idempotente ; ne passez jamais en production sans plan de rollback.","Évitez les DDL bloquants sur les grandes tables ; utilisez des outils de modification de schéma en ligne (PG : pg-osc/pgroll ; MySQL : gh-ost, pt-osc).","Appliquez le schéma expand-contract pour des modifications rétrocompatibles et compatibles en avant : ajoutez d'abord, déployez, puis supprimez l'ancien.","Déplacez les gros volumes de données par petits lots ; ne créez pas de verrous ni de gonflement du WAL avec un seul UPDATE géant.","Répétez d'abord la migration dans un environnement proche de la production avec un volume de données réaliste et mesurez sa durée.","Consignez à l'avance dans un plan écrit le moment de la bascule, les étapes de validation et la décision de rollback."],"level":"senior"},{"id":"schema-versioning","title":"Versionnement du schéma (Flyway/Liquibase)","category":"Exploitation","summary":"Les modifications de schéma doivent être gérées par des migrations qui vivent avec le code, sont versionnées et appliquées automatiquement ; l'intervention manuelle est source d'erreurs.","points":["Versionnez toutes les modifications de schéma avec des outils tels que Flyway/Liquibase et conservez-les dans le contrôle de source.","N'exécutez pas de DDL manuellement en production ; chaque modification doit passer par un fichier de migration relu.","Gardez les migrations ordonnées et uniques ; ne modifiez jamais un fichier de migration déjà appliqué, corrigez par une nouvelle migration.","Appliquez et validez automatiquement les migrations dans le pipeline CI/CD ; garantissez la cohérence du schéma entre les environnements.","Faites évoluer le code applicatif et la version du schéma de concert ; adoptez le schéma expand-contract pour des transitions rétrocompatibles."],"level":"senior"},{"id":"least-privilege","title":"Principe du moindre privilège","category":"Sécurité","summary":"Chaque utilisateur et chaque application ne doit disposer que des privilèges nécessaires à sa tâche ; un excès de privilèges élargit la surface d'attaque.","points":["Utilisez pour l'application des comptes distincts aux privilèges limités ; ne vous connectez jamais en superuser/admin.","Accordez les privilèges à des rôles plutôt que directement aux utilisateurs ; gérez l'accès par l'appartenance aux rôles.","N'autorisez que les tables et opérations requises (SELECT/INSERT/UPDATE) ; évitez les GRANT ALL trop larges.","Revoyez périodiquement les privilèges et révoquez ceux qui sont inutilisés ; supprimez immédiatement l'accès au départ d'un employé/service.","N'intégrez pas de secrets (mots de passe, chaînes de connexion) dans le code ; utilisez un gestionnaire de secrets (vault) et renouvelez-les régulièrement."],"level":"junior"},{"id":"encryption-basics","title":"Bases du chiffrement (at-rest/in-transit)","category":"Sécurité","summary":"Les données doivent être chiffrées au stockage comme en transmission ; le chiffrement est une protection de base pour la conformité et contre les fuites de données.","points":["Imposez TLS en transit ; refusez les connexions non chiffrées et activez la vérification des certificats.","Appliquez le chiffrement au repos (TDE ou chiffrement disque/volume) pour qu'un disque/une sauvegarde volé ne puisse pas être lu.","Prenez la gestion des clés au sérieux : conservez les clés dans un KMS/HSM séparé et renouvelez-les régulièrement.","Chiffrez en plus au niveau applicatif (field-level) les champs très sensibles tels que les numéros de carte bancaire et les identifiants nationaux.","Chiffrez aussi les sauvegardes ; étendez la protection de la production aux sauvegardes et aux réplicas.","Masquez ou anonymisez les données sensibles dans les environnements de test/développement (data masking)."],"level":"senior"},{"id":"monitoring-basics","title":"Bases de la supervision (golden signals)","category":"Exploitation","summary":"Une supervision efficace consiste à collecter les bonnes métriques et à alerter sur des seuils pertinents ; elle détecte les problèmes avant que les utilisateurs ne les remarquent.","points":["Surveillez les golden signals : latence, trafic, taux d'erreur et saturation.","Ajoutez des métriques propres à la base : requêtes lentes, temps de verrouillage/d'attente, nombre de connexions, cache hit ratio, retard de réplication.","Associez les alertes à l'impact et à des seuils actionnables plutôt qu'aux symptômes ; les alertes bruyantes provoquent une lassitude face aux alertes.","Gardez le slow query log et les statistiques de requêtes (comme pg_stat_statements) activés en permanence et examinez-les régulièrement.","Conservez l'historique des métriques ; établissez une baseline pour les tendances et la planification de capacité.","Alertez toujours sur les conditions critiques telles que l'espace disque restant et la réussite des sauvegardes."],"level":"junior"},{"id":"query-design","title":"Conception des requêtes et prévention du N+1","category":"Performances","summary":"Une requête efficace ne récupère que les données nécessaires avec un minimum d'allers-retours ; le N+1 est le bug de performance le plus courant et le plus insidieux.","points":["Ne sélectionnez que les colonnes nécessaires au lieu de SELECT * ; cela améliore le réseau, la mémoire et les possibilités de covering index.","Évitez le problème N+1 : utilisez un JOIN ou une requête par lot (IN) au lieu de requêtes unitaires dans une boucle ; activez l'eager loading dans l'ORM.","Paginez les grands jeux de résultats ; préférez la pagination keyset (seek) à OFFSET sur les gros volumes.","Laissez autant que possible la logique de traitement à la requête ; ne récupérez pas toute la table pour filtrer/agréger dans l'application.","Examinez le plan réel et les estimations de lignes avec EXPLAIN ANALYZE ; éliminez les full table scans et les mauvaises estimations.","Appuyez les requêtes d'agrégation fréquentes et coûteuses sur une vue matérialisée ou une table de synthèse précalculée."],"level":"junior"},{"id":"large-table-management","title":"Gestion des grandes tables (archivage/partitionnement)","category":"Performances","summary":"Les tables en croissance continue ralentissent avec le temps les requêtes, la maintenance et les sauvegardes ; maîtrisez-les par l'archivage et le partitionnement.","points":["Séparez les données chaudes (actives) et froides (historiques) ; déplacez les anciennes données vers une table d'archive ou un stockage bon marché.","Partitionnez la table par date/plage ; purgez instantanément les anciennes données avec DROP de partition au lieu de DELETE.","Effectuez les grosses suppressions/mises à jour par petits lots ; un seul DELETE géant crée des verrous, un gonflement du WAL et du bloat de table.","Surveillez régulièrement le bloat et la taille des tables/index ; appliquez une réorganisation en ligne (VACUUM/reorg) si nécessaire.","Conservez les index adaptés aux requêtes fréquentes ; maintenir des index inutiles sur une grande table est très coûteux."],"level":"senior"},{"id":"caching-strategy","title":"Stratégie de cache","category":"Performances","summary":"Un cache réduit la latence et la charge en éloignant les lectures répétées de la base ; la vraie difficulté est l'invalidation.","points":["Utilisez le cache pour les données coûteuses, fréquemment lues et rarement modifiées ; n'essayez pas de tout mettre en cache.","Choisissez une stratégie d'invalidation : TTL, mise à jour à l'écriture (write-through) ou suppression à l'écriture (write-invalidate).","Définissez la tolérance aux données obsolètes selon les règles métier ; ne vous fiez pas au cache là où une cohérence stricte est requise.","Évitez les cache stampedes (de nombreuses requêtes remplissant simultanément un cache vide) par verrouillage ou regroupement des requêtes (request coalescing).","Surveillez le cache hit ratio ; un hit ratio faible montre que le cache n'apporte pas de valeur.","Un cache est une couche d'accélération, pas un état durable ; le système doit continuer à fonctionner correctement en cas de perte du cache."],"level":"senior"},{"id":"statistics-maintenance","title":"Discipline des statistiques et de la maintenance","category":"Exploitation","summary":"Des statistiques à jour et une maintenance régulière garantissent que le planificateur prend les bonnes décisions et que les performances ne se dégradent pas avec le temps.","points":["Gardez les statistiques à jour (ANALYZE / auto-analyze) ; des statistiques obsolètes poussent le planificateur vers un mauvais plan.","Exécutez régulièrement VACUUM/le nettoyage pour supprimer les lignes mortes et le bloat ; c'est essentiel sur les moteurs MVCC.","Surveillez la fragmentation des index et reconstruisez-les en ligne si nécessaire (REINDEX/rebuild).","Mettez à jour manuellement les statistiques après un chargement massif ; évitez que le planificateur travaille avec l'ancienne distribution.","Planifiez les jobs de maintenance dans les fenêtres de faible trafic, surveillez leur durée/impact et automatisez-les.","Surveillez les régressions de plan (une requête qui ralentit soudainement) ; la fraîcheur des statistiques est la première chose à vérifier."],"level":"senior"}]},"guide/best-practices-2.json":{"topics":[{"id":"iac","title":"Gérer l'infrastructure as code (IaC)","category":"Exploitation","summary":"Les serveurs de bases de données, le réseau et la configuration doivent être définis dans du code versionné, et non à la main, pour que les environnements soient reproductibles, auditables et réversibles.","points":["Conservez sous forme de code, dans le contrôle de source, les définitions de serveurs, parameter groups, réseau et utilisateurs avec des outils tels que Terraform/Pulumi/CloudFormation.","Interdisez les modifications manuelles dans la console (click-ops) ; exécutez une détection de drift et corrigez les ressources qui s'écartent du code.","Gérez les différences entre environnements (dev/stage/prod) avec des variables ; construisez des environnements proches de la production à partir du même module.","Gardez le fichier d'état distant et verrouillé (par ex. S3 + verrou DynamoDB) ; ne laissez pas fuir de secrets en clair dans l'état.","Faites relire les modifications via un flux plan/apply ; placez des étapes d'approbation devant la production."],"level":"senior"},{"id":"automation-idempotency","title":"Automatisation et idempotence","category":"Exploitation","summary":"Les tâches d'exploitation doivent passer de commandes répétées manuellement à une automatisation idempotente qui produit le même résultat quel que soit le nombre d'exécutions.","points":["Écrivez chaque script de façon idempotente pour que sa réexécution n'échoue pas et ne corrompe pas l'état (par ex. CREATE IF NOT EXISTS au lieu de CREATE).","Rattachez les tâches de maintenance récurrentes (sauvegarde, nettoyage, statistiques) à une automatisation planifiée et journalisée ; ne les laissez pas à des exécutions manuelles.","Intégrez aux scripts des vérifications explicites de préconditions et des sorties sûres ; réexécuter une opération à moitié terminée doit la compléter là où elle s'est arrêtée.","Exécutez l'automatisation à partir du code (Ansible/scripts + CI) ; ne dépendez pas de quelqu'un tapant des commandes dans un terminal en production.","Prévoyez un mode dry-run pour les opérations critiques ; montrez l'impact avant de l'appliquer."],"level":"senior"},{"id":"observability","title":"Observabilité (métriques/logs/traces)","category":"Exploitation","summary":"L'observabilité combine métriques, logs et traces distribuées pour passer de « ce qui s'est passé » à « pourquoi cela s'est passé ».","points":["Collectez les trois signaux ensemble : métriques (tendances), logs (contexte), traces (chemin des requêtes) ; aucun d'eux ne révèle seul la cause racine.","Tracez les requêtes de bout en bout : corrélez les traces applicatives avec la latence de la base (à l'aide de standards tels qu'OpenTelemetry).","Produisez des logs structurés (JSON) avec un correlation/trace id ; les logs en texte libre compliquent la recherche.","Évitez les labels à forte cardinalité ; maîtrisez l'explosion de la cardinalité et les coûts.","Définissez des SLO et suivez l'error budget ; reliez l'observabilité aux objectifs métier."],"level":"senior"},{"id":"alerting-strategy","title":"Stratégie d'alerting (peu de bruit, actionnable)","category":"Exploitation","summary":"Une bonne stratégie d'alerting ne réveille un humain que pour des conditions réelles et actionnables ; le bruit provoque lassitude et épuisement.","points":["Alertez sur l'impact et l'expérience utilisateur (violations de SLO) plutôt que sur les symptômes ; ne déclenchez pas d'astreinte à chaque fluctuation de métrique.","Chaque alerte a besoin d'un responsable, d'une condition et d'un runbook clairs ; une alerte sans action définie ne vaut rien.","Utilisez une hystérésis et une durée (for: 5m) sur les seuils ; évitez que des pics momentanés déclenchent des alertes.","Séparez l'astreinte (paging) de la notification (ticket/e-mail) ; ne déclenchez l'astreinte que pour ce qui exige une intervention immédiate.","Revoyez régulièrement les alertes ; corrigez ou supprimez celles qui se déclenchent en permanence et sont ignorées.","Couvrez toujours les sinistres silencieux tels que les échecs de sauvegarde, les disques pleins et l'arrêt de la réplication."],"level":"senior"},{"id":"dr-testing","title":"Discipline des exercices DR/failover","category":"Sauvegarde et HA","summary":"Un plan de reprise après sinistre n'est fiable que s'il est validé par des exercices réguliers et réalistes ; un plan non testé n'est qu'une hypothèse.","points":["Planifiez des exercices de failover et de restauration et exécutez-les périodiquement ; un plan sur papier relu une fois par an ne suffit pas.","Mesurez le RTO et le RPO réels à chaque exercice ; rendez compte de l'écart par rapport à l'objectif et améliorez.","Répétez le failover planifié dans un environnement proche de la production avec du trafic réel ; ne vous contentez pas de démarrer et d'arrêter le standby.","Simulez des scénarios tels que la perte d'une région, une panne disque et une corruption de données lors de game days.","Consignez les enseignements dans le runbook après chaque exercice ; transformez les étapes connues de mémoire en automatisation."],"level":"expert"},{"id":"blue-green-deploy","title":"Déploiement blue-green / sans interruption","category":"Exploitation","summary":"Les déploiements blue-green et canary exécutent la nouvelle version aux côtés de l'ancienne et basculent le trafic progressivement, minimisant interruption et risque.","points":["Maintenez deux environnements de production (blue/green) ; déployez la nouvelle version sur celui qui est inactif, validez-la, puis basculez le trafic en une seule étape.","En cas de problème, rebasculez immédiatement le trafic vers l'ancien environnement ; le rollback ne doit pas nécessiter de redéploiement.","Avec canary, envoyez un faible pourcentage du trafic vers la nouvelle version, surveillez les métriques et augmentez progressivement.","Découplez le schéma de la base du déploiement : les deux versions du code doivent fonctionner avec le même schéma (expand-contract).","Définissez à l'avance les health checks et les seuils de rollback automatique ; réduisez le temps passé à attendre des décisions humaines manuelles."],"level":"senior"},{"id":"expand-contract","title":"Modifications de schéma rétrocompatibles (expand-contract)","category":"Conception","summary":"Le schéma expand-contract découpe une modification de schéma en ajout (expand), puis migration, et enfin suppression de l'ancien (contract), ce qui permet une transition sans interruption.","points":["Commencez par l'expand : ajoutez la nouvelle colonne/table de manière rétrocompatible (nullable ou avec une valeur par défaut) sans casser l'ancienne.","Déployez l'application pour qu'elle fonctionne avec l'ancien comme avec le nouveau schéma, en double écriture ou double lecture.","Migrez les données vers la nouvelle structure en arrière-plan par petits lots et validez-les ; ne verrouillez pas avec une seule migration géante.","Ne passez au contract qu'une fois toutes les instances passées à la nouvelle version et la validation terminée : supprimez l'ancienne colonne/l'ancien code.","Réalisez chaque étape dans un déploiement distinct ; ne faites jamais « ajouter et supprimer l'ancien en même temps », sinon le rollback devient impossible.","Ajoutez NOT NULL et les autres contraintes en dernier, une fois les données cohérentes."],"level":"expert"},{"id":"secrets-management","title":"Gestion des secrets (mots de passe/clés)","category":"Sécurité","summary":"Les mots de passe de bases de données, clés d'API et chaînes de connexion ne doivent pas être intégrés au code ni à la configuration ; ils doivent être stockés de façon sécurisée et renouvelés via un gestionnaire de secrets centralisé.","points":["N'intégrez pas de secrets dans le code, les dépôts, les fichiers env ou les images ; utilisez un coffre de secrets tel que Vault, AWS Secrets Manager ou KMS.","Injectez les secrets dans les applications à l'exécution ; ne les laissez pas en clair sur disque.","Renouvelez régulièrement mots de passe et clés ; lorsque c'est possible, utilisez des identifiants de courte durée générés automatiquement.","Accordez l'accès selon le moindre privilège et avec un journal d'audit ; il doit être possible de retracer qui a accédé à quel secret et quand.","Analysez la base de code à la recherche de secrets divulgués (git-secrets, trufflehog) ; révoquez et renouvelez immédiatement un secret divulgué.","Rendez l'accès humain temporaire et soumis à approbation (just-in-time) plutôt que personnel et permanent."],"level":"senior"},{"id":"cost-optimization","title":"Optimisation des coûts cloud","category":"Exploitation","summary":"Le coût d'une base de données cloud peut être nettement réduit sans sacrifier les performances grâce au bon dimensionnement, à la hiérarchisation du stockage et au nettoyage des ressources inutilisées.","points":["Dimensionnez les instances selon les métriques d'utilisation réelles ; réduisez les serveurs durablement peu utilisés (right-sizing).","Utilisez la capacité réservée/les savings plans pour la charge de base prévisible ; laissez la charge variable en on-demand.","Déplacez les données froides vers un niveau de stockage moins cher ; séparez les données chaudes des données rarement consultées.","Nettoyez les ressources inutilisées : snapshots orphelins, réplicas inactifs et bases de test abandonnées sont des fuites de coûts.","Attribuez les coûts à des responsables avec des tags et mettez en place des alertes budgétaires ; voyez les surprises immédiatement, pas en fin de mois.","Revoyez la rétention des sauvegardes et le nombre de réplicas au regard des besoins métier ; l'excès est à la fois un risque et un coût."],"level":"senior"},{"id":"multi-tenancy","title":"Conception multi-tenant","category":"Conception","summary":"Dans les systèmes multi-tenant, le modèle d'isolation (table partagée, schéma séparé, base séparée) est un compromis délibéré entre sécurité, coût et scalabilité.","points":["Choisissez le modèle d'isolation : table partagée (tenant_id), schéma par tenant ou base par tenant ; chacun offre un équilibre coût/isolation différent.","Dans le modèle partagé, appliquez un filtre tenant_id à chaque requête ; garantissez l'absence de fuite au niveau du moteur avec la row-level security (RLS).","Placez tenant_id en premier dans les index concernés ; évitez les parcours inter-tenants et les points chauds.","Appliquez des limites de ressources et des quotas contre l'effet noisy neighbor ; un tenant ne doit pas priver les autres de ressources.","Concevez dès le départ une voie (migration de tenant) pour déplacer les tenants volumineux ou premium vers un schéma/une base séparé.","Pensez à la sauvegarde, à la suppression et à la portabilité des données par tenant ; vous devez pouvoir honorer les demandes d'effacement KVKK/RGPD."],"level":"expert"},{"id":"time-series-design","title":"Conception des données de séries temporelles","category":"Conception","summary":"Les données de séries temporelles sont particulières en raison d'un volume d'écriture élevé et de schémas de requête basés sur le temps ; elles doivent être conçues avec un partitionnement par date, des rollups et une politique de rétention.","points":["Découpez les données avec un partitionnement RANGE basé sur l'horodatage ; purgez les anciennes données à moindre coût avec DROP de partition.","Au lieu d'accumuler les données brutes telles quelles, produisez des synthèses à intervalles réguliers (rollup/continuous aggregate) ; laissez les requêtes s'exécuter rapidement sur ces synthèses.","Placez en premier dans l'index les composantes temps et étiquette (tag/source) ; le schéma de requête typique est « source donnée, plage donnée ».","Choisissez le bon outil : à fort volume, les moteurs de séries temporelles tels que TimescaleDB et ClickHouse peuvent être bien plus efficaces que les moteurs relationnels.","Définissez la politique de rétention dès le départ ; conservez peu de temps les données brutes, longtemps les données agrégées.","Stockez toujours le temps en UTC ; gérez l'heure locale et les changements d'heure dans la couche de requête."],"level":"senior"},{"id":"event-sourcing","title":"Fondamentaux de l'event sourcing / CQRS","category":"Conception","summary":"L'event sourcing enregistre non pas l'état, mais les événements qui le modifient ; CQRS sépare les modèles de lecture et d'écriture pour que chacun évolue indépendamment.","points":["Au lieu d'écraser l'état courant, accumulez un flux d'événements immuable ; dérivez l'état courant en rejouant les événements.","Avec CQRS, séparez le modèle d'écriture (commands) du modèle de lecture (queries) ; accélérez le côté lecture avec des projections dénormalisées.","Versionnez le schéma des événements et faites-le évoluer de manière rétrocompatible ; ne supprimez jamais d'anciens événements, convertissez-les vers la nouvelle version par upcasting.","Acceptez que les projections de lecture soient cohérentes à terme (eventually consistent) et tenez compte du décalage dans la conception.","Prenez des snapshots pour éviter le coût de rejouer à chaque fois un long flux d'événements depuis le début.","N'utilisez pas ce schéma partout, mais dans les domaines qui ont besoin d'une piste d'audit, de règles métier complexes et d'un historique dans le temps ; ne sous-estimez pas la complexité qu'il apporte."],"level":"expert"},{"id":"data-retention","title":"Politique de rétention et d'archivage des données","category":"Exploitation","summary":"Toutes les données ne sont pas conservées indéfiniment ; une politique de rétention et d'archivage définie selon les exigences légales, métier et de coût assure à la fois conformité et performances.","points":["Définissez la durée de rétention de chaque type de données selon les exigences métier et légales (KVKK/RGPD) ; un vague « on garde au cas où » n'est pas une politique.","Archivez ou supprimez automatiquement les données expirées ; ne les laissez pas à un nettoyage manuel, vite oublié pendant que les tables gonflent.","Conservez les données chaudes sur un stockage rapide et les archives sur un stockage bon marché/froid ; hiérarchisez selon la fréquence d'accès.","Concevez dès le départ le mécanisme de traitement des demandes de suppression/d'anonymisation des données personnelles (droit à l'effacement).","Gardez les données archivées récupérables et interrogeables au besoin ; archivé ne doit pas signifier inaccessible.","Enregistrez les suppressions dans le journal d'audit ; ce qui a été supprimé, quand et pourquoi doit être traçable."],"level":"senior"},{"id":"change-management","title":"Gestion des changements et sécurité de la production","category":"Exploitation","summary":"Toute modification d'une base de production doit être relue, réversible et traçable ; une intervention manuelle non contrôlée est la cause d'interruption la plus fréquente.","points":["N'exécutez pas de DDL/DML ad hoc à la main en production ; chaque modification doit passer par une migration ou un script relu par un pair.","Appliquez les modifications dans une fenêtre de faible trafic avec un plan de rollback prêt ; ne touchez pas à la production sans planification.","Répétez d'abord les commandes risquées dans un environnement proche de la production avec des volumes de données réels ; mesurez leur durée et leur impact en termes de verrous.","Restreignez et auditez l'accès à la production : qui a exécuté quelle commande et quand doit être journalisé (accès just-in-time).","Avant une suppression/mise à jour, vérifiez la clause WHERE et le nombre de lignes concernées ; exécutez dans une transaction et confirmez d'abord avec un SELECT.","Associez les modifications à un calendrier et à une communication ; ne surprenez pas les autres équipes avec des modifications de production non annoncées."],"level":"senior"}]},"guide/best-practices-3.json":{"topics":[{"id":"plan-reading-expert","title":"Comment lire un plan d'exécution","category":"Performances","summary":"Un plan d'exécution montre les estimations de l'optimiseur et les opérateurs physiques qu'il a choisis ; le goulet d'étranglement se cache dans l'écart entre nombre de lignes estimé et réel.","points":["Distinguez les méthodes d'accès : Full/Table Scan pour les lectures larges sur de grandes tables, Index Seek/Range Scan pour les prédicats sélectifs, Index Full Scan pour les lectures ordonnées ; Rows Removed by Filter ou la répartition predicate/access montre où la sélectivité est perdue.","Adaptez la méthode de jointure à la charge : Nested Loop est idéal pour un petit ensemble externe + un ensemble interne indexé, Hash Join pour les grandes équi-jointures, Merge Join pour deux ensembles déjà triés ; un mauvais choix résulte généralement d'une mauvaise estimation de cardinalité.","Traquez les erreurs d'estimation de cardinalité : comparez lignes estimées et réelles avec EXPLAIN ANALYZE (PG), DBMS_XPLAN ALLSTATS LAST (Oracle) ou SET STATISTICS PROFILE (MSSQL) ; un écart supérieur à 10x suggère que le choix du plan peut être fondamentalement erroné.","Lisez le plan selon le coût réel : trouvez l'opérateur qui consomme le plus de temps/de lectures logiques (logical reads, buffers) ; l'unité de « coût » de l'optimiseur est une estimation, pas le véritable goulet d'étranglement.","Repérez les avertissements de spill/d'espace temp : un opérateur Hash/Sort qui ne tient pas en mémoire et déborde sur disque (PG 'Sort Method: external merge', MSSQL Sort/Hash Warning avec SpillToTempDb dans le plan) indique un work_mem/une zone de tri insuffisant.","Lisez l'arbre des opérateurs de l'intérieur vers l'extérieur et de bas en haut : les nœuds internes s'exécutent en premier et les lignes remontent ; calculez le coût total par opérateur en multipliant les loops par le temps réel."],"level":"expert"},{"id":"parameter-sniffing-deep","title":"Parameter sniffing et bind peeking","category":"Performances","summary":"L'optimiseur construit et met en cache un plan en fonction de la valeur de paramètre du premier appel ; lorsque des valeurs ultérieures de distribution différente héritent de ce plan, les performances peuvent changer radicalement.","points":["Reconnaissez le symptôme : la même requête est rapide avec certains paramètres et très lente avec d'autres ; cela montre que le plan en cache a été compilé pour une « première » valeur atypique.","Le risque est élevé sur les colonnes à distribution asymétrique ; les histogrammes rendent l'optimiseur sensible aux valeurs alors qu'un plan unique en cache le rend insensible, et cette tension est la source du problème.","Dans MSSQL, OPTION (RECOMPILE) construit un plan à chaque exécution, OPTIMIZE FOR UNKNOWN choisit un plan équilibré fondé sur la densité moyenne, et OPTIMIZE FOR (@p = value) fige une valeur typique.","Dans Oracle, l'adaptive cursor sharing et les curseurs bind-aware produisent des plans différents pour les valeurs asymétriques ; dans PG, le choix entre plan custom et plan generic se contrôle avec plan_cache_mode.","Pour une correction durable, séparez les plans selon les valeurs : concevez un découpage des instructions, des index filtrés/partiels ou un chemin d'accès dédié pour les valeurs très atypiques.","En OLTP, un RECOMPILE excessif ajoute un coût CPU et de compilation (hard parse) ; mesurez et gardez l'équilibre sur les requêtes fréquemment exécutées."],"level":"expert"},{"id":"plan-stability","title":"Stabilité des plans : baselines, hints et plan guides","category":"Performances","summary":"Des mécanismes de persistance propres à chaque moteur servent à figer les plans vérifiés des requêtes critiques et à éviter les régressions de plan inattendues lors de changements de statistiques/de version.","points":["Dans Oracle, figez les plans approuvés avec les SQL Plan Baselines (SPM) ; les nouveaux plans sont seulement collectés comme candidats jusqu'à leur « acceptation », et sont vérifiés de manière contrôlée avec evolve.","Dans MSSQL, détectez les régressions de plan avec Query Store et forcez le plan réputé bon avec 'Force Plan' ; Automatic Plan Correction peut automatiser cela.","PostgreSQL ne dispose d'aucun mécanisme intégré de gel des plans ; la stabilité provient de l'extension pg_hint_plan ou de réécritures de requêtes, de statistiques étendues et de la conception des index.","Utilisez les hints en dernier recours, justifiés et commentés ; un hint présuppose le schéma physique et la distribution des données, et devient nuisible lorsqu'ils changent.","Auditez périodiquement l'inventaire des baselines/plans forcés ; les plans figés peuvent perdre leur optimalité avec la croissance des données, ne tombez donc pas dans le piège du « stable mais lent ».","Capturez les plans des requêtes critiques avant une montée de version ; préparez des baselines comme filet de sécurité contre les changements de comportement de l'optimiseur."],"level":"expert"},{"id":"latch-mutex-contention","title":"Contention de latches, mutex et spinlocks","category":"Performances","summary":"Les latches/mutex sont des verrous légers protégeant des structures en mémoire ; sous forte concurrence, la contention sur ceux-ci consomme du CPU dans des boucles de spinlock et empêche la montée en charge.","points":["Distinguez verrous et latches : les verrous protègent des données logiques (ligne/table) pendant la durée d'une transaction, les latches protègent des structures mémoire physiques (buffers, hash chains) pendant quelques microsecondes ; leurs méthodes de diagnostic diffèrent.","Dans Oracle, la contention sur le latch 'cache buffers chains' signale généralement un hot block ; une petite table fréquemment lue ou le bord d'attaque d'un index monotone croissant (right-hand index) en est une cause courante.","Les insertions avec des clés séquentiellement croissantes (identity/sequence) provoquent une contention sur la dernière page/le dernier bloc (hot page, PAGELATCH) ; répartissez-les par partitionnement par hachage, reverse-key index ou clé aléatoire.","Dans MSSQL, surveillez les statistiques de spinlocks et les attentes PAGELATCH_* ; pour la contention d'allocation dans tempdb, utilisez plusieurs fichiers de données et une configuration de tempdb appropriée.","La contention de spinlocks se manifeste par un CPU élevé avec un faible débit ; la solution consiste à réduire la contention au niveau du code/de l'architecture, et ajouter du matériel l'aggrave souvent.","La prise en compte de NUMA et la limitation du nombre de connexions (pooling) réduisent la contention de latches ; un parallélisme excessif peut accroître la pression sur les latches."],"level":"expert"},{"id":"wait-event-methodology","title":"Méthodologie d'analyse des événements d'attente","category":"Performances","summary":"Gérez les performances en mesurant où le système passe son temps à attendre, et non en devinant ; l'analyse descendante des attentes est la méthode scientifique qui circonscrit systématiquement le goulet d'étranglement.","points":["Décomposez le DB time : séparez le temps passé sur le CPU du temps d'attente ; commencez par la plus grande composante et ne dérivez pas vers la micro-optimisation.","Trouvez le principal événement d'attente avec ASH/AWR et 'Top Wait Events' dans Oracle, pg_stat_activity + pg_wait_sampling dans PG, et sys.dm_os_wait_stats et Query Store dans MSSQL.","Interprétez la wait class : les attentes d'E/S renvoient au stockage/aux plans, les attentes de verrous à la conception de la concurrence, les attentes de latches aux points chauds, et les attentes réseau/commit à des problèmes de configuration.","Descendez au niveau session/requête : partez des attentes totales, passez au SQL qui y contribue le plus puis au plan de ce SQL pour isoler la cause (drill-down).","Rattachez la mesure à la fenêtre de charge : prenez la différence (delta) des compteurs cumulatifs entre deux points ; les totaux sont trompeurs, ce sont les taux et les intervalles de temps qui comptent.","Écartez du bruit les attentes idle/bénignes ; toute attente n'est pas un problème, ce qui compte, ce sont les attentes foreground qui contribuent au temps de réponse utilisateur."],"level":"expert"},{"id":"plan-regression-detection","title":"Détecter les régressions de plans d'exécution","category":"Performances","summary":"Une requête qui ralentit soudainement est généralement due à un changement de plan, et non de code ; détecter les régressions de manière proactive coûte moins cher que d'éteindre des incendies.","points":["Conservez et comparez l'historique des plans : suivez les changements de plan et les tendances de durée/ressources pour un même sql_id/query_hash avec MSSQL Query Store et Oracle AWR/SQL Monitor.","Identifiez le déclencheur de la régression : mises à jour de statistiques, croissance du volume de données, montées de version, modifications de paramètres ou index supprimés sont les causes les plus fréquentes.","Mettez en place une alerte automatique : déclenchez une alerte lorsque le plan hash change et que la durée moyenne/les lectures logiques dépassent un seuil ; suivez des métriques par exécution, pas cumulatives.","Préparez une voie de retour rapide : figez temporairement le plan réputé bon avec Force Plan (MSSQL) ou une SQL Plan Baseline (Oracle) et corrigez la cause racine ensuite.","Pour les régressions liées aux statistiques, examinez les statistiques verrouillées/actuelles et la fraîcheur des histogrammes ; la collecte automatique des statistiques peut être planifiée pendant des heures critiques.","Exécutez des tests de régression avant une montée de version : rejouez une charge représentative sur l'ancien et le nouvel optimiseur et rendez compte des différences de plans (approche de type Oracle SPA)."],"level":"expert"},{"id":"mvcc-vacuum-internals","title":"Fonctionnement interne du MVCC et du vacuum/undo","category":"Fonctionnement interne","summary":"Le MVCC évite les blocages entre lecteurs et écrivains en conservant les anciennes versions des lignes ; la manière et l'endroit où ces versions sont conservées (undo ou dans la table) déterminent la stratégie de maintenance.","points":["Dans PostgreSQL, les anciennes versions restent dans la table elle-même sous forme de dead tuples ; VACUUM les nettoie, sinon du bloat et une croissance des index se produisent.","Oracle et MySQL/InnoDB conservent les anciennes versions dans les segments undo/rollback ; si une longue lecture épuise l'undo, des erreurs telles que 'ORA-01555 snapshot too old' apparaissent.","Ajustez l'autovacuum à la charge : réduisez le scale factor et le cost delay sur les tables à fort taux d'update/delete ; donnez aux tables très actives des réglages par table.","Gardez la visibility map saine ; les index-only scans et le suivi des frozen tuples en dépendent, et les performances se dégradent si elle est négligée.","Surveillez le risque de transaction ID wraparound : suivez la métrique datfrozenxid/age dans PG et évitez que le vacuum agressif/anti-wraparound se déclenche pendant les heures ouvrées.","Favorisez les mises à jour HOT (Heap-Only Tuple) : les mises à jour qui ne modifient pas de colonne indexée sont effectuées sur place sans faire croître l'index ; un fillfactor approprié y contribue."],"level":"expert"},{"id":"wal-redo-log-mechanics","title":"Mécanique du WAL/redo/journal des transactions","category":"Fonctionnement interne","summary":"La durabilité est obtenue en écrivant la modification dans le journal avant l'écriture des pages de données sur disque ; le débit et la taille du flux de journal déterminent à la fois la durabilité et les performances d'écriture.","points":["Comprenez le principe du write-ahead : pour qu'un commit rende la main, il suffit que les enregistrements de journal correspondants soient sur un stockage durable ; les pages de données modifiées sont écrites plus tard, de manière différée.","Réglez le group commit et le comportement de vidage du journal : équilibrez la fréquence des fsync et les exigences de durabilité de la charge avec commit_delay/synchronous_commit dans PG et le comportement du log writer dans Oracle.","Surveillez le débit de génération du journal (génération de WAL/redo) ; une hausse soudaine signale des mises à jour massives, des images de pages complètes après un checkpoint (full_page_writes/FPI) ou de la maintenance d'index, et augmente le retard DR.","Le journal est une source non seulement pour la récupération mais aussi pour la réplication et le CDC ; la réplication logique et le WAL shipping s'alimentent de ce flux, concevez donc la politique d'archivage/de nettoyage en conséquence.","Séparez le disque du journal de celui des données et placez-le sur un stockage à faible latence ; les écritures de journal sont séquentielles et sensibles à la latence, et le débit de commits en dépend directement.","Prévenez la croissance incontrôlée du journal : dans MSSQL, utilisez le bon mode de récupération et des sauvegardes de log régulières ; dans PG, assurez-vous que l'archivage/les replication slots ne prennent pas de retard."],"level":"expert"},{"id":"buffer-pool-management","title":"Gestion du buffer/de la shared pool et éviction","category":"Fonctionnement interne","summary":"Le buffer pool réduit les E/S disque en conservant les données chaudes en mémoire ; l'algorithme qui décide quelle page est évincée et quand régit le hit ratio et la stabilité.","points":["Ne visez pas uniquement le buffer cache hit ratio ; même un ratio élevé peut masquer la latence des lectures physiques, examinez donc aussi les E/S absolues et le temps d'attente.","Comprenez l'algorithme d'éviction : face à un LRU pur pollué par les parcours séquentiels, PG utilise le clock-sweep, Oracle une variante touch-count/LRU, et InnoDB un LRU à insertion au point médian (midpoint insertion).","Empêchez les grands parcours ponctuels d'expulser les données chaudes du pool (pollution du cache) ; protégez-les avec des buffer pools séparés ou des stratégies d'accès adaptées aux parcours.","Maîtrisez le ratio de dirty pages et la pression d'écriture : trop de dirty pages provoquent des pics d'E/S au checkpoint, une écriture trop agressive génère des E/S inutiles ; mesurez l'équilibre.","Dimensionnez la mémoire selon le working set ; l'objectif est de couvrir l'ensemble activement consulté, non la totalité des données, et le surdimensionnement offre des rendements décroissants.","Dans la shared pool d'Oracle, surveillez la contention du library/dictionary cache et les hard parses fréquents ; utiliser des variables de liaison au lieu de SQL littéral réduit la pression sur la shared pool."],"level":"expert"},{"id":"checkpoint-tuning","title":"Réglage des checkpoints","category":"Fonctionnement interne","summary":"Un checkpoint raccourcit le temps de récupération en écrivant les pages modifiées sur disque ; des checkpoints trop fréquents créent des pics d'E/S et trop espacés une longue récupération, et ce compromis doit être réglé.","points":["Équilibrez temps de récupération et E/S en fonctionnement : des checkpoints espacés signifient moins de charge d'écriture mais une récupération après crash plus longue, des checkpoints fréquents l'inverse.","Dans PG, étalez les checkpoints dans le temps avec checkpoint_timeout et max_wal_size ; lissez le pic d'E/S en répartissant les écritures sur l'intervalle avec checkpoint_completion_target.","L'indirect checkpoint de MSSQL (target recovery time) assure une écriture continue en arrière-plan ; il rend la latence prévisible en évitant les grosses vagues de checkpoint soudaines.","Corrélez la fréquence des checkpoints avec le débit de génération du journal/WAL ; pendant les périodes d'écriture intensive, vérifiez si c'est le déclencheur de taille plutôt que le déclencheur temporel qui intervient.","full_page_writes (PG) journalise une page complète lors de la première modification après un checkpoint ; si les checkpoints sont trop fréquents, le volume de WAL explose, ce qui augmente les coûts DR et disque.","Repérez les symptômes de « checkpoint sink » : hausse de la latence d'écriture et attentes fsync pendant les checkpoints ; examinez la file du stockage et la répartition."],"level":"expert"},{"id":"statistics-strategy","title":"Stratégie de collecte des statistiques","category":"Fonctionnement interne","summary":"La qualité des décisions de l'optimiseur dépend de la fraîcheur et de la résolution des statistiques ; le taux d'échantillonnage, les histogrammes et les statistiques étendues déterminent les estimations de cardinalité.","points":["Choisissez le taux d'échantillonnage selon la taille et l'asymétrie des données : un échantillon trop petit produit des estimations erronées sur les distributions asymétriques, un échantillon trop grand un coût de collecte inutile.","Modélisez l'asymétrie avec des histogrammes : default_statistics_target de PG, les histogrammes FREQUENCY/HYBRID d'Oracle et les histogrammes de statistiques de MSSQL exposent à l'optimiseur les valeurs fréquentes et déséquilibrées.","Capturez la corrélation entre colonnes avec des statistiques étendues : CREATE STATISTICS (ndistinct/dependencies) de PG et les extended stats d'Oracle corrigent les erreurs d'estimation sur les colonnes interdépendantes.","Découplez les plannings de collecte de la charge : évitez que le job automatique de statistiques/gather s'exécute aux heures critiques et provoque des E/S et une instabilité des plans.","Gérez le risque de statistiques obsolètes sur les tables à croissance rapide ; les mises à jour automatiques basées sur des seuils peuvent prendre du retard, ajoutez donc une collecte manuelle/planifiée pour les tables critiques.","Sachez que les changements de statistiques peuvent provoquer des régressions de plan ; dans les environnements critiques, verrouillez les statistiques (locked stats) ou publiez-les de manière contrôlée avec pending publish."],"level":"expert"},{"id":"partition-pruning-join","title":"Partition pruning et jointures partition-wise","category":"Scaling/Architecture","summary":"Le partitionnement ne se contente pas de découper une table ; le vrai gain est que les requêtes ne lisent jamais les partitions non pertinentes (pruning) et que les jointures s'exécutent en parallèle par partition.","points":["Choisissez la clé de partitionnement selon le filtre le plus fréquent et le plus sélectif ; le pruning ne fonctionne que si le prédicat de la requête inclut la clé de partitionnement, sinon toutes les partitions sont parcourues.","Distinguez pruning statique et dynamique : les prédicats constants éliminent des partitions à la compilation, les valeurs de bind/de jointure à l'exécution (runtime pruning) ; vérifiez le plan en conséquence.","Pour les jointures partition-wise, partitionnez les deux tables sur la même clé avec un schéma compatible ; l'optimiseur réduit une grande jointure à de petites jointures partition par partition et les parallélise.","Équilibrez le nombre et la granularité des partitions : trop de partitions signifie un coût de métadonnées et de planification, trop peu un pruning faible ; dimensionnez-les selon le schéma d'accès.","Combinez la gestion du cycle de vie avec le partitionnement : DETACH/DROP ou archivez les anciennes partitions ; cela permet un nettoyage instantané et peu journalisé au lieu de DELETE.","N'oubliez pas que les fonctions/conversions de type ou les expressions masquant la clé cassent le pruning ; vérifiez le nombre de partitions parcourues avec EXPLAIN."],"level":"expert"},{"id":"shard-key-selection","title":"Choix de la shard key et points chauds","category":"Scaling/Architecture","summary":"La shard key détermine le sort d'un système distribué ; un mauvais choix concentre la charge sur un seul nœud (hot shard), empêche la montée en charge et rend coûteuses les requêtes cross-shard.","points":["Choisissez une clé à forte cardinalité qui répartit uniformément la charge ; une clé à faible cardinalité ou temporelle/monotone croissante concentre la charge sur un seul shard et crée un point chaud.","Concevez la clé pour que les requêtes les plus fréquentes soient résolues sur un seul shard ; les requêtes cross-shard (scatter-gather) multiplient la latence et le coût de coordination.","Le hash sharding équilibre la charge mais disperse les requêtes par plage ; le range sharding convient aux requêtes par plage mais accroît le risque de point chaud ; choisissez selon le schéma d'accès.","Minimisez la redistribution lors de l'ajout/du retrait de nœuds avec le consistent hashing ; un hachage modulo fixe déplace presque toutes les données lorsque le nombre de nœuds change.","Concevez le plan de resharding dès le départ ; changer la clé plus tard est coûteux et risqué, modélisez donc à l'avance le scénario de croissance.","En cas de découpage par tenant/client, prévoyez un shard dédié ou une stratégie de sous-partitionnement (split) pour qu'un tenant géant (whale) ne remplisse pas un seul shard."],"level":"expert"},{"id":"read-write-splitting","title":"Séparation lecture-écriture et routage vers les réplicas","category":"Scaling/Architecture","summary":"Router les lectures vers les réplicas soulage le primaire d'écriture ; mais comme le retard de réplication peut rompre les garanties de cohérence, le routage doit être fait délibérément.","points":["En répartissant la charge de lecture sur les réplicas, surveillez en continu le retard de réplication ; un réplica en retard peut montrer d'anciennes données aux utilisateurs.","Routez vers le primaire les flux nécessitant le read-your-writes (l'utilisateur qui écrit lit immédiatement), ou garantissez un réplica à jour avec des causal reads/tokens.","Effectuez le routage dans la couche d'infrastructure, pas dans l'application : répartissez de manière transparente avec un proxy/routeur (ProxySQL pour MySQL ; pour PG, Pgpool-II ou HAProxy avec des endpoints lecture/écriture séparés, car PgBouncer ne sépare pas lectures et écritures ; pour MSSQL, read-only routing via ApplicationIntent).","Retirez un réplica du pool lorsque le seuil de retard est dépassé ; envoyer des lectures à un réplica très en retard produit une incohérence des données sous l'illusion du « scaling ».","Isolez les lectures lourdes tolérantes au retard, comme l'analytique/le reporting, sur un réplica dédié ; protégez les réplicas OLTP de cette charge.","Testez que le routage des lectures se met à jour automatiquement dans un scénario de failover ; un routeur qui ignore le nouveau primaire provoque un split-brain ou une interruption."],"level":"expert"},{"id":"connection-pool-sizing","title":"Calcul du dimensionnement du pool de connexions","category":"Scaling/Architecture","summary":"La taille du pool se détermine par le calcul, à partir de la loi de Little et du nombre de cœurs, et non par intuition ; trop de connexions réduisent le débit par la contention et les changements de contexte.","points":["Appliquez la loi de Little : concurrence requise ≈ débit de requêtes (throughput) × temps de traitement moyen ; déduisez la concurrence cible de valeurs mesurées, non de suppositions.","Un petit pool est souvent plus rapide : lorsque les connexions actives dépassent le parallélisme physique cœurs/disques, les changements de contexte CPU et la contention de latches réduisent le débit.","Comme point de départ approximatif, utilisez un noyau de connexions proche du nombre de cœurs (par ex. un petit nombre de connexions réelles derrière PgBouncer) et ajustez en mesurant sous charge.","Planifiez le pool côté application avec le max_connections de la base ; N instances applicatives × taille du pool ne doit pas dépasser la limite de la base.","Sur les moteurs au coût de processus par connexion élevé comme PG, utilisez PgBouncer/le transaction pooling ; chaque connexion coûte de la mémoire et de l'ordonnancement.","Surveillez la saturation du pool, le temps d'attente dans la file et les timeouts ; une file en croissance constante signale un goulet d'étranglement ou un sous-dimensionnement, n'agrandissez donc pas le pool à l'aveugle."],"level":"expert"},{"id":"lock-escalation-deadlock","title":"Escalade de verrous et schémas de prévention des interblocages","category":"Fiabilité","summary":"De nombreux verrous de ligne peuvent escalader en verrou de table et tuer la concurrence ; un interblocage est le résultat évitable d'un ordre de verrouillage incohérent.","points":["Découpez les grosses opérations de masse qui déclenchent l'escalade de verrous (batch/chunk) ; verrouiller des millions de lignes dans une seule transaction provoque escalade et blocages.","Le schéma de base pour prévenir les interblocages est un ordre de verrouillage cohérent : tous les chemins de code doivent verrouiller les ressources dans le même ordre (par ex. selon l'ordre de la clé primaire).","Gardez les transactions courtes et effectuez une seule unité de travail atomique ; les transactions contenant une interaction utilisateur, des appels distants ou de longs calculs conservent des verrous inutilement longtemps.","Choisissez délibérément le niveau d'isolation : une isolation inutilement élevée provoque un verrouillage excessif, inutilement basse des anomalies ; dans les moteurs MVCC, les lectures évitent déjà la plupart des verrous.","Collectez les graphes/traces d'interblocage (deadlock graph MSSQL, log_lock_waits PG, trace Oracle) ; journalisez la victime et remontez les schémas récurrents jusqu'à la cause racine.","Implémentez une logique de réessai : réessayez de manière idempotente la transaction victime de l'interblocage ; c'est un filet de sécurité pour une contention inévitable par conception."],"level":"expert"},{"id":"long-transaction-bloat","title":"Transactions longues et gestion du bloat/de l'undo","category":"Fiabilité","summary":"Une transaction longue laissée ouverte bloque le nettoyage des anciennes versions dans les moteurs MVCC, déclenchant une chaîne de bloat, de pression sur l'undo et d'effondrement des performances.","points":["Surveillez les transactions longues/ouvertes et alertez sur celles-ci : idle in transaction dans PG et les longues requêtes retenant de l'undo actif dans Oracle bloquent le nettoyage (vacuum/récupération de l'undo).","Dans PG, une seule transaction longue empêche VACUUM de supprimer les dead tuples ; tables et index gonflent, et une seule session « zombie » peut ralentir toute la base.","Terminez automatiquement les transactions bloquées avec idle_in_transaction_session_timeout et statement_timeout ; établissez une ligne de défense contre les fuites applicatives.","Dans Oracle/InnoDB, une longue lecture épuise l'undo et produit 'snapshot too old' ; dimensionnez l'espace/la rétention de l'undo selon la plus longue requête de la charge.","Découpez les traitements batch avec des intervalles de commit courts ; une seule transaction géante accumule undo/WAL et prolonge dangereusement la durée du rollback.","Mesurez et récupérez le bloat existant : mesurez le bloat avec pgstattuple et récupérez l'espace et l'efficacité des index avec VACUUM FULL/pg_repack ou une réorganisation en ligne."],"level":"expert"},{"id":"dr-game-day","title":"Exercices DR (game day) et validation RPO/RTO","category":"Fiabilité","summary":"Un plan de sauvegarde et de failover non testé est une hypothèse ; la capacité DR ne se prouve qu'en mesurant le RPO/RTO lors d'exercices réalistes.","points":["Déduisez le RPO et le RTO des exigences métier et mesurez-les en pratique : le retard de sauvegarde/réplication détermine le RPO, le temps de récupération + validation détermine le RTO ; comparez l'objectif à la réalité.","Restaurez les sauvegardes régulièrement et automatiquement : une sauvegarde jamais restaurée est une sauvegarde de Schrodinger ; sans test de restauration, l'existence d'une sauvegarde n'est pas vérifiée.","Planifiez des exercices game day dans un environnement proche de la production avec des scénarios de panne réalistes (perte du primaire, panne de région, corruption de données).","Exécutez de bout en bout les procédures de failover et de failback ; incluez le routage DNS/des connexions, la reconnexion des applications et la promotion des réplicas.","Prouvez la capacité de restauration à un instant donné (PITR) : testez que vous pouvez revenir à un moment précis avec les archives de journaux + sauvegardes full dans un scénario de corruption logique.","Produisez à chaque exercice le RTO/RPO mesuré, les surprises et les actions à mener ; mettez à jour le runbook et vérifiez l'amélioration lors de l'exercice suivant."],"level":"expert"},{"id":"observability-slo","title":"Observabilité et SLO/golden signals","category":"Fiabilité","summary":"Pour gérer la base selon l'impact métier, reliez les golden signals (latence, trafic, erreurs, saturation) aux SLO et priorisez avec un error budget.","points":["Surveillez les quatre golden signals : latence, trafic (débit), taux d'erreur (errors) et saturation ; ils résument l'état de santé sans plonger dans la cause racine.","Mesurez la latence avec des percentiles (p95/p99), pas des moyennes ; la moyenne masque la latence de queue et la pire expérience utilisateur.","Définissez des SLO et un error budget ; alertez sur les symptômes (violations de SLO), et non sur chaque fluctuation de métrique bas niveau, pour éviter la lassitude face aux alertes.","Appliquez la méthode USE (Utilization, Saturation, Errors) aux ressources : observez séparément la saturation du CPU, de la mémoire, des E/S disque et du pool de connexions.","Corrélez la supervision entre les couches : visualisez la chaîne requête -> événement d'attente -> saturation des ressources -> métrique métier sur un même axe temporel.","Mettez en place métriques + logs + traces ensemble ; les métriques seules répondent au « quoi », les logs/traces au « pourquoi », et sans eux le MTTR augmente."],"level":"expert"},{"id":"least-privilege-roles","title":"Moindre privilège et conception des rôles","category":"Sécurité","summary":"Que chaque compte ne détienne que les privilèges minimaux nécessaires à sa tâche réduit le rayon d'impact d'une compromission ; les rôles offrent une abstraction gérable au lieu d'accorder directement des privilèges.","points":["Accordez les privilèges à des rôles, pas à des utilisateurs ; concevez les rôles par fonction (lecteur, rédacteur, DDL, exploitation) et assignez les utilisateurs aux rôles, en évitant les grants directs.","Ne donnez aux comptes applicatifs que les privilèges d'objets et d'opérations requis ; DDL, SUPERUSER/DBA et privilèges larges sur les schémas ne doivent pas exister sur un compte applicatif de production.","Personnalisez et auditez l'accès des administrateurs humains : utilisez des identités individuelles + un accès élevé à la demande (JIT/break-glass) au lieu d'un compte admin partagé.","Révoquez les privilèges larges PUBLIC/par défaut ; configurez les default privileges et les contrôles au niveau schéma pour éviter que des droits par défaut se propagent aux nouveaux objets.","Revoyez régulièrement les comptes à privilèges (recertification des accès) ; nettoyez périodiquement les comptes inutilisés, ceux des employés partis et les comptes sur-privilégiés.","Imposez la séparation des tâches ; une même personne ne doit pas pouvoir modifier la production et effacer la piste d'audit, protégez donc l'écriture de l'audit par un privilège distinct."],"level":"expert"},{"id":"encryption-decision","title":"Décisions de chiffrement : at-rest, in-transit, TDE","category":"Sécurité","summary":"Le chiffrement se choisit selon le modèle de menace ; chaque couche protège contre une attaque différente, et la gestion des clés est souvent plus critique que l'algorithme de chiffrement.","points":["Clarifiez la menace : at-rest (disques/sauvegardes volés), in-transit (écoute réseau) et niveau applicatif (exposé à personne, DBA compris) sont des protections différentes ; décidez de celles dont vous avez besoin.","Pour l'in-transit, imposez TLS sur les connexions client-serveur ; ne désactivez pas la validation des certificats, car cela vous expose aux attaques de l'homme du milieu (MITM).","Pour l'at-rest, comparez TDE aux options de chiffrement du système de fichiers/du disque ; TDE protège les datafiles et les sauvegardes, mais pas les données en mémoire ni l'accès d'un DBA autorisé.","Pour les champs les plus sensibles, utilisez un chiffrement applicatif/au niveau colonne ou une approche côté client comme Always Encrypted, afin que même le DBA ne voie pas le texte en clair.","Prenez la gestion des clés au sérieux : stockez les clés dans un KMS/HSM séparé des données et planifiez la rotation et le contrôle d'accès ; si la clé fuit, le chiffrement ne vaut plus rien.","Mesurez l'impact sur les performances et les fonctionnalités : l'indexation/la recherche est restreinte sur les colonnes chiffrées et TDE ajoute un coût CPU ; prenez la décision comme un compromis mesuré."],"level":"expert"},{"id":"audit-strategy","title":"Stratégie d'audit","category":"Sécurité","summary":"La piste d'audit fournit des preuves pour les événements de sécurité et les exigences de conformité ; ce qui est journalisé, la destination de la piste et sa durée de conservation déterminent l'équilibre entre bruit et preuve.","points":["Déduisez ce qu'il faut auditer des menaces et de la conformité : priorisez les opérations privilégiées, les connexions/échecs de connexion, les modifications de schéma (DDL) et l'accès aux données sensibles.","Ne tombez pas dans le piège du « tout journaliser » ; un audit excessif dégrade les performances et noie le signal dans le bruit, ciblez donc les événements critiques de manière sélective et réfléchie.","Écrivez la piste d'audit vers une cible séparée et sécurisée que le DBA de production ne peut pas modifier (collecteur de logs externe/SIEM) ; empêchez la falsification de la piste par la séparation des tâches.","Utilisez les capacités du moteur : ajustez le niveau de détail à vos besoins avec Oracle Unified/Fine-Grained Audit, MSSQL SQL Audit et PG pgAudit.","Fixez la rétention selon les exigences de conformité (KVKK/RGPD/PCI-DSS, etc.) et automatisez la politique d'archivage/de nettoyage ; préservez l'intégrité de la piste.","Ne vous contentez pas de collecter la piste ; examinez-la régulièrement et reliez-la à des alertes : l'audit ne crée de la valeur que lorsqu'il signale des anomalies et les transforme en actions, et non comme une archive morte."],"level":"expert"}]},"guide/best-practices-4.json":{"topics":[{"id":"oracle-rac-cache-fusion","title":"Oracle RAC : Cache Fusion et l'interconnect","category":"Oracle","summary":"Dans RAC, plusieurs instances partagent la même base ; Cache Fusion maintient la cohérence en transférant les blocs entre nœuds via l'interconnect plutôt que par le disque.","points":["Lorsqu'une instance a besoin d'un bloc présent dans le buffer cache d'une autre instance, Cache Fusion le transfère de RAM à RAM via l'interconnect privé ; il ne l'écrit pas sur disque pour le relire ensuite (disk ping).","Global Cache Service (processus GCS/LMS) gère la propriété et le mode (shared/exclusive) des blocs, tandis que Global Enqueue Service (GES) gère les verrous et les enqueues.","Chaque bloc possède un nœud master ; l'emplacement des blocs est suivi dans le Global Resource Directory et peut être remasterisé avec DRM (Dynamic Resource Mastering) selon l'intensité des accès.","L'interconnect nécessite un réseau dédié à faible latence ; utilisez des Jumbo Frames (MTU 9000) et, si nécessaire, RDMA/RoCE ou InfiniBand, et ne le partagez jamais avec le réseau public.","Les événements d'attente 'gc buffer busy' et 'gc cr/current block busy' indiquent une contention sur des hot blocks ; augmenter le CACHE des séquences, utiliser des reverse key indexes ou répartir la charge par partitionnement peut aider.","Si la statistique gc blocks lost ne cesse d'augmenter, il y a une perte de paquets sur l'interconnect ; revoyez la carte réseau, les tampons du switch et la taille des tampons de socket (rmem/wmem)."],"level":"expert"},{"id":"oracle-rac-node-eviction","title":"Oracle RAC : node eviction et split-brain","category":"Oracle","summary":"Clusterware évince du cluster un nœud qui ne répond pas pour éviter le split-brain ; il décide en fonction du heartbeat réseau, du heartbeat disque et de la majorité des voting disks.","points":["CSSD surveille deux heartbeats distincts : le heartbeat réseau via l'interconnect (misscount, 30 s par défaut) et le heartbeat disque via les voting disks (disktimeout, 200 s par défaut).","En cas de split-brain, le sous-cluster survivant (cohort) est déterminé par la majorité des voting disks ; le nombre de voting disks doit donc être impair (3 ou 5) et ceux-ci doivent résider dans des failure groups distincts.","L'éviction s'effectue généralement par un redémarrage du nœud ou un fencing sans reboot (redémarrage de la seule pile) ; l'objectif est d'empêcher le nœud défaillant d'écrire sur le stockage partagé.","Causes d'éviction courantes : perte de l'interconnect, CSSD privé de CPU sous forte charge, latence d'E/S des voting disks et problèmes d'accès à l'OCR/aux voting disks.","Pour l'analyse de la cause racine, examinez ocssd.log, les traces CSSD, l'alert log et les messages de l'OS avec les données CHM/oclumon (Cluster Health Monitor)."],"level":"expert"},{"id":"oracle-asm-redundancy-rebalance","title":"Oracle ASM : redondance des disk groups et rebalance","category":"Oracle","summary":"ASM protège les données en les répartissant (striping) sur les disques d'un disk group et en les mettant en miroir par failure group ; il rééquilibre automatiquement les extents lors de l'ajout ou du retrait de disques.","points":["Niveaux de redondance : EXTERNAL (pas de miroir, la protection est laissée à la baie de stockage), NORMAL (2 copies, tolère la perte d'1 disque), HIGH (3 copies, tolère 2 pertes simultanées).","ASM met en miroir des failure groups, et non des disques ; l'extent primaire réside dans un failure group et ses copies miroir dans d'autres failure groups, si bien qu'aucune donnée n'est perdue même si un failure group entier tombe en panne.","L'ajout ou le retrait d'un disque déclenche un rebalance qui redistribue les extents ; ASM_POWER_LIMIT (0-1024) règle l'équilibre entre vitesse et consommation de ressources.","La taille de l'AU (Allocation Unit) est fixée à la création du disk group ; pour les grandes bases, une AU de 4 MB améliore l'efficacité des E/S.","Les voting disks et l'OCR peuvent être stockés dans un disk group en redondance normal/high ; un quorum failure group sert uniquement aux voting disks et ne contient aucun extent de données."],"level":"expert"},{"id":"oracle-data-guard-modes","title":"Oracle Data Guard : types de standby et modes de protection","category":"Oracle","summary":"Data Guard assure la reprise après sinistre en transférant le redo vers un standby ; le choix entre standby physique/logique et les trois modes de protection déterminent l'équilibre entre perte de données et disponibilité.","points":["Un standby physique est une copie bloc pour bloc identique au primaire (Redo Apply/MRP) ; avec Active Data Guard, il peut être interrogé en lecture seule tout en étant ouvert. Un standby logique convertit le redo en SQL et l'applique (SQL Apply), et peut héberger des structures/index différents.","Maximum Performance (par défaut) : le redo est transmis de manière asynchrone (ASYNC) et la base primaire n'attend pas d'accusé de réception ; aucun impact sur les performances, mais un faible risque de perte de données.","Maximum Availability : le redo est transmis de manière synchrone (SYNC/AFFIRM) ; si la standby est injoignable, la base primaire ne s'arrête pas mais revient au comportement MAX PERFORMANCE, puis se resynchronise au retour de la standby.","Maximum Protection : la base primaire ne valide pas tant qu'au moins une standby n'a pas accusé réception ; aucune perte de données n'est garantie, mais la base primaire s'arrête si toutes les standbys sont perdues.","Fast-Start Failover (FSFO) utilise un processus observer pour basculer automatiquement en cas de défaillance de la base primaire ; un RPO=0 est visé avec FastStartFailoverThreshold et un transport synchrone.","Surveillez le retard d'application sur la standby avec v$dataguard_stats et les métriques 'apply lag'/'transport lag' ; le real-time apply applique le redo avant son archivage."],"level":"expert"},{"id":"oracle-exadata-offload","title":"Oracle Exadata : Smart Scan et storage indexes","category":"Oracle","summary":"Sur Exadata, les cellules de stockage prennent en charge une partie du traitement des requêtes (offload) et ne renvoient au nœud de base de données que les données utiles ; Smart Scan, les storage indexes et HCC réduisent considérablement les E/S.","points":["Pour les requêtes en parcours complet, Smart Scan effectue le filtrage des prédicats et la projection des colonnes dans la cellule de stockage ; seules les lignes/colonnes nécessaires, et non des blocs, sont renvoyées au nœud de base de données, ce qui réduit le trafic sur l'interconnect.","Smart Scan ne fonctionne qu'avec les lectures direct path ; il ne s'active pas pour les blocs lus depuis le buffer cache ni pour certaines opérations ; mesurez le gain avec les statistiques 'cell physical IO bytes eligible for predicate offload' et 'cell physical IO interconnect bytes returned by smart scan' ('cell physical IO bytes saved by storage index' ne montre que l'économie due au storage index).","Un storage index conserve en mémoire les valeurs min/max des colonnes pour chaque région de stockage de 1 MB ; si le prédicat de la requête sort de la plage, la région est ignorée sans être lue (il fonctionne comme un index négatif).","Les storage indexes ne sont pas persistants ; ils résident en mémoire et sont reconstruits au redémarrage de la cellule. Des données physiquement ordonnées (clustering) augmentent le taux d'ignorance des régions.","HCC (Hybrid Columnar Compression) compresse les données par colonne au sein de Compression Units ; QUERY LOW/HIGH est destiné aux données très interrogées, tandis qu'ARCHIVE LOW/HIGH vise les données d'archive rarement consultées et offre une compression très élevée au prix d'un coût CPU important."],"level":"expert"},{"id":"oracle-awr-ash-analysis","title":"Analyse des performances avec Oracle AWR et ASH","category":"Oracle","summary":"AWR collecte périodiquement des snapshots de performance, tandis qu'ASH échantillonne les sessions actives chaque seconde ; ensemble, ils répondent à la question « où est passé le temps » en termes d'événements d'attente.","points":["Par défaut, AWR prend un snapshot toutes les 60 minutes et le conserve 8 jours ; un rapport AWR entre deux snapshots montre quels SQL et événements d'attente ont consommé le DB Time (licence Diagnostics Pack requise).","Commencez toujours l'analyse par 'Top 10 Foreground Events by Total Wait Time' et le DB Time ; observez-y l'équilibre CPU / E/S / concurrence au lieu de vous perdre dans des statistiques isolées.","ASH échantillonne v$active_session_history une fois par seconde et capture les problèmes brefs et soudains (pics) ; il révèle, par SQL_ID et session bloquante, les tempêtes momentanées de verrous/latches qu'AWR ne voit pas.","Average Active Sessions (AAS = DB Time / temps écoulé) résume la charge du système en un seul chiffre ; un AAS supérieur au nombre de cœurs CPU indique une saturation.","Pour le tuning SQL, prenez le SQL_ID le plus coûteux dans la section 'SQL ordered by Elapsed Time/Gets' d'AWR, confirmez avec ASH l'étape sur laquelle ce SQL attend, puis examinez le plan ligne par ligne avec un rapport SQL Monitor."],"level":"expert"},{"id":"oracle-scn-read-consistency","title":"Oracle SCN et cohérence en lecture","category":"Oracle","summary":"Le System Change Number (SCN) est l'horloge logique de la base ; Oracle isole les lecteurs des écrivains en construisant à partir de l'undo une image cohérente (read consistency) au SCN de début de la requête.","points":["Le SCN croît de manière monotone à chaque commit et est enregistré dans le redo/undo, le fichier de contrôle et les en-têtes de bloc ; si le SCN de dernière modification d'un bloc est supérieur au SCN de la requête, le bloc est « rejoué à l'envers ».","Pour la cohérence en lecture, Oracle produit une copie CR (Consistent Read) du bloc courant et la ramène au SCN de début de la requête à l'aide de l'undo ; ainsi, « les lecteurs ne bloquent pas les écrivains et les écrivains ne bloquent pas les lecteurs ».","Si l'undo est insuffisant ou si undo_retention est dépassé, une requête longue reçoit 'ORA-01555 snapshot too old' ; la solution consiste à agrandir le tablespace d'undo, ajuster la rétention ou raccourcir la requête.","La cohérence au niveau instruction utilise un SCN distinct pour chaque instruction SQL ; une transaction SERIALIZABLE ou en lecture seule conserve pour toutes ses instructions l'unique SCN du début de la transaction.","En RAC, le SCN est maintenu synchronisé entre toutes les instances (Lamport ou broadcast-on-commit) ; l'avancement du SCN est coordonné entre les nœuds pour garantir une cohérence globale."],"level":"expert"},{"id":"postgres-patroni-failover","title":"PostgreSQL : failover automatique avec Patroni","category":"PostgreSQL","summary":"Patroni assure l'élection du leader et le failover automatique en conservant l'état du cluster PostgreSQL dans un magasin de consensus distribué (DCS) ; il prévient le split-brain avec un watchdog.","points":["L'état du cluster et le verrou du leader sont conservés dans un DCS tel qu'etcd/Consul/ZooKeeper ; le leader renouvelle périodiquement un verrou doté d'un TTL et, s'il n'y parvient pas, le verrou expire et une nouvelle élection démarre.","Lors d'un failover, Patroni choisit le réplica le plus à jour (celui dont le LSN est le plus élevé) ; le nouveau leader est promu et les autres réplicas se reconnectent à lui (reinitialize/rewind).","Le watchdog (softdog) est un filet de sécurité de niveau matériel qui réinitialise la machine lorsque l'ancien leader perd le contact avec le DCS, empêchant deux nœuds d'être leader en même temps (split-brain).","maximum_lag_on_failover empêche un réplica très en retard de devenir leader et de provoquer une perte de données ; synchronous_mode permet de viser un RPO=0, mais la latence d'écriture augmente.","Les clients ne doivent pas se connecter directement à un nœud mais via HAProxy, avec le health check REST de Patroni, ou via une VIP ; le trafic est ainsi routé automatiquement vers le nouveau leader lors d'un changement de leader.","Une majorité DCS (quorum) est obligatoire ; utilisez un cluster etcd de 3 ou 5 membres. Si le DCS est injoignable, Patroni joue la prudence et peut rétrograder le leader courant en lecture seule."],"level":"expert"},{"id":"postgres-logical-replication-upgrade","title":"PostgreSQL : montée de version majeure sans interruption avec la réplication logique","category":"PostgreSQL","summary":"Comme la réplication logique peut transporter les modifications de lignes entre versions, elle permet des montées de version majeure avec une interruption quasi nulle ; l'ancienne et la nouvelle version fonctionnent en parallèle pendant un temps.","points":["La réplication logique décode logiquement les modifications depuis le WAL (INSERT/UPDATE/DELETE) ; contrairement à la réplication physique, elle fonctionne entre versions majeures et plateformes différentes.","Déroulement : créez un schéma vide sur la nouvelle version, définissez une PUBLICATION sur l'ancienne et une SUBSCRIPTION sur la nouvelle ; les tables sont d'abord copiées (synchronisation initiale), puis les modifications en direct commencent à circuler.","Chaque table doit avoir une REPLICA IDENTITY ; pour les tables sans clé primaire, la réplication des UPDATE/DELETE exige REPLICA IDENTITY FULL, faute de quoi une erreur est levée.","Les modifications DDL ne sont pas répliquées automatiquement et les valeurs courantes des séquences ne sont pas reportées ; lors de la bascule, avancez manuellement les séquences sur la nouvelle version, sinon des clés en conflit apparaîtront.","Lors de la bascule, arrêtez brièvement les écritures de l'application sur l'ancienne version et, une fois le retard à zéro (vérifiez avec pg_stat_replication), redirigez l'application vers la nouvelle version ; si nécessaire, gardez une souscription inverse prête pour un retour arrière rapide.","Limites : les large objects et certains DDL ne sont pas répliqués ; wal_level=logical et des valeurs suffisantes de max_replication_slots/max_wal_senders doivent être configurés."],"level":"expert"},{"id":"postgres-pgvector-similarity","title":"PostgreSQL : recherche par similarité avec pgvector","category":"PostgreSQL","summary":"pgvector stocke des embeddings vectoriels et effectue une recherche approximative des plus proches voisins (ANN) ; les index HNSW et IVFFlat déterminent le compromis précision-vitesse-mémoire.","points":["Choisissez l'opérateur de distance selon le cas d'usage : <-> L2 (euclidienne), <=> cosinus, <#> produit scalaire ; l'index doit utiliser la même classe d'opérateurs que la requête, sinon il n'est pas utilisé.","HNSW construit un graphe multicouche ; il offre un recall élevé et des requêtes rapides, mais consomme plus de mémoire et se construit lentement. m (nombre de connexions) et ef_construction règlent la qualité de construction, ef_search règle la précision à l'exécution.","IVFFlat répartit les vecteurs en lists clusters et ne parcourt que probes clusters par requête ; il consomme moins de mémoire, mais l'index doit être créé APRÈS le chargement des données (les clusters n'ont aucun sens sur une table vide).","Pour IVFFlat, commencez avec lists ≈ rows/1000 jusqu'à 1M de lignes et ≈ sqrt(rows) au-delà de 1M ; augmenter probes améliore le recall mais ralentit la requête, réglez donc ce compromis par la mesure.","L'ANN renvoie des résultats approximatifs ; si une exactitude de 100 % est requise, un parcours exact sans index est nécessaire. Avec des recherches filtrées (WHERE), le recall de l'index peut baisser ; envisagez si besoin un iterative scan ou un index partiel.","La dimension des vecteurs et le nombre de lignes déterminent les besoins en mémoire ; gardez maintenance_work_mem suffisamment grand pendant la construction, sinon la construction HNSW déborde sur disque et devient très lente."],"level":"expert"},{"id":"postgres-citus-distributed","title":"PostgreSQL : mise à l'échelle distribuée avec Citus","category":"PostgreSQL","summary":"Citus fait évoluer PostgreSQL horizontalement en découpant les tables en shards selon une colonne de distribution et en les répartissant sur des nœuds workers ; le choix de la colonne de distribution détermine les performances.","points":["Le nœud coordinator reçoit la requête et la route vers les shards concernés selon la colonne de distribution ; les nœuds workers stockent les shards et les traitent en parallèle. Les shards sont des tables PostgreSQL standard (découpées par plage de hachage).","La colonne de distribution doit être le filtre commun de la plupart des requêtes et JOIN (par ex. tenant_id) ; les lignes ayant la même valeur arrivent dans le même shard, de sorte que les JOIN et les clés étrangères restent locaux au même nœud (colocation).","La colocation est essentielle : les tables jointes entre elles s'exécutent sans brassage de données entre nœuds (repartition) si elles sont distribuées avec la même colonne de distribution et le même nombre de shards.","Faites des petites tables fréquemment jointes à tous les shards des reference tables ; elles sont entièrement copiées sur chaque worker et éliminent le coût réseau des JOIN distribués.","Les requêtes qui n'incluent pas la colonne de distribution se diffusent à tous les shards et sont coûteuses ; en SaaS multi-tenant, filtrer par tenant_id réduit la requête à un seul shard (router execution)."],"level":"expert"},{"id":"postgres-timescaledb-hypertable","title":"PostgreSQL : hypertables et continuous aggregates TimescaleDB","category":"PostgreSQL","summary":"TimescaleDB fait évoluer les données de séries temporelles grâce aux hypertables, qui les découpent automatiquement en chunks par intervalle de temps, et aux continuous aggregates précalculés.","points":["Une hypertable ressemble de l'extérieur à une table unique, mais elle est découpée en coulisses en chunks selon la colonne de temps (et éventuellement d'espace) ; les requêtes ne touchent que les chunks de la plage temporelle concernée (chunk exclusion).","Réglez chunk_time_interval pour que les chunks les plus actifs tiennent en mémoire (shared_buffers) ; trop petit, il entraîne un surcoût excessif de chunks/planification ; trop grand, une exclusion peu efficace.","Un continuous aggregate est une vue matérialisée rafraîchie de manière incrémentale ; il est tenu à jour en arrière-plan par une refresh policy qui ne recalcule que les intervalles de temps modifiés.","Avec la real-time aggregation, un continuous aggregate combine les données anciennes matérialisées aux données les plus récentes de la table brute pour renvoyer des résultats à jour au moment de la requête.","Les anciens chunks peuvent être fortement réduits grâce à la compression en colonnes ; une retention policy supprime automatiquement avec drop_chunks les chunks plus anciens qu'un âge donné (bien moins coûteux qu'un DELETE ligne par ligne)."],"level":"expert"},{"id":"postgres-vacuum-xid-wraparound","title":"PostgreSQL : VACUUM et XID wraparound","category":"PostgreSQL","summary":"MVCC laisse derrière lui des versions de lignes mortes ; VACUUM les nettoie et marque les lignes comme 'frozen', évitant la perte de données causée par le bouclage de l'identifiant de transaction (XID) sur 32 bits.","points":["PostgreSQL utilise des XID de 32 bits, qui sont circulaires ; chaque version de ligne est marquée d'un xmin. Si les lignes suffisamment anciennes ne sont pas gelées, au bouclage des XID, les transactions « passées » semblent appartenir au « futur » et des lignes peuvent soudain devenir invisibles.","autovacuum remplit deux rôles : le nettoyage régulier (dead tuples, bloat) et le gel (freezing) ; pour les tables approchant autovacuum_freeze_max_age, un vacuum 'anti-wraparound' obligatoire est déclenché même si autovacuum est désactivé.","Des avertissements sont journalisés à l'approche du wraparound ; si la limite est très proche, la base refuse les nouvelles écritures et passe en lecture seule pour éviter la perte de données, et la solution consiste à exécuter un VACUUM agressif (FREEZE).","Surveillez le plus ancien XID non gelé avec pg_database.datfrozenxid et age() ; les transactions ouvertes de longue durée (idle in transaction) et les slots de réplication inutilisés bloquent la progression de VACUUM, augmentant le bloat et le risque de wraparound.","Sous forte charge d'écriture, un autovacuum en retard entraîne du bloat ; augmentez autovacuum_vacuum_cost_limit, réduisez naptime et appliquez des paramètres autovacuum spécifiques aux grandes tables/tables chaudes.","VACUUM ne rend généralement pas l'espace au système d'exploitation ; il le marque comme réutilisable. Une réduction physique exige VACUUM FULL (verrouille la table) ou pg_repack (en ligne)."],"level":"expert"},{"id":"mysql-innodb-cluster","title":"MySQL : Group Replication et InnoDB Cluster","category":"MySQL","summary":"InnoDB Cluster est un cluster à majorité (quorum) basé sur Group Replication qui assure la haute disponibilité en présentant à l'application un point de connexion unique via MySQL Router.","points":["Group Replication propage les transactions au groupe via une couche de communication de groupe basée sur Paxos ; une transaction n'est validée qu'après accord d'une majorité du groupe (quorum), ce qui assure failover automatique et cohérence.","Le mode single-primary (par défaut) n'autorise les écritures que sur un seul nœud, et le groupe élit automatiquement un nouveau primaire en cas de défaillance ; en mode multi-primary, tous les nœuds acceptent les écritures, mais la gestion des conflits (certification) se complexifie.","MySQL Router lit les métadonnées du cluster et offre deux ports à l'application : les écritures vont au primaire courant (6446), les lectures aux réplicas (6447) ; lors d'un failover, le routage est mis à jour sans modifier la connexion de l'application.","La perte de la majorité (par ex. 2 nœuds sur 3 perdus) bloque le groupe ; pour éviter le split-brain, le cluster n'accepte plus aucune écriture, et la reprise se fait manuellement avec group_replication_force_members.","Group Replication exige un binlog en mode ligne (format ROW), GTID et InnoDB ; une clé primaire sur chaque table est obligatoire, sinon le nœud ne peut pas rejoindre le cluster.","Un nœud qui rejoint le groupe se synchronise d'abord par transfert d'état depuis un donneur existant (distributed recovery : clone plugin ou binlog), puis bascule sur le flux en direct."],"level":"expert"},{"id":"mysql-semisync-replication","title":"MySQL : réplication semi-synchrone","category":"MySQL","summary":"La réplication semi-synchrone réduit la perte de données en faisant attendre le primaire, avant d'acquitter un commit, qu'au moins un réplica ait reçu la transaction dans son relay log.","points":["Avec la réplication asynchrone, le primaire acquitte le commit sans attendre le réplica ; si le primaire tombe, les transactions non encore transmises sont perdues. Le semi-sync attend un accusé de réception d'au moins N réplicas (rpl_semi_sync_source_wait_for_replica_count).","AFTER_SYNC (par défaut, recommandé) : le primaire reçoit d'abord l'accusé du réplica, puis renvoie le commit au client ; cela garantit que chaque transaction vue comme « réussie » par le client existe sur au moins un réplica.","En mode AFTER_COMMIT, le primaire valide d'abord localement puis attend le réplica ; si le réplica est en retard, d'autres sessions peuvent voir des données pas encore répliquées (comparable à une lecture fantôme).","Si rpl_semi_sync_source_timeout est dépassé, le primaire repasse automatiquement en asynchrone sans s'arrêter ; il revient en semi-sync au retour du réplica, la « garantie » est donc conditionnelle.","Le semi-sync garantit « reçu », et non « appliqué sur le réplica » ; pour un RPO=0 strict, Group Replication ou une couche de consensus externe est plus appropriée."],"level":"expert"},{"id":"mysql-undo-purge","title":"MySQL InnoDB : tablespaces d'undo et purge","category":"MySQL","summary":"InnoDB conserve les anciennes versions de lignes dans l'undo log pour MVCC ; les threads de purge nettoient les enregistrements d'undo et les versions mortes dont plus aucun lecteur n'a besoin.","points":["Chaque modification produit un enregistrement d'undo log pour le rollback et les lectures cohérentes ; une transaction longue maintient en vie d'anciennes read views, bloquant la purge et faisant croître l'undo (la history list length augmente).","Si la history list length (SHOW ENGINE INNODB STATUS) ne cesse de croître, la purge ne suit pas ; la cause principale est généralement des transactions longtemps ouvertes ou d'anciennes lectures maintenues actives (par ex. mysqldump --single-transaction).","innodb_purge_threads contrôle le parallélisme de la purge, et innodb_max_purge_lag ajoute un délai artificiel aux DML lorsque la purge prend beaucoup de retard, évitant que la charge ne submerge la purge.","Dans MySQL 8, l'undo est conservé dans des tablespaces d'undo distincts et les fichiers d'undo qui grossissent peuvent être réduits par troncature automatique (innodb_undo_log_truncate) ; dans les versions plus anciennes, l'undo restait dans le tablespace système et ne diminuait jamais.","Un undo gonflé oblige les lectures cohérentes à parcourir des chaînes de versions plus longues (E/S et CPU) ; des transactions courtes et une discipline d'autocommit minimisent la pression sur l'undo."],"level":"expert"},{"id":"mysql-buffer-pool-lru","title":"MySQL InnoDB : instances du buffer pool et LRU","category":"MySQL","summary":"Le buffer pool InnoDB conserve en mémoire les pages de données et d'index ; l'algorithme LRU à insertion au point médian empêche les parcours de table d'évincer les données chaudes de la mémoire.","points":["Sur un serveur de base de données dédié, le buffer pool est généralement fixé à ~70-80 % de la RAM (innodb_buffer_pool_size ; moins sur un serveur partagé) ; l'objectif est de garder en mémoire le working set fréquemment accédé et de minimiser les E/S disque.","La liste LRU est divisée en sous-listes 'young' (chaude) et 'old' (froide) ; une page nouvellement lue n'est pas insérée en tête de liste mais en tête de la région old (point médian, ~37 %), de sorte qu'un parcours ponctuel n'évince pas les pages chaudes.","Une page de la région old n'est promue en young que si elle est de nouveau accédée après l'écoulement d'innodb_old_blocks_time ; cela empêche les grands parcours complets de polluer le buffer pool.","innodb_buffer_pool_instances divise le pool en plusieurs instances indépendantes ; chaque instance ayant son propre mutex, la contention sur le mutex du buffer pool diminue sous forte concurrence (pertinent pour les grands pools).","Surveillez le taux de succès du buffer pool et l'équilibre pages libres/dirty ; un grand nombre de dirty pages et un flush agressif peuvent provoquer une « tempête de checkpoints », et innodb_io_capacity fixe le débit de flush en fonction du stockage."],"level":"expert"},{"id":"sqlserver-always-on-ag","title":"SQL Server : Always On Availability Groups","category":"SQL Server","summary":"Un Availability Group réplique un groupe de bases vers des réplicas synchrones/asynchrones ; sur WSFC, il fournit le failover automatique, des réplicas lisibles et le read-only routing.","points":["Avec un réplica en commit synchrone, le primaire attend que le secondaire ait durci l'enregistrement de log avant de valider ; cela assure un RPO=0, mais si le réplica est en retard, la latence de commit augmente sur le primaire.","Un réplica en commit asynchrone ne fait pas attendre le primaire ; il convient aux sites distants de reprise après sinistre (DR), mais le failover peut perdre des données (RPO>0) et seul le failover manuel forcé (FORCE_FAILOVER_ALLOW_DATA_LOSS) est pris en charge.","Le failover automatique ne se produit qu'entre une paire de réplicas en commit synchrone et en mode 'automatic failover', tant que le quorum WSFC est maintenu ; les contrôles de santé sont effectués avec sp_server_diagnostics et la détection de santé au niveau base.","Un réplica secondaire lisible décharge le primaire des rapports ; grâce au read-only routing, les clients qui se connectent avec ApplicationIntent=ReadOnly sont automatiquement routés via le listener vers un réplica lisible.","Les requêtes sur un réplica lisible utilisent automatiquement l'isolation snapshot ; pour cela, une balise de version de 14 octets est ajoutée aux lignes modifiées sur le primaire, et le version store de tempdb sur le secondaire est sollicité ; de plus, si l'application du redo prend du retard, les données du réplica peuvent être obsolètes.","Le listener AG présente aux clients un point de connexion unique avec un nom réseau virtuel + IP ; lors d'un failover, le listener passe au nouveau primaire et l'application continue de fonctionner sans modifier sa chaîne de connexion."],"level":"expert"},{"id":"sqlserver-columnstore","title":"SQL Server : index columnstore et batch mode","category":"SQL Server","summary":"Un index columnstore stocke les données par colonne avec une forte compression ; le traitement en batch mode et la segment elimination accélèrent fortement les requêtes analytiques.","points":["Les données sont découpées en segments de colonnes dans des rowgroups (~1 million de lignes) et chaque segment est compressé ; seules les colonnes nécessaires à la requête sont lues, ce qui réduit fortement les E/S.","Chaque segment contient des métadonnées min/max ; si le prédicat de la requête sort de la plage de valeurs du segment, celui-ci est ignoré sans être lu (segment elimination) ; charger les données triées est donc très important.","Le batch mode traite les lignes par groupes d'environ 900 au lieu de ligne par ligne, augmentant le travail effectué par cycle CPU ; il est bien plus rapide que le row mode pour les agrégats et les jointures (depuis SQL 2019+, le batch mode est aussi possible sur rowstore).","Un columnstore clustered est idéal pour les grandes tables de faits analytiques/DW ; pour les charges mixtes OLTP + analytique, l'analytique opérationnelle en temps réel (HTAP) peut être mise en place avec un columnstore nonclustered sur une table rowstore.","Les lignes chaudes (pas encore compressées) sont conservées par ligne dans le deltastore et compressées par le tuple mover une fois celui-ci plein ; de nombreux petits chargements provoquent de la fragmentation, préférez donc le chargement en masse."],"level":"expert"},{"id":"sqlserver-inmemory-oltp","title":"SQL Server : In-Memory OLTP (Hekaton)","category":"SQL Server","summary":"In-Memory OLTP supprime les goulets d'étranglement des charges OLTP très concurrentes en traitant des tables optimisées en mémoire avec des structures sans verrou ni latch et des procédures compilées en natif.","points":["Les tables optimisées en mémoire sont entièrement conservées en mémoire, ainsi que toutes les versions de lignes ; la concurrence est gérée par un MVCC optimiste sans verrou, éliminant la contention classique sur les latches et les verrous.","Pour la durabilité, les modifications sont toujours écrites dans le log et vidées dans des fichiers de checkpoint (data/delta) ; les tables SCHEMA_ONLY ne sont pas journalisées et sont vidées au redémarrage (idéal par ex. pour des données transitoires de staging/session).","Une procédure stockée compilée en natif compile le T-SQL en code machine (DLL), éliminant le coût d'interprétation ; elle ne peut accéder qu'aux tables optimisées en mémoire et prend en charge un sous-ensemble limité de T-SQL.","La concurrence étant optimiste, les conflits sont détectés à l'exécution ; un conflit écriture-écriture annule la transaction avec une erreur (nécessitant une nouvelle tentative), l'application doit donc intégrer une logique de retry.","Scénarios les plus adaptés : taux d'insertion très élevés (par ex. ingestion IoT/télémétrie), tables chaudes souffrant de goulets sur verrous/latches et tables temporaires utilisant tempdb ; toutes les données doivent tenir en mémoire et la pression mémoire doit être surveillée de près."],"level":"expert"},{"id":"sqlserver-query-store","title":"SQL Server : gestion des plans avec Query Store","category":"SQL Server","summary":"Query Store conserve de manière persistante les textes de requêtes, les plans d'exécution et les statistiques d'exécution ; il permet de détecter les régressions de plan et de forcer un plan précis.","points":["Query Store conserve, dans la base, tous les plans utilisés par chaque requête au fil du temps avec les métriques durée/CPU/lectures par plan ; il répond avec des données historiques à la question « pourquoi cette requête était-elle rapide hier et lente aujourd'hui ».","Les régressions de plan (un plan nouvellement compilé moins bon que l'ancien) sont repérées avec les rapports 'Regressed Queries' et 'Top Resource Consumers' ; la stabilité est rétablie en forçant le bon plan connu avec sys.sp_query_store_force_plan.","Avec les Query Store hints de SQL Server 2022, des hints (par ex. RECOMPILE, MAXDOP) peuvent être appliqués sans forcer de plan ni modifier le texte de la requête ; c'est très utile pour optimiser les requêtes d'applications tierces.","La correction automatique des plans (automatic tuning, FORCE_LAST_GOOD_PLAN) détecte les régressions, force automatiquement le bon plan précédent et revient en arrière en l'absence d'amélioration ; activez-la en production sous supervision.","Configurez correctement le mode de capture (ALL/AUTO/CUSTOM) et la taille de stockage ; AUTO filtre les requêtes rares/insignifiantes pour réduire le surcoût et l'espace, et un Query Store plein passe en mode lecture seule et cesse de collecter des données."],"level":"expert"},{"id":"mongodb-sharding-deep","title":"MongoDB : sharding, chunks et balancer","category":"MongoDB","summary":"MongoDB découpe les données en chunks selon la shard key et les répartit entre les shards ; le balancer équilibre les chunks, et le choix de la shard key détermine la répartition et le routage des requêtes.","points":["Les données sont découpées en chunks par plages de shard key et les chunks sont répartis entre les shards ; le routeur mongos examine la shard key et ne route la requête que vers le shard concerné (targeted), sinon il la diffuse à tous les shards (scatter-gather).","Une bonne shard key a une cardinalité élevée, une faible fréquence et n'est pas croissante de manière monotone ; une clé monotone (par ex. timestamp/ObjectId) concentre toutes les nouvelles écritures sur un seul shard (hotspot), et le hashed sharding les répartit.","Lorsque le nombre de chunks devient déséquilibré entre shards, le balancer migre des chunks en arrière-plan ; la cohérence entre source et cible est préservée pendant la migration, mais celle-ci a un coût en E/S et peut être planifiée dans une fenêtre de maintenance.","Un jumbo chunk est un chunk qui dépasse la taille maximale et ne peut être découpé parce que la shard key a une faible cardinalité ; il ne peut pas être migré et crée un déséquilibre, la solution étant une shard key plus sélective (composée).","Le zone sharding (tag-aware) associe des plages de shard key précises à des shards précis ; il sert à orienter le placement des données selon la géographie ou le matériel (par ex. données UE sur un shard de la région UE).","Bien que les versions modernes introduisent un équilibrage basé sur la taille et une défragmentation automatique au lieu du nombre de chunks, le principe reste le même : pour des requêtes ciblées, la shard key doit figurer dans chaque lecture/écriture."],"level":"expert"},{"id":"mongodb-oplog-election","title":"MongoDB : oplog du replica set et élections","category":"MongoDB","summary":"Dans un replica set, le primaire enregistre les opérations d'écriture dans l'oplog et les secondaires les appliquent ; en cas de défaillance du primaire, un nouveau primaire est choisi par une élection de type Raft.","points":["L'oplog est une capped collection qui enregistre chaque modification du primaire sous forme idempotente (réapplicable sans risque) ; les secondaires suivent et appliquent l'oplog pour rester synchronisés.","La taille de l'oplog détermine la fenêtre de réplication ; si un secondaire reste hors ligne plus longtemps que ce que couvre l'oplog, il devient 'stale' et nécessite une resynchronisation complète (initial sync), l'oplog doit donc être dimensionné selon la charge.","En cas de défaillance du primaire, les secondaires votent selon un protocole de type Raft ; le candidat qui obtient la majorité des voix devient primaire, on conserve donc un nombre impair de votes (avec un arbiter si besoin) pour préserver la majorité.","writeConcern 'majority' garantit qu'une écriture a atteint la majorité des nœuds et ne sera donc pas annulée lors d'un failover (protection contre le rollback) ; w:1 est plus rapide, mais l'écriture peut être perdue si le primaire tombe.","readConcern et la read preference contrôlent la fraîcheur des lectures ; lire sur les secondaires répartit la charge mais peut renvoyer des données obsolètes en raison du retard de réplication, tandis que 'majority'/'linearizable' offrent des garanties plus fortes.","Si l'ancien primaire se retrouve minoritaire après une partition réseau, il redevient secondaire ; lorsqu'il rejoint le nouveau primaire, il déplace ses écritures non validées dans un fichier de rollback (le write concern majority évite cela)."],"level":"expert"},{"id":"mongodb-wiredtiger-checkpoint","title":"MongoDB : checkpoints WiredTiger et journal","category":"MongoDB","summary":"WiredTiger assure la durabilité grâce à des checkpoints périodiques et à un journal write-ahead ; il offre un débit élevé avec une concurrence au niveau document et la compression.","points":["Par défaut, WiredTiger prend un checkpoint environ toutes les 60 secondes, en écrivant sur disque un snapshot cohérent de la mémoire ; entre deux checkpoints, la durabilité est assurée par l'enregistrement de chaque écriture dans le journal (WAL).","Sans journal, un crash fait perdre les écritures effectuées après le dernier checkpoint ; avec le journal, la récupération charge le dernier checkpoint et rejoue les opérations suivantes depuis le journal.","Le contrôle de concurrence au niveau document permet de mettre à jour simultanément différents documents d'une même collection ; les snapshots MVCC assurent des lectures cohérentes.","Le cache WiredTiger (par défaut ~50 % de la RAM) contient les données et index fréquemment accédés ; si le working set ne tient pas dans le cache, l'éviction et les E/S disque augmentent, et la saturation du cache dégrade fortement les performances.","La compression du stockage utilise par défaut snappy (équilibre vitesse/taux) ; zstd/zlib offrent des taux plus élevés mais consomment plus de CPU. Les index sont en outre réduits par la prefix compression."],"level":"expert"},{"id":"redis-cluster-hashslot","title":"Redis Cluster : hash slots et resharding","category":"Redis","summary":"Redis Cluster évolue horizontalement en divisant l'espace de clés en 16384 hash slots répartis entre les nœuds ; les clients trouvent le bon nœud grâce aux redirections MOVED/ASK.","points":["Chaque clé est associée à un slot par CRC16(key) mod 16384 ; chaque master possède des plages de slots précises, de sorte que le cluster répartit les données sans routeur central.","Si un client s'adresse au mauvais nœud, celui-ci répond par MOVED pour indiquer le propriétaire permanent du slot, et le client met à jour sa table des slots. Pendant le resharding, une redirection ASK temporaire est utilisée pour le slot en cours de migration.","Le resharding déplace à chaud des slots (et les clés qu'ils contiennent) d'un nœud à un autre ; la migration se fait clé par clé et le cluster continue de servir pendant toute l'opération.","Les opérations multi-clés (MSET, transactions, Lua) ne fonctionnent que sur des clés du même slot ; les hash tags ({...}) servent à regrouper des clés liées dans le même slot, par ex. user:{123}:name et user:{123}:email.","Pour la haute disponibilité, donnez à chaque master au moins un réplica ; en cas de défaillance d'un master, son réplica est promu. Le cluster décide à la majorité des masters, et les écritures s'arrêtent si la majorité est perdue.","Si un slot n'est pas assigné ou n'a pas de master, le cluster rejette toutes les écritures (lorsque cluster-require-full-coverage est activé) ; les 16384 slots doivent tous être assignés."],"level":"expert"},{"id":"redis-replication-psync","title":"Redis : réplication et PSYNC","category":"Redis","summary":"En réplication asynchrone Redis, le master transfère un snapshot des données au réplica puis diffuse les commandes suivantes ; PSYNC effectue une resynchronisation partielle après de courtes déconnexions.","points":["Lors de la synchronisation initiale, le master produit un snapshot RDB et l'envoie au réplica (full resync) ; pendant ce temps, il met en tampon les écritures entrantes dans un replication backlog buffer et les diffuse après le RDB.","PSYNC suit l'offset de réplication du réplica et l'ID de réplication du master ; après une courte déconnexion, si les données manquées sont encore dans le backlog buffer, seule la partie manquante est envoyée (partial resync), évitant un full resync coûteux.","repl-backlog-size détermine la durée de déconnexion tolérable ; avec un débit d'écriture élevé, un petit backlog impose un full resync à chaque déconnexion, ce qui implique la génération d'un RDB sur le master et un coût réseau.","La réplication est asynchrone : le master acquitte une écriture sans attendre les réplicas, de sorte qu'en cas de crash soudain du master les dernières écritures peuvent être perdues ; la commande WAIT permet d'attendre qu'un nombre donné de réplicas l'aient reçue, mais ce n'est pas une véritable garantie synchrone.","Avec la réplication diskless (repl-diskless-sync), le master peut diffuser le RDB directement sur le socket sans l'écrire sur disque ; cela accélère la synchronisation initiale dans les environnements aux disques lents mais au réseau rapide."],"level":"expert"},{"id":"redis-memory-fragmentation","title":"Redis : fragmentation mémoire et jemalloc","category":"Redis","summary":"Redis utilise jemalloc par défaut ; les allocations et libérations de tailles variables provoquent avec le temps une fragmentation mémoire, faisant paraître le RSS plus grand que les données réellement utilisées.","points":["mem_fragmentation_ratio = used_memory_rss / used_memory ; une valeur proche de 1 est idéale, nettement supérieure à 1 (par ex. >1.5) elle indique une fragmentation, et inférieure à 1 elle indique que des données sont swappées sur disque (mauvais).","La fragmentation apparaît lorsque des clés de tailles différentes sont sans cesse créées et supprimées, laissant des trous inutilisables dans les arenas jemalloc ; des modifications fréquentes de grandes valeurs l'aggravent.","La défragmentation active (activedefrag yes) compacte les blocs mémoire à l'exécution avec l'aide de jemalloc pour réduire la fragmentation ; ses seuils (active-defrag-ignore-bytes, -threshold-lower/upper) sont réglés pour limiter le surcoût.","Configurez correctement maxmemory et maxmemory-policy ; le RSS réellement alloué par jemalloc pouvant dépasser maxmemory, gardez maxmemory en dessous de la RAM pour éviter un OOM du système d'exploitation.","Si used_memory est faible mais que le RSS reste élevé, un redémarrage (ou un nœud redémarré via failover) réinitialise généralement durablement la fragmentation ; la défragmentation active réduit le besoin de redémarrages dans la plupart des cas."],"level":"expert"},{"id":"elasticsearch-shard-sizing","title":"Elasticsearch : dimensionnement des shards","category":"Elasticsearch","summary":"Chaque shard est un index Lucene et porte un coût en ressources fixe ; trop de petits shards (oversharding) ou des shards trop volumineux dégradent les performances et la stabilité.","points":["Règle empirique : gardez un shard dans la plage 10-50 GB ; des shards très volumineux ralentissent la récupération et le rééquilibrage, tandis que des shards très petits (oversharding) chargent inutilement le cluster state et le heap.","Chaque shard, et surtout chaque champ mappé, consomme du heap. L'ancienne règle \"~20 shards par GB de heap\" ne s'applique plus à partir de 8.3 (le surcoût de heap par shard a fortement diminué) ; la recommandation actuelle est de prévoir ~1 KB de heap par champ mappé et par index sur les nœuds data, plus le surcoût, et de garder le nombre de shards par nœud sous cluster.max_shards_per_node (1000 par défaut). Trop de shards font toujours grossir le cluster state et augmentent le surcoût de coordination.","La latence des requêtes est liée au nombre de shards : une requête se diffuse à tous les shards concernés et les résultats sont fusionnés ; plus de shards que nécessaire ralentissent même les petites requêtes à cause du surcoût de coordination.","Pour les données de séries temporelles/logs, utilisez des index temporels (data streams) plutôt qu'un index géant unique ; déplacer/supprimer les anciennes données par index (ILM) devient peu coûteux et les shards restent gérables.","Le nombre de shards primaires est fixé à la création de l'index et ne peut plus être modifié (sauf via reindex/split/shrink) ; planifiez-le donc correctement dès le départ selon les prévisions de croissance, tandis que le nombre de réplicas peut être ajusté dynamiquement."],"level":"expert"},{"id":"elasticsearch-ilm-tiers","title":"Elasticsearch : ILM et niveaux hot-warm-cold-frozen","category":"Elasticsearch","summary":"Index Lifecycle Management gère économiquement les données de séries temporelles en déplaçant automatiquement les index entre niveaux coût/performance selon leur phase de cycle de vie.","points":["Une politique ILM fait passer un index par des phases : hot (écritures actives + requêtes fréquentes, SSD rapide), warm (écritures terminées, requêtes occasionnelles), cold (requêtes rares, matériel moins cher), frozen (très rare, servi depuis un stockage objet via des searchable snapshots).","Le rollover ouvre un nouvel index lorsque l'index hot atteint une taille/un âge/un nombre de documents donné ; ainsi, un index ne grossit pas indéfiniment et chaque phase est gérée par index.","Le niveau frozen conserve les données dans un stockage objet (S3/blob) via des searchable snapshots et ne les charge partiellement en mémoire qu'en cas de besoin ; il réduit fortement le coût disque, mais la latence des requêtes augmente.","Lors du passage en warm/cold, le stockage et le heap sont optimisés par force merge, shrink et réduction des réplicas ; les données ne changeant plus dans ces phases, les segments peuvent être fusionnés en un seul.","Associez les niveaux de données aux rôles de nœuds (data_hot/data_warm/data_cold/data_frozen) ; ILM utilise des filtres d'allocation de shards pour placer l'index sur les nœuds disposant du matériel adéquat."],"level":"expert"},{"id":"elasticsearch-segment-merge","title":"Elasticsearch : fusion de segments et force merge","category":"Elasticsearch","summary":"Lucene crée de nouveaux segments immuables à chaque refresh ; la fusion en arrière-plan combine et supprime des segments, tandis que force merge est une opération manuelle à utiliser avec prudence.","points":["Chaque refresh (1 s par défaut) produit un nouveau segment, et les suppressions sont marquées comme 'tombstones' dans le segment ; de nombreux petits segments ralentissent les lectures, la fusion en arrière-plan les combine donc.","Les documents supprimés/mis à jour ne sont physiquement retirés du disque que lors d'une fusion ; sous forte charge de mises à jour, les 'deleted docs' s'accumulent dans les segments et l'utilisation disque/mémoire gonfle.","Sur les index qui ne reçoivent plus d'écritures (lecture seule), un force merge à 1 segment améliore la vitesse des requêtes et l'efficacité disque ; c'est une étape typique de la phase warm d'ILM.","N'exécutez JAMAIS de force merge sur un index activement alimenté ; le très gros segment produit n'entre pas dans le cycle de fusion normal, les suppressions ultérieures ne peuvent pas être nettoyées, et il étouffe les E/S et le heap pendant longtemps.","Augmenter temporairement refresh_interval pendant les chargements en masse (par ex. -1 ou 30s) produit des segments moins nombreux et plus gros, réduisant la charge de fusion et améliorant le débit d'écriture."],"level":"expert"},{"id":"elasticsearch-node-roles","title":"Elasticsearch : rôles des nœuds et topologie du cluster","category":"Elasticsearch","summary":"Les nœuds Elasticsearch assument des rôles tels que master, data, coordinating et ingest ; séparer les rôles dans les grands clusters apporte stabilité et évolutivité.","points":["Les nœuds master-eligible gèrent le cluster state (index, placement des shards, mappings) ; pour des élections stables, utilisez un nombre impair (généralement 3) de nœuds master dédiés et appuyez-vous sur un quorum (majorité) pour éviter le split-brain.","Les nœuds data hébergent les shards et traitent les E/S d'indexation et de requête ; ils consomment le plus de CPU/RAM/disque et peuvent être répartis en niveaux de données (hot/warm/cold/frozen).","Un nœud coordinating-only n'a aucun autre rôle : il distribue les requêtes aux shards concernés et fusionne les résultats (scatter-gather) ; il protège les nœuds data en prenant en charge la fusion des recherches/agrégations lourdes et joue le rôle de répartiteur de charge.","Un nœud ingest traite les données via des pipelines (transformation, enrichissement) avant l'indexation ; déplacer les pipelines d'ingestion lourds vers des nœuds ingest dédiés protège les performances de requête des nœuds data.","Ne faites jamais partager aux nœuds master une charge lourde de requêtes/données ; un master ralenti par la charge retarde les mises à jour du cluster state et provoque une instabilité dans tout le cluster (allocation de shards retardée, élections)."],"level":"expert"}]},"guide/glossary.json":{"terms":[{"term":"ACID","full":"Atomicity, Consistency, Isolation, Durability","definition":"Les quatre propriétés fondamentales qui rendent les transactions fiables : Atomicité (tout ou rien), Cohérence (les règles sont préservées), Isolation (les transactions concurrentes ne se corrompent pas mutuellement) et Durabilité (les données validées ne sont pas perdues). C'est la garantie transactionnelle des bases relationnelles.","tags":["transaction","transaction","acid","consistency","consistency"]},{"term":"BASE","full":"Basically Available, Soft state, Eventually consistent","definition":"Une approche de cohérence relâchée, alternative à ACID, privilégiée dans les systèmes distribués et NoSQL. Le système donne la priorité à la disponibilité permanente et accepte que les données deviennent cohérentes avec le temps (cohérence à terme). Il sacrifie la cohérence stricte au profit de l'évolutivité.","tags":["base","distributed","distributed","eventual-consistency","nosql"]},{"term":"MVCC","full":"Multi-Version Concurrency Control","definition":"Une méthode de concurrence qui conserve plusieurs versions d'une même ligne afin que lecteurs et écrivains travaillent sans se verrouiller mutuellement. Chaque transaction voit son propre snapshot des données, de sorte que les lectures ne bloquent pas les écritures et inversement. Utilisée dans des systèmes tels que PostgreSQL et Oracle.","tags":["concurrency","concurrency","lock","mvcc","snapshot"]},{"term":"WAL","full":"Write-Ahead Log","definition":"Le principe selon lequel un enregistrement de journal est écrit durablement sur disque AVANT que la modification ne soit écrite dans les fichiers de données. La récupération après crash et la durabilité en dépendent. Il s'appelle WAL dans PostgreSQL et transaction log dans SQL Server.","tags":["wal","log","recovery","recovery","durability"]},{"term":"Redo Log","full":"","definition":"Le journal qui enregistre les modifications apportées à la base et sert à réappliquer (redo) le travail perdu lors d'un crash. Pendant la récupération, il rejoue les modifications validées mais pas encore écrites sur disque. Le redo log d'Oracle correspond au concept général de WAL.","tags":["redo","log","recovery","recovery","oracle"]},{"term":"Undo / Rollback Segment","full":"","definition":"La zone qui stocke l'image antérieure des données modifiées par une transaction. Elle a deux rôles : annuler une modification par rollback et fournir l'ancienne version aux lecteurs sous MVCC. Elle s'appelle undo/rollback segment dans Oracle et undo/version store dans d'autres systèmes.","tags":["undo","rollback","rollback","mvcc","segment"]},{"term":"Buffer Pool / Buffer Cache","full":"","definition":"Le cache mémoire où sont conservées les pages de données et d'index fréquemment utilisées. Les lectures disque étant coûteuses, les performances de la base dépendent fortement du taux de succès de ce cache (cache hit ratio). Il s'appelle buffer pool dans MySQL InnoDB et buffer cache dans SQL Server.","tags":["buffer-pool","buffer-cache","cache","cache","memory"]},{"term":"Page sale (dirty page)","full":"","definition":"Une page de données modifiée en mémoire mais pas encore écrite sur disque. La durabilité est assurée par le WAL ; la page elle-même reste dirty jusqu'à ce que le checkpoint ou le background writer la vide sur disque. Trop de dirty pages allongent le temps de récupération.","tags":["dirty-page","dirty-page","memory","checkpoint","io"]},{"term":"LSN","full":"Log Sequence Number","definition":"Un numéro de séquence unique, toujours croissant, attribué à chaque enregistrement du WAL/transaction log. Il sert à déterminer jusqu'à quel enregistrement de journal une page est à jour et où la récupération doit reprendre. Il permet aussi de suivre la progression de la réplication.","tags":["lsn","log","sequence-number","recovery","replication"]},{"term":"SCN","full":"System Change Number","definition":"Dans Oracle, un horodatage logique croissant de manière monotone, attribué à chaque point de commit de la base. Il sert à la cohérence en lecture, au MVCC et à l'ordonnancement des événements lors de la récupération. On peut le voir comme l'équivalent Oracle du LSN.","tags":["scn","oracle","timestamp","read-consistency","commit"]},{"term":"Niveau d'isolation","full":"Niveau d'isolation","definition":"Le paramètre qui détermine dans quelle mesure des transactions concurrentes peuvent voir les modifications des autres. Les niveaux sont Read Uncommitted, Read Committed, Repeatable Read et Serializable ; plus le niveau est élevé, plus la cohérence augmente et la concurrence diminue. Il contrôle les anomalies de lecture dirty/non-repeatable/phantom.","tags":["isolation","isolation-level","transaction","concurrency","concurrency"]},{"term":"Lecture sale (dirty read)","full":"","definition":"Une transaction qui lit une modification qu'une autre transaction n'a pas encore validée (et pourrait annuler). Les données lues pouvant être invalides, c'est une anomalie de cohérence. Elle n'est possible qu'au niveau d'isolation Read Uncommitted.","tags":["dirty-read","anomaly","isolation","transaction","consistency"]},{"term":"Lecture non répétable","full":"","definition":"Obtenir une valeur différente en lisant deux fois la même ligne dans la même transaction, parce qu'une autre transaction a validé entre-temps. Peut se produire en Read Committed ; évitée en Repeatable Read et au-delà.","tags":["non-repeatable-read","anomaly","isolation","transaction","non-repeatable"]},{"term":"Lecture fantôme","full":"","definition":"L'ensemble de lignes change lorsqu'une requête est réexécutée avec la même condition dans la même transaction, en raison d'une insertion/suppression intermédiaire (de nouvelles lignes « fantômes » apparaissent). Selon la norme SQL, elle est évitée en Serializable ; PostgreSQL l'évite aussi en Repeatable Read.","tags":["phantom-read","phantom","anomaly","serializable","isolation"]},{"term":"Interblocage (deadlock)","full":"","definition":"Situation où deux transactions ou plus attendent chacune un verrou détenu par l'autre, si bien qu'aucune ne peut progresser. La base la détecte et rompt le cycle, généralement en choisissant la transaction la moins coûteuse comme « victime » et en l'annulant. Acquérir toujours les verrous dans le même ordre réduit le risque.","tags":["deadlock","deadlock","lock","lock","concurrency"]},{"term":"Escalade de verrous","full":"","definition":"Lorsqu'une transaction détient de nombreux verrous de ligne/page, la base les convertit en un seul verrou plus grossier (par exemple un verrou de table). Cela réduit la mémoire de gestion des verrous mais diminue la concurrence et peut accroître les blocages. Observé surtout dans SQL Server.","tags":["lock-escalation","lock","lock","concurrency","sql-server"]},{"term":"Latch","full":"","definition":"Un verrou de concurrence léger et peu coûteux, utilisé pour protéger très brièvement des structures en mémoire (par exemple les pages du buffer pool). Contrairement aux verrous transactionnels, il protège la structure mémoire physique plutôt que les données logiques, et n'est détenu que très peu de temps.","tags":["latch","lock","memory","concurrency","internal"]},{"term":"Index B-Tree","full":"Index en arbre équilibré","definition":"Le type d'index le plus courant, qui conserve les données dans une structure arborescente triée et équilibrée. Il est efficace pour les requêtes d'égalité et de plage, le tri et les recherches par préfixe. C'est le type d'index par défaut dans la plupart des bases.","tags":["btree","index","index","search","range"]},{"term":"Index de hachage","full":"","definition":"Un type d'index qui localise une clé en la passant par une fonction de hachage. Il est très rapide uniquement pour les recherches d'égalité exacte (=) ; il ne peut pas servir aux requêtes de plage ni au tri. Disponible par exemple dans PostgreSQL et dans les tables Memory de MySQL.","tags":["hash-index","index","index","equality","search"]},{"term":"Index bitmap","full":"","definition":"Un index qui conserve des tableaux de bits pour chaque valeur distincte, adapté aux colonnes à faible cardinalité (peu de valeurs distinctes). Il permet de combiner rapidement plusieurs conditions avec AND/OR ; efficace pour les charges data warehouse/OLAP mais inefficace sous de fortes écritures.","tags":["bitmap-index","index","index","olap","cardinality"]},{"term":"Index couvrant","full":"","definition":"Un index qui contient toutes les colonnes dont une requête a besoin ; la requête est satisfaite par l'index seul, sans accéder à la table (heap/clustered). Cela améliore les performances en éliminant les coûteuses recherches de lignes. Il peut être créé avec des colonnes INCLUDE.","tags":["covering-index","index","index","performance","lookup"]},{"term":"Index clustered","full":"","definition":"Un index qui maintient les lignes de la table physiquement ordonnées selon la clé d'index ; la table elle-même est stockée dans cet ordre. Une table ne peut en avoir qu'un. Il est rapide pour les requêtes de plage ; dans InnoDB, la clé primaire est toujours clustered.","tags":["clustered-index","index","index","physical","primary-key"]},{"term":"Cardinalité","full":"","definition":"Le nombre de valeurs distinctes d'une colonne. Une cardinalité élevée (par ex. e-mail) signifie de nombreuses valeurs différentes, une faible cardinalité (par ex. genre) peu de valeurs. C'est une mesure essentielle pour l'optimiseur lors du choix des index et de l'estimation du coût des plans.","tags":["cardinality","cardinality","statistics","optimizer","distinct"]},{"term":"Sélectivité","full":"","definition":"Indique quelle fraction du total des lignes une condition de filtre renvoie. Une sélectivité élevée (peu de lignes) rend l'usage d'un index intéressant ; avec une faible sélectivité (beaucoup de lignes), un parcours complet peut être moins coûteux. L'optimiseur l'utilise pour choisir un plan.","tags":["selectivity","selectivity","optimizer","index","statistics"]},{"term":"Plan d'exécution","full":"","definition":"La feuille de route des étapes (type de parcours, méthode de jointure, tri, etc.) choisies par l'optimiseur pour exécuter une requête. L'examiner avec EXPLAIN/EXPLAIN ANALYZE révèle la cause profonde des problèmes de performance. Il est important de comparer coûts estimés et réels.","tags":["execution-plan","query-plan","explain","optimizer","performance"]},{"term":"Optimiseur basé sur les coûts","full":"CBO","definition":"L'optimiseur de requêtes qui estime, à partir des statistiques, le coût des plans d'exécution possibles et choisit le moins coûteux. Des statistiques à jour sont indispensables pour de bonnes décisions ; des statistiques obsolètes mènent à de mauvais plans. C'est l'approche standard des bases modernes.","tags":["cbo","optimizer","cost","statistics","query-plan"]},{"term":"Statistiques","full":"Statistiques","definition":"Métadonnées collectées pour une table/un index afin que l'optimiseur puisse estimer le nombre de lignes, la cardinalité et la distribution des valeurs. Des statistiques à jour donnent de bons plans ; obsolètes, elles doivent être rafraîchies avec ANALYZE/UPDATE STATISTICS. Elles sont l'une des causes les plus fréquentes de mauvaises performances.","tags":["statistics","statistics","optimizer","analyze","query-plan"]},{"term":"Histogramme","full":"","definition":"Une structure statistique qui résume la distribution des valeurs d'une colonne en les répartissant en plages (buckets). Elle permet à l'optimiseur d'estimer plus précisément le nombre de lignes, surtout pour les distributions asymétriques. C'est un composant des statistiques.","tags":["histogram","statistics","statistics","optimizer","distribution"]},{"term":"Partition","full":"","definition":"Une technique qui découpe physiquement une grande table en morceaux plus petits (par exemple par date ou par plage) tout en restant logiquement une seule table. Les requêtes ne parcourent que le morceau concerné (partition pruning), et la maintenance comme l'archivage deviennent plus simples. Différence avec le sharding : tout reste sur le même serveur/la même instance.","tags":["partition","partitioning","scaling","pruning","big-data"]},{"term":"Sharding","full":"","definition":"Une méthode de répartition horizontale des données sur plusieurs serveurs/bases distincts selon une shard key. Elle permet une mise à l'échelle horizontale au-delà des limites d'une seule machine, mais complique la gestion des requêtes et des transactions. Différence avec le partitionnement : les données sont réparties sur différents nœuds.","tags":["sharding","scaling","distributed","distributed","horizontal-scaling"]},{"term":"Réplication (synchrone/asynchrone)","full":"","definition":"La copie des données d'un nœud primaire vers une ou plusieurs copies (réplicas). En réplication synchrone, le commit attend l'accusé du réplica (aucune perte de données, latence plus élevée) ; en asynchrone, il n'attend pas (rapide, mais perte de données possible si le réplica est en retard). Utilisée pour la HA et la mise à l'échelle des lectures.","tags":["replication","replication","synchronous","asynchronous","ha"]},{"term":"Réplication logique vs physique","full":"","definition":"La réplication physique crée une copie exacte au niveau des blocs disque/du WAL (même version, base entière). La réplication logique transfère les modifications au niveau ligne/SQL ; elle permet une réplication sélective de tables et un transfert entre versions et schémas différents. La réplication logique est privilégiée pour la CDC et les montées de version.","tags":["logical-replication","physical-replication","replication","cdc","wal"]},{"term":"Failover","full":"","definition":"Le transfert du service, automatiquement ou manuellement, vers un nœud standby lorsque le nœud primaire tombe. Il minimise l'interruption ; le RPO détermine la quantité de données pouvant être perdue et le RTO la durée de la reprise. Il intervient lors des pannes non planifiées.","tags":["failover","ha","high-availability","standby","rpo"]},{"term":"Switchover","full":"","definition":"Un échange planifié et contrôlé des rôles primaire et standby (par exemple pour une maintenance). Différence avec le failover : il s'agit d'une opération planifiée, non d'une panne, généralement réalisée sans perte de données.","tags":["switchover","planned","ha","maintenance","standby"]},{"term":"RPO","full":"Recovery Point Objective","definition":"La quantité maximale acceptable de données perdues après un sinistre (exprimée en temps). Par exemple, avec un RPO de 5 minutes, au plus 5 minutes de perte de données sont tolérées. La fréquence des sauvegardes et le type de réplication sont choisis pour atteindre cet objectif.","tags":["rpo","data-loss","dr","disaster-recovery","backup"]},{"term":"RTO","full":"Recovery Time Objective","definition":"La durée cible maximale pour que le système redevienne opérationnel après une interruption. Par exemple, avec un RTO de 1 heure, le service doit être rétabli au plus tard en 1 heure. L'architecture HA/DR et le niveau d'automatisation sont conçus autour de cet objectif.","tags":["rto","recovery-time","dr","disaster-recovery","ha"]},{"term":"HA","full":"High Availability","definition":"L'objectif de conception consistant à maintenir un système en service sans interruption, ou presque, malgré les pannes. Il est atteint par la redondance, le failover automatique et les contrôles de santé. Il s'exprime généralement en pourcentage de disponibilité (par ex. 99,99 %).","tags":["ha","high-availability","high-availability","failover","redundancy"]},{"term":"DR","full":"Disaster Recovery","definition":"Les plans et processus permettant de rétablir systèmes et données, généralement dans une région géographiquement distincte, après des sinistres majeurs tels qu'un incendie, une inondation ou une panne régionale. Elle se mesure par les objectifs RPO et RTO. La HA vise les pannes courantes, la DR les sinistres majeurs.","tags":["dr","disaster-recovery","disaster-recovery","rpo","rto"]},{"term":"Quorum","full":"","definition":"La majorité minimale de nœuds (généralement N/2 + 1) requise pour qu'une décision (par exemple une élection de leader ou un accusé d'écriture) soit valide dans un système distribué. Il sert à prévenir le split-brain et à préserver la cohérence. C'est pourquoi les clusters comptent le plus souvent un nombre impair de nœuds.","tags":["quorum","majority","distributed","distributed","split-brain"]},{"term":"Split-Brain","full":"","definition":"Situation où, après une partition réseau, les deux moitiés d'un cluster se croient primaires et continuent d'écrire indépendamment ; cela entraîne des données conflictuelles et de la corruption. Il est évité grâce au quorum et aux mécanismes de fencing. C'est l'un des scénarios de panne les plus dangereux des clusters HA.","tags":["split-brain","distributed","quorum","ha","consistency"]},{"term":"TDE","full":"Transparent Data Encryption","definition":"Une méthode de chiffrement, transparente pour l'application, qui maintient chiffrés sur disque les fichiers de la base (données et log). Elle protège les données même en cas de vol de disques ou de fichiers de sauvegarde (data at rest). Elle ne protège pas les données sur le réseau (in transit) ; TLS est nécessaire pour cela.","tags":["tde","encryption","encryption","security","security"]},{"term":"RBAC","full":"Role-Based Access Control","definition":"Un modèle de contrôle d'accès dans lequel les privilèges sont attribués à des rôles plutôt que directement aux utilisateurs, ceux-ci étant rendus membres des rôles. Il simplifie la gestion des privilèges et facilite l'application du principe du moindre privilège. Il garde l'administration évolutive à mesure que le nombre d'utilisateurs augmente.","tags":["rbac","role","privilege","security","security"]},{"term":"Pool de connexions","full":"","definition":"Un pool qui crée des connexions à l'avance et les réutilise au lieu d'ouvrir et fermer sans cesse les connexions de l'application à la base. Il réduit le coût d'établissement des connexions, limite la consommation de ressources et contrôle la charge. Il protège la base en empêchant les connexions excessives.","tags":["connection-pool","connection-pool","performance","resource","application"]},{"term":"Requête préparée","full":"","definition":"Une construction dans laquelle une instruction SQL est analysée et planifiée une seule fois, puis exécutée de façon répétée avec différentes valeurs de paramètres. Elle améliore les performances par la réutilisation du plan et protège contre l'injection SQL. Elle s'utilise avec les variables de liaison.","tags":["prepared-statement","bind","security","sql-injection","performance"]},{"term":"Variable de liaison","full":"","definition":"Un paramètre transmis via un espace réservé au lieu d'insérer la valeur dans le texte SQL. Elle exploite efficacement le cache de plans en évitant que la même requête soit replanifiée pour des valeurs différentes, et prévient l'injection SQL. Elle est essentielle pour les requêtes dont les valeurs littérales changent souvent.","tags":["bind-variable","parameter","prepared-statement","plan-cache","security"]},{"term":"Parameter Sniffing","full":"","definition":"Le fait que l'optimiseur génère et mette en cache le plan d'une requête préparée/paramétrée d'après la valeur de paramètre de sa première exécution. Si cette valeur est atypique, un plan peu performant pour les valeurs ultérieures peut devenir persistant. Les solutions incluent la recompilation du plan (recompile) et les hints d'optimiseur.","tags":["parameter-sniffing","plan-cache","optimizer","performance","sql-server"]},{"term":"VACUUM / Bloat","full":"","definition":"À cause du MVCC, les versions mortes des lignes supprimées/mises à jour s'accumulent dans la table ; ce gonflement s'appelle bloat. VACUUM récupère l'espace de ces lignes mortes et met à jour les statistiques (PostgreSQL). S'il n'est pas exécuté régulièrement, le disque gonfle et les performances chutent.","tags":["vacuum","bloat","bloated","mvcc","postgresql"]},{"term":"Compaction","full":"","definition":"Dans les moteurs de stockage basés sur des LSM-trees (par ex. Cassandra, RocksDB), le processus qui fusionne de nombreux petits fichiers de données et purge les enregistrements morts/obsolètes. Il améliore les performances de lecture et récupère de l'espace disque. C'est l'équivalent de VACUUM dans le monde LSM.","tags":["compaction","lsm-tree","merge","cassandra","rocksdb"]},{"term":"Oplog","full":"Operations Log","definition":"Dans MongoDB, une collection spéciale de taille fixe (capped) où toutes les opérations d'écriture appliquées au primaire sont enregistrées dans l'ordre. Les membres du replica set la lisent pour rester synchronisés avec le primaire. C'est la base de la CDC et de la réplication.","tags":["oplog","mongodb","replication","cdc","log"]},{"term":"Binlog","full":"Binary Log","definition":"Dans MySQL, le journal binaire qui enregistre toutes les opérations (ou modifications de lignes) qui modifient les données. Il sert à la réplication, à la restauration à un instant donné et à la CDC. Il peut être conservé au format row, statement ou mixed.","tags":["binlog","mysql","replication","cdc","recovery"]},{"term":"CDC","full":"Change Data Capture","definition":"Une technique qui capture les modifications insert/update/delete d'une base et les diffuse vers d'autres systèmes (data warehouse, moteur de recherche, file de messages). Elle fonctionne généralement avec un faible surcoût en lisant le journal de transactions (binlog, WAL, oplog). C'est la base de l'intégration de données en temps réel.","tags":["cdc","change-data-capture","data-streaming","binlog","integration"]},{"term":"Vue matérialisée","full":"","definition":"Une vue qui stocke physiquement sur disque le résultat d'une requête ; contrairement à une vue ordinaire, elle n'est pas recalculée à chaque requête. Elle accélère les agrégations et jointures coûteuses, mais ses données deviennent obsolètes et doivent être mises à jour par un refresh. Courante en OLAP/reporting.","tags":["materialized-view","view","cache","olap","reporting"]},{"term":"Clé étrangère","full":"","definition":"Une contrainte d'intégrité référentielle qui permet à une colonne d'une table de référencer la clé primaire/candidate d'une autre table. Elle empêche les références à des lignes inexistantes et préserve les relations. Des règles telles que ON DELETE/UPDATE CASCADE définissent le comportement des lignes dépendantes.","tags":["foreign-key","foreign-key","integrity","relationship","constraint"]},{"term":"Normalisation","full":"Normalisation","definition":"Le processus consistant à répartir les données dans des tables liées pour réduire la redondance et le risque d'incohérence (formes normales telles que 1NF, 2NF, 3NF). Elle évite les anomalies de mise à jour et est privilégiée dans les systèmes OLTP. En contrepartie, les requêtes peuvent nécessiter davantage de jointures.","tags":["normalization","normalization","design","oltp","redundancy"]},{"term":"Dénormalisation","full":"Dénormalisation","definition":"Une technique qui duplique délibérément des données pour réduire le nombre de jointures et améliorer les performances de lecture. Elle accélère les requêtes mais introduit duplication des données et complexité des mises à jour. Privilégiée pour le reporting/OLAP et les charges à dominante lecture.","tags":["denormalization","denormalization","performance","olap","read"]},{"term":"OLTP","full":"Online Transaction Processing","definition":"Un type de système qui traite un grand nombre de transactions courtes, fréquentes et concurrentes (insert/update/delete, lectures d'une seule ligne) ; par ex. banque, e-commerce. Il exige une faible latence et une forte concurrence et est généralement conçu normalisé. C'est le pendant opérationnel de l'OLAP.","tags":["oltp","transaction","transaction","operational","normalization"]},{"term":"OLAP","full":"Online Analytical Processing","definition":"Un type de système qui exécute des requêtes complexes d'agrégation et d'analyse sur de grands volumes de données ; par ex. reporting, business intelligence. Peu de requêtes, mais lourdes, y sont exécutées, et il est généralement conçu dénormalisé et en colonnes. C'est le pendant analytique de l'OLTP.","tags":["olap","analytics","reporting","data-warehouse","columnar"]},{"term":"ETL","full":"Extract, Transform, Load","definition":"Le processus qui extrait les données des sources (Extract), les transforme selon les règles métier (Transform) et les charge dans le système cible, généralement un data warehouse (Load). La variante où la transformation a lieu dans la cible après le chargement s'appelle ELT. C'est l'approche classique de l'intégration de données.","tags":["etl","data-integration","data-warehouse","elt","transformation"]},{"term":"Données chaudes/froides","full":"","definition":"Les données actuelles et fréquemment accédées sont dites 'chaudes' ; les données anciennes/d'archive rarement accédées sont dites 'froides'. Les données chaudes sont conservées sur un stockage rapide/coûteux et les froides sur un stockage bon marché/lent (data tiering). C'est une stratégie d'équilibre entre coût et performance.","tags":["hot-data","cold-data","hot-cold","archive","tiering"]},{"term":"Fragmentation d'index","full":"","definition":"Avec le temps, les insertions/suppressions/mises à jour font diverger l'ordre logique des pages d'index de leur ordre physique et baisser leur taux de remplissage. Cela génère des E/S inutiles et de l'espace perdu, dégradant les performances. Elle se corrige avec REBUILD ou REORGANIZE.","tags":["index-fragmentation","fragmentation","index","maintenance","performance"]},{"term":"Validation en deux phases","full":"2PC","definition":"Un protocole qui assure un commit atomique pour une transaction distribuée couvrant plusieurs bases/ressources. Il fonctionne en deux phases : le coordinateur demande d'abord à tous les participants s'ils sont prêts (prepare) ; si tous acceptent, il valide, et si l'un refuse, tous annulent. Il est lent et sensible à la défaillance du coordinateur.","tags":["two-phase-commit","2pc","distributed","distributed","atomic"]}]},"guide/glossary-2.json":{"terms":[{"term":"LSM Tree","full":"Log-Structured Merge Tree","definition":"Une structure de stockage qui accumule d'abord les écritures dans une structure en mémoire (memtable) puis les vide sur disque sous forme de blocs triés. Elle transforme les écritures aléatoires en écritures séquentielles pour un débit d'écriture élevé ; les lectures parcourent plusieurs niveaux. Elle est à la base de RocksDB, Cassandra et LevelDB.","tags":["storage","storage engine","lsm","write"]},{"term":"B+ Tree","full":"","definition":"Une structure arborescente équilibrée dans laquelle toutes les données sont dans les nœuds feuilles et les nœuds internes ne contiennent que des clés de routage. Les feuilles chaînées accélèrent les parcours de plage. C'est la structure la plus courante pour les index des bases relationnelles.","tags":["index","index structure","storage","storage engine","btree"]},{"term":"Filtre de Bloom","full":"","definition":"Une structure de données probabiliste qui vérifie avec peu de mémoire si un élément appartient à un ensemble. Elle répond \"certainement pas\" ou \"probablement oui\" ; les faux positifs sont possibles, mais pas les faux négatifs. Utilisée dans les moteurs basés sur LSM pour éviter des lectures disque inutiles.","tags":["storage","storage engine","probabilistic","filter","performance"]},{"term":"Hachage cohérent","full":"","definition":"Une méthode de répartition qui garantit que seule une petite fraction des clés se déplace lors de l'ajout ou du retrait de nœuds. Clés et nœuds sont placés sur un anneau virtuel. Utilisée pour la répartition des données dans les caches distribués et les systèmes NoSQL.","tags":["distributed systems","distributed","hashing","sharding","scaling"]},{"term":"Horloge vectorielle","full":"","definition":"Une structure qui conserve un compteur par nœud pour suivre l'ordre causal des événements dans les systèmes distribués. Elle détermine si deux mises à jour descendent l'une de l'autre ou sont concurrentes (en conflit). Utilisée pour la détection des conflits et la cohérence à terme.","tags":["distributed systems","distributed","clock","causality","consistency"]},{"term":"Protocole gossip","full":"","definition":"Une méthode de communication dans laquelle chaque nœud diffuse périodiquement son état à quelques pairs choisis au hasard, de sorte que l'information atteint tout le cluster à la manière d'une épidémie. Elle ne nécessite aucun coordinateur central, est évolutive et tolérante aux pannes. Utilisée pour la gestion des membres dans Cassandra et Consul.","tags":["distributed systems","distributed","gossip","cluster","membership"]},{"term":"Raft","full":"","definition":"Un algorithme de consensus conçu pour être plus facile à comprendre que Paxos. Il élit un leader, ajoute toutes les écritures à un log répliqué via le leader et les rend durables par accusé de la majorité. Utilisé par etcd et Consul.","tags":["distributed systems","distributed","consensus","raft","replication"]},{"term":"Paxos","full":"","definition":"L'algorithme de consensus classique qui garantit qu'une majorité s'accorde sur une valeur unique sur un réseau non fiable. Il est correct mais réputé complexe à implémenter ; il constitue le fondement théorique de nombreux systèmes distribués. Utilisé dans des systèmes tels que Google Chubby.","tags":["distributed systems","distributed","consensus","paxos","agreement"]},{"term":"Élection de leader","full":"","definition":"Le processus de sélection d'un nœud unique (le leader) chargé de coordonner une tâche précise dans un cluster distribué. Si le leader courant tombe, un nouveau leader est élu automatiquement. Elle exige généralement l'approbation d'une majorité (quorum) pour éviter le split-brain.","tags":["distributed systems","distributed","leader","election","coordination"]},{"term":"Écriture/lecture par quorum","full":"","definition":"Le nombre minimal de nœuds requis pour qu'une lecture ou une écriture soit considérée comme valide. Lorsque W + R > N, l'accès aux données les plus récentes est garanti. Cela permet d'ajuster le compromis entre cohérence et disponibilité.","tags":["distributed systems","distributed","quorum","consistency","replication"]},{"term":"WAL Shipping","full":"Write-Ahead Log Shipping","definition":"L'envoi des enregistrements WAL produits par le serveur primaire vers les serveurs standby et leur rejeu sur ceux-ci. Le standby conserve ainsi une copie quasi à jour de la base primaire. C'est la base de la streaming replication de PostgreSQL.","tags":["replication","replication","wal","high-availability","standby"]},{"term":"Log Shipping","full":"","definition":"Une méthode de réplication simple qui consiste à copier à intervalles réguliers les sauvegardes du journal de transactions vers un serveur standby et à les restaurer. Son retard est plus élevé qu'en streaming, mais elle est simple à mettre en place. Couramment utilisée pour la reprise après sinistre dans SQL Server.","tags":["replication","replication","log","backup","standby"]},{"term":"Snapshot Isolation","full":"","definition":"Un niveau d'isolation dans lequel chaque transaction voit un snapshot cohérent des données tel qu'au moment de son démarrage. Les lecteurs ne bloquent pas les écrivains ; il reste toutefois exposé à certaines anomalies comme le write skew. Implémenté avec le MVCC.","tags":["isolation level","isolation","mvcc","snapshot","concurrency"]},{"term":"Serializable Snapshot Isolation (SSI)","full":"Serializable Snapshot Isolation","definition":"Une méthode qui assure une véritable sérialisabilité au-dessus de la snapshot isolation en suivant les dépendances lecture-écriture dangereuses. Lorsqu'un conflit est détecté, l'une des transactions est annulée. Utilisée dans le niveau SERIALIZABLE de PostgreSQL.","tags":["isolation level","isolation","serializable","ssi","concurrency"]},{"term":"Gap Lock","full":"","definition":"Un verrou sur l'intervalle entre des enregistrements d'index (une plage qui n'existe pas encore) qui empêche l'insertion de nouvelles lignes dans cette plage. Utilisé pour éviter les lectures fantômes. Il intervient au niveau REPEATABLE READ d'InnoDB.","tags":["locking","lock","innodb","phantom","concurrency"]},{"term":"Next-Key Lock","full":"","definition":"La combinaison d'un verrou sur un enregistrement d'index et d'un verrou sur l'intervalle qui le précède (gap lock). Il empêche les insertions fantômes en protégeant à la fois la ligne existante et la plage. C'est le comportement de verrouillage de ligne par défaut d'InnoDB.","tags":["locking","lock","innodb","gap","concurrency"]},{"term":"Verrouillage optimiste","full":"","definition":"Une méthode qui suppose les conflits rares et fonctionne sans prendre de verrous, en vérifiant seulement au commit si les données ont changé. Généralement implémentée avec un numéro de version ou un horodatage. En cas de conflit, l'opération est retentée.","tags":["locking","lock","optimistic","concurrency","concurrency"]},{"term":"Verrouillage pessimiste","full":"","definition":"Une méthode qui suppose que des conflits surviendront et prend immédiatement un verrou lors de la lecture ou de la modification des données. Les autres transactions attendent la libération du verrou. Elle protège l'intégrité des données dans les environnements à forte contention mais réduit la concurrence.","tags":["locking","lock","pessimistic","concurrency","concurrency"]},{"term":"Idempotence","full":"","definition":"La propriété selon laquelle exécuter une opération une ou plusieurs fois produit le même résultat. Elle permet des nouvelles tentatives sûres après des erreurs réseau. Dans les systèmes distribués, elle empêche les requêtes en double de provoquer des effets de bord.","tags":["distributed systems","distributed","idempotent","retry","reliability"]},{"term":"Circuit Breaker","full":"","definition":"Un design pattern qui protège le système en coupant temporairement les appels vers un service qui échoue sans cesse. Tant que le circuit est \"ouvert\", les requêtes sont rejetées rapidement, et un appel d'essai est effectué au bout d'un moment. Il évite les pannes en cascade.","tags":["distributed systems","distributed","fault tolerance","resilience","pattern"]},{"term":"Saga Pattern","full":"","definition":"Un pattern qui gère les transactions longues couvrant plusieurs services en faisant de chaque étape une transaction locale et en utilisant des transactions de compensation en cas d'échec. Il assure une cohérence à terme au lieu d'ACID dans un environnement distribué. Courant dans les architectures microservices.","tags":["distributed systems","distributed","saga","microservices","pattern"]},{"term":"Outbox Pattern","full":"","definition":"Un pattern qui écrit le message dans une table \"outbox\" au sein de la même transaction afin de rendre atomiques la mise à jour de la base et la publication du message. Un processus distinct lit cette table et publie le message. Cela évite toute incohérence entre les données et la publication d'événements.","tags":["distributed systems","distributed","outbox","messaging","pattern"]},{"term":"Fan-out","full":"","definition":"La distribution d'une opération unique vers de nombreuses sous-opérations ou cibles ; par exemple, écrire une publication dans les fils de milliers d'abonnés. Elle peut se faire à l'écriture (write fan-out) ou à la lecture (read fan-out). Une décision de conception importante pour la mise à l'échelle.","tags":["distributed systems","distributed","scaling","fanout","architecture"]},{"term":"Hotspot","full":"","definition":"La concentration excessive de la charge sur un nœud, une partition ou une clé particulière. Ce point devient un goulet d'étranglement et limite les performances de tout le système. Une sharding key mal choisie en est une cause fréquente.","tags":["distributed systems","distributed","hotspot","performance","sharding"]},{"term":"Skew (déséquilibre des données)","full":"","definition":"Une répartition inégale des données ou de la charge entre les partitions. Certaines partitions débordent tandis que d'autres restent vides, et l'efficacité du parallélisme baisse. Rééquilibré par le choix de la clé ou le salting.","tags":["distributed systems","distributed","skew","imbalance","sharding"]},{"term":"Index partiel","full":"","definition":"Un index qui ne couvre que les lignes de la table satisfaisant une condition WHERE. Il réduit la taille de l'index, rend la maintenance moins coûteuse et accélère le sous-ensemble fréquemment interrogé. Utilisé par exemple pour n'indexer que les lignes actives.","tags":["index","index type","postgresql","partial","performance"]},{"term":"Index sur expression","full":"","definition":"Un index construit sur le résultat d'une expression ou d'une fonction plutôt que sur une colonne. Par exemple, construit sur LOWER(email), il accélère les recherches insensibles à la casse. La requête doit utiliser la même expression.","tags":["index","index type","expression","function","performance"]},{"term":"Index GIN","full":"Generalized Inverted Index","definition":"Un type d'index inversé qui indexe plusieurs composants au sein d'une valeur. Idéal lorsqu'un champ contient de nombreuses clés, comme les tableaux, le JSONB et la recherche plein texte. Utilisé dans PostgreSQL.","tags":["index","index type","postgresql","gin","jsonb"]},{"term":"Index GiST","full":"Generalized Search Tree","definition":"Un framework d'index arborescent et extensible ; des stratégies de recherche personnalisées peuvent être définies pour différents types de données. Bien adapté aux requêtes géospatiales (geometry), de plage et de plus proches voisins. Il constitue la base de PostGIS.","tags":["index","index type","postgresql","gist","geospatial"]},{"term":"TOAST","full":"The Oversized-Attribute Storage Technique","definition":"La technique de PostgreSQL qui compresse les grandes valeurs de champ ne tenant pas dans une page et les stocke dans une zone distincte. Les grandes valeurs texte et JSONB sont découpées de manière transparente. Elle préserve les performances en gardant la table principale compacte.","tags":["storage","storage engine","postgresql","toast","large-values"]},{"term":"Index-Only Scan","full":"","definition":"Un parcours qui produit le résultat à partir de l'index seul, sans lire la table, car toutes les données nécessaires se trouvent dans l'index. Il réduit fortement les accès disque. Dans PostgreSQL, il fonctionne avec la visibility map.","tags":["index","index scan","scan","performance","postgresql"]},{"term":"HTAP","full":"Hybrid Transactional/Analytical Processing","definition":"Une approche qui exécute conjointement les charges transactionnelles (OLTP) et analytiques (OLAP) dans un seul système. Elle élimine le délai lié au transfert des données vers un stockage distinct pour l'analyse (ETL). Combine généralement stockage en lignes et en colonnes.","tags":["analytics","oltp","olap","htap","architecture"]},{"term":"Sharding Key","full":"","definition":"La ou les colonnes qui déterminent le shard sur lequel arrive une ligne. Une clé bien choisie répartit la charge uniformément et évite les hotspots. Un mauvais choix entraîne un déséquilibre des données et des requêtes cross-shard coûteuses.","tags":["distributed systems","distributed","sharding","key","scaling"]},{"term":"Rééquilibrage","full":"","definition":"La redistribution des données entre partitions lors de l'ajout ou du retrait de nœuds ou lorsque la charge devient déséquilibrée. L'objectif est d'égaliser la charge et d'utiliser efficacement les ressources. Il entraîne un coût réseau et E/S pendant son exécution.","tags":["distributed systems","distributed","rebalancing","scaling","sharding"]},{"term":"Fencing","full":"","definition":"Un mécanisme qui évite la corruption des données en empêchant un nœud obsolète ou isolé d'écrire sur une ressource partagée. On utilise généralement un \"fencing token\" croissant afin de rejeter les requêtes obsolètes. Il limite l'impact du split-brain.","tags":["distributed systems","distributed","fencing","safety","split-brain"]},{"term":"Watermark","full":"","definition":"Dans les systèmes de traitement de flux, un marqueur de progression indiquant que tous les événements jusqu'à un certain instant sont considérés comme arrivés. Utilisé pour gérer les événements en retard et décider quand clôturer les calculs de fenêtre.","tags":["stream processing","streaming","watermark","event time","event"]},{"term":"Tombstone","full":"","definition":"Un marqueur spécial écrit pour indiquer qu'un enregistrement a été supprimé. Dans les systèmes LSM et distribués où les données ne sont pas supprimées physiquement tout de suite, il garantit la propagation de la suppression à tous les réplicas. Le nettoyage effectif a lieu lors de la compaction.","tags":["storage","storage engine","lsm","tombstone","delete"]},{"term":"Compaction (LSM)","full":"","definition":"La fusion et la réécriture des fichiers triés (SSTables) sur disque dans les LSM-trees. Elle améliore les performances de lecture et l'utilisation du disque en purgeant les anciennes versions et les tombstones. Elle s'exécute en arrière-plan et génère une charge d'écriture.","tags":["storage","storage engine","lsm","compaction","maintenance"]},{"term":"Read Repair","full":"","definition":"Une technique qui, lorsqu'une incohérence entre réplicas est détectée lors d'une lecture, la répare en écrivant la valeur la plus récente sur les nœuds en retard. Le système se rend cohérent grâce au trafic de lecture normal. Utilisée dans des systèmes tels que Cassandra.","tags":["distributed systems","distributed","repair","consistency","replication"]},{"term":"Hinted Handoff","full":"","definition":"Le stockage d'une écriture sous forme de \"hint\" sur un autre nœud lorsque le nœud cible est temporairement injoignable, puis sa livraison au retour du nœud. Il garantit que les écritures ne sont pas perdues lors de pannes transitoires. Il soutient la cohérence à terme.","tags":["distributed systems","distributed","handoff","availability","replication"]},{"term":"Cohérence à terme","full":"","definition":"Un modèle de cohérence faible qui garantit que toutes les réplicas convergeront vers la même valeur si les nouvelles mises à jour cessent. Il tolère des incohérences de courte durée mais offre une haute disponibilité et une faible latence. C'est le comportement par défaut de nombreux systèmes NoSQL.","tags":["distributed systems","distributed","consistency","eventual","nosql"]},{"term":"Cohérence forte","full":"","definition":"Un modèle de cohérence dans lequel tous les lecteurs voient immédiatement la valeur la plus récente dès qu'une écriture est terminée. Il est intuitif à utiliser, mais comme il exige de la coordination, son coût en latence et en disponibilité est élevé. Privilégié dans les systèmes financiers.","tags":["distributed systems","distributed","consistency","strong","strong consistency"]},{"term":"Linéarisabilité","full":"","definition":"Le niveau de cohérence le plus fort, qui garantit que chaque opération semble prendre effet en un instant unique entre son début et sa fin, et que toutes les opérations s'inscrivent dans un ordre unique en temps réel. Le système se comporte comme s'il n'existait qu'une seule copie. Il est coûteux à mettre en œuvre.","tags":["distributed systems","distributed","consistency","linearizability","strong consistency"]},{"term":"Verrouillage en deux phases (2PL)","full":"Two-Phase Locking","definition":"Une règle de verrouillage en deux phases dans laquelle une transaction ne fait d'abord qu'acquérir des verrous (phase croissante) puis ne fait que les libérer (phase décroissante). Cette discipline garantit la sérialisabilité. Dans sa forme stricte (Strict 2PL), les verrous sont conservés jusqu'au commit.","tags":["locking","lock","2pl","serializable","concurrency"]},{"term":"Détection des interblocages","full":"","definition":"Un mécanisme qui détecte un interblocage, situation où deux transactions ou plus attendent mutuellement des verrous qu'elles détiennent. Il est généralement trouvé en recherchant un cycle dans le wait-for graph, et une transaction est choisie comme victime et annulée. Le système recommence alors à avancer.","tags":["locking","lock","deadlock","deadlock detection","concurrency"]}]},"guide/glossary-3.json":{"terms":[{"term":"Write Amplification","full":"Write Amplification","definition":"Une seule écriture logique provoquant beaucoup plus d'écritures physiques dans la couche de stockage (compaction LSM, réécritures de pages, WAL, effacements de blocs SSD). Des valeurs élevées usent les disques et réduisent le débit d'écriture.","tags":["storage","lsm","performance","ssd"]},{"term":"Read Amplification","full":"Read Amplification","definition":"Une seule lecture logique nécessitant en réalité plusieurs lectures physiques/internes. La recherche d'une clé à travers de nombreux niveaux d'un LSM-tree en est l'exemple typique ; les filtres de Bloom et la compaction la réduisent.","tags":["storage","lsm","performance"]},{"term":"LSM-tree","full":"Log-Structured Merge Tree","definition":"Une structure de données qui absorbe d'abord les écritures dans une structure en mémoire (memtable), les vide sur disque sous forme de blocs triés (SSTables) et les fusionne en arrière-plan (compaction). Idéale pour les charges à dominante écriture ; utilisée par RocksDB et Cassandra.","tags":["storage","lsm","write-heavy"]},{"term":"B+tree","full":"B+ Tree","definition":"La structure classique des index des bases relationnelles. Toutes les données sont conservées dans les nœuds feuilles, chaînés en liste ; elle offre des performances de lecture équilibrées pour les requêtes ponctuelles et de plage.","tags":["index","btree","storage"]},{"term":"Fill Factor","full":"Fill Factor","definition":"Le taux qui définit le niveau de remplissage initial d'une page d'index/de table. Laisser de l'espace libre réduit les page splits lors des futures mises à jour/insertions ; fillfactor dans PG, fill factor dans MSSQL.","tags":["index","storage","tuning"]},{"term":"HOT Update","full":"Heap-Only Tuple Update","definition":"Dans PostgreSQL, une mise à jour qui ne modifie aucune colonne indexée crée la nouvelle version de ligne dans la même page et ne met pas à jour les index. Elle réduit le bloat des index et le coût des mises à jour.","tags":["postgres","mvcc","update","performance"]},{"term":"Bloat","full":"Table/Index Bloat","definition":"Une table/un index occupant plus d'espace que nécessaire dans les moteurs MVCC à cause de versions de lignes mortes non nettoyées ou de pages vides. Cela augmente le coût des parcours ; l'espace est récupéré avec VACUUM/reorg.","tags":["mvcc","postgres","maintenance","bloat"]},{"term":"Autovacuum","full":"Automatic VACUUM","definition":"Le processus d'arrière-plan de PostgreSQL qui effectue automatiquement le nettoyage des dead tuples, la collecte des statistiques et le gel des identifiants de transaction. Il est ajusté à la charge via des paramètres de seuil et de coût.","tags":["postgres","vacuum","maintenance"]},{"term":"Freeze / Wraparound","full":"Transaction ID Freeze / Wraparound","definition":"Dans PostgreSQL, le marquage des anciennes lignes comme 'frozen' pour éviter le bouclage des identifiants de transaction 32 bits. S'il est négligé, la base cesse d'accepter les écritures pour éviter la perte de données.","tags":["postgres","mvcc","vacuum","wraparound"]},{"term":"Checkpoint","full":"Checkpoint","definition":"L'événement au cours duquel les dirty pages en mémoire sont écrites sur disque et le point de départ de la récupération est enregistré. Il raccourcit le temps de récupération après crash ; sa fréquence est un compromis entre pics d'E/S et durée de récupération.","tags":["recovery","wal","internals","checkpoint"]},{"term":"Fuzzy Checkpoint","full":"Fuzzy Checkpoint","definition":"Un type de checkpoint qui écrit les dirty pages en continu en arrière-plan, de manière étalée dans le temps, sans arrêter le système. Il garde des temps de réponse prévisibles en évitant les pics soudains d'E/S (checkpoint spikes).","tags":["recovery","checkpoint","internals"]},{"term":"Redo","full":"Redo Log / Roll Forward","definition":"L'enregistrement conservé pour réappliquer (roll forward) les modifications d'une transaction lors de la récupération. Il restaure les modifications validées qui n'avaient pas atteint le disque avant un crash.","tags":["recovery","oracle","wal","redo"]},{"term":"Undo","full":"Undo Log / Rollback","definition":"L'enregistrement conservé pour annuler une transaction et fournir les anciennes versions de lignes au MVCC. Dans Oracle/InnoDB, les anciennes versions sont lues dans les segments d'undo.","tags":["mvcc","oracle","innodb","undo","rollback"]},{"term":"Snapshot MVCC","full":"Snapshot MVCC","definition":"La vue logique à un instant donné qui détermine quelles versions de lignes sont visibles, afin qu'une transaction effectue une lecture cohérente. Elle est prise au début de la transaction ou de l'instruction (selon le niveau d'isolation).","tags":["mvcc","concurrency","isolation","snapshot"]},{"term":"Anomalie d'isolation","full":"Anomalies d'isolation","definition":"Incohérences produites par des transactions concurrentes sous une isolation insuffisante : dirty read (lecture de données non validées), non-repeatable read (la même ligne change), phantom (de nouvelles lignes apparaissent) et write skew (des écritures apparemment indépendantes violent ensemble une règle).","tags":["isolation","concurrency","anomaly","acid"]},{"term":"2PL","full":"Two-Phase Locking","definition":"Un protocole qui assure la sérialisabilité au moyen de verrous : une transaction ne fait d'abord qu'acquérir des verrous (phase croissante) et, au-delà d'un certain point, ne fait que les libérer (phase décroissante). Strict 2PL conserve les verrous jusqu'au commit.","tags":["concurrency","locking","serializable","2pl"]},{"term":"MVTO","full":"Multi-Version Timestamp Ordering","definition":"Une méthode de contrôle de concurrence qui attribue des horodatages aux transactions et ordonne en conséquence les lectures/écritures sur des données multiversions. Elle résout les conflits par horodatage plutôt que par verrous.","tags":["concurrency","mvcc","timestamp","protocol"]},{"term":"Mutex","full":"Mutual Exclusion","definition":"Une primitive d'exclusion mutuelle qui garantit qu'un seul thread accède à une ressource à la fois. Dans les bases de données, elle joue un rôle proche du latch pour protéger les structures de mémoire partagée.","tags":["concurrency","internals","mutex"]},{"term":"Spinlock","full":"Spinlock","definition":"Un verrou de très courte durée qui fait boucler le thread (busy-wait) au lieu de l'endormir jusqu'à la libération du verrou. Peu coûteux pour des sections critiques très courtes ; sous contention, il consomme du CPU.","tags":["concurrency","internals","spinlock","cpu"]},{"term":"Buffer Pool","full":"Buffer Pool / Buffer Cache","definition":"Un cache qui réduit les E/S disque en conservant en mémoire les pages de données fréquemment accédées. L'algorithme d'éviction (LRU, clock-sweep) décide quelle page est évincée ; sa taille est fixée selon le working set.","tags":["memory","cache","internals","buffer"]},{"term":"Working Set","full":"Working Set","definition":"L'ensemble des données auxquelles une charge accède activement dans une fenêtre de temps donnée. Lorsque le buffer pool est assez grand pour contenir cet ensemble, les E/S physiques sont minimisées ; il diffère de la taille totale des données.","tags":["memory","cache","sizing","performance"]},{"term":"Skew","full":"Data Skew","definition":"Situation où les valeurs ne sont pas réparties uniformément et où certaines apparaissent bien plus souvent. C'est la principale cause des erreurs d'estimation de cardinalité, des problèmes de parameter sniffing et des hotspots de sharding ; on la traite avec des histogrammes.","tags":["statistics","distribution","skew","performance"]},{"term":"Plan Cache","full":"Plan Cache","definition":"La zone mémoire où les plans d'exécution compilés sont stockés pour être réutilisés. Elle évite le coût du hard parse mais entraîne des risques de parameter sniffing et de plans obsolètes ; plan cache dans MSSQL, library cache dans Oracle.","tags":["optimizer","plan-cache","parsing"]},{"term":"Hard/Soft Parse","full":"Hard Parse / Soft Parse","definition":"Hard parse : le SQL est analysé, optimisé et un plan est généré de zéro (coûteux). Soft parse : un plan existant est trouvé dans le cache et réutilisé (peu coûteux). Les variables de liaison augmentent le taux de soft parse.","tags":["parsing","oracle","performance","plan-cache"]},{"term":"Connection Storming","full":"Tempête de connexions","definition":"L'ouverture simultanée d'un grand nombre de nouvelles connexions à la base en peu de temps. Elle submerge la base par le coût d'établissement des connexions et la pression mémoire ; elle résulte généralement de l'épuisement d'un pool ou d'une vague de reconnexions.","tags":["connections","pooling","overload"]},{"term":"Thundering Herd","full":"Thundering Herd Problem","definition":"De nombreux clients sollicitant la même ressource au même moment après un événement (expiration du cache, libération d'un verrou, retour d'un service). Atténué par le backpressure, le jitter et les patterns single-flight.","tags":["concurrency","cache","overload","resilience"]},{"term":"Backpressure","full":"Backpressure","definition":"Un système qui ralentit ou rejette le producteur au lieu d'accepter une charge qu'il ne peut pas absorber (limites de file, rate limits). Il permet une dégradation maîtrisée au lieu d'un effondrement sous la surcharge.","tags":["resilience","flow-control","overload"]},{"term":"Lease","full":"Lease","definition":"Un droit de leadership/sur une ressource accordé à un nœud pour une durée fixe, qui devient automatiquement invalide à son expiration. Étant limité dans le temps, il empêche un nœud bloqué de rester leader indéfiniment.","tags":["distributed","leader-election","lease"]},{"term":"Read-Your-Writes","full":"Read-Your-Writes Consistency","definition":"Une cohérence de session qui garantit qu'un utilisateur voit toujours sa propre modification dès la lecture suivante. Lors du routage vers des réplicas en retard, elle est obtenue par un routage/des jetons spécifiques.","tags":["distributed","consistency","session","replication"]},{"term":"Cohérence causale","full":"Cohérence causale","definition":"Un modèle de cohérence plus fort que la cohérence à terme, qui garantit que les opérations liées causalement sont vues dans le même ordre sur tous les nœuds. Les opérations non liées (concurrentes) peuvent être vues dans des ordres différents.","tags":["distributed","consistency","causal"]},{"term":"CAP","full":"Consistency, Availability, Partition tolerance","definition":"Le théorème selon lequel, lors d'une partition réseau, un système distribué ne peut choisir qu'entre cohérence et disponibilité. En l'absence de partition, ce compromis n'est pas obligatoire.","tags":["distributed","cap","consistency","theory"]},{"term":"PACELC","full":"Théorème PACELC","definition":"Un modèle qui étend CAP : en cas de partition (P), le choix se fait entre disponibilité (Availability) et cohérence (Consistency), sinon (E), entre latence (Latency) et cohérence (Consistency). Il couvre aussi le compromis latence-cohérence en fonctionnement normal.","tags":["distributed","cap","pacelc","latency","theory"]},{"term":"Hot Partition","full":"Hot Partition / Hot Spot","definition":"Une charge qui s'accumule de façon disproportionnée sur une seule partition/un seul shard. Elle provient d'une shard key à faible cardinalité, temporelle ou croissante de manière monotone ; elle crée un goulet sur ce nœud et empêche la mise à l'échelle.","tags":["distributed","sharding","hot-spot","skew"]},{"term":"Columnstore","full":"Stockage orienté colonnes","definition":"Un format dans lequel les données sont stockées par colonne plutôt que par ligne. Les requêtes analytiques ne lisent que les colonnes nécessaires, avec une forte compression et une exécution vectorisée ; idéal pour l'OLAP.","tags":["olap","columnar","analytics","compression"]},{"term":"Exécution vectorisée","full":"Exécution vectorisée","definition":"Des opérateurs de requête qui traitent un bloc (batch) de valeurs de colonne à la fois au lieu de ligne par ligne. Elle augmente fortement le débit des requêtes analytiques en exploitant mieux les caches CPU et le SIMD.","tags":["olap","execution","performance","columnar"]},{"term":"Zone Map / Min-Max","full":"Zone Map / Min-Max Index","definition":"Des métadonnées légères qui stockent les valeurs minimale et maximale d'une colonne pour chaque bloc de données. Elles réduisent le coût des parcours en ignorant sans les lire les blocs qui ne peuvent pas satisfaire le prédicat (block skipping).","tags":["olap","index","pruning","columnar"]},{"term":"Réplication logique","full":"Réplication logique","definition":"Une réplication qui publie et souscrit à des modifications au niveau ligne (événements logiques) plutôt qu'à des blocs physiques. Elle permet la sélection de tables, des scénarios inter-versions et bidirectionnels ; la publication/subscription de PG la fournit.","tags":["replication","postgres","logical","cdc"]},{"term":"GTID","full":"Global Transaction Identifier","definition":"Un identifiant globalement unique attribué à chaque transaction dans la réplication MySQL. En suivant la position du réplica par identifiant de transaction plutôt que par fichier/offset, il rend le failover et la reconfiguration sûrs et simples.","tags":["mysql","replication","gtid","failover"]},{"term":"High Water Mark","full":"High Water Mark","definition":"La limite de bloc la plus haute jamais utilisée dans un segment/une table. Dans Oracle, un full table scan lit jusqu'à cette limite ; comme elle ne baisse pas après des suppressions massives, elle peut entraîner le parcours de blocs vides et du bloat.","tags":["oracle","storage","internals","scan"]}]},"playbooks/playbooks.json":{"schemaVersion":1,"playbooks":[{"id":"db-slow","title":"La base est lente","icon":"🐢","symptom":"Lenteur à l'échelle de l'application ; les requêtes prennent bien plus de temps que d'habitude et les utilisateurs se plaignent.","steps":[{"title":"Précisez d'abord ce qui est lent","detail":"Tout est-il lent, ou une requête/un écran précis ? Quand cela a-t-il commencé, et cela coïncide-t-il avec un déploiement/un patch/une croissance des données ? Est-ce global ou limité à une seule base ? Cela ramène la recherche à un axe : CPU, E/S, verrouillage ou changement de plan."},{"title":"Examinez les sessions actives et les événements d'attente","detail":"Qu'est-ce qui s'exécute et qu'est-ce qui attend en ce moment ? Un événement d'attente dominant (E/S, verrou, log) indique le goulet d'étranglement.","engines":{"oracle":"SELECT sid, username, status, event, sql_id FROM v$session WHERE status='ACTIVE' AND type='USER';","postgres":"SELECT pid, state, wait_event_type, wait_event, query FROM pg_stat_activity WHERE state='active';","mysql":"SELECT id, user, command, time, state, info FROM information_schema.processlist WHERE command<>'Sleep' ORDER BY time DESC;","mssql":"SELECT session_id, status, wait_type, wait_time, blocking_session_id FROM sys.dm_exec_requests WHERE session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE is_user_process = 1);","mongodb":"db.currentOp({ active: true, secs_running: { $gt: 1 } })","redis":"SLOWLOG GET 10"}},{"title":"Recherchez les blocages","detail":"La cause la plus fréquente de lenteur est une session qui en bloque d'autres. Trouvez la session bloquante et identifiez la tête de la chaîne.","engines":{"oracle":"SELECT blocking_session, sid, seconds_in_wait, event FROM v$session WHERE blocking_session IS NOT NULL;","postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by, query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid))>0;","mysql":"SELECT * FROM sys.innodb_lock_waits\\G","mssql":"SELECT session_id, blocking_session_id, wait_type FROM sys.dm_exec_requests WHERE blocking_session_id<>0;"}},{"title":"Extrayez les requêtes les plus coûteuses","detail":"Trouvez les 10 requêtes qui consomment le plus de ressources. En général, 90 % du problème vient d'une poignée de requêtes.","engines":{"oracle":"SELECT sql_id, elapsed_time/1e6 sec, executions, sql_text FROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT calls, total_exec_time, mean_exec_time, query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;","mysql":"SELECT digest_text, count_star, sum_timer_wait/1e12 sec FROM performance_schema.events_statements_summary_by_digest ORDER BY sum_timer_wait DESC LIMIT 10;","mssql":"SELECT TOP 10 total_elapsed_time/execution_count avg_us, execution_count, SUBSTRING(t.text,1,200) FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t ORDER BY total_elapsed_time DESC;"}},{"title":"Examinez le plan de la requête suspecte","detail":"Obtenez l'EXPLAIN de la requête lente. Cherchez les full table scans, les index manquants, un mauvais ordre de jointure ou des statistiques obsolètes. Si le plan a changé soudainement, envisagez de collecter les statistiques.","engines":{"oracle":"SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));","postgres":"EXPLAIN (ANALYZE, BUFFERS) <query>;","mysql":"EXPLAIN ANALYZE <query>;","mssql":"SET STATISTICS PROFILE ON; <query>; SET STATISTICS PROFILE OFF;","mongodb":"db.my_collection.find({...}).explain('executionStats')  // COLLSCAN or IXSCAN?"}},{"title":"Vérifiez les ressources système","detail":"Au niveau OS, vérifiez le CPU, l'attente E/S (iowait), la mémoire et l'utilisation disque. Vérifiez le cache hit ratio ; s'il est faible, il peut y avoir un goulet mémoire. Le problème peut se situer hors de la base (disque plein, VM voisine bruyante)."},{"title":"Décidez : correctif rapide ou définitif","detail":"Pour un soulagement immédiat : tuez la requête incontrôlée, ajoutez l'index manquant, collectez les statistiques. Pour un correctif définitif : revoyez la requête/l'index, corrigez les paramètres mémoire, assurez la stabilité des plans (baseline/hint)."}],"related":["active-sessions","blocking-sessions","top-slow-queries","explain-plan","cache-hit-ratio"]},{"id":"disk-full","title":"Disque / tablespace plein","icon":"💽","symptom":"Erreurs 'unable to extend', 'disk full', 'log file full' ; les écritures s'arrêtent.","steps":[{"title":"Pas de panique ; trouvez d'abord ce qui est plein","detail":"Est-ce un datafile, le journal de transactions, la zone d'archive/redo, temp ou le disque OS ? Nettoyer au mauvais endroit ne sert à rien. Vérifiez le disque avec df -h au niveau OS et les vues d'espace dans la base."},{"title":"Mesurez l'utilisation","detail":"Quel tablespace/fichier/répertoire est plein, et à quel pourcentage ?","engines":{"oracle":"SELECT d.tablespace_name, ROUND((1-(d.alloc-NVL(f.free,0))/d.maxb)*100,1) pct_free FROM (SELECT tablespace_name, SUM(bytes) alloc, SUM(GREATEST(bytes,maxbytes)) maxb FROM dba_data_files GROUP BY tablespace_name) d LEFT JOIN (SELECT tablespace_name, SUM(bytes) free FROM dba_free_space GROUP BY tablespace_name) f ON f.tablespace_name = d.tablespace_name;","postgres":"SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY 2 DESC; -- use OS df for the disk","mysql":"SELECT table_schema, ROUND(SUM(data_length+index_length)/1048576,1) mb FROM information_schema.tables GROUP BY table_schema ORDER BY mb DESC;","mssql":"SELECT name, size/128.0 size_mb, size/128.0 - CAST(FILEPROPERTY(name,'SpaceUsed') AS int)/128.0 free_mb FROM sys.database_files;","redis":"INFO memory  # used_memory vs maxmemory"}},{"title":"Trouvez les plus gros consommateurs","detail":"Identifiez les plus grandes tables/segments, les plus gros fichiers de log/d'archive et les anciennes sauvegardes inutiles. Le plus souvent, une seule table incontrôlée ou des journaux archivés accumulés sont en cause.","engines":{"oracle":"SELECT segment_name, bytes/1048576 mb FROM dba_segments ORDER BY bytes DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;","mssql":"SELECT TOP 10 OBJECT_NAME(object_id), SUM(reserved_page_count)*8/1024 mb FROM sys.dm_db_partition_stats GROUP BY object_id ORDER BY mb DESC;"}},{"title":"Récupérez de l'espace en toute sécurité (rapide)","detail":"Pour les logs/archives : sauvegardez-les, puis purgez les anciens journaux archivés/sauvegardes (Oracle : RMAN delete archivelog ; MSSQL : faites une sauvegarde du log puis réduisez-le). Dans Redis : maxmemory-policy et éviction. Ne supprimez JAMAIS manuellement un datafile en service ; ne supprimez JAMAIS de journaux archivés sans sauvegarde."},{"title":"Augmentez durablement la capacité","detail":"Ajoutez de l'espace au datafile ou activez l'autoextend (Oracle : ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE), agrandissez le disque, ajoutez un tablespace. Corrigez la cause profonde : rotation des logs, job de nettoyage des archives, politique de rétention."},{"title":"Évitez la récidive","detail":"Mettez en place des alertes d'utilisation disque (seuils 80 %/90 %), automatisez le nettoyage des archives/logs et définissez clairement la politique de rétention. Évitez d'avoir à rouvrir ce playbook."}],"related":["free-space","largest-tables","database-size","reclaim-space","last-backup-status"]},{"id":"blocking","title":"Crise de blocages et de verrous","icon":"🔒","symptom":"Les requêtes restent bloquées, 'lock wait timeout', de nombreuses sessions attendent mais le CPU est faible.","steps":[{"title":"Trouvez la tête de la chaîne de blocage (root blocker)","detail":"Déterminez qui bloque qui ; le vrai coupable est la session qui n'attend rien elle-même mais fait attendre les autres. Concentrez-vous sur la session au sommet de la chaîne.","engines":{"oracle":"SELECT blocking_session, sid, serial#, username, seconds_in_wait, sql_id FROM v$session WHERE blocking_session IS NOT NULL ORDER BY seconds_in_wait DESC;","postgres":"SELECT pid, usename, pg_blocking_pids(pid) blocked_by, state, query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid))>0;","mysql":"SELECT waiting_pid, waiting_query, blocking_pid, blocking_query FROM sys.innodb_lock_waits;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_resource FROM sys.dm_exec_requests WHERE blocking_session_id<>0;","mongodb":"db.currentOp({ waitingForLock: true })"}},{"title":"Comprenez ce que fait le root blocker","detail":"S'agit-il d'une transaction ouverte non validée ? L'application l'a-t-elle laissée 'idle in transaction' ? Est-ce un long batch ? Comprenez de quoi il s'agit avant de tuer à l'aveugle : elle est peut-être sur le point de se terminer.","engines":{"oracle":"SELECT s.sid, s.prev_sql_id, t.start_time, t.used_ublk FROM v$transaction t JOIN v$session s ON t.ses_addr=s.saddr;","postgres":"SELECT pid, state, xact_start, now()-xact_start age, query FROM pg_stat_activity WHERE pid=<root_pid>;"}},{"title":"Décidez : attendre, résoudre côté application ou tuer","detail":"Si elle est sur le point de se terminer, attendez. Si l'application peut valider/annuler, déclenchez-le. Sinon, si l'activité est paralysée, terminez le root blocker. Avant de tuer, évaluez l'impact (durée du rollback, données)."},{"title":"Terminez le root blocker si nécessaire","detail":"Ne tuez que la session en tête de chaîne ; les sessions en dessous sont libérées automatiquement.","engines":{"oracle":"ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;","postgres":"SELECT pg_terminate_backend(<root_pid>);","mysql":"KILL <root_id>;","mssql":"KILL <root_session_id>;","mongodb":"db.killOp(<opid>)"}},{"title":"Corrigez la cause profonde","detail":"Un index manquant peut provoquer des verrous étendus ; l'application peut garder des transactions ouvertes trop longtemps ; le niveau d'isolation peut être inadapté. Ajoutez des index, raccourcissez les transactions, définissez un timeout 'idle in transaction' et choisissez un niveau d'isolation approprié."}],"related":["blocking-sessions","kill-session","open-transactions"]},{"id":"connection-storm","title":"Tempête de connexions (trop de connexions)","icon":"🌊","symptom":"'too many connections', 'ORA-00018', 'remaining connection slots reserved' : les nouvelles connexions sont rejetées.","steps":[{"title":"Mesurez l'état actuel","detail":"Combien de connexions y a-t-il, quelle est la limite, et combien sont actives ou inactives ?","engines":{"oracle":"SELECT COUNT(*) total, SUM(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END) active FROM v$session WHERE type='USER';","postgres":"SELECT count(*), state FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY state;","mysql":"SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';","mssql":"SELECT COUNT(*), status FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY status;","redis":"INFO clients"}},{"title":"Identifiez la source : qui/d'où","detail":"Quelle application/quel hôte/quel utilisateur ouvre les connexions ? Généralement, le pool d'un serveur d'application a explosé ou il y a une fuite de connexions (une pile de sessions idle in transaction).","engines":{"postgres":"SELECT client_addr, usename, state, count(*) FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY 1,2,3 ORDER BY 4 DESC;","mysql":"SELECT host, user, count(*) FROM information_schema.processlist GROUP BY host, user ORDER BY 3 DESC;","mssql":"SELECT host_name, program_name, login_name, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY host_name, program_name, login_name ORDER BY 4 DESC;"}},{"title":"Donnez-vous immédiatement de la marge","detail":"Nettoyez les connexions inactives de longue date/idle in transaction. Si nécessaire, coupez en masse les connexions de l'application fautive. Utilisez le slot réservé à la connexion d'administration (PG superuser_reserved_connections)."},{"title":"La bonne solution : le connection pooling","detail":"Augmenter max_connections sans limite consomme de la mémoire et ne fait que repousser le problème. La bonne solution est un pool de connexions côté application (HikariCP, PgBouncer, ProxySQL) avec une taille de pool raisonnable. La taille du pool est typiquement d'environ (cores*2 + nombre de disques) ; voir le calculateur Outils > Connection Pool."},{"title":"Évitez les fuites et la récidive","detail":"Assurez-vous que l'application ferme les connexions (finally/using). Définissez idle_in_transaction_session_timeout et wait_timeout. Mettez en place une alerte sur le nombre de connexions."}],"related":["connection-count","kill-session","open-transactions"]},{"id":"replication-lag","title":"Retard de réplication / réplication arrêtée","icon":"🔁","symptom":"La standby/le réplica prend du retard ou s'est complètement arrêté ; les réplicas en lecture affichent des données obsolètes.","steps":[{"title":"Quelle est l'ampleur du retard, et augmente-t-il ou est-il stable","detail":"Quel est le retard en secondes/octets ? Est-ce un décalage stable ou en croissance continue ? S'il croît, le réplica ne suit pas l'application ; s'il est stable, il y a eu un blocage momentané.","engines":{"oracle":"SELECT name, value, unit FROM v$dataguard_stats WHERE name LIKE '%lag%';","postgres":"SELECT client_addr, state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;","mysql":"SHOW REPLICA STATUS\\G  -- Seconds_Behind_Source","mssql":"SELECT ar.replica_server_name, drs.redo_queue_size, drs.log_send_queue_size FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_replicas ar ON drs.replica_id=ar.replica_id;","redis":"INFO replication  # master_repl_offset vs slave offset"}},{"title":"La réplication fonctionne-t-elle, y a-t-il des erreurs","detail":"Quel est l'état des threads IO/SQL, du processus d'application, la connexion est-elle rompue ? En cas d'erreur (par ex. ligne en conflit, table manquante), l'application s'est arrêtée ; lisez le message d'erreur.","engines":{"mysql":"SHOW REPLICA STATUS\\G  -- Replica_IO_Running / Replica_SQL_Running / Last_Error","postgres":"SELECT status, last_msg_receipt_time, latest_end_lsn FROM pg_stat_wal_receiver;  -- on the standby","oracle":"SELECT process, status, sequence#, thread# FROM v$managed_standby WHERE process LIKE 'MRP%' OR process LIKE 'RFS%';"}},{"title":"Trouvez le goulet d'étranglement","detail":"Pourquoi ne suit-il pas ? Application mono-thread avec une forte charge d'écriture (PG/MySQL appliquent surtout en série), goulet E/S sur la standby, réseau, ou requête longue bloquant l'application (PG hot_standby_feedback / recovery conflict) ?"},{"title":"Résorbez le retard","detail":"Accélérez l'application : activez l'application parallèle (MySQL replica_parallel_workers, slave_parallel_workers avant 8.0.26 ; PG n'en a pas, mais il existe des paramètres de recovery), améliorez les E/S de la standby, résolvez la requête qui bloque l'application. Si elle s'est arrêtée sur une erreur : corrigez l'erreur (résolvez manuellement la ligne en conflit) et relancez la réplication."},{"title":"Reconstruisez si elle est cassée","detail":"Si le réplica est très en retard/cassé, le plus propre est de resynchroniser : reconstruisez la standby à partir d'une sauvegarde récente (PG pg_basebackup, MySQL clone/xtrabackup, Oracle RMAN duplicate). Essentiel : trouvez la cause avant de reconstruire, sinon cela se reproduira."}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"high-cpu","title":"CPU élevé","icon":"🔥","symptom":"CPU du serveur à 90 %+ ; tout le système est lent et le load average est élevé.","steps":[{"title":"Vérifiez que c'est bien la base qui consomme le CPU","detail":"Utilisez top/mpstat au niveau OS pour vérifier que le processus qui consomme le CPU est bien la base. Parfois, un processus voisin ou une sauvegarde/un antivirus est en cause. Si l'iowait est élevé, le problème vient du disque, pas du CPU."},{"title":"Trouvez les requêtes les plus gourmandes en CPU","detail":"Extrayez les requêtes principales triées par temps CPU. En général, quelques requêtes (mauvais plan, parcours complet, index manquant) consomment le CPU.","engines":{"oracle":"SELECT sql_id, cpu_time/1e6 cpu_sec, executions, sql_text FROM v$sql ORDER BY cpu_time DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;","mysql":"SELECT digest_text, count_star, sum_timer_wait/1e12 sec FROM performance_schema.events_statements_summary_by_digest ORDER BY sum_timer_wait DESC LIMIT 10;","mssql":"SELECT TOP 10 total_worker_time/execution_count avg_cpu, execution_count, SUBSTRING(t.text,1,200) FROM sys.dm_exec_query_stats q CROSS APPLY sys.dm_exec_sql_text(q.sql_handle) t ORDER BY total_worker_time DESC;"}},{"title":"Recherchez un problème de plan / d'index","detail":"Obtenez le plan de la requête fautive. Les full table scans, les explosions de nested loops, les débordements de hash join ou les mauvais plans dus au parameter sniffing/à des statistiques obsolètes consomment le CPU. Ajoutez un index ou collectez les statistiques."},{"title":"Soulagez immédiatement","detail":"Tuez la requête incontrôlée et, si besoin, limitez-la avec Resource Manager/Resource Governor. Pour une charge apparue soudainement, examinez le dernier déploiement/changement de paramètre."},{"title":"Corrigez durablement","detail":"Corrigez la requête/l'index, assurez la stabilité des plans et planifiez le job de collecte des statistiques. Si le CPU est constamment à 100 %, une mise à l'échelle (ajout de CPU, répartition des lectures sur des réplicas) peut être nécessaire."}],"related":["top-slow-queries","explain-plan","active-sessions","update-statistics"]},{"id":"memory-pressure","title":"Pression mémoire / OOM","icon":"🧠","symptom":"L'OOM killer tue le processus, 'out of memory', cache hit ratio faible, swap excessif, Redis 'OOM command not allowed'.","steps":[{"title":"Qui consomme la mémoire","detail":"Est-ce la mémoire allouée à la base (buffer pool/SGA), la mémoire par session (PGA/sort/work_mem), ou l'OS dans son ensemble ? Vérifiez free/vmstat au niveau OS et les vues mémoire dans la base.","engines":{"oracle":"SELECT component, current_size/1048576 mb FROM v$memory_dynamic_components WHERE current_size>0;","postgres":"SHOW shared_buffers; SHOW work_mem; -- work_mem * connections * number of sorts can blow up in total","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW STATUS LIKE 'Innodb_buffer_pool%';","redis":"INFO memory  # used_memory, maxmemory, mem_fragmentation_ratio"}},{"title":"Distinguez la configuration de la charge","detail":"Le total des paramètres mémoire dépasse-t-il la RAM physique (over-commit) ? Calculez shared_buffers + (work_mem * concurrence). Ou s'agit-il d'une charge momentanée (un tri/hash énorme, de nombreuses connexions) ?"},{"title":"Spécifique à Redis : éviction et fragmentation","detail":"Si used_memory a atteint maxmemory, vérifiez maxmemory-policy (avec noeviction, les écritures sont rejetées). Si mem_fragmentation_ratio est élevé, utilisez MEMORY PURGE / activedefrag. Trouvez les grosses clés avec --bigkeys.","engines":{"redis":"CONFIG GET maxmemory-policy\nMEMORY DOCTOR\nredis-cli --bigkeys"}},{"title":"Soulagez immédiatement","detail":"Tuez la requête/la session incontrôlée qui dévore la mémoire, abaissez temporairement la mémoire de session comme work_mem, coupez les connexions inutiles. Si le système swappe, sortir du swap est la priorité."},{"title":"Dimensionnez correctement","detail":"Répartissez correctement la RAM avec le calculateur Outils > Memory/Buffer Pool. Règle générale : sur un serveur dédié, buffer pool/SGA ~70-80 % de la RAM ; la mémoire totale des sessions multipliée par la concurrence ne doit pas dépasser la RAM. Ajoutez de la RAM si nécessaire."}],"related":["cache-hit-ratio","show-config-param","reclaim-space"]},{"id":"log-growth","title":"Gonflement du journal de transactions / de l'undo","icon":"📈","symptom":"Le fichier du journal de transactions ou la zone undo/redo grossit rapidement ; 'log full', 'ORA-01555 snapshot too old', 'unable to extend undo'.","steps":[{"title":"Trouvez la transaction ouverte qui fait grossir le log","detail":"Le log/l'undo ne peut pas être libéré tant que la plus ancienne transaction active n'est pas terminée. Une seule longue transaction ouverte peut retenir tout le log.","engines":{"oracle":"SELECT s.sid, s.username, t.start_time, t.used_ublk FROM v$transaction t JOIN v$session s ON t.ses_addr=s.saddr ORDER BY t.start_time;","postgres":"SELECT pid, state, xact_start, now()-xact_start age, query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start;","mysql":"SELECT trx_id, trx_started, trx_mysql_thread_id FROM information_schema.innodb_trx ORDER BY trx_started;","mssql":"DBCC OPENTRAN;"}},{"title":"Comprenez la cause profonde propre au moteur","detail":"MSSQL : en mode de récupération FULL, le log croît indéfiniment si aucune sauvegarde du log n'est effectuée. Oracle : ORA-01555 = rétention/taille d'undo insuffisante + requête longue. PG : si un slot de réplication n'avance pas, le WAL s'accumule ; une longue transaction bloque VACUUM."},{"title":"La bonne action pour le log MSSQL","detail":"Faites une sauvegarde du log (BACKUP LOG) : le log redevient réutilisable. Si le mode de récupération doit vraiment être SIMPLE, changez-le. Ensuite, si besoin (et seulement si besoin), réduisez le log.","engines":{"mssql":"BACKUP LOG [DB] TO DISK='C:\\backup\\DB_log.trn';\n-- Then, if needed: DBCC SHRINKFILE (N'DB_log', 512);"}},{"title":"Vérification du WAL / des slots de réplication PG","detail":"Un slot de réplication qui n'avance pas (inactif) provoque une accumulation de WAL. Avant de le supprimer, vérifiez à qui appartient le slot (slot_type, database, plugin) : il peut s'agir d'une standby temporairement déconnectée ou d'un abonné logique/CDC (par ex. Debezium). Si le slot est supprimé, la standby rattachée doit être reconstruite et l'abonné/la CDC resynchronisé. Ne le supprimez que s'il est réellement inutilisé ; pour une protection durable, fixez une limite avec max_slot_wal_keep_size (PG13+).","engines":{"postgres":"SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) retained FROM pg_replication_slots ORDER BY 3 DESC;\n-- If unneeded: SELECT pg_drop_replication_slot('slot_name');"}},{"title":"Ajustez l'undo/la rétention et évitez la récidive","detail":"Oracle : agrandissez le tablespace UNDO, réglez undo_retention selon les requêtes longues (ou RETENTION GUARANTEE). Évitez les longues transactions ouvertes dans l'application. Surveillez l'exécution du job de sauvegarde du log."}],"related":["open-transactions","reclaim-space","last-backup-status"]},{"id":"deadlocks","title":"Analyse des interblocages","icon":"♻️","symptom":"'deadlock detected', 'ORA-00060', 'chosen as deadlock victim' : des transactions se verrouillent mutuellement et l'une est interrompue.","steps":[{"title":"Les interblocages sont normaux, mais ne doivent pas être fréquents","detail":"La base détecte l'interblocage et le résout en choisissant une victime : c'est voulu. L'objectif est d'en réduire la fréquence. L'application doit réessayer la transaction victime. Capturez d'abord le graphe d'interblocage."},{"title":"Obtenez le graphe/les détails de l'interblocage","detail":"Quelles deux sessions (ou plus), quelles ressources et quels modes de verrou sont entrés en conflit ?","engines":{"oracle":"-- Check the trace file referenced in the alert log (deadlock graph). Also:\nSELECT * FROM dba_hist_active_sess_history WHERE event LIKE 'enq: TX%' ORDER BY sample_time DESC;","postgres":"-- log_lock_waits=on; deadlock details are in postgresql.log. Check the deadlock_timeout setting.","mysql":"SHOW ENGINE INNODB STATUS\\G  -- LATEST DETECTED DEADLOCK section","mssql":"-- Deadlock XML in the system_health Extended Events session (ring_buffer, recent events):\nSELECT x.ev.value('@timestamp','datetime2') AS event_time, x.ev.query('.') AS deadlock_xml\nFROM (SELECT CAST(t.target_data AS XML) td FROM sys.dm_xe_session_targets t\n      JOIN sys.dm_xe_sessions s ON s.address = t.event_session_address\n      WHERE s.name = N'system_health' AND t.target_name = N'ring_buffer') r\nCROSS APPLY r.td.nodes('//RingBufferTarget/event[@name=\"xml_deadlock_report\"]') AS x(ev);\n-- For longer history, the event_file target:\n-- SELECT CAST(event_data AS XML) FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) WHERE object_name = N'xml_deadlock_report';"}},{"title":"Résolvez le schéma de conflit","detail":"La cause la plus fréquente : deux transactions qui accèdent aux ressources dans l'ordre INVERSE. Accédez aux ressources dans le MÊME ordre dans tous les chemins de code. La deuxième cause la plus fréquente : des verrous de plage étendus dus à un index manquant ; ajoutez l'index requis."},{"title":"Réduisez la durée et la portée des verrous","detail":"Gardez les transactions courtes (n'incluez pas d'interaction utilisateur dans une transaction). Choisissez un niveau d'isolation approprié. Si nécessaire, acquérez tôt les verrous de ligne (SELECT ... FOR UPDATE avec un ordre déterministe)."},{"title":"Le retry dans l'application est obligatoire","detail":"Les interblocages ne peuvent pas être totalement éliminés ; implémentez un retry automatique (exponential backoff) pour la transaction victime. L'utilisateur ne voit ainsi pas d'erreur."}],"related":["blocking-sessions","open-transactions","list-indexes"]},{"id":"db-down","title":"La base ne s'ouvre pas / est injoignable","icon":"🚨","symptom":"Impossible de se connecter au service, 'connection refused', l'instance ne démarre pas, le listener ne répond pas.","steps":[{"title":"Où cela casse-t-il : réseau, service ou instance","detail":"Le serveur est-il joignable (ping/ssh) ? Le processus/service de la base tourne-t-il (ps/systemctl/services.msc) ? Le port est-il à l'écoute (ss -ltnp / telnet host port) ? Cela situe le problème dans une couche : réseau / service / instance de base."},{"title":"Vérifiez le service et le listener","detail":"Si le processus de la base n'est pas démarré, essayez de le démarrer ; s'il tourne mais que les connexions échouent, examinez le listener/le port et la couche d'authentification.","engines":{"oracle":"-- OS: lsnrctl status  (listener)\n-- SQL*Plus: STARTUP;  (if the instance is down)\nSELECT status FROM v$instance;","postgres":"-- OS: systemctl status postgresql ; pg_isready -h host\n-- Logs: the last lines tell why it did not start","mysql":"-- OS: systemctl status mysqld ; mysqladmin ping","mssql":"-- Services / SQL Server Configuration Manager; SQL Server Error Log","redis":"redis-cli ping  # expect PONG"}},{"title":"Lisez les logs : la réponse s'y trouve","detail":"Si la base ne s'ouvre pas, la raison est écrite dans le log : alert log (Oracle), postgresql.log / démarrage, error log de mysqld, ERRORLOG de SQL Server, log redis. Disque plein, fichier corrompu, permissions, conflit de port, fichier manquant : tout y apparaît."},{"title":"Traitez les causes courantes : disque, permissions, port, corruption","detail":"Le disque est-il plein à 100 % (la cause la plus fréquente) ? Les permissions du répertoire de données sont-elles cassées ? Le port est-il pris par un autre processus ? Une récupération est-elle nécessaire (après un crash) ? Un fichier de contrôle/redo log est-il corrompu ? Ciblez chaque point d'après le message du log."},{"title":"Récupérez et consignez l'incident","detail":"La récupération après crash se termine généralement automatiquement ; soyez patient. En cas de corruption, récupérez depuis une sauvegarde/PITR (voir le playbook Corruption). Ensuite, rédigez une analyse de cause profonde (RCA) : ce qui s'est passé, pourquoi, et comment éviter la récidive."}],"related":["version-uptime","connect-cli","last-backup-status"]},{"id":"corruption","title":"Corruption de données","icon":"🧯","symptom":"'ORA-01578', 'page corruption', 'checksum mismatch', 'MISCONF', erreurs de cohérence E/S lors des lectures.","steps":[{"title":"Restez calme et stoppez la propagation","detail":"Une intervention dans la précipitation aggrave la situation. Déterminez d'abord l'étendue et, si possible, stoppez les écritures sur l'objet concerné. Si c'est matériel (disque/contrôleur), vérifiez aussi les logs OS/matériel : réparer la base ne répare pas le disque."},{"title":"Déterminez l'étendue : quelle part est corrompue","detail":"S'agit-il d'un seul bloc/d'une seule page/d'un seul objet, ou est-ce généralisé ? Lancez une vérification d'intégrité.","engines":{"oracle":"RMAN> VALIDATE DATABASE;  -- or: SELECT * FROM v$database_block_corruption;","postgres":"-- With data_checksums enabled it shows up in the logs; check indexes/heap with pg_amcheck:\n-- pg_amcheck --heapallindexed -d mydb","mysql":"CHECK TABLE customer;  -- for InnoDB, innodb_force_recovery is the last resort","mssql":"DBCC CHECKDB ('MyDB') WITH NO_INFOMSGS, ALL_ERRORMSGS;","redis":"redis-check-rdb dump.rdb\n# AOF (Redis 7+ multi-part: under appendonlydir/, pass the manifest file):\nredis-check-aof appendonlydir/appendonly.aof.manifest\n# --fix TRUNCATES everything after the corrupted point (writes in that part are lost); back up the directory first:\n# redis-check-aof --fix appendonlydir/appendonly.aof.manifest"}},{"title":"Priorité : la restauration depuis une sauvegarde","detail":"En cas de corruption, la voie la plus sûre est la restauration depuis une bonne sauvegarde. Si la récupération au niveau bloc/page est prise en charge, ne récupérez que la partie corrompue (Oracle RMAN BLOCKRECOVER, MSSQL page restore). Les outils de réparation (DBCC REPAIR_ALLOW_DATA_LOSS, innodb_force_recovery) ENTRAÎNENT UNE PERTE DE DONNÉES : ils sont le dernier recours."},{"title":"Vérifiez l'intégrité","detail":"Après la récupération, relancez la vérification (DBCC CHECKDB / RMAN VALIDATE / CHECK TABLE) et confirmez qu'elle revient propre. Vérifiez aussi la cohérence des données côté application (nombre de lignes, totaux critiques)."},{"title":"Cause profonde : généralement matérielle","detail":"La corruption provient généralement d'une défaillance disque/mémoire/contrôleur. Testez/remplacez le matériel, activez les checksums (data_checksums, PAGE_VERIFY CHECKSUM) et renforcez la routine sauvegarde + vérification. Planifiez des DBCC CHECKDB / RMAN VALIDATE réguliers."}],"related":["last-backup-status","restore-dump","db-down"]},{"id":"long-running-query","title":"Requête / job de longue durée","icon":"⏱️","symptom":"Une seule requête/un seul job tourne depuis des heures sans se terminer ; il consomme des ressources ou affecte les autres.","steps":[{"title":"Identifiez la requête et sa durée","detail":"Quelle requête, depuis combien de temps tourne-t-elle, quel utilisateur/quelle application ?","engines":{"oracle":"SELECT sid, serial#, sql_id, last_call_et/60 running_min, username FROM v$session WHERE status='ACTIVE' AND type='USER' ORDER BY last_call_et DESC;","postgres":"SELECT pid, now()-query_start dur, usename, query FROM pg_stat_activity WHERE state='active' ORDER BY query_start;","mysql":"SELECT id, user, time, state, info FROM information_schema.processlist WHERE command='Query' ORDER BY time DESC;","mssql":"SELECT session_id, total_elapsed_time/1000 sec, status, command FROM sys.dm_exec_requests WHERE session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE is_user_process = 1) ORDER BY total_elapsed_time DESC;","mongodb":"db.currentOp({ active: true, secs_running: { $gt: 60 } })"}},{"title":"Progresse-t-elle ou est-elle bloquée","detail":"S'exécute-t-elle vraiment, ou attend-elle un verrou ? Consultez les indicateurs de progression (Oracle v$session_longops, vues PG pg_stat_progress_*). Si elle attend, passez au playbook Blocages.","engines":{"oracle":"SELECT sid, opname, sofar, totalwork, ROUND(sofar/NULLIF(totalwork,0)*100,1) pct, time_remaining FROM v$session_longops WHERE sofar<totalwork;","postgres":"SELECT * FROM pg_stat_progress_create_index;  -- or pg_stat_progress_vacuum / _cluster / _analyze"}},{"title":"Décidez : attendre ou annuler","detail":"Si elle progresse et va bientôt se terminer, attendez (le coût du rollback d'une annulation peut prendre encore plus de temps). Si elle est bloquée, s'exécute de manière incorrecte ou affecte la production, terminez-la. Tenez compte de la durée du rollback avant d'annuler."},{"title":"Terminez-la si nécessaire","detail":"Préférez annuler uniquement la requête (en conservant la session) ; si cela ne fonctionne pas, tuez la session.","engines":{"oracle":"ALTER SYSTEM CANCEL SQL 'sid,serial#';  -- 18c+; otherwise KILL SESSION","postgres":"SELECT pg_cancel_backend(pid);  -- otherwise pg_terminate_backend(pid)","mysql":"KILL QUERY <id>;","mssql":"KILL <session_id>;","mongodb":"db.killOp(<opid>)"}},{"title":"Corrigez durablement","detail":"Optimisez la requête (index, plan, pagination/traitement par lots), déplacez les jobs lourds hors des heures de pointe et, si besoin, exécutez-les par morceaux. Pour les rapports récurrents, envisagez une vue matérialisée / un réplica en lecture."}],"related":["active-sessions","kill-session","explain-plan","top-slow-queries"]},{"id":"xid-wraparound","title":"Crise XID wraparound / autovacuum dans Postgres","icon":"🧬","symptom":"'database is not accepting commands to avoid wraparound data loss' ; autovacuum ne suit pas, datfrozenxid vieillit.","steps":[{"title":"Mesurez l'urgence","detail":"Les identifiants de transaction PostgreSQL sont sur 32 bits ; à l'approche de leur épuisement, le serveur ARRÊTE LES ÉCRITURES pour éviter la perte de données. C'est une situation critique et urgente. La bonne nouvelle : elle se résout ; il faut de la méthode, pas de la panique."},{"title":"Mesurez la proximité de la limite","detail":"Extrayez l'âge du plus ancien XID non gelé pour chaque base ; la proximité d'autovacuum_freeze_max_age (200M par défaut) indique le danger.","engines":{"postgres":"SELECT datname, age(datfrozenxid) AS xid_age\nFROM pg_database ORDER BY xid_age DESC;"}},{"title":"Trouvez les tables les plus anciennes","detail":"Le problème vient généralement de quelques grandes tables/tables très écrites ; concentrez-vous sur les relfrozenxid les plus anciens.","engines":{"postgres":"SELECT relname, age(relfrozenxid) AS xid_age\nFROM pg_class WHERE relkind IN ('r','m','t')\nORDER BY xid_age DESC LIMIT 20;"}},{"title":"Levez ce qui bloque VACUUM","detail":"Les longues transactions ouvertes, les slots de réplication abandonnés et les transactions préparées bloquent le nettoyage (freeze). Trouvez-les et résolvez-les, sinon VACUUM ne progresse pas, quelle que soit sa durée.","engines":{"postgres":"SELECT pid, now()-xact_start AS age, state FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY age DESC;\nSELECT slot_name, active FROM pg_replication_slots WHERE NOT active;\nSELECT * FROM pg_prepared_xacts;"}},{"title":"Lancez un FREEZE agressif","detail":"Une fois les blocages levés, forcez le gel en commençant par les tables les plus anciennes. Même si le serveur a cessé d'attribuer de nouveaux identifiants de transaction, ne passez pas en mode mono-utilisateur (postgres --single) : la documentation actuelle de PostgreSQL indique qu'il n'est plus nécessaire et doit être évité. Se connecter en superuser en mode normal et lancer VACUUM suffit ; en PG14+, le mécanisme failsafe de VACUUM s'active à l'approche du wraparound.","engines":{"postgres":"VACUUM (FREEZE, VERBOSE) oldest_table;\n-- Entire database: vacuumdb --all --freeze --jobs=4"}},{"title":"Définitif : renforcez et surveillez autovacuum","detail":"Augmentez autovacuum_vacuum_cost_limit, réduisez autovacuum_naptime et définissez des paramètres de stockage spécifiques sur les tables très écrites. Mettez en place une alerte sur age(datfrozenxid) ; ne revivez jamais cette crise."}],"related":["open-transactions","reclaim-space"]},{"id":"tempdb-contention","title":"Contention sur tempdb (SQL Server)","icon":"🌀","symptom":"Attentes PAGELATCH_UP/EX, contention sur les pages d'allocation de tempdb (PFS/GAM/SGAM), lenteur générale.","steps":[{"title":"Confirmez la contention","detail":"Des attentes PAGELATCH sur les pages système de tempdb (comme 2:1:1) indiquent une contention d'allocation.","engines":{"mssql":"SELECT session_id, wait_type, wait_duration_ms, resource_description\nFROM sys.dm_os_waiting_tasks\nWHERE wait_type LIKE 'PAGELATCH%' AND resource_description LIKE '2:%'\nORDER BY wait_duration_ms DESC;"}},{"title":"Détaillez l'utilisation de tempdb","detail":"Qui utilise combien de tempdb ? Des objets internes (débordements de sort/hash), le version store ou des objets utilisateur ?","engines":{"mssql":"SELECT * FROM sys.dm_db_file_space_usage;\nSELECT TOP 10 session_id, user_objects_alloc_page_count, internal_objects_alloc_page_count\nFROM sys.dm_db_task_space_usage ORDER BY internal_objects_alloc_page_count DESC;"}},{"title":"Corrigez la configuration des fichiers","detail":"Utilisez pour tempdb plusieurs fichiers de données de taille ÉGALE, en nombre égal aux cœurs CPU (jusqu'à 8). SQL 2016+ le recommande à l'installation ; sur les versions plus anciennes, utilisez les trace flags 1117/1118. Placez les fichiers sur un disque rapide (NVMe local)."},{"title":"Trouvez la requête incontrôlée","detail":"Les requêtes avec de gros débordements de sort/hash font exploser tempdb. Ciblez celles dont le memory grant est insuffisant ou le plan mauvais ; réduisez les débordements par les index/plans.","engines":{"mssql":"SELECT TOP 10 t.text, mg.requested_memory_kb, mg.granted_memory_kb\nFROM sys.dm_exec_query_memory_grants mg CROSS APPLY sys.dm_exec_sql_text(mg.sql_handle) t\nORDER BY mg.requested_memory_kb DESC;"}},{"title":"Équivalents dans les autres bases","detail":"La même « contention sur l'espace temporaire » correspond au tablespace TEMP dans Oracle (v$tempseg_usage / v$sort_usage) et aux temp_files et débordements de work_mem dans PostgreSQL. Le correctif de fond est toujours le même : corriger la requête qui déborde et accélérer l'espace temporaire."}],"related":["free-space","top-slow-queries","reclaim-space"]},{"id":"cert-expiry","title":"TLS / certificat expiré","icon":"🔐","symptom":"'certificate has expired', 'SSL handshake failed', les clients ne peuvent soudain plus se connecter (sans modification de code).","steps":[{"title":"Où cela casse-t-il : serveur, client ou CA ?","detail":"Est-ce le certificat serveur qui a expiré, un certificat client ou une CA de la chaîne ? Le symptôme est généralement « cela marchait hier, plus aujourd'hui, aucun code n'a changé »."},{"title":"Vérifiez l'expiration","detail":"Lisez la date d'expiration du certificat directement sur le serveur (méthode rapide, indépendante de la base).","engines":{"postgres":"openssl s_client -connect host:5432 -starttls postgres </dev/null 2>/dev/null | openssl x509 -noout -dates","mongodb":"openssl s_client -connect host:27017 </dev/null 2>/dev/null | openssl x509 -noout -enddate","redis":"openssl s_client -connect host:6379 </dev/null 2>/dev/null | openssl x509 -noout -enddate"}},{"title":"Installez et chargez le nouveau certificat","detail":"Remplacez le certificat et, dans la plupart des cas, rechargez le service sans le redémarrer.","engines":{"postgres":"-- Renew ssl_cert_file/ssl_key_file, then:\nSELECT pg_reload_conf();","mysql":"ALTER INSTANCE RELOAD TLS;","mssql":"-- SQL Server Configuration Manager > Certificate; a service restart is required","mongodb":"// Renew the net.tls.certificateKeyFile file (at the same path), then load it without a restart (5.0+):\ndb.adminCommand({rotateCertificates: 1})","redis":"CONFIG SET tls-cert-file /path/new.crt\nCONFIG SET tls-key-file /path/new.key","elasticsearch":"# ES watches the certificate/key files (PEM, PKCS#12) defined under xpack.security.*.ssl and\n# reloads them automatically when the file at the SAME path changes (no restart needed).\n# If the path, file name or keystore password changes, a rolling restart is required.\n# Note: POST _nodes/reload_secure_settings is only for reloadable keystore settings, not for TLS certificates.\n# Verify: check the new expiry date with openssl s_client and look in the ES logs for\n# an entry showing the certificate was reloaded."}},{"title":"Vérifiez","detail":"Confirmez avec la commande openssl ci-dessus que le handshake TLS réussit avec le nouveau certificat et que l'expiration a été repoussée ; connectez-vous de bout en bout avec un client."},{"title":"Définitif : plus jamais","detail":"Mettez en place une alerte 30 jours avant l'expiration des certificats ; utilisez le renouvellement automatique (ACME/Let's Encrypt, HashiCorp Vault, cert-manager). Automatisez le rechargement après renouvellement."}],"related":["connect-cli"]},{"id":"upgrade-rollback","title":"Montée de version / retour arrière d'un patch","icon":"⏪","symptom":"Problèmes après une montée de version/un patch : régression de performance, incompatibilité applicative ou base qui ne s'ouvre pas.","steps":[{"title":"Arrêtez-vous d'abord : avez-vous un plan de repli ?","detail":"Si vous n'avez pas pris une sauvegarde complète + (si possible) un snapshot de stockage AVANT la montée de version en production, la voie la plus sûre est de corriger vers l'avant. Dans la plupart des bases, revenir en arrière signifie restaurer depuis une sauvegarde. Cette étape aurait dû être planifiée AVANT la montée de version."},{"title":"Classez le problème","detail":"S'agit-il d'une régression de plan de l'optimiseur (requêtes ralenties), d'une incompatibilité application/driver, ou d'un échec d'ouverture/d'une corruption ? La solution varie fortement en conséquence."},{"title":"Voie de retour arrière par base","detail":"Oracle : retour arrière du patch avec datapatch, ou Flashback Database (si vous avez créé un guaranteed restore point). PostgreSQL : retour arrière après pg_upgrade = anciens binaires + sauvegarde de l'ancien répertoire de données. MySQL : le DOWNGRADE en place n'est généralement PAS PRIS EN CHARGE ; restaurez depuis une sauvegarde. SQL Server : aucun downgrade possible ; restaurez la sauvegarde d'avant la montée de version. Dans tous les cas : le snapshot/la sauvegarde est votre assurance."},{"title":"S'il s'agit d'une régression de plan : corrigez sans retour arrière","detail":"Il n'est généralement pas nécessaire de revenir sur toute la version ; seuls les plans ont changé.","engines":{"oracle":"ALTER SYSTEM SET optimizer_features_enable='19.1.0';  -- pin to the old behavior\n-- or lock in the good plan with a SQL Plan Baseline","postgres":"ANALYZE;  -- fresh statistics first\n-- if needed, temporarily: steer the plan with settings such as SET enable_nestloop=off;"}},{"title":"Définitif : la prochaine fois","detail":"Testez d'abord la montée de version en préproduction avec une charge réelle, appliquez des montées de version canary/rolling, prenez un guaranteed restore point / snapshot et documentez la procédure de repli."}],"related":["last-backup-status","restore-dump","version-uptime"]},{"id":"es-red-cluster","title":"Cluster Elasticsearch red/yellow et shards non assignés","icon":"🟥","symptom":"_cluster/health 'red' ou 'yellow' ; shards non assignés ; lectures/écritures restreintes ou données inaccessibles.","steps":[{"title":"Couleur et cause","detail":"yellow = des shards réplicas ne sont pas assignés (les données sont présentes, la durabilité est réduite). red = au moins un shard PRIMAIRE manque (ces données sont inaccessibles). Déterminez d'abord lequel et pourquoi.","engines":{"elasticsearch":"GET _cluster/health?level=indices\nGET _cluster/allocation/explain"}},{"title":"Listez les shards non assignés et leur raison","detail":"La colonne unassigned.reason donne la cause profonde (NODE_LEFT, ALLOCATION_FAILED, DISK, etc.).","engines":{"elasticsearch":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason&s=state"}},{"title":"Traitez les causes courantes","detail":"1) Disk watermark → au-delà de low, le nœud ne reçoit plus de nouveaux shards ; à flood_stage, les index passent en lecture seule. 2) Un nœud est tombé / est parti. 3) L'allocation a été laissée désactivée. 4) Nombre de réplicas > nombre de nœuds éligibles."},{"title":"Corrigez","detail":"Débloquez selon la cause en jeu.","engines":{"elasticsearch":"# If disk space was freed, remove the read-only block (7.4+ also lifts it automatically once below the high watermark):\nPUT _all/_settings {\"index.blocks.read_only_allow_delete\": null}\n# If allocation was left restricted, return it to the default (all):\nPUT _cluster/settings {\"persistent\":{\"cluster.routing.allocation.enable\":null}}\n# Retry failed allocations:\nPOST _cluster/reroute?retry_failed=true"}},{"title":"Si c'est toujours red et que le primaire est réellement perdu","detail":"Restaurez l'index concerné depuis un snapshot ou réindexez-le à partir des données source (_reindex). Définitif : alertes sur le disk watermark, nombre de réplicas approprié (≥1) et surveillance de la santé des nœuds."}],"related":["replication-status","list-replicas","free-space"]},{"id":"time-skew","title":"Décalage d'horloge / problème NTP","icon":"⏱️","symptom":"Écarts d'horloge entre les nœuds du cluster ; incohérences de réplication/d'élection, 'certificate not yet valid', sauts temporels dans les logs.","steps":[{"title":"Reconnaissez le symptôme","detail":"Le décalage d'horloge est insidieux : élections Mongo/Redis Sentinel instables (flapping), erreurs TLS 'not yet valid/expired', erreurs Kerberos, horodatages qui reculent dans les logs. Dans les systèmes distribués, il est à l'origine de la plupart des bizarreries « inexplicables »."},{"title":"Mesurez l'écart","detail":"Sur chaque nœud, comparez la source de temps et l'offset : `timedatectl`, `chronyc tracking` (ou `ntpq -p`), et `date -u` entre les nœuds. De combien de ms/s diffèrent-ils ?"},{"title":"Corrigez NTP/chrony","detail":"chronyd/ntpd tourne-t-il, atteint-il ses sources de temps, est-il synchronisé ? Démarrez le service et corrigez la source ; pour de grands offsets, faites un step avec prudence (un saut brutal peut perturber certaines bases)."},{"title":"Vérifiez le composant affecté","detail":"Une fois l'horloge corrigée, vérifiez : l'élection du replica set MongoDB est-elle stable, Redis Sentinel/Cluster est-il sain, les handshakes TLS réussissent-ils, les métriques de retard de réplication sont-elles revenues à la normale ?"},{"title":"Définitif","detail":"Rendez NTP obligatoire sur tous les nœuds et surveillez l'offset (alerte : >100ms). Sur les machines virtuelles, tranchez le paramètre de synchronisation horaire hôte-invité ; utilisez de préférence une hiérarchie de sources de temps unique et fiable."}],"related":["replication-status"]}]},"playbooks/hadr.json":{"schemaVersion":1,"playbooks":[{"id":"hadr-oracle-dataguard","title":"Mise en place d'Oracle Data Guard (standby physique)","icon":"🛡️","symptom":"Objectif : mettre en place la reprise après sinistre et la haute disponibilité pour Oracle avec une standby physique (Data Guard) ; pouvoir basculer sur la standby en cas de défaillance de la base primaire.","steps":[{"title":"Passez la base primaire en mode FORCE LOGGING et ARCHIVELOG","detail":"Pour que la standby reste cohérente avec la base primaire, toutes les modifications doivent être écrites dans le redo. FORCE LOGGING journalise même les opérations NOLOGGING. Pour le mode ARCHIVELOG, la base est démarrée à l'état MOUNT.","engines":{"oracle":"ALTER DATABASE FORCE LOGGING;\n-- For ARCHIVELOG:\nSHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;"}},{"title":"Ajoutez des fichiers standby redo log","detail":"Les standby redo logs (SRL) doivent compter un groupe de plus que les online redo logs, avec la même taille. Ils sont créés sur la base primaire comme sur la standby ; ils sont requis pour le real-time apply. Dans l'exemple, si l'online redo fait 200M, les SRL font aussi 200M.","engines":{"oracle":"ALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl01.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl02.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl03.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl04.log') SIZE 200M;"}},{"title":"Définissez log_archive_dest et les paramètres associés","detail":"Sur la base primaire, définissez dest_2 pour transmettre le redo à la standby. Configurez DB_UNIQUE_NAME, LOG_ARCHIVE_CONFIG (le db_unique_name des deux côtés), FAL_SERVER et STANDBY_FILE_MANAGEMENT=AUTO. Les fichiers sont ainsi créés automatiquement sur la standby.","engines":{"oracle":"ALTER SYSTEM SET LOG_ARCHIVE_CONFIG='DG_CONFIG=(orcl_prim,orcl_stby)';\nALTER SYSTEM SET LOG_ARCHIVE_DEST_2='SERVICE=orcl_stby ASYNC VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=orcl_stby';\nALTER SYSTEM SET FAL_SERVER='orcl_stby';  -- for role changes; on the standby FAL_SERVER='orcl_prim'\nALTER SYSTEM SET STANDBY_FILE_MANAGEMENT=AUTO;"}},{"title":"Créez la standby avec RMAN DUPLICATE FOR STANDBY","detail":"Une fois le fichier de mots de passe et le listener statique/tnsnames configurés sur le serveur standby, RMAN se connecte à la base primaire et à la standby (AUXILIARY). FROM ACTIVE DATABASE copie via le réseau sans sauvegarde ; DORECOVER applique le redo.","engines":{"oracle":"rman TARGET sys/password@orcl_prim AUXILIARY sys/password@orcl_stby\nRUN {\n  DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE\n  DORECOVER\n  NOFILENAMECHECK;\n}"}},{"title":"Démarrez l'application du redo (MRP) et vérifiez","detail":"Démarrez le managed recovery pendant que la standby est à l'état MOUNT ; utilisez USING CURRENT LOGFILE pour le real-time apply. Vérifiez avec v$managed_standby que le processus d'application (MRP0) tourne.","engines":{"oracle":"ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;\nSELECT process, status, sequence# FROM v$managed_standby WHERE process LIKE 'MRP%';"}},{"title":"Activez Data Guard Broker","detail":"Le broker centralise la configuration et réduit le failover/switchover à une seule commande. Définissez DG_BROKER_START=TRUE sur les deux instances, puis créez et activez la configuration avec dgmgrl. En 12c+, le broker gère le transport du redo : si LOG_ARCHIVE_DEST_2 a été défini manuellement, ADD DATABASE échoue avec ORA-16698 ; videz-le d'abord avec ALTER SYSTEM SET LOG_ARCHIVE_DEST_2=''.","engines":{"oracle":"ALTER SYSTEM SET DG_BROKER_START=TRUE;\n-- In dgmgrl:\nCREATE CONFIGURATION dgconf AS PRIMARY DATABASE IS orcl_prim CONNECT IDENTIFIER IS orcl_prim;\nADD DATABASE orcl_stby AS CONNECT IDENTIFIER IS orcl_stby;\nENABLE CONFIGURATION;"}},{"title":"Surveillez l'écart de synchronisation et terminez les tests","detail":"L'écart de séquence entre la base primaire et la standby doit être surveillé en continu ; un écart croissant indique un goulet réseau/d'application. dgmgrl SHOW CONFIGURATION doit afficher 'SUCCESS'. Testez éventuellement un changement de rôle avec un switchover.","engines":{"oracle":"-- dgmgrl (SHOW DATABASE shows the Transport Lag / Apply Lag values):\nSHOW CONFIGURATION;\nSHOW DATABASE orcl_stby;\n-- SQL (standby): SELECT name, value FROM v$dataguard_stats WHERE name LIKE '%lag';"}}],"related":["replication-status","promote-failover","last-backup-status"]},{"id":"hadr-mssql-alwayson","title":"Mise en place de SQL Server Always On Availability Group","icon":"🔁","symptom":"Objectif : mettre en place le failover automatique et un réplica secondaire lisible pour SQL Server avec un Always On Availability Group (AG).","steps":[{"title":"Mettez en place un Windows Server Failover Cluster (WSFC)","detail":"Always On repose sur un WSFC sous-jacent. Installez la fonctionnalité Failover Clustering sur tous les nœuds et configurez le réseau et le témoin. Créez le cluster avec PowerShell.","engines":{"mssql":"-- PowerShell (the Failover-Clustering feature must be installed on every node):\nNew-Cluster -Name sqlclu -Node node1,node2 -StaticAddress 10.0.0.50\n-- Witness:\nSet-ClusterQuorum -NodeAndFileShareMajority \\\\fs01\\quorum"}},{"title":"Activez Always On sur chaque instance","detail":"Activez la fonctionnalité Always On Availability Groups avec SQL Server Configuration Manager ou PowerShell. Redémarrez ensuite le service SQL Server. Cela lie l'appartenance au cluster à l'instance.","engines":{"mssql":"-- PowerShell:\nEnable-SqlAlwaysOn -ServerInstance node1\\SQL01 -Force\nEnable-SqlAlwaysOn -ServerInstance node2\\SQL01 -Force"}},{"title":"Sauvegardez la base et restaurez-la sur le secondaire WITH NORECOVERY","detail":"La base à ajouter à l'AG doit être en mode de récupération FULL. Effectuez une sauvegarde complète et une sauvegarde du log sur le primaire ; restaurez-les sur le secondaire WITH NORECOVERY (pour qu'il puisse continuer à recevoir le redo). C'est le seeding manuel ; l'alternative est l'automatic seeding.","engines":{"mssql":"-- On the primary:\nBACKUP DATABASE app TO DISK='\\\\fs01\\bak\\app.bak';\nBACKUP LOG app TO DISK='\\\\fs01\\bak\\app.trn';\n-- On the secondary:\nRESTORE DATABASE app FROM DISK='\\\\fs01\\bak\\app.bak' WITH NORECOVERY;\nRESTORE LOG app FROM DISK='\\\\fs01\\bak\\app.trn' WITH NORECOVERY;"}},{"title":"Créez les endpoints et définissez l'Availability Group","detail":"Créez un endpoint HADR sur le port 5022 pour le trafic redo entre nœuds. Le mode synchrone (SYNCHRONOUS_COMMIT) assure un failover automatique sans perte de données ; le mode asynchrone (ASYNCHRONOUS_COMMIT) est décalé mais performant pour une DR distante.","engines":{"mssql":"CREATE ENDPOINT hadr_endpoint STATE=STARTED\n  AS TCP (LISTENER_PORT=5022)\n  FOR DATA_MIRRORING (ROLE=ALL, ENCRYPTION=REQUIRED ALGORITHM AES);\n\nCREATE AVAILABILITY GROUP appag\n  FOR DATABASE app\n  REPLICA ON\n    'NODE1\\SQL01' WITH (ENDPOINT_URL='TCP://node1:5022', AVAILABILITY_MODE=SYNCHRONOUS_COMMIT, FAILOVER_MODE=AUTOMATIC, SECONDARY_ROLE(ALLOW_CONNECTIONS=ALL)),\n    'NODE2\\SQL01' WITH (ENDPOINT_URL='TCP://node2:5022', AVAILABILITY_MODE=SYNCHRONOUS_COMMIT, FAILOVER_MODE=AUTOMATIC, SECONDARY_ROLE(ALLOW_CONNECTIONS=ALL));"}},{"title":"Joignez le secondaire à l'AG et joignez la base","detail":"Faites un JOIN de l'AG sur l'instance secondaire, puis ajoutez la base restaurée à l'AG. Cette étape est nécessaire en cas de seeding manuel ; avec l'automatic seeding, elle est gérée sur le primaire via SET(SEEDING_MODE).","engines":{"mssql":"-- On the secondary:\nALTER AVAILABILITY GROUP appag JOIN;\nALTER DATABASE app SET HADR AVAILABILITY GROUP = appag;"}},{"title":"Créez l'Availability Group Listener","detail":"Le listener est le nom virtuel et l'IP auxquels se connectent les applications ; après un failover, les connexions sont redirigées automatiquement vers le nouveau primaire. Utilisez ce nom et MultiSubnetFailover=True dans la chaîne de connexion de l'application.","engines":{"mssql":"ALTER AVAILABILITY GROUP appag\n  ADD LISTENER 'appag-listener' (WITH IP ((N'10.0.0.60', N'255.255.255.0')), PORT=1433);"}},{"title":"Testez le failover et vérifiez l'état de synchronisation","detail":"Lorsque la synchronisation est 'HEALTHY', testez un changement de rôle avec un failover planifié. Pour le failover automatique, le mode doit être synchrone et FAILOVER_MODE=AUTOMATIC. Surveillez l'état avec sys.dm_hadr_availability_replica_states.","engines":{"mssql":"SELECT ag.name, ars.role_desc, ars.synchronization_health_desc\nFROM sys.dm_hadr_availability_replica_states ars\nJOIN sys.availability_groups ag ON ag.group_id=ars.group_id;\n-- Planned failover (on the node that will become the new primary):\nALTER AVAILABILITY GROUP appag FAILOVER;"}}],"related":["replication-status","promote-failover","list-replicas"]},{"id":"hadr-postgres-streaming","title":"Streaming replication PostgreSQL + failover automatique","icon":"🐘","symptom":"Objectif : mettre en place une hot standby pour PostgreSQL avec la streaming replication et ajouter Patroni/repmgr pour le failover automatique.","steps":[{"title":"Définissez les paramètres WAL et de connexion sur le primaire","detail":"Pour la streaming replication, wal_level doit être au moins 'replica'. max_wal_senders doit couvrir le nombre de réplicas + les connexions de sauvegarde, et la suppression prématurée du WAL doit être empêchée avec wal_keep_size ou un slot de réplication. Les modifications nécessitent un redémarrage.","engines":{"postgres":"ALTER SYSTEM SET wal_level = 'replica';\nALTER SYSTEM SET max_wal_senders = 10;\nALTER SYSTEM SET max_replication_slots = 10;\nALTER SYSTEM SET hot_standby = on;\n-- To apply the postgresql.conf change:\n-- systemctl restart postgresql"}},{"title":"Définissez le rôle de réplication et l'accès pg_hba","detail":"Créez un rôle doté de l'attribut REPLICATION pour la connexion de la standby. Ajoutez à pg_hba.conf une ligne autorisant l'accès à la base 'replication' depuis l'IP de la standby, puis rechargez avec pg_reload_conf().","engines":{"postgres":"CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secret';\n-- Add to pg_hba.conf:\n-- host replication replicator 10.0.0.20/32 scram-sha-256\nSELECT pg_reload_conf();"}},{"title":"Créez un slot de réplication persistant sur le primaire","detail":"Un slot de réplication physique empêche le primaire de supprimer le WAL nécessaire même lorsque le standby prend du retard ; la gestion par slot est plus sûre que wal_keep_size. Le nom du slot sera utilisé dans la configuration du standby.","engines":{"postgres":"SELECT pg_create_physical_replication_slot('standby1_slot');\nSELECT slot_name, active, restart_lsn FROM pg_replication_slots;"}},{"title":"Prenez une sauvegarde de base sur le serveur standby avec pg_basebackup","detail":"Avec le répertoire de données du standby vide, prenez une copie complète depuis le primaire. L'option -R écrit automatiquement le fichier standby.signal et primary_conninfo ; -C peut aussi créer le slot. Le nom du slot est passé avec -S.","engines":{"postgres":"pg_basebackup -h 10.0.0.10 -U replicator -D /var/lib/pgsql/data \\\n  -R -X stream -S standby1_slot -P -v"}},{"title":"Vérifiez primary_conninfo et hot_standby, démarrez la standby","detail":"Vérifiez primary_conninfo (host, user, slot) dans le postgresql.auto.conf écrit par -R ; la présence du fichier standby.signal détermine le mode standby. Avec hot_standby=on, la standby accepte les requêtes en lecture seule. Démarrez le service.","engines":{"postgres":"-- Expected in postgresql.auto.conf:\n-- primary_conninfo = 'host=10.0.0.10 user=replicator password=secret'\n-- primary_slot_name = 'standby1_slot'\n-- systemctl start postgresql\nSELECT pg_is_in_recovery();  -- should return true on the standby"}},{"title":"Surveillez le retard de réplication depuis le primaire","detail":"Sur le primaire, pg_stat_replication affiche pour chaque standby le LSN envoyé/rejoué et le retard. Les retards write/flush/replay doivent rester faibles ; sync_state vaut 'sync' si un réplica synchrone est utilisé.","engines":{"postgres":"SELECT client_addr, state, sync_state,\n       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes,\n       write_lag, flush_lag, replay_lag\nFROM pg_stat_replication;"}},{"title":"Ajoutez Patroni ou repmgr pour le failover automatique","detail":"La streaming replication seule exige une promotion manuelle. Le failover automatique nécessite un gestionnaire de cluster : Patroni (élection du leader sur etcd/Consul + promotion automatique) ou repmgr (surveillance/failover avec repmgrd). Côté application, fournissez un point d'accès unique avec HAProxy/pgBouncer. Si une promotion manuelle est nécessaire : pg_ctl promote. Avant de promouvoir, assurez-vous que l'ancien primaire est réellement arrêté et que les applications ne peuvent pas y écrire (risque de split-brain). Réintégrer l'ancien primaire comme standby du nouveau primaire nécessite pg_rewind ; pour cela, wal_log_hints = on ou les data checksums doivent être activés.","engines":{"postgres":"-- Manual promote (if there is no cluster manager):\npg_ctl promote -D /var/lib/pgsql/data\n-- or via SQL:\nSELECT pg_promote();\n-- With Patroni: patronictl -c /etc/patroni.yml switchover mycluster"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-mysql-innodb-cluster","title":"Mise en place de MySQL InnoDB Cluster (Group Replication)","icon":"🐬","symptom":"Objectif : mettre en place la haute disponibilité avec failover automatique pour MySQL à l'aide d'InnoDB Cluster (Group Replication + MySQL Shell + MySQL Router).","steps":[{"title":"Remplissez les prérequis","detail":"InnoDB Cluster exige MySQL 8.0+, un server_id unique, le mode GTID et le moteur de stockage InnoDB sur chaque nœud. Toutes les instances doivent être joignables entre elles par le réseau, et les tables doivent avoir une clé primaire. Préparez un utilisateur clusterAdmin pour l'administration.","engines":{"mysql":"-- my.cnf on each instance:\n-- server_id unique, gtid_mode=ON, enforce_gtid_consistency=ON\nCREATE USER 'icadmin'@'%' IDENTIFIED BY 'secret';\nGRANT ALL PRIVILEGES ON *.* TO 'icadmin'@'%' WITH GRANT OPTION;"}},{"title":"Configurez chaque instance avec MySQL Shell","detail":"Dans mysqlsh, dba.configureInstance() rend chaque nœud apte à Group Replication ; il corrige les paramètres manquants et demande un redémarrage si nécessaire. Répétez sur tous les nœuds.","engines":{"mysql":"-- mysqlsh:\ndba.configureInstance('icadmin@node1:3306')\ndba.configureInstance('icadmin@node2:3306')\ndba.configureInstance('icadmin@node3:3306')"}},{"title":"Créez le cluster (nœud seed)","detail":"Connectez-vous à un nœud et démarrez le cluster avec dba.createCluster() ; ce nœud devient le premier primaire. Un objet cluster est renvoyé et utilisé pour les ajouts suivants.","engines":{"mysql":"-- mysqlsh, while connected to node1:\nvar cluster = dba.createCluster('appCluster')"}},{"title":"Ajoutez les autres instances au cluster","detail":"Ajoutez les nœuds secondaires avec cluster.addInstance(). Choisissez clone (clone physique, rapide pour un nouveau nœud) ou incremental (via GTID) comme recoveryMethod. Au moins 3 nœuds assurent une majorité impaire et le failover automatique.","engines":{"mysql":"-- mysqlsh:\ncluster.addInstance('icadmin@node2:3306', {recoveryMethod: 'clone'})\ncluster.addInstance('icadmin@node3:3306', {recoveryMethod: 'clone'})"}},{"title":"Initialisez MySQL Router (bootstrap)","detail":"MySQL Router route entre l'application et le cluster ; il expose des ports lecture/écriture (6446) et lecture seule (6447) et redirige automatiquement le trafic lorsque le primaire change. Le bootstrap génère la configuration à partir des métadonnées du cluster.","engines":{"mysql":"mysqlrouter --bootstrap icadmin@node1:3306 --user=mysqlrouter --directory /etc/mysqlrouter\n# Then: mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf &"}},{"title":"Vérifiez l'état du cluster et le failover","detail":"cluster.status() affiche le rôle de chaque nœud (primaire R/W, secondaire R/O) et son état (ONLINE). clusterName et la topologie doivent être sains. Testez le failover en arrêtant le primaire et en observant l'élection automatique d'un nouveau primaire.","engines":{"mysql":"-- mysqlsh:\nvar cluster = dba.getCluster('appCluster')\ncluster.status()\n-- Members via SQL:\nSELECT member_host, member_role, member_state FROM performance_schema.replication_group_members;"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-mongodb-replicaset","title":"Mise en place d'un replica set MongoDB + sharding","icon":"🍃","symptom":"Objectif : mettre en place un replica set avec failover automatique pour MongoDB et l'étendre à un cluster shardé pour la mise à l'échelle horizontale.","steps":[{"title":"Démarrez les processus mongod avec le même nom de replSet","detail":"Démarrez chaque membre du replica set avec le même nom --replSet et un --bind_ip joignable. En production, l'authentification interne par keyFile ou TLS est recommandée. Visez un nombre impair de membres votants pour une majorité claire.","engines":{"mongodb":"mongod --replSet rs0 --port 27017 --dbpath /data/n1 --bind_ip localhost,10.0.0.11\nmongod --replSet rs0 --port 27017 --dbpath /data/n2 --bind_ip localhost,10.0.0.12\nmongod --replSet rs0 --port 27017 --dbpath /data/n3 --bind_ip localhost,10.0.0.13"}},{"title":"Initialisez le replica set","detail":"Connectez-vous à un membre avec mongosh, appelez rs.initiate() et définissez tous les membres. Cela déclenche la première élection et un primaire est choisi. Vérifiez les rôles PRIMARY/SECONDARY avec rs.status().","engines":{"mongodb":"rs.initiate({\n  _id: 'rs0',\n  members: [\n    { _id: 0, host: '10.0.0.11:27017' },\n    { _id: 1, host: '10.0.0.12:27017' },\n    { _id: 2, host: '10.0.0.13:27017' }\n  ]\n})\nrs.status()"}},{"title":"Ajoutez des membres, définissez les priorités et un arbiter si besoin","detail":"Ajoutez des membres ultérieurement avec rs.add(). priority détermine quel membre tend à devenir primaire (0 = ne devient jamais primaire). Un arbiter ne fait que voter et ne contient aucune donnée ; il sert à départager avec un nombre pair de nœuds de données, mais un vrai membre est préférable pour la sécurité des données. Dans les topologies avec arbiter (surtout PSA), le write concern majority ne peut pas être satisfait lorsqu'un nœud de données tombe et les écritures attendent ; en 5.0+, le write concern par défaut du cluster peut devoir être défini explicitement avec setDefaultRWConcern avant d'ajouter un arbiter.","engines":{"mongodb":"rs.add({ host: '10.0.0.14:27017', priority: 0.5 })\n// Arbiter only if really needed (a data-bearing member is preferred):\nrs.addArb('10.0.0.15:27017')\n// change priority:\ncfg = rs.conf(); cfg.members[0].priority = 2; rs.reconfig(cfg)"}},{"title":"Mettez en place le replica set des config servers pour le sharding","detail":"Les métadonnées du cluster shardé sont conservées dans un replica set de config servers (--configsvr). Il est lui-même initialisé comme un replica set ; 3 membres sont recommandés pour la haute disponibilité.","engines":{"mongodb":"mongod --configsvr --replSet cfgrs --port 27019 --dbpath /data/cfg1 --bind_ip localhost,10.0.0.21\n-- mongosh (on the config server):\nrs.initiate({ _id: 'cfgrs', configsvr: true, members: [\n  { _id: 0, host: '10.0.0.21:27019' },\n  { _id: 1, host: '10.0.0.22:27019' },\n  { _id: 2, host: '10.0.0.23:27019' } ] })"}},{"title":"Démarrez les replica sets des shards et le routeur mongos","detail":"Chaque shard est son propre replica set démarré avec --shardsvr (la durabilité des données vient de la réplication au sein du shard). Le routeur mongos est démarré en pointant vers les config servers ; les applications se connectent à mongos.","engines":{"mongodb":"mongod --shardsvr --replSet shard1rs --port 27018 --dbpath /data/s1 --bind_ip localhost,10.0.0.31\n# initiate shard1rs with rs.initiate, then mongos:\nmongos --configdb cfgrs/10.0.0.21:27019,10.0.0.22:27019,10.0.0.23:27019 --port 27017 --bind_ip localhost,10.0.0.40"}},{"title":"Ajoutez les shards et shardez la collection","detail":"Connectez-vous à mongos et ajoutez chaque replica set de shard avec sh.addShard(). Activez le sharding sur la base et répartissez la collection selon une shard key avec sh.shardCollection(). Le choix de la clé est essentiel : elle doit avoir une cardinalité élevée et une répartition uniforme. Surveillez la répartition des chunks avec sh.status().","engines":{"mongodb":"sh.addShard('shard1rs/10.0.0.31:27018')\nsh.addShard('shard2rs/10.0.0.32:27018')\nsh.enableSharding('appdb')\nsh.shardCollection('appdb.events', { customer_id: 'hashed' })\nsh.status()"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-redis-sentinel","title":"Haute disponibilité avec Redis Sentinel + Cluster","icon":"⚡","symptom":"Objectif : mettre en place un déploiement master-réplica avec failover automatique pour Redis à l'aide de Sentinel ; passer en mode Redis Cluster si une mise à l'échelle horizontale est nécessaire.","steps":[{"title":"Configurez le master et les réplicas","detail":"Exécutez un master et au moins deux réplicas. Les réplicas suivent le master avec REPLICAOF ('replicaof <host> <port>' peut aussi être écrit dans redis.conf). Activez AOF ou RDB pour la persistance ; l'authentification avec requirepass/masterauth est recommandée.","engines":{"redis":"# at runtime on the replica nodes:\nREPLICAOF 10.0.0.10 6379\n# to make it persistent, redis.conf:\n# replicaof 10.0.0.10 6379\n# masterauth secret\nINFO replication"}},{"title":"Configurez Sentinel avec monitor et quorum","detail":"Définissez le master à surveiller et le quorum dans le sentinel.conf de chaque nœud Sentinel. quorum est le nombre minimal de Sentinels requis pour considérer le master 'down'. En production, 3 Sentinels sur 3 serveurs distincts sont recommandés (contre le split-brain).","engines":{"redis":"# sentinel.conf:\nsentinel monitor mymaster 10.0.0.10 6379 2\nsentinel down-after-milliseconds mymaster 5000\nsentinel failover-timeout mymaster 60000\nsentinel parallel-syncs mymaster 1"}},{"title":"Démarrez les processus Sentinel","detail":"Démarrez Sentinel sur chaque nœud. Les Sentinels se découvrent automatiquement entre eux ainsi que les réplicas ; ils écrivent la configuration dans le fichier à l'exécution. sentinel.conf doit donc être accessible en écriture.","engines":{"redis":"redis-sentinel /etc/redis/sentinel.conf\n# or:\nredis-server /etc/redis/sentinel.conf --sentinel"}},{"title":"Interrogez l'état via Sentinel et testez le failover","detail":"Interrogez le master courant et les réplicas avec les commandes SENTINEL. Les applications doivent connaître le master courant en interrogeant Sentinel avec 'get-master-addr-by-name'. Vérifiez la bascule automatique avec un failover planifié.","engines":{"redis":"SENTINEL master mymaster\nSENTINEL get-master-addr-by-name mymaster\nSENTINEL replicas mymaster\n# planned switchover:\nSENTINEL failover mymaster"}},{"title":"Alternative : partitionnez les données avec Redis Cluster","detail":"Si la mémoire/le débit d'un seul master ne suffit pas, utilisez Redis Cluster : les données sont découpées en 16384 hash slots répartis sur plusieurs masters ; chaque master a son propre réplica. L'application doit utiliser un client compatible cluster. Mettez-le en place avec redis-cli --cluster create.","engines":{"redis":"redis-cli --cluster create \\\n  10.0.0.11:6379 10.0.0.12:6379 10.0.0.13:6379 \\\n  10.0.0.21:6379 10.0.0.22:6379 10.0.0.23:6379 \\\n  --cluster-replicas 1"}},{"title":"Vérifiez l'état du cluster","detail":"Une fois le cluster créé, vérifiez que tous les slots sont assignés et que les nœuds sont connectés. cluster_state doit valoir 'ok' et les 16384 slots doivent apparaître assignés. Surveillez la répartition des nœuds et des slots avec les commandes CLUSTER.","engines":{"redis":"redis-cli --cluster check 10.0.0.11:6379\nredis-cli -c -h 10.0.0.11 CLUSTER INFO\nredis-cli -c -h 10.0.0.11 CLUSTER NODES"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-elasticsearch-ha","title":"Résilience Elasticsearch : réplica + snapshot + CCR","icon":"🔎","symptom":"Objectif : renforcer la résilience d'Elasticsearch avec des shards réplicas, des nœuds master/data dédiés, des sauvegardes par snapshot (SLM) et la réplication inter-clusters (CCR).","steps":[{"title":"Séparez les rôles : nœuds master et data dédiés","detail":"Pour la stabilité du cluster, les nœuds master-eligible votants (de préférence 3, pour une majorité impaire) sont séparés de la charge data. Attribuez les rôles avec node.roles dans elasticsearch.yml et définissez les seed hosts pour la découverte. 3 masters évitent le split-brain.","engines":{"elasticsearch":"# elasticsearch.yml on master nodes:\n# node.roles: [ master ]\n# on data nodes:\n# node.roles: [ data, ingest ]\n# discovery.seed_hosts: [ m1, m2, m3 ]\n# cluster.initial_master_nodes: [ m1, m2, m3 ]"}},{"title":"Définissez le nombre de réplicas de l'index","detail":"number_of_replicas est le nombre de copies conservées pour chaque shard primaire ; il doit être d'au moins 1 pour que les données restent disponibles en cas de perte d'un nœud data. Il peut être modifié dynamiquement sur un index en service ; le nombre de réplicas doit être inférieur au nombre de nœuds data.","engines":{"elasticsearch":"PUT /orders/_settings\n{\n  \"index\": { \"number_of_replicas\": 1 }\n}\n# default via template (composable template; the old _template API is legacy):\n# PUT _index_template/order-tpl { \"index_patterns\": [\"order*\"], \"template\": { \"settings\": { \"number_of_replicas\": 1 } } }"}},{"title":"Définissez un dépôt de snapshots","detail":"Les sauvegardes sont effectuées dans un dépôt : un système de fichiers partagé (fs) ou les plugins S3/GCS/Azure. Pour pouvoir récupérer les données même si le cluster est supprimé, le dépôt doit se trouver sur un stockage durable externe au cluster.","engines":{"elasticsearch":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": { \"location\": \"/mnt/es_backups\", \"compress\": true }\n}"}},{"title":"Planifiez des snapshots automatiques avec SLM","detail":"Snapshot Lifecycle Management (SLM) prend des snapshots réguliers et applique une politique de rétention. Une expression cron définit la fréquence, les index inclus et le nombre de snapshots conservés.","engines":{"elasticsearch":"PUT /_slm/policy/daily_backup\n{\n  \"schedule\": \"0 30 1 * * ?\",\n  \"name\": \"<daily-{now/d}>\",\n  \"repository\": \"backup_repo\",\n  \"config\": { \"indices\": [\"*\"] },\n  \"retention\": { \"expire_after\": \"30d\", \"min_count\": 5, \"max_count\": 50 }\n}"}},{"title":"Vérifiez le snapshot et la restauration","detail":"Déclenchez manuellement la politique SLM pour vérifier qu'elle fonctionne ; l'état du snapshot doit être SUCCESS. Effectuez régulièrement des tests de restauration, car une sauvegarde non testée n'est pas une sauvegarde.","engines":{"elasticsearch":"POST /_slm/policy/daily_backup/_execute\nGET /_snapshot/backup_repo/_all\n# restore:\nPOST /_snapshot/backup_repo/<snapshot_name>/_restore"}},{"title":"Mettez en place une DR distante avec Cross-Cluster Replication (CCR)","detail":"Pour la reprise après sinistre inter-régions, un second cluster (follower) suit les index du cluster primaire distant (leader). Définissez d'abord le cluster distant, puis rattachez l'index follower au leader ; les modifications du leader sont propagées automatiquement. CCR est une fonctionnalité Platinum (commerciale).","engines":{"elasticsearch":"# define the remote cluster on the follower cluster:\nPUT /_cluster/settings\n{ \"persistent\": { \"cluster\": { \"remote\": { \"primary\": { \"seeds\": [\"leader1:9300\"] } } } } }\n# create the follower index:\nPUT /orders-follower/_ccr/follow\n{ \"remote_cluster\": \"primary\", \"leader_index\": \"orders\" }"}}],"related":["replication-status","list-replicas","last-backup-status"]}]},"playbooks/security.json":{"schemaVersion":1,"playbooks":[{"id":"harden-sql","title":"Durcissement de la sécurité : bases SQL (Oracle, PostgreSQL, MySQL, SQL Server)","icon":"🔒","symptom":"Objectif : durcir les bases relationnelles selon les recommandations CIS : comptes par défaut, politique de mots de passe robuste, moindre privilège, restriction réseau, TLS, audit et chiffrement (TDE).","steps":[{"title":"Supprimez ou verrouillez les comptes par défaut/inutiles, supprimez les schémas d'exemple","detail":"Les comptes par défaut et les schémas d'exemple fournis avec l'installation constituent une surface d'attaque bien connue. Verrouillez ou supprimez ceux qui ne servent pas ; renommez les comptes de haut niveau/de service.","engines":{"oracle":"-- Lock unused default accounts and expire their passwords:\nALTER USER SCOTT ACCOUNT LOCK PASSWORD EXPIRE;\nALTER USER HR ACCOUNT LOCK PASSWORD EXPIRE;\n-- Audit locked/open accounts:\nSELECT username, account_status FROM dba_users ORDER BY account_status;\n-- Remove sample schemas: $ORACLE_HOME/demo/schema/drop_sch.sql","postgres":"-- Keep the default superuser 'postgres' role; do NOT drop/rename it.\n-- Audit roles:\nSELECT rolname, rolsuper, rolcanlogin FROM pg_roles ORDER BY rolsuper DESC;\n-- Remove unneeded login roles:\nDROP ROLE IF EXISTS test_user;\n-- Drop sample/test databases: DROP DATABASE IF EXISTS test;","mysql":"# Interactive hardening wizard (removes anonymous users, the test DB, remote root):\nmysql_secure_installation\n-- Manual: remove anonymous and test accounts:\nDROP USER IF EXISTS ''@'localhost';\nDROP USER IF EXISTS ''@'%';\nDROP DATABASE IF EXISTS test;\nDELETE FROM mysql.db WHERE Db LIKE 'test%'; FLUSH PRIVILEGES;","mssql":"-- Disable and rename the 'sa' account:\nALTER LOGIN sa DISABLE;\nALTER LOGIN sa WITH NAME = [sa_disabled_acct];\n-- Restrict the guest user — only in USER databases\n-- (guest must stay enabled in master, tempdb and msdb; disabling it in tempdb/msdb causes problems):\nUSE appdb;\nREVOKE CONNECT FROM guest;\n-- Remove sample DBs: DROP DATABASE AdventureWorks;"}},{"title":"Imposez une politique de mots de passe robuste et leur expiration","detail":"Les mots de passe faibles ou jamais changés sont le point d'entrée le plus courant. Imposez des limites de tentatives de connexion, une complexité et une durée de vie des mots de passe ; stockez les mots de passe avec un hachage robuste.","engines":{"oracle":"-- Attempt limit, lifetime and complexity via a profile:\nCREATE PROFILE app_profile LIMIT\n  FAILED_LOGIN_ATTEMPTS 5\n  PASSWORD_LIFE_TIME 90\n  PASSWORD_REUSE_MAX 10\n  PASSWORD_VERIFY_FUNCTION ora12c_verify_function;\nALTER USER app_user PROFILE app_profile;","postgres":"-- Strong hash algorithm (SCRAM):\nSHOW password_encryption;              -- should be 'scram-sha-256'\nALTER SYSTEM SET password_encryption = 'scram-sha-256';\n-- Load the passwordcheck extension for complexity:\n-- shared_preload_libraries = 'passwordcheck'\n-- Lifetime: ALTER ROLE app_user VALID UNTIL '2026-12-31';","mysql":"-- Install the validate_password component and set the policy:\nINSTALL COMPONENT 'file://component_validate_password';\nSET PERSIST validate_password.policy = 'STRONG';\nSET PERSIST validate_password.length = 14;\n-- Attempt limit and lifetime:\nALTER USER app_user PASSWORD EXPIRE INTERVAL 90 DAY\n  FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;","mssql":"-- Enforce the Windows password policy and expiration on the login:\nALTER LOGIN app_user WITH CHECK_POLICY = ON, CHECK_EXPIRATION = ON;\n-- Audit policy/expiration status:\nSELECT name, is_policy_checked, is_expiration_checked\nFROM sys.sql_logins;"}},{"title":"Moindre privilège : révoquez les privilèges PUBLIC/étendus, accordez des privilèges par rôle","detail":"Les privilèges étendus accordés à tous (PUBLIC) facilitent les mouvements latéraux. Révoquez-les, créez des rôles et n'accordez que le nécessaire.","engines":{"oracle":"-- Revoke dangerous PUBLIC privileges:\nREVOKE EXECUTE ON UTL_HTTP FROM PUBLIC;\nREVOKE EXECUTE ON UTL_FILE FROM PUBLIC;\nREVOKE EXECUTE ON DBMS_SCHEDULER FROM PUBLIC;\n-- Role-based privileges:\nCREATE ROLE app_read;\nGRANT SELECT ON app.orders TO app_read;\nGRANT app_read TO app_user;","postgres":"-- Revoke the default PUBLIC privileges on the schema:\nREVOKE ALL ON DATABASE appdb FROM PUBLIC;\nREVOKE CREATE ON SCHEMA public FROM PUBLIC;\n-- Role-based, read-only access:\nCREATE ROLE app_read NOLOGIN;\nGRANT USAGE ON SCHEMA app TO app_read;\nGRANT SELECT ON ALL TABLES IN SCHEMA app TO app_read;\nGRANT app_read TO app_user;","mysql":"-- Audit and revoke broad global privileges:\nSHOW GRANTS FOR app_user;\nREVOKE ALL PRIVILEGES, GRANT OPTION FROM app_user;\n-- Role-based, object-level privileges (MySQL 8+):\nCREATE ROLE app_read;\nGRANT SELECT ON appdb.* TO app_read;\nGRANT app_read TO app_user;\nSET DEFAULT ROLE app_read TO app_user;","mssql":"-- Revoke excessive privileges granted to the public role:\nREVOKE SELECT ON SCHEMA::dbo FROM public;\n-- Role-based privileges:\nCREATE ROLE app_read;\nGRANT SELECT ON SCHEMA::app TO app_read;\nALTER ROLE app_read ADD MEMBER app_user;\n-- At server level: limit fixed server roles (sysadmin)."}},{"title":"Restreignez le réseau/l'accès : seuls les clients connus peuvent se connecter","detail":"Évitez que la base écoute ouvertement le monde entier. Restreignez l'adresse d'écoute, appliquez des règles d'accès par hôte/IP et limitez le port avec un pare-feu.","engines":{"oracle":"# listener.ora / sqlnet.ora — valid node checking:\nTCP.VALIDNODE_CHECKING = YES\nTCP.INVITED_NODES = (10.0.0.5, 10.0.0.6)\nTCP.EXCLUDED_NODES = (0.0.0.0/0)\n# After the change: lsnrctl reload","postgres":"# postgresql.conf — listen only on specific interfaces:\nlisten_addresses = '10.0.0.10,localhost'\n# pg_hba.conf — host/IP + strong authentication:\nhostssl appdb app_user 10.0.0.0/24 scram-sha-256\nhost    all      all       0.0.0.0/0      reject\n# Reload: SELECT pg_reload_conf();","mysql":"# my.cnf — bind only to a specific interface:\n[mysqld]\nbind-address = 10.0.0.10\n-- Host-based user (only from the application server):\nCREATE USER 'app_user'@'10.0.0.5'\n  IDENTIFIED BY '****' REQUIRE SSL;\n-- Never give remote root the '%' host.","mssql":"# Limit the TCP port with the firewall (application subnet only):\nNew-NetFirewallRule -DisplayName 'SQL-1433' -Direction Inbound `\n  -Protocol TCP -LocalPort 1433 -RemoteAddress 10.0.0.0/24 -Action Allow\n-- Audit listening addresses:\nSELECT * FROM sys.dm_tcp_listener_states;\n-- Disable the SQL Browser service if not used."}},{"title":"Imposez TLS/les connexions chiffrées","detail":"Le trafic réseau est exposé à l'écoute passive. Configurez un certificat serveur et imposez aux clients une connexion chiffrée.","engines":{"oracle":"# sqlnet.ora — TCPS/TLS (server certificate in the wallet):\nWALLET_LOCATION = (SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=/etc/oracle/wallet)))\nSSL_CLIENT_AUTHENTICATION = FALSE\nSSL_VERSION = 1.2\n# Listen only on TCPS (port 2484) in listener.ora. Note: SQLNET.ENCRYPTION_SERVER is Native Network Encryption, not TLS.","postgres":"# postgresql.conf:\nssl = on\nssl_cert_file = 'server.crt'\nssl_key_file  = 'server.key'\nssl_min_protocol_version = 'TLSv1.2'\n# Use 'hostssl' lines in pg_hba.conf; reject unencrypted connections over plain 'host'.","mysql":"# my.cnf:\n[mysqld]\nrequire_secure_transport = ON\nssl_cert = /etc/mysql/server-cert.pem\nssl_key  = /etc/mysql/server-key.pem\nssl_ca   = /etc/mysql/ca.pem\n-- At user level: ALTER USER app_user REQUIRE SSL;","mssql":"-- Force connection encryption (SQL Server Configuration Manager):\n-- Protocols for MSSQLSERVER > Properties > Force Encryption = Yes\n-- Assign a certificate; then restart the service.\n-- Check the status:\nSELECT session_id, encrypt_option FROM sys.dm_exec_connections;"}},{"title":"Activez l'audit : rendez visible qui a fait quoi","detail":"Une piste d'audit est indispensable pour surveiller les accès privilégiés et les activités suspectes. Journalisez les connexions, les changements de privilèges et les accès aux objets sensibles.","engines":{"oracle":"-- Create and enable a policy with Unified Auditing:\nCREATE AUDIT POLICY sec_pol\n  ACTIONS LOGON, LOGOFF, ALTER USER, GRANT, CREATE USER, DROP USER;\nAUDIT POLICY sec_pol;\n-- View the records:\nSELECT * FROM unified_audit_trail ORDER BY event_timestamp DESC;","postgres":"# With the pgaudit extension:\n# postgresql.conf:\nshared_preload_libraries = 'pgaudit'\npgaudit.log = 'ddl, role, write'\n-- Create the extension:\nCREATE EXTENSION pgaudit;\n-- log_connections = on and log_disconnections = on should also be enabled.","mysql":"-- Load the Enterprise/Percona audit_log plugin:\nINSTALL PLUGIN audit_log SONAME 'audit_log.so';\nSET PERSIST audit_log_policy = 'ALL';\n-- MariaDB: server_audit plugin\n-- INSTALL SONAME 'server_audit'; SET GLOBAL server_audit_logging=ON;","mssql":"-- Create and start a Server Audit:\nCREATE SERVER AUDIT sec_audit\n  TO FILE (FILEPATH = 'C:\\SQLAudit\\');\nALTER SERVER AUDIT sec_audit WITH (STATE = ON);\nCREATE SERVER AUDIT SPECIFICATION sec_spec\n  FOR SERVER AUDIT sec_audit\n  ADD (FAILED_LOGIN_GROUP), ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP)\n  WITH (STATE = ON);"}},{"title":"Appliquez le chiffrement des données (at rest / TDE)","detail":"En cas de vol d'un disque, d'une sauvegarde ou d'un fichier, les données doivent être illisibles. Activez le chiffrement des données au repos (TDE) au niveau colonne/tablespace/base.","engines":{"oracle":"-- Transparent Data Encryption (keystore + tablespace):\nADMINISTER KEY MANAGEMENT CREATE KEYSTORE '/etc/oracle/wallet'\n  IDENTIFIED BY \"WalletPwd1\";\nADMINISTER KEY MANAGEMENT SET KEY IDENTIFIED BY \"WalletPwd1\" WITH BACKUP;\nCREATE TABLESPACE sec_ts DATAFILE '/u01/sec01.dbf' SIZE 100M\n  ENCRYPTION USING 'AES256' DEFAULT STORAGE(ENCRYPT);","postgres":"-- There is no built-in block-level TDE; two common approaches:\n-- 1) Column-level pgcrypto:\nCREATE EXTENSION pgcrypto;\nINSERT INTO t(ssn) VALUES (pgp_sym_encrypt('123-45', 'my_key'));\nSELECT pgp_sym_decrypt(ssn, 'my_key') FROM t;\n-- 2) File system/block encryption (LUKS) or an EDB/Cybertec TDE build.","mysql":"# InnoDB tablespace encryption — keyring COMPONENT (8.0.24+; the keyring_file plugin was removed in 8.4)\n# 1) Global manifest in the mysqld binary directory: mysqld.my\n{ \"components\": \"file://component_keyring_file\" }\n# 2) In plugin_dir: component_keyring_file.cnf\n{ \"path\": \"/var/lib/mysql-keyring/component_keyring_file\", \"read_only\": false }\n# 3) Restart the server; verify:\n-- SELECT * FROM performance_schema.keyring_component_status;\n-- Encrypt the table:\nALTER TABLE app.orders ENCRYPTION = 'Y';\n-- For new tables: SET PERSIST default_table_encryption = ON;\n# Before 8.0.24 (8.0 only): early-plugin-load = keyring_file.so + keyring_file_data\n# WARNING: back up the keyring file SEPARATELY from the data directory and securely;\n# if the keyring is lost, encrypted tables and their backups become permanently unreadable.","mssql":"-- Transparent Data Encryption (DEK + certificate) — the key and certificate must be in master:\nUSE master;\nCREATE MASTER KEY ENCRYPTION BY PASSWORD = '<StrongPassword1>';\nCREATE CERTIFICATE tde_cert WITH SUBJECT = 'TDE Cert';\n-- MANDATORY: back up the certificate with its private key immediately and store it off the server.\n-- Without this backup, if the server is lost the encrypted DB and its backups CANNOT be restored.\nBACKUP CERTIFICATE tde_cert TO FILE = 'D:\\secure\\tde_cert.cer'\n  WITH PRIVATE KEY (FILE = 'D:\\secure\\tde_cert.pvk',\n    ENCRYPTION BY PASSWORD = '<CertBackupPassword1>');\nUSE appdb;\nCREATE DATABASE ENCRYPTION KEY\n  WITH ALGORITHM = AES_256 ENCRYPTION BY SERVER CERTIFICATE tde_cert;\nALTER DATABASE appdb SET ENCRYPTION ON;\n-- Progress: SELECT DB_NAME(database_id), encryption_state, percent_complete FROM sys.dm_database_encryption_keys;"}},{"title":"Maintenez les patchs à jour et désactivez les fonctionnalités inutiles","detail":"Les vulnérabilités connues sont le risque le plus exploité. Appliquez régulièrement les patchs de sécurité ; désactivez les protocoles, plugins et fonctionnalités inutilisés (réduisez la surface d'attaque).","engines":{"oracle":"-- Check the installed version and patches:\nSELECT version_full FROM v$instance;\n-- Apply the quarterly Critical Patch Update (CPU): OPatch/opatchauto\n-- Remove unused components (e.g. XML DB, APEX) and\n-- disable the external procedure (extproc) listener.","postgres":"# Check the version and minor patch level:\nSELECT version();\n# Upgrade to the current minor release with the package manager (e.g. latest 16.x).\n# Remove unused extensions: DROP EXTENSION IF EXISTS ...;\n# postgresql.conf: do not load unused language/PL extensions.","mysql":"-- Check the version: SELECT VERSION();\n-- Upgrade to the LTS/security release (8.0.x/8.4 LTS).\n-- Disable local file reading:\nSET PERSIST local_infile = OFF;\n-- Remove unused plugins: UNINSTALL PLUGIN ...;","mssql":"-- Check the product version/patch level:\nSELECT SERVERPROPERTY('ProductVersion'), SERVERPROPERTY('ProductLevel');\n-- Apply the latest Cumulative Update (CU).\n-- Disable unused features (with sp_configure):\nEXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;\nEXEC sp_configure 'Ole Automation Procedures', 0; RECONFIGURE;"}}],"related":["list-users","create-user-grant","enable-audit","change-password"]},{"id":"harden-nosql","title":"Durcissement de la sécurité : MongoDB / Redis / Elasticsearch","icon":"🛡️","symptom":"Objectif : durcir les magasins NoSQL/de données selon les recommandations CIS : authentification, restriction réseau, blocage des commandes dangereuses, TLS, rôles à moindre privilège et audit.","steps":[{"title":"ACTIVEZ l'authentification : coupez tout accès non authentifié","detail":"Ces produits sont souvent livrés avec l'authentification DÉSACTIVÉE par défaut et sont compromis lorsqu'ils restent exposés sur Internet. Activez l'authentification et créez un utilisateur administrateur.","engines":{"mongodb":"// mongod.conf:\nsecurity:\n  authorization: enabled\n// Create an administrator user (first time, via the localhost exception):\nuse admin\ndb.createUser({ user: \"admin\", pwd: passwordPrompt(),\n  roles: [ { role: \"userAdminAnyDatabase\", db: \"admin\" } ] })\n// Then connect with: mongosh -u admin --authenticationDatabase admin","redis":"# redis.conf — ACL (Redis 6+): disable the default user, define named users:\nuser default off\nuser admin on >AdminPassword789! ~* &* +@all\nuser app on >AppPassword456! ~app:* +@read +@write\n# Note: requirepass is a legacy compatibility setting that only sets the 'default' user's password;\n# do not confuse it with ACL users. You can also keep users in an aclfile (persisted with ACL SAVE).","elasticsearch":"# 8.x: security (authentication + TLS) is configured automatically on first start; elasticsearch.yml:\nxpack.security.enabled: true\n# Set/reset the password of the 'elastic' user:\nbin/elasticsearch-reset-password -u elastic\n# (elasticsearch-setup-passwords is deprecated since 8.0)\n# Create separate users with dedicated roles for Kibana/clients instead of 'elastic'."}},{"title":"Restreignez le réseau : n'ouvrez que les interfaces et ports nécessaires","detail":"Évitez que le service écoute le monde entier sur 0.0.0.0. Fixez l'adresse de liaison sur une interface interne/le loopback et activez le protected mode.","engines":{"mongodb":"# mongod.conf — internal interface + loopback only:\nnet:\n  bindIp: 127.0.0.1,10.0.0.10\n  port: 27017\n# Never use bindIpAll: true; limit the port with a firewall.","redis":"# redis.conf:\nbind 127.0.0.1 10.0.0.10\nprotected-mode yes\nport 6379\n# protected-mode blocks external access when no password is set;\n# still limit 6379 with a firewall.","elasticsearch":"# elasticsearch.yml — pin the listening interface:\nnetwork.host: 10.0.0.10\nhttp.port: 9200\ntransport.port: 9300\n# Open only the required ports in the firewall;\n# 9300 (transport) must never be exposed externally."}},{"title":"Restreignez les commandes dangereuses","detail":"Une seule commande peut effacer toutes les données. Renommez ou supprimez les commandes destructrices/d'administration, ou bloquez le ciblage par joker.","engines":{"mongodb":"// Use role-based control instead of a separate command block:\n// Grant destructive privileges (dropDatabase, dropCollection) only to\n// dbAdmin/clusterAdmin roles; do not grant them to the application user.\ndb.grantRolesToUser(\"app\", [ { role: \"readWrite\", db: \"appdb\" } ])\n// Do NOT grant dbAdmin/root to the application user.","redis":"# For Redis 6+ the preferred way is ACLs (do not give the application user dangerous commands):\n# ACL SETUSER app ... -@dangerous\n# Legacy method — disable/rename commands in redis.conf:\nrename-command FLUSHALL \"\"\nrename-command FLUSHDB \"\"\nrename-command KEYS \"\"\nrename-command DEBUG \"\"\n# Caution: renaming CONFIG can break Sentinel, CONFIG REWRITE and management tools.","elasticsearch":"# elasticsearch.yml — block wildcard/all-index deletion:\naction.destructive_requires_name: true\n# This rejects DELETE /* or DELETE /_all;\n# bulk deletion is not possible without naming the index explicitly."}},{"title":"Activez TLS/les connexions chiffrées","detail":"Les identifiants et les données ne doivent pas circuler en clair sur un réseau ouvert. Activez TLS pour le trafic client et entre nœuds.","engines":{"mongodb":"# mongod.conf — client TLS:\nnet:\n  tls:\n    mode: requireTLS\n    certificateKeyFile: /etc/ssl/mongodb.pem\n    CAFile: /etc/ssl/ca.pem\n# Client: mongosh --tls --tlsCAFile ca.pem","redis":"# redis.conf — enable TLS, close the plain port:\nport 0\ntls-port 6379\ntls-cert-file /etc/redis/redis.crt\ntls-key-file  /etc/redis/redis.key\ntls-ca-cert-file /etc/redis/ca.crt","elasticsearch":"# elasticsearch.yml — HTTP and transport TLS:\nxpack.security.http.ssl.enabled: true\nxpack.security.http.ssl.keystore.path: http.p12\nxpack.security.transport.ssl.enabled: true\nxpack.security.transport.ssl.verification_mode: certificate\nxpack.security.transport.ssl.keystore.path: transport.p12"}},{"title":"Accordez des rôles à moindre privilège : évitez les rôles étendus","detail":"Donnez à chaque utilisateur uniquement l'accès requis par sa fonction. Au lieu de rôles étendus/d'administration, utilisez des rôles fins limités à des bases, des motifs de clés ou des index précis.","engines":{"mongodb":"// Instead of broad readWrite, grant only on the required DB;\n// define a custom role if needed:\ndb.createRole({ role: \"ordersRW\",\n  privileges: [ { resource: { db: \"appdb\", collection: \"orders\" },\n    actions: [ \"find\", \"insert\", \"update\" ] } ], roles: [] })\ndb.grantRolesToUser(\"app\", [ { role: \"ordersRW\", db: \"appdb\" } ])","redis":"# Limit by key pattern and command category with ACLs:\nACL SETUSER app on >AppPassword456! ~app:* +@read +@write -@dangerous\n# Only keys with the 'app:' prefix, read/write; dangerous commands forbidden.\nACL LIST\nACL WHOAMI","elasticsearch":"# Define a fine-grained role (read-only on specific indices):\nPOST /_security/role/orders_read\n{ \"indices\": [ { \"names\": [ \"orders-*\" ],\n    \"privileges\": [ \"read\" ] } ] }\n# Assign it to the user:\nPOST /_security/user/app\n{ \"password\": \"...\", \"roles\": [ \"orders_read\" ] }"}},{"title":"Activez l'audit/la journalisation : surveillez les accès et les modifications","detail":"Les tentatives d'accès non autorisées et les opérations d'administration doivent être enregistrées. Activez le journal d'audit et envoyez-le vers un emplacement central.","engines":{"mongodb":"# Auditing (requires MongoDB Enterprise) — mongod.conf:\nauditLog:\n  destination: file\n  format: JSON\n  path: /var/log/mongodb/audit.json\n# Filter: capture events such as authenticate, createUser, dropDatabase.","redis":"# Redis has no full audit; use ACL LOG to see rejected\n# authentication/command attempts:\nACL LOG\nACL LOG RESET\n# Also: route the loglevel notice and logfile settings to a central collector.","elasticsearch":"# elasticsearch.yml — enable security auditing:\nxpack.security.audit.enabled: true\nxpack.security.audit.logfile.events.include:\n  [ authentication_failed, access_denied, connection_denied ]\n# Records are written to the <cluster>_audit.json file."}}],"related":["list-users","create-user-grant","enable-audit","change-password"]}]},"playbooks/cloud.json":{"schemaVersion":1,"playbooks":[{"id":"cloud-managed-db","title":"Bases managées dans le cloud (RDS/Aurora/Cloud SQL/Atlas/Azure SQL)","icon":"☁️","symptom":"Objectif : connaître les principales différences entre les bases managées dans le cloud et les installations autogérées ; comprendre le fonctionnement de la sauvegarde, des paramètres, du failover, de la surveillance, des montées de version et de la connectivité dans le cloud.","steps":[{"title":"Changement d'état d'esprit : vous accédez à un service, pas à un serveur","detail":"Avec les bases managées, vous n'avez AUCUN accès shell à l'OS, au système de fichiers ou au processus de la base ; vous ne pouvez pas vous connecter en SSH, modifier les fichiers de configuration à la main ni devenir superuser. Vous disposez uniquement de l'API/la console d'administration du fournisseur et d'une connexion SQL normale. AWS RDS/Aurora : l'administration se fait au niveau de la ressource (instance/cluster), le serveur est masqué. GCP Cloud SQL : API d'instance uniquement, pas d'OS. MongoDB Atlas : cluster entièrement managé, aucun accès à mongod. Azure SQL Database : PaaS, ni le serveur ni l'OS ne sont visibles (Managed Instance offre un peu plus de contrôle). La plupart des réflexes de l'autogéré, comme « regarder le fichier », « tuer le processus » ou « lancer un outil OS », ne s'appliquent donc pas."},{"title":"Sauvegarde : snapshots automatiques + restauration à un instant donné (PITR)","detail":"Vous ne planifiez pas les sauvegardes ; le fournisseur les effectue automatiquement. La récupération consiste généralement à « restaurer à un instant précis » (PITR) ou à créer une nouvelle instance à partir d'un snapshot ; l'instance existante n'est pas écrasée. AWS RDS/Aurora : sauvegardes automatiques + fenêtre de rétention ; une restauration PITR à la seconde près s'ouvre comme une nouvelle instance/un nouveau cluster ; des snapshots manuels sont aussi possibles. GCP Cloud SQL : sauvegardes automatiques + PITR via les binary logs ; la restauration se fait vers une nouvelle instance. MongoDB Atlas : sauvegarde cloud continue + PITR (M10+) ; restauration depuis des snapshots. Azure SQL : sauvegardes automatiques intégrées, rétention de 1 à 35 jours, PITR à n'importe quel instant et restauration des bases supprimées. Règle commune : vérifiez la configuration des sauvegardes (fenêtre, durée de rétention) et effectuez des tests de restauration réguliers."},{"title":"Paramètres : parameter groups / flags, pas de fichier de configuration","detail":"Au lieu de modifier postgresql.conf ou my.cnf à la main, vous utilisez l'abstraction de paramètres du fournisseur ; certains paramètres sont verrouillés et certains exigent un redémarrage (statiques). AWS RDS/Aurora : définis via un DB parameter group (et un cluster parameter group) ; les paramètres dynamiques s'appliquent immédiatement, les statiques au reboot. GCP Cloud SQL : les « database flags » se définissent depuis la console/gcloud. MongoDB Atlas : « Additional Settings » limités + taille de l'oplog etc. ; la plupart des paramètres internes sont managés. Azure SQL : la plupart des réglages au niveau serveur sont managés ; le comportement se modifie via SCOPED CONFIGURATION et le choix du niveau de service. Avant d'appliquer, vérifiez si le paramètre est dynamique ou statique."},{"title":"Haute disponibilité / failover : Multi-AZ et réplicas managés","detail":"Vous ne construisez pas le failover vous-même ; vous cochez une case et laissez faire le fournisseur. Le standby et le failover automatique sont managés, et les connexions sont redirigées vers le nouveau primaire via un endpoint stable. AWS RDS : Multi-AZ (standby synchrone, failover automatique) ; Aurora : stockage multi-copies + failover automatique, le cluster endpoint pointe vers le writer. GCP Cloud SQL : configuration HA (régionale, standby dans une zone secondaire) en un clic ; les réplicas en lecture s'ajoutent séparément. MongoDB Atlas : replica set intégré, déploiement multirégion/multicloud disponible, failover automatique. Azure SQL : HA intégrée (réplicas locaux en Business Critical), option zone-redundant et active geo-replication / failover groups inter-régions. L'application doit toujours se connecter à l'endpoint, jamais à une IP fixe."},{"title":"Surveillance : le service de métriques et de logs du fournisseur","detail":"Comme vous ne pouvez pas exécuter d'outils OS (top, iostat), vous surveillez via la plateforme de métriques du fournisseur ; les métriques CPU, nombre de connexions, IOPS, retard de réplication et stockage s'y trouvent. AWS : métriques CloudWatch + Enhanced Monitoring + Performance Insights (analyse au niveau requête). GCP Cloud SQL : Cloud Monitoring (anciennement Stackdriver) + Query Insights. MongoDB Atlas : métriques/graphiques Atlas intégrés + Performance Advisor + panneau temps réel. Azure SQL : Azure Monitor + SQL Analytics + Query Performance Insight. Configurez toujours des alarmes de seuil (utilisation du stockage, CPU, limite de connexions, retard des réplicas)."},{"title":"Montées de version et maintenance : depuis la console, dans une fenêtre de maintenance","detail":"Au lieu de mettre à jour les binaires à la main, vous choisissez une version cible ; les patchs de version mineure sont le plus souvent automatiques, tandis que les montées de version majeure sont planifiées et impliquent généralement une interruption/un failover HA. AWS RDS/Aurora : choix de la version du moteur + option de montée mineure automatique + montées majeures à faible risque avec Blue/Green Deployments. GCP Cloud SQL : fenêtre de maintenance + montée de version majeure en libre-service. MongoDB Atlas : montée de version en un clic, interruption quasi nulle en mode rolling. Azure SQL : patché automatiquement par la plateforme (la notion de version est largement abstraite) ; le comportement se contrôle avec le niveau de compatibilité. Essayez toujours d'abord sur une copie de test/préproduction et planifiez la fenêtre de maintenance hors des heures ouvrées."},{"title":"Connectivité et sécurité : endpoint + IAM/TLS","detail":"Les connexions passent toujours par l'endpoint DNS fourni par le fournisseur et sont chiffrées (TLS) ; l'accès réseau et l'identité peuvent être liés au système d'identité du cloud. AWS RDS/Aurora : endpoint d'instance/de cluster + Security Group pour le réseau + authentification IAM à la base en option + TLS (certificat rds-ca). GCP Cloud SQL : IP publique + Authorized Networks ou IP privée/VPC, Cloud SQL Auth Proxy, authentification IAM à la base, TLS. MongoDB Atlas : chaîne de connexion SRV (mongodb+srv://), IP access list + private endpoint/peering, SCRAM ou IAM/X.509, TLS obligatoire. Azure SQL : endpoint FQDN du serveur + pare-feu/Private Link + authentification Microsoft Entra ID (anciennement Azure AD) + TLS obligatoire. Règle : utilisez l'endpoint, et non une IP fixe, dans la chaîne de connexion, imposez TLS et connectez-vous avec une identité IAM du cloud lorsque c'est possible."}],"related":["last-backup-status","promote-failover","connect-cli"]}]},"playbooks/automation.json":{"schemaVersion":1,"playbooks":[{"id":"auto-ansible","title":"Automatisation des bases de données avec Ansible","icon":"🤖","symptom":"Objectif : rendre les tâches d'installation, de configuration, d'utilisateurs et de sauvegarde des bases reproductibles et idempotentes (relancer le même playbook ne casse pas le système) avec Ansible.","steps":[{"title":"Définissez l'inventaire et les groupes","detail":"Regroupez les serveurs dans l'inventaire par rôle de base ; placez les serveurs primaires, réplicas et de sauvegarde dans des groupes distincts. Utilisez un inventaire statique INI/YAML ou un inventaire dynamique pour le cloud (aws_ec2, gcp_compute). Placez les variables de groupe sous group_vars/ et celles propres à un hôte sous host_vars/.","engines":{"postgres":"[pg_primary]\ndb1.example.local\n\n[pg_replica]\ndb2.example.local\n\n[postgres:children]\npg_primary\npg_replica"}},{"title":"Installez la bonne collection (community.postgresql / community.mysql / community.mongodb)","detail":"Ansible core fournit désormais les modules de base de données dans des collections séparées ; installez la collection de votre moteur via requirements.yml. Utilisez community.postgresql (postgresql_db, postgresql_user, postgresql_privs) pour PostgreSQL, community.mysql (mysql_db, mysql_user) pour MySQL/MariaDB et community.mongodb pour MongoDB. Le client Python correspondant (psycopg2, PyMySQL, pymongo) doit être installé sur l'hôte cible.","engines":{"postgres":"# requirements.yml\ncollections:\n  - name: community.postgresql\n\n# install:\nansible-galaxy collection install -r requirements.yml","mysql":"collections:\n  - name: community.mysql","mongodb":"collections:\n  - name: community.mongodb"}},{"title":"Chiffrez les secrets avec Ansible Vault","detail":"Ne laissez pas en clair les mots de passe des bases, les secrets de réplication et les identifiants superuser ; conservez-les dans un fichier de variables chiffré avec ansible-vault. Déchiffrez à l'exécution avec --ask-vault-pass ou --vault-password-file. Les secrets peuvent alors être stockés en toute sécurité dans Git et le playbook reste idempotent.","engines":{"postgres":"ansible-vault create group_vars/postgres/vault.yml\n# contents:\nvault_pg_admin_password: \"S3cret!\"\n\n# run:\nansible-playbook site.yml --ask-vault-pass"}},{"title":"Mettez en place la structure de rôles (roles/postgres, roles/mysql)","detail":"Créez un rôle par moteur et répartissez la logique dans les sous-dossiers tasks/, defaults/, templates/ et handlers/. Cette structure améliore la réutilisabilité : defaults/main.yml pour les valeurs par défaut, templates/ pour les modèles de configuration jinja2, handlers/ pour la logique « redémarrer uniquement si quelque chose a changé ». Vous pouvez générer le squelette du rôle avec ansible-galaxy init roles/postgres.","engines":{"postgres":"ansible-galaxy init roles/postgres\n# roles/postgres/\n#   defaults/main.yml\n#   tasks/main.yml\n#   templates/postgresql.conf.j2\n#   handlers/main.yml"}},{"title":"Écrivez les tâches d'installation et de configuration de manière idempotente","detail":"Installez le paquet, initialisez le répertoire de données et générez postgresql.conf/my.cnf à partir d'un modèle ; redémarrez le service via un handler lorsque la configuration change. Les modules sont idempotents : un paquet déjà installé n'est pas réinstallé et le handler n'est pas déclenché si le fichier n'a pas changé. Générez le modèle de configuration avec le module template et reliez-le au handler avec 'notify'.","engines":{"postgres":"- name: postgresql.conf template\n  ansible.builtin.template:\n    src: postgresql.conf.j2\n    dest: /etc/postgresql/16/main/postgresql.conf\n  notify: restart postgresql\n\n# handlers/main.yml\n- name: restart postgresql\n  ansible.builtin.service:\n    name: postgresql\n    state: restarted","mysql":"- name: my.cnf template\n  ansible.builtin.template:\n    src: my.cnf.j2\n    dest: /etc/mysql/my.cnf\n  notify: restart mysql"}},{"title":"Définissez les tâches de base et d'utilisateurs","detail":"Créez la base, les utilisateurs et les privilèges avec des modules ; prenez le mot de passe dans une variable Vault. Les modules community.postgresql sont idempotents : si l'utilisateur/la base existe, ils ne le recréent pas et n'appliquent que la différence. Accordez les privilèges séparément avec postgresql_privs ; dans MySQL, on utilise le champ priv de mysql_user.","engines":{"postgres":"- name: application database\n  community.postgresql.postgresql_db:\n    name: appdb\n    state: present\n\n- name: application user\n  community.postgresql.postgresql_user:\n    name: app_user\n    password: \"{{ vault_app_password }}\"\n    db: appdb\n    state: present","mysql":"- name: application user\n  community.mysql.mysql_user:\n    name: app_user\n    password: \"{{ vault_app_password }}\"\n    priv: 'appdb.*:ALL'\n    state: present"}},{"title":"Ajoutez la tâche de sauvegarde et sa planification","detail":"Déployez le script de sauvegarde à partir d'un modèle et planifiez-le avec ansible.builtin.cron ; le job de sauvegarde est alors lui aussi géré comme du code. Faites du répertoire cible, de la durée de rétention et de la planification des variables. Le module cron est idempotent et identifie l'entrée par 'name', une relance ne la duplique donc pas.","engines":{"postgres":"- name: nightly backup (cron)\n  ansible.builtin.cron:\n    name: \"pg dump nightly\"\n    minute: \"0\"\n    hour: \"2\"\n    job: \"pg_dump -Fc appdb > /backup/appdb_$(date +\\\\%F).dump\""}}],"related":["schedule-job","check-version","logical-dump"]},{"id":"auto-terraform","title":"Provisionnement des bases avec Terraform (IaC)","icon":"🏗️","symptom":"Objectif : définir l'infrastructure des bases (instance, parameter group, réseau) sous forme de code (Infrastructure as Code) avec Terraform ; mettre en place le flux plan/apply, l'état distant et la gestion de la dérive.","steps":[{"title":"Choisissez et configurez le provider (aws / google / azurerm)","detail":"Définissez le provider du cloud que vous utilisez et figez sa version. Verrouillez le provider hashicorp/aws pour AWS, hashicorp/google pour GCP et hashicorp/azurerm pour Azure dans le bloc required_providers. Figer la version garantit un comportement cohérent dans l'équipe.","engines":{"postgres":"terraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\"\n      version = \"~> 5.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"eu-central-1\"\n}"}},{"title":"Mettez en place l'état distant et le verrouillage","detail":"Ne conservez pas le fichier d'état en local ; utilisez un backend distant pour le travail en équipe et la sécurité. Les choix courants sont le backend S3 + une table de verrouillage DynamoDB sur AWS, le backend gcs sur GCP et le backend azurerm (Storage Account) sur Azure. L'état distant empêche les apply concurrents grâce au verrouillage et fait de l'état la source unique de vérité. Comme des mots de passe peuvent figurer en clair dans l'état, gardez le backend chiffré et à accès restreint.","engines":{"postgres":"terraform {\n  backend \"s3\" {\n    bucket         = \"company-tfstate\"\n    key            = \"db/terraform.tfstate\"\n    region         = \"eu-central-1\"\n    dynamodb_table = \"tf-lock\"\n    encrypt        = true\n  }\n}"}},{"title":"Définissez la ressource de base de données","detail":"Écrivez la ressource principale pour votre moteur et votre cloud : aws_db_instance sur AWS (ou aws_rds_cluster pour Aurora), google_sql_database_instance sur GCP, azurerm_mssql_database sur Azure (pour SQL Server, sous un azurerm_mssql_server). Indiquez-y le moteur, la version, la classe, le stockage et la fenêtre de sauvegarde.","engines":{"postgres":"resource \"aws_db_instance\" \"pg\" {\n  identifier        = \"app-db\"\n  engine            = \"postgres\"\n  engine_version    = \"16\"\n  instance_class    = \"db.t3.medium\"\n  allocated_storage = 50\n  db_name           = \"appdb\"\n  username          = \"pgadmin\"\n  password          = var.db_password\n  backup_retention_period = 7\n}","mssql":"resource \"azurerm_mssql_database\" \"db\" {\n  name      = \"appdb\"\n  server_id = azurerm_mssql_server.srv.id\n  sku_name  = \"S1\"\n}"}},{"title":"Écrivez le parameter group sous forme de code","detail":"Définissez les paramètres de la base (max_connections, work_mem, jeu de caractères) avec une ressource de paramètres, et non manuellement dans la console. Utilisez aws_db_parameter_group sur AWS, settings.database_flags dans google_sql_database_instance sur GCP, et la ressource de configuration serveur sur Azure. Les paramètres sont alors versionnés et reproductibles.","engines":{"postgres":"resource \"aws_db_parameter_group\" \"pg\" {\n  name   = \"app-pg16\"\n  family = \"postgres16\"\n\n  parameter {\n    name  = \"max_connections\"\n    value = \"200\"\n  }\n}\n# inside aws_db_instance:\n# parameter_group_name = aws_db_parameter_group.pg.name"}},{"title":"Transmettez les secrets via des variables et un gestionnaire de secrets","detail":"N'écrivez pas le mot de passe en dur dans un fichier .tf ; définissez-le comme variable sensible (variable ... sensitive = true) et fournissez la valeur via une variable d'environnement TF_VAR_, un gestionnaire de secrets (AWS Secrets Manager, Vault) ou un secret CI. Attention : la valeur est écrite dans le fichier d'état, l'état distant doit donc être chiffré. L'approche la plus propre consiste à ne jamais transmettre le mot de passe à Terraform et à le lire depuis une ressource du gestionnaire de secrets.","engines":{"postgres":"variable \"db_password\" {\n  type      = string\n  sensitive = true\n}\n\n# CI/shell:\nexport TF_VAR_db_password=\"$(vault kv get -field=pw secret/db)\""}},{"title":"Exécutez le flux plan et apply","detail":"Préparez d'abord le provider/le backend avec terraform init, voyez ce qui va changer avec terraform plan, approuvez, puis appliquez avec terraform apply. N'appliquez pas sans avoir examiné la sortie du plan ; en CI, le flux sûr consiste à publier le 'plan' en commentaire de PR et à appliquer après approbation humaine. Repérez les changements destructeurs (par ex. recréation d'instance) grâce au marqueur -/+ dans la sortie du plan.","engines":{"postgres":"terraform init\nterraform plan -out=plan.tfplan\nterraform apply plan.tfplan"}},{"title":"Détectez et corrigez la dérive","detail":"Si quelqu'un fait une modification manuelle dans la console, l'infrastructure réelle et l'état divergent (drift). Les différences qui apparaissent dans terraform plan « alors que rien n'aurait dû changer » révèlent la dérive ; la différence se lit avec terraform refresh/plan. Soit vous reportez la modification dans le code, soit vous ramenez l'infrastructure au code avec apply. Interdisez par principe les modifications manuelles ; le code doit être la source unique de vérité.","engines":{"postgres":"# drift check (when no changes are expected):\nterraform plan\n# expect 'No changes'; any diff means drift\n# bring an existing resource into state with import:\nterraform import aws_db_instance.pg app-db"}}],"related":["check-version","apply-patch","schedule-job"]},{"id":"auto-k8s-operator","title":"Bases de données sur Kubernetes (operator)","icon":"☸️","symptom":"Objectif : exécuter la base sur Kubernetes avec un operator ; au lieu d'un simple StatefulSet, utiliser les fonctionnalités de failover automatique, de stockage persistant et de sauvegarde fournies par l'operator.","steps":[{"title":"Pourquoi un operator : un StatefulSet seul ne suffit pas","detail":"Un simple StatefulSet fournit des pods à identité stable et des PVC, mais pas la logique de failover, de mise en place de la réplication, de sauvegarde et de montée de version. Un operator empaquette ce savoir opérationnel propre à la base sous forme de contrôleur + ressource personnalisée (CRD). Vous écrivez une CR appelée 'Cluster', et l'operator gère en coulisses le StatefulSet, le Service, le Secret et les jobs de sauvegarde. En production, l'exécution des bases avec un operator est généralement privilégiée.","engines":{"postgres":"# operators for PostgreSQL:\n#  - CloudNativePG (CNPG)\n#  - Zalando postgres-operator\n#  - Crunchy PGO"}},{"title":"Choisissez et installez l'operator","detail":"Choisissez un operator pour votre moteur : CloudNativePG ou Zalando postgres-operator pour PostgreSQL ; les operators Percona (Percona Operator for MySQL / for MongoDB) sont courants pour MySQL et MongoDB. Installez l'operator avec Helm ou un manifeste bundle ; cette étape ajoute les CRD et le pod contrôleur au cluster.","engines":{"postgres":"# CloudNativePG installation\nkubectl apply --server-side -f \\\n  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.24.0.yaml","mysql":"# Percona Operator for MySQL (Helm)\nhelm install my-op percona/pxc-operator","mongodb":"# Percona Operator for MongoDB (Helm)\nhelm install psmdb-op percona/psmdb-operator"}},{"title":"Planifiez le stockage (StorageClass / PVC)","detail":"L'état de la base réside dans les PVC ; indiquez dans la CR la StorageClass à utiliser. Choisissez une StorageClass offrant un stockage bloc à faible latence, idéalement de type SSD local ; les systèmes de fichiers réseau (NFS) sont déconseillés pour la plupart des bases. Définissez correctement dès le départ la taille des PVC et leur extensibilité (allowVolumeExpansion), car la réduction est difficile.","engines":{"postgres":"spec:\n  storage:\n    storageClass: fast-ssd\n    size: 50Gi"}},{"title":"Appliquez la CR Cluster (réplicas et failover automatique)","detail":"Indiquez dans la CR le nombre d'instances/réplicas ; l'operator met en place la réplication synchrone/asynchrone et, en cas de défaillance du primaire, effectue un failover automatique en promouvant un réplica. L'application doit se connecter au Service '-rw' (lecture-écriture) créé par l'operator ; ce service pointe toujours vers le primaire courant. La cible de connexion ne change donc pas lors d'un failover.","engines":{"postgres":"apiVersion: postgresql.cnpg.io/v1\nkind: Cluster\nmetadata:\n  name: app-db\nspec:\n  instances: 3\n  storage:\n    storageClass: fast-ssd\n    size: 50Gi\n# the application connects to: app-db-rw"}},{"title":"Écrivez les sauvegardes vers S3 via la CR","detail":"Les operators gèrent aussi les sauvegardes de manière déclarative : vous définissez dans la CR la destination principale des sauvegardes (stockage objet/S3) et l'archivage du WAL. Dans CloudNativePG, les informations S3 sont fournies avec spec.backup.barmanObjectStore (obsolète depuis 1.26 ; pour les nouveaux déploiements, utilisez le Barman Cloud Plugin et sa ressource ObjectStore) ; l'archivage continu du WAL + les sauvegardes de base permettent la restauration à un instant donné. Référencez les identifiants depuis un Secret Kubernetes.","engines":{"postgres":"spec:\n  backup:\n    barmanObjectStore:\n      destinationPath: s3://company-backup/app-db\n      s3Credentials:\n        accessKeyId:\n          name: s3-creds\n          key: ACCESS_KEY_ID\n        secretAccessKey:\n          name: s3-creds\n          key: SECRET_ACCESS_KEY"}},{"title":"CR de sauvegarde planifiée et de restauration","detail":"Créez une CR Backup pour une sauvegarde ponctuelle et une CR ScheduledBackup pour les sauvegardes planifiées ; prenez des sauvegardes de base régulières avec une expression cron. Les restaurations sont aussi déclaratives : vous initialisez un nouveau Cluster à partir d'une sauvegarde existante du stockage objet avec 'bootstrap.recovery'. Faites régulièrement des exercices de restauration ; prendre des sauvegardes ne suffit pas, testez la récupération.","engines":{"postgres":"apiVersion: postgresql.cnpg.io/v1\nkind: ScheduledBackup\nmetadata:\n  name: nightly-backup\nspec:\n  schedule: \"0 0 2 * * *\"\n  cluster:\n    name: app-db"}}],"related":["setup-cdc-replication","logical-dump","restore-dump"]},{"id":"auto-schema-migration","title":"CI/CD des migrations de schéma (Flyway/Liquibase)","icon":"🧬","symptom":"Objectif : gérer les modifications de schéma avec des fichiers de migration versionnés ; les appliquer automatiquement mais de manière contrôlée dans la CI/CD avec Flyway ou Liquibase.","steps":[{"title":"Créez des fichiers de migration versionnés (Vxxx__name.sql)","detail":"Écrivez chaque modification de schéma dans un fichier nommé avec un numéro de version croissant ; la convention Flyway est V<version>__<description>.sql (par ex. V1__initial_schemas.sql, V2__orders_table.sql). Ces fichiers sont conservés dans Git et appliqués dans l'ordre des versions. Les migrations appliquées sont enregistrées dans une table de suivi (flyway_schema_history dans Flyway), de sorte que chaque migration s'exécute exactement une fois.","engines":{"postgres":"-- V2__orders_table.sql\nCREATE TABLE orders (\n  id       bigserial PRIMARY KEY,\n  amount    numeric(12,2) NOT NULL,\n  created_at   timestamptz DEFAULT now()\n);"}},{"title":"Préservez l'immuabilité grâce aux checksums","detail":"Flyway/Liquibase stockent le checksum de chaque migration appliquée. Si vous modifiez ensuite un fichier déjà appliqué, le checksum ne correspond plus et l'outil échoue. Règle : ne modifiez JAMAIS une migration appliquée ; faites la correction dans un nouveau fichier de version. Cela garantit que tous les environnements (dev/staging/prod) partagent le même historique.","engines":{"postgres":"# repair on checksum mismatch (use with care):\nflyway repair\n# the right way: add the change in a new Vx file"}},{"title":"Établissez une baseline sur une base existante","detail":"Utilisez baseline lorsque vous rattachez l'outil de migration à une base déjà alimentée sans historique ; il considère l'état actuel comme la « version de départ » et ne tente pas de recréer les objets existants. Les migrations postérieures à baselineVersion sont appliquées. Dans un environnement construit de zéro, aucune baseline n'est nécessaire ; les migrations s'exécutent à partir de V1.","engines":{"postgres":"flyway baseline -baselineVersion=1 -baselineDescription=\"existing schema\""}},{"title":"Migrez automatiquement dans le pipeline CI","detail":"Exécutez l'outil de migration dans une étape CI pour appliquer automatiquement les modifications aux bases de test et de préproduction ; la commande migrate n'exécute que les nouveaux fichiers (non appliqués) et est idempotente. Appliquer d'abord la migration à une base temporaire/éphémère et la valider permet de détecter le SQL défectueux avant qu'il n'atteigne la prod. Le schéma doit être migré AVANT le déploiement du code applicatif (avec des modifications rétrocompatibles).","engines":{"postgres":"# CI step (Flyway)\nflyway -url=jdbc:postgresql://staging/appdb \\\n  -user=ci -password=$PW migrate","mysql":"flyway -url=jdbc:mysql://staging/appdb \\\n  -user=ci -password=$PW migrate"}},{"title":"Ajoutez une validation manuelle pour la prod","detail":"Le test/la préproduction sont automatiques, mais faites en sorte que le migrate de prod exige une approbation manuelle ; ajoutez une étape d'« approval gate » (approbation manuelle) dans la CI/CD. Avant l'approbation, affichez les migrations qui seront exécutées avec 'flyway info' ou 'liquibase status'. Prenez toujours une sauvegarde récente avant d'appliquer en prod afin de pouvoir revenir en arrière en cas de problème.","engines":{"postgres":"# what will run before approval?\nflyway info\n# liquibase equivalent:\nliquibase status --verbose"}},{"title":"Définissez la réversibilité et une stratégie de retour arrière","detail":"Flyway Community n'offre pas d'undo automatique ; adoptez le principe de migration forward-only : au lieu d'annuler une erreur, écrivez une nouvelle migration qui la corrige. Les changesets Liquibase peuvent définir un bloc rollback et être annulés avec 'liquibase rollback'. La stratégie la plus robuste : gardez les modifications petites et rétrocompatibles, faites une sauvegarde avant les opérations risquées (suppression de colonnes) et préparez un plan de retour arrière écrit.","engines":{"postgres":"-- Liquibase rollback example (inside a changeset)\n--changeset dev:2\nCREATE TABLE orders (...);\n--rollback DROP TABLE orders;"}}],"related":["apply-patch","logical-dump","check-version"]},{"id":"auto-backup","title":"Sauvegardes automatisées (pgBackRest/Barman/Xtrabackup/cron)","icon":"💾","symptom":"Objectif : automatiser les sauvegardes des bases ; construire une chaîne de récupération fiable avec complètes+incrémentales+archivage WAL/binlog planifiés, rétention, vérification et copie hors site.","steps":[{"title":"Choisissez le bon outil de sauvegarde","detail":"Choisissez un outil pour votre moteur : pgBackRest ou Barman pour PostgreSQL (sauvegarde physique + archive WAL + PITR) ; Percona XtraBackup (sauvegarde physique en ligne) pour MySQL/MariaDB, ou mysqldump/mydumper (logique) pour les petits volumes. Les outils physiques offrent des sauvegardes rapides et cohérentes + la restauration à un instant donné sur les grandes bases ; les dumps logiques sont portables mais lents sur de gros volumes.","engines":{"postgres":"# pgBackRest or Barman","mysql":"# physical: xtrabackup\n# logical: mysqldump / mydumper"}},{"title":"Construisez la chaîne complète + incrémentale + archivage continu","detail":"Mettez en place la restauration à un instant donné avec des sauvegardes complètes hebdomadaires/quotidiennes, des sauvegardes incrémentales/différentielles entre les deux et un archivage continu du WAL (PostgreSQL) ou du binlog (MySQL). pgBackRest gère les types de sauvegarde full/diff/incrémentale et l'archive WAL ; dans PostgreSQL, vous faites pointer archive_command vers pgBackRest. Les sauvegardes incrémentales + l'archive permettent de revenir à n'importe quel instant.","engines":{"postgres":"# postgresql.conf\narchive_mode = on\narchive_command = 'pgbackrest --stanza=appdb archive-push %p'\n\n# full backup\npgbackrest --stanza=appdb --type=full backup\n# incremental\npgbackrest --stanza=appdb --type=incr backup","mysql":"# full\nxtrabackup --backup --target-dir=/backup/full\n# incremental\nxtrabackup --backup --target-dir=/backup/incr1 \\\n  --incremental-basedir=/backup/full"}},{"title":"Planifiez les sauvegardes avec cron/un timer","detail":"Exécutez régulièrement les commandes de sauvegarde avec cron ou un timer systemd : par exemple une sauvegarde complète le dimanche soir et des incrémentales les autres nuits. Planifiez hors des heures ouvrées et assurez-vous que les jobs full et incr ne se chevauchent pas. Écrivez les logs de sortie quelque part et déclenchez une alerte en cas d'échec.","engines":{"postgres":"# crontab\n0 1 * * 0 pgbackrest --stanza=appdb --type=full backup\n0 1 * * 1-6 pgbackrest --stanza=appdb --type=incr backup"}},{"title":"Définissez une politique de rétention","detail":"N'accumulez pas les sauvegardes indéfiniment ; fixez le nombre de sauvegardes complètes et de jours d'archives à conserver, et laissez l'outil faire expirer automatiquement les anciennes. Dans pgBackRest, cela se gère avec repo1-retention-full et les paramètres de rétention des archives ; dans Barman, avec retention_policy. La rétention doit correspondre à vos objectifs de récupération (jusqu'à combien de jours en arrière vous devez pouvoir revenir).","engines":{"postgres":"# pgbackrest.conf\n[global]\nrepo1-retention-full=4\nrepo1-retention-archive=8","mysql":"# purge old backups with cron (example)\nfind /backup -type d -mtime +14 -exec rm -rf {} +"}},{"title":"Copiez les sauvegardes hors site / vers S3","detail":"Les sauvegardes ne doivent pas rester sur la même machine/région que la base ; copiez-les vers un autre emplacement ou un stockage objet (S3, GCS). pgBackRest peut écrire directement sur un stockage compatible S3 ; sinon, synchronisez vers une cible hors site avec rsync/rclone. En cas de sinistre (perte du disque local/de la région), seule la copie hors site vous sauvera.","engines":{"postgres":"# pgbackrest.conf S3 repository\n[global]\nrepo1-type=s3\nrepo1-s3-bucket=company-backup\nrepo1-s3-region=eu-central-1\nrepo1-s3-endpoint=s3.eu-central-1.amazonaws.com"}},{"title":"Vérifiez la sauvegarde","detail":"L'existence d'une sauvegarde ne signifie pas qu'elle peut être restaurée ; vérifiez régulièrement son intégrité. Dans pgBackRest, la commande 'verify' et les contrôles de cohérence de l'archive, et dans XtraBackup une étape prepare (--prepare) réussie, montrent que la sauvegarde est saine. Exécutez la vérification automatiquement après chaque sauvegarde et déclenchez une alarme en cas d'échec.","engines":{"postgres":"pgbackrest --stanza=appdb verify","mysql":"# make the backup consistent (verification)\nxtrabackup --prepare --target-dir=/backup/full"}},{"title":"Faites des exercices de restauration réguliers","detail":"L'étape la plus critique : restaurez périodiquement et RÉELLEMENT la sauvegarde sur un serveur séparé et vérifiez que la base s'ouvre et que les données sont cohérentes. Cet exercice vous permet de mesurer votre temps de récupération (RTO) et de repérer les lacunes de la procédure en dehors d'un incident. Une sauvegarde non testée n'est pas une sauvegarde.","engines":{"postgres":"# restore + PITR on a separate server\npgbackrest --stanza=appdb \\\n  --type=time --target=\"2026-07-01 03:00:00\" restore","mysql":"# copy the prepared backup back to the data directory\nxtrabackup --copy-back --target-dir=/backup/full"}}],"related":["logical-dump","restore-dump","schedule-job"]},{"id":"auto-gitops-bluegreen","title":"GitOps et déploiement blue-green / sans interruption","icon":"🟦","symptom":"Objectif : déployer les modifications de schéma et d'application sans interruption ; approcher le zéro downtime avec des migrations expand-contract, des environnements blue-green, des feature flags et un retour arrière automatique.","steps":[{"title":"GitOps : faites de Git la source unique de vérité","detail":"Définissez l'infrastructure, les migrations de schéma et la version de l'application dans Git ; le déploiement est déclenché par un merge dans le dépôt (GitOps). Un réconciliateur (Argo CD/Flux ou CI/CD) applique l'état souhaité à l'environnement réel. Chaque modification est alors auditable, reproductible et peut être ramenée à un commit précédent si nécessaire.","engines":{"postgres":"# repo/\n#   migrations/  (Vxxx__*.sql)\n#   k8s/         (Deployment, Service)\n#   flags/       (feature flag config)"}},{"title":"Modifiez le schéma de manière rétrocompatible (expand-contract)","detail":"Pour un zéro downtime, ne modifiez pas le schéma en une seule étape cassante ; découpez-la en deux phases. EXPAND : ajoutez la nouvelle structure (nouvelle colonne/table) de sorte que l'ancien et le nouveau code puissent fonctionner en même temps (colonne nullable, avec valeur par défaut, écritures doubles). APRÈS le passage de l'application à la nouvelle structure, CONTRACT : supprimez l'ancienne colonne/table devenue inutile dans une migration séparée. Les anciennes et nouvelles versions de l'application cohabitent alors sans problème pendant le déploiement.","engines":{"postgres":"-- EXPAND (V5): backward-compatible addition\nALTER TABLE customer ADD COLUMN email text;\n\n-- for a while the code writes/reads both columns\n\n-- CONTRACT (V9): drop the old column\nALTER TABLE customer DROP COLUMN mail_old;"}},{"title":"Placez les comportements risqués derrière un feature flag","detail":"Exécutez le nouveau chemin de code (comme la lecture de la nouvelle colonne) de manière conditionnelle derrière un feature flag ; activez le flag lorsque le schéma est prêt et désactivez-le sans toucher au déploiement en cas de problème. Un flag sépare le déploiement du code (deploy) de l'activation de la fonctionnalité (release). La phase expand est livrée, mais le changement de comportement est activé de manière contrôlée et peut être annulé rapidement.","engines":{"postgres":"-- application layer (pseudocode)\n-- if flag('new_email_field'):\n--     SELECT email FROM customer ...\n-- else:\n--     SELECT mail_old FROM customer ..."}},{"title":"Mettez en place l'environnement blue-green","detail":"Conservez deux environnements identiques : 'blue' (en production) et 'green' (nouvelle version). Déployez la nouvelle version de l'application sur green et validez-la avant qu'elle ne reçoive du trafic réel ; une fois prête, basculez le routeur/le trafic vers green. Point clé : la base est généralement PARTAGÉE, le schéma doit donc être compatible avec les deux versions (l'expand-contract le garantit). Même si la cible de connexion change lors de la bascule, la compatibilité du schéma évite toute interruption.","engines":{"postgres":"# traffic is switched to green (new version)\n# shared DB: schema must be compatible with both blue and green"}},{"title":"Validez le déploiement par la surveillance (canary/observation)","detail":"Avant de basculer tout le trafic, surveillez des métriques telles que le taux d'erreurs, la latence, le nombre d'erreurs/de verrous de la base et le retard de réplication ; envoyez éventuellement un petit pourcentage du trafic (canary) vers green et observez. Si les indicateurs de santé sont bons, augmentez progressivement le trafic ; s'ils sont mauvais, n'allez pas plus loin. La surveillance alimente la décision de retour arrière automatique.","engines":{"postgres":"-- live health queries\nSELECT count(*) FROM pg_stat_activity WHERE wait_event_type='Lock';\nSELECT * FROM pg_stat_replication;"}},{"title":"Préparez le retour arrière automatique","detail":"Si les métriques dépassent le seuil, le déploiement doit revenir en arrière automatiquement : en blue-green, rebasculez rapidement le trafic vers blue (l'ancienne version). Le retour arrière applicatif est instantané car blue tourne toujours. Côté schéma, le retour arrière n'est généralement PAS NÉCESSAIRE : grâce à l'expand-contract, le nouveau schéma est aussi compatible avec l'ancien code, il suffit donc de revenir à l'ancienne application. C'est pourquoi les modifications de schéma doivent rester indépendantes du déploiement applicatif et toujours être rétrocompatibles.","engines":{"postgres":"# traffic instantly returns to blue (application rollback)\n# schema is not rolled back: expand-contract is compatible with old code\n# if truly needed: write a new corrective migration"}}],"related":["setup-cdc-replication","apply-patch","check-version"]}]},"install/index.json":{"schemaVersion":1,"categories":[{"id":"install","title":"Installation"},{"id":"ha","title":"Haute disponibilité & réplication"},{"id":"backup","title":"Sauvegarde & restauration"},{"id":"ops","title":"Exploitation courante & incidents"},{"id":"pooling","title":"Connexions & répartition de charge"},{"id":"patch","title":"Patchs & mises à jour mineures"},{"id":"upgrade","title":"Upgrade majeur & migration"},{"id":"monitoring","title":"Supervision & administration"},{"id":"security","title":"Sécurité & durcissement"}],"guides":[{"id":"pg-install","file":"install/guides/pg-install.json","group":"PostgreSQL","engineId":"postgres","category":"install","title":"Installation de PostgreSQL sur un serveur unique (prête pour la production)","summary":"Installation de PostgreSQL prête pour la production sur un serveur unique depuis le dépôt PGDG : répertoire de données séparé, data checksums, paramètres calculés selon la RAM et le CPU, accès sécurisé (peer + scram-sha-","level":"Débutant","duration":"30–45 min","topology":"1 serveur","tags":["postgresql","postgres","installation","install","pgdg","initdb","tuning","rhel","ubuntu"],"order":1,"steps":23,"scenarios":[]},{"id":"pg-streaming","file":"install/guides/pg-streaming.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"Réplication en flux PostgreSQL (primary + standby)","summary":"Ajoute un ou plusieurs hot standby à un primary existant avec pg_basebackup (utilisateur de réplication, pg_hba, replication slot, mode asynchrone ou synchrone ANY/FIRST, suivi du retard) et produit les runbooks d'exploi","level":"Intermédiaire","duration":"45–90 min","topology":"1 primary + 1..N standby","tags":["postgresql","streaming replication","réplication","standby","hot standby","pg_basebackup","replication slot","synchronous","réplication synchrone","failover","switchover","pg_promote","pg_rewind","ha","cascading","delayed standby","recovery_min_apply_delay","nettoyage des slots","pg_wal plein","synchronous_standby_names","reconstruire le standby"],"order":2,"steps":24,"scenarios":[{"k":"mode","v":"install","l":"Installation : ajouter des standby au primary","m":"Ajouter / reconstruire un standby mettre en place la streaming replication ajouter un standby hot standby pg_basebackup read replica réplica en lecture seule physical replication réplication physique standby.signal primary_conninfo Un ou plusieurs hot standby sont ajoutés à un primary existant avec pg_basebackup (slot, synchrone/asynchrone, supervision). Le primary reste ouvert ; la copie initiale"},{"k":"mode","v":"switchover","l":"Switchover planifié (inversion des rôles, sans perte de données)","m":"Inversion des rôles switchover planned switchover inversion des rôles bascule planifiée maintenance du primary promouvoir le standby en primary pg_promote pg_ctl promote Maintenance planifiée : le primary est arrêté proprement, le premier standby est promu, l'ancien primary revient en standby. Aucune perte de données ; l'interruption des écritures dure généralement moins d'une minute."},{"k":"mode","v":"failover","l":"Failover manuel (primary tombé, promouvoir le standby)","m":"Inversion des rôles could not connect to the primary server Connection refused primary down primary tombé primary injoignable failover manual failover promote standby pg_promote split-brain could not receive data from WAL stream serveur primary perdu Le primary est tombé ou injoignable : l'ancien primary est isolé (fencing), le standby le plus à jour est promu manuellement. En mode asynchrone, les"},{"k":"mode","v":"rejoin","l":"Réintégrer l'ancien primary comme standby avec pg_rewind","m":"Inversion des rôles requested timeline is not a child of this server's history target server must be shut down cleanly could not find previous WAL record pg_rewind diverged timeline réintégrer l'ancien primary rejoin old primary Après un failover, l'ancien primary est resynchronisé sur le nouveau primary avec pg_rewind et réintégré comme standby ; c'est beaucoup plus rapide qu'une copie complète."},{"k":"mode","v":"cascade","l":"Ajouter un nouveau standby alimenté par un standby (cascade)","m":"Ajouter / reconstruire un standby cascading replication cascade standby réplication en cascade standby depuis un standby data center distant DR standby Le nouveau standby reçoit le WAL du premier standby au lieu du primary : dans un data center distant ou une architecture à nombreux standby, la charge réseau et d'envoi de WAL du primary diminue. Le primary n'est pas affecté."},{"k":"mode","v":"delayed","l":"Mettre en place un standby différé (delayed)","m":"Ajouter / reconstruire un standby recovery_min_apply_delay delayed standby standby différé supprimé par erreur annuler un DROP TABLE accidental delete pg_wal_replay_pause Standby qui applique le WAL avec plusieurs heures de retard (`recovery_min_apply_delay`) : pour récupérer des données supprimées par erreur sans restaurer une sauvegarde. Ce n'est pas un candidat au failover."},{"k":"mode","v":"rebuild","l":"Reconstruire un standby à partir de zéro","m":"Ajouter / reconstruire un standby requested WAL segment has already been removed could not receive data from WAL stream database system identifier differs wal_status lost exceeds max_slot_wal_keep_size reconstruire le standby standby corrompu re-clone standby could not start WAL streaming wal_removed standby en retard Le standby a pris trop de retard, son WAL a été supprimé ou il est corrompu : le"},{"k":"mode","v":"slotclean","l":"pg_wal grossit, le disque se remplit (nettoyage des slots inutilisés)","m":"Maintenance et dépannage No space left on device could not write to file \"pg_wal pg_wal plein pg_wal grossit gonflement du WAL disque plein replication slot inactive max_slot_wal_keep_size répertoire pg_wal volumineux WAL disk full La cause de l'accumulation de WAL est identifiée (slot inactif, échec d'archivage, wal_keep_size) ; le replication slot inutilisé est supprimé et l'espace WAL est récup"},{"k":"mode","v":"syncchg","l":"Ajouter / retirer un standby synchrone (synchronous_standby_names)","m":"Maintenance et dépannage canceling wait for synchronous replication synchronous_standby_names COMMIT bloqué commit hangs sync standby standby synchrone sync_state potential synchronous_commit Réécrit la liste synchrone selon le mode du formulaire (ANY/FIRST, k) ou repasse en asynchrone ; appliqué par un reload, sans redémarrage. Un mauvais réglage peut laisser des commits en attente ; il est donc "}]},{"id":"pg-patroni","file":"install/guides/pg-patroni.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"Cluster à failover automatique avec Patroni + etcd + HAProxy","summary":"Met en place et exploite un cluster PostgreSQL à failover automatique : sur chaque nœud, PostgreSQL est géré par Patroni, l'élection du leader se fait dans un etcd à 3 (ou 5) membres, et les applications se connectent au","level":"Avancé","duration":"2 à 3 heures","topology":"3 nœuds PostgreSQL + etcd (partagés ou séparés) + HAProxy (+ VIP)","tags":["postgresql","patroni","etcd","haproxy","keepalived","vip","failover automatique","auto failover","switchover","ha","haute disponibilité","cluster","cluster postgres","patronictl","dcs","reinit","pg_rewind","edit-config","pause","resume","etcdctl member","quorum","failsafe","ajouter un nœud","retirer un nœud","mise à niveau patroni","arrêt du cluster"],"order":3,"steps":40,"scenarios":[{"k":"mode","v":"install","l":"Installation : nouveau cluster Patroni","m":"Installation installation patroni patroni install installation etcd haproxy failover automatique automatic failover haute disponibilité haute disponibilité cluster HA Construit de zéro un cluster etcd + Patroni + HAProxy (+ VIP). Les serveurs ne doivent pas contenir de cluster PostgreSQL : initdb est exécuté par Patroni (pg-install ne s'applique pas)."},{"k":"mode","v":"ops","l":"Runbook d'exploitation : switchover, test de failover, mode maintenance, changement de paramètre","m":"Contrôle et exercice test de failover failover test exercice DR drill runbook Déroule dans l'ordre les opérations de base sur un cluster nouvellement installé : état, switchover, test de failover (le leader est arrêté, courte interruption des écritures), mode maintenance, changement de paramètre. À exécuter dans une fenêtre de maintenance."},{"k":"mode","v":"health","l":"Le cluster est-il sain ? (contrôle de santé)","m":"Contrôle et exercice patronictl list Pending restart Maintenance mode: on start failed patroni health état du cluster replica lag retard de réplication timeline différente Lecture seule : rôles, retard, timeline, API REST, santé d'etcd, slots, redémarrage en attente et mode maintenance."},{"k":"mode","v":"switchover","l":"Switchover planifié (déplacer le leader sur un autre nœud)","m":"Changement de rôle candidate name does not match with sync_standby Switchover failed does not exist in cluster or is tagged as nofailover switchover changer de leader changement de rôle maintenance planifiée Avant une maintenance, le rôle de leader passe de façon contrôlée à un replica ; quelques secondes d'interruption des écritures, pas de perte de données."},{"k":"mode","v":"rejoin","l":"Après un failover, l'ancien leader ne réintègre pas le cluster (pg_rewind / reinit)","m":"Changement de rôle requested timeline is not a child of this server's history could not find previous WAL record pg_rewind: error target server needs to use either data checksums or wal_log_hints start failed diverged ancien leader après failover pg_rewind: fatal could not find common ancestor timeline history mismatch Lorsque l'ancien leader tombé redémarre, il doit rejoindre le cluster comme rep"},{"k":"mode","v":"reinit","l":"Reconstruire un replica de zéro (reinit)","m":"Maintenance d'un membre requested WAL segment has already been removed could not receive data from WAL stream invalid record length invalid page patronictl reinit replica corrompu replica en retard rebuild replica replica start failed creating replica Le replica est corrompu, très en retard ou a perdu des WAL : son répertoire de données est effacé et reconstruit à partir du leader (ou d'une sauveg"},{"k":"mode","v":"addmember","l":"Ajouter un nouveau nœud au cluster","m":"Maintenance d'un membre ajouter un nœud add member nouveau replica scale out agrandir le cluster Ajoutez le nouveau nœud à la fin de la liste du formulaire : les logiciels sont installés, pg_hba et HAProxy sont mis à jour sur les nœuds existants, et le nœud rejoint le cluster comme replica."},{"k":"mode","v":"rmmember","l":"Retirer un nœud du cluster","m":"Maintenance d'un membre retirer un nœud remove member decommission décommissionner un serveur retirer un replica Un nœud replica est retiré définitivement : Patroni est arrêté, l'entrée DCS et le slot sont nettoyés, et le nœud est retiré de HAProxy."},{"k":"mode","v":"editcfg","l":"Modifier un paramètre PostgreSQL ou Patroni (edit-config)","m":"Paramètres et maintenance edit-config Pending restart paramètre postgresql.conf perdu ALTER SYSTEM modifier un paramètre max_connections shared_buffers change parameter work_mem paramètre disparu paramètre non appliqué Le paramètre est écrit dans le DCS et appliqué à tous les nœuds ; pour les paramètres qui exigent un redémarrage, `restart --pending` (les replicas d'abord)."},{"k":"mode","v":"pause","l":"Suspendre le failover automatique pour une maintenance (pause / resume)","m":"Paramètres et maintenance Maintenance mode: on patronictl pause patronictl resume maintenance mode mode maintenance désactiver le failover automatique En pause, Patroni ne touche pas à PostgreSQL et n'effectue pas de failover ; pour une maintenance réseau/etcd ou une intervention manuelle. Faites resume une fois le travail terminé."},{"k":"mode","v":"upgrade","l":"Mettre à niveau le logiciel Patroni (rolling)","m":"Paramètres et maintenance mise à niveau patroni mise à jour patroni version patroni dnf update patroni apt upgrade patroni patroni 3 to 4 Le cluster est mis en pause, Patroni est mis à jour et redémarré nœud par nœud ; PostgreSQL reste démarré, aucun failover n'a lieu."},{"k":"mode","v":"stopstart","l":"Arrêter / démarrer tout le cluster (dans le bon ordre)","m":"Paramètres et maintenance arrêter le cluster démarrer le cluster shutdown order arrêt complet arrêt planifié maintenance datacenter coupure électrique Arrêt complet planifié (maintenance du stockage, du datacenter) : les replicas d'abord, puis le leader, etcd en dernier ; démarrage dans l'ordre inverse."},{"k":"mode","v":"etcdmember","l":"Remplacer ou ajouter un membre etcd (etcdctl member)","m":"etcd (DCS) etcdserver: unhealthy cluster member has already been bootstrapped Peer URLs already exists member count is unequal etcdctl member etcd member add etcd member remove membre etcd unhealthy Le membre etcd défaillant est retiré et un nouveau serveur est ajouté (ou le nombre de membres est augmenté) ; la liste etcd de Patroni est mise à jour."},{"k":"mode","v":"dcsloss","l":"Quorum etcd perdu : le leader est passé en lecture seule","m":"etcd (DCS) Failed to get list of machines DCS is not accessible demoting self etcdserver: request timed out etcdserver: no leader cannot execute INSERT in a read-only transaction quorum failsafe etcd quorum lost pas de leader no leader Sans quorum etcd, Patroni met le leader en lecture seule et aucun failover n'a lieu non plus. etcd est rétabli ou reconstruit à partir d'un seul membre."}]},{"id":"pg-pgbouncer","file":"install/guides/pg-pgbouncer.json","group":"PostgreSQL","engineId":"postgres","category":"pooling","title":"Pool de connexions PgBouncer","summary":"Installation de PgBouncer depuis le dépôt PGDG : mode de pool transaction ou session, authentification scram-sha-256 via auth_query (fonction SECURITY DEFINER) ou userlist.txt, tailles de pool calculées à partir du max_c","level":"Intermédiaire","duration":"30–45 min","topology":"PgBouncer sur le serveur de base de données ou sur un serveur séparé ; avec Patroni, devant HAProxy/VIP","tags":["postgresql","pgbouncer","connection pool","pool de connexions","pooling","transaction pooling","auth_query","userlist","scram-sha-256","so_reuseport","max_client_conn","default_pool_size","patroni","haproxy","vip","failover"],"order":4,"steps":15,"scenarios":[]},{"id":"pg-pgbackrest","file":"install/guides/pg-pgbackrest.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"Sauvegarde et PITR avec pgBackRest","summary":"Sauvegarde complète / différentielle / incrémentale avec pgBackRest, archivage WAL continu et restauration à un instant donné (PITR). Pour un serveur unique, une streaming replication (primary + standbys) ou un cluster P","level":"Intermédiaire","duration":"45–90 min","topology":"1 serveur, primary + standbys ou cluster Patroni ; serveur de sauvegarde optionnel","tags":["postgresql","pgbackrest","sauvegarde","backup","restore","pitr","wal","archive","s3","minio","chiffrement","retention","restauration à un instant donné","patroni","streaming","standby","cron","backup-standby","systemd timer","delta restore","db-include","target-action","restore point","xid","lsn","clone","clonage","sinistre","disaster recovery","verify","expire","type=standby","exercice de restauration"],"order":10,"steps":28,"scenarios":[{"k":"purpose","v":"kur","l":"Installation de pgBackRest + première sauvegarde + restauration de test","m":"Installation stanza-create archive_command archive-push installation pgbackrest pgbackrest install mettre en place la sauvegarde backup setup plan de sauvegarde full diff incr sauvegarde planifiée ERROR: [087] archive_mode must be enabled configurer l'archivage WAL première sauvegarde pgBackRest n'est pas encore en place : logiciel, dépôt, archivage WAL, première sauvegarde, planification et une r"},{"k":"purpose","v":"geri","l":"Sinistre : restauration sur place (jusqu'au dernier état ou à un instant donné)","m":"Restauration (même serveur) recovery_target_time recovery_target_xid recovery_target_lsn pg_create_restore_point PITR point in time recovery retour à un instant donné supprimé par erreur DROP TABLE DELETE sans WHERE UPDATE sans WHERE TRUNCATE restore point restauration à une date récupérer les données Ramenez la base sur place à un instant couvert par la sauvegarde et l'archive : dernier instant, "},{"k":"purpose","v":"delta","l":"Fichier corrompu ou supprimé dans le répertoire de données (réparer avec un delta restore)","m":"Restauration (même serveur) invalid page in block could not read block page verification failed checksum verification failed could not open file \"base/ could not access status of transaction missing chunk number No such file or directory could not read file bloc corrompu corruption de données corruption de bloc fichier de données supprimé ERROR:  could not read block PANIC:  could not open file PA"},{"k":"purpose","v":"dbinc","l":"Récupérer une seule base de données (--db-include)","m":"Restauration (même serveur) db-include DROP DATABASE une seule base single database restore base de données supprimée récupérer une table table restore restauration d'une seule table Le cluster reste ouvert : la base est restaurée dans une instance temporaire (répertoire et port séparés), extraite avec pg_dump puis chargée sous un nouveau nom sur le serveur de production. Cette fiche sert aussi po"},{"k":"purpose","v":"clone","l":"Restaurer la sauvegarde sur un autre serveur (clone / test / reporting)","m":"Nouveau serveur : clone, sinistre, standby repo1-cipher-pass clone clonage environnement de test rafraîchir l'environnement de test refresh test staging restauration sur un nouveau serveur serveur de reporting dépôt chiffré Le serveur de production n'est pas touché. La sauvegarde est restaurée sur un nouveau serveur ; l'archivage WAL y est désactivé pour que le clone n'écrive pas dans le dépôt de "},{"k":"purpose","v":"dr","l":"Serveur entièrement perdu : restaurer la sauvegarde sur un nouveau serveur et le mettre en service à sa place","m":"Nouveau serveur : clone, sinistre, standby PANIC: could not locate a valid checkpoint record sinistre disaster recovery DR perte de serveur panne de disque server lost répertoire de données perdu nouveau primary dépôt chiffré could not open file \"global/pg_control\" le serveur ne démarre pas PostgreSQL ne démarre pas Perte de disque ou de serveur : le nouveau serveur est restauré depuis la sauvegar"},{"k":"purpose","v":"standby","l":"Créer un nouveau standby depuis la sauvegarde (--type=standby)","m":"Nouveau serveur : clone, sinistre, standby type=standby créer un standby ajouter un réplica standby depuis la sauvegarde standby rebuild à la place de pg_basebackup add standby Nouveau standby sans charge de copie sur le primary : les fichiers viennent de la sauvegarde, les WAL manquants de l'archive puis du streaming. Le primary reste ouvert."},{"k":"purpose","v":"verify","l":"Les sauvegardes sont-elles vraiment saines ? (info / check / verify et exercice de restauration)","m":"Contrôle et maintenance ERROR: [082] ERROR: [028] ERROR: [056] pgbackrest verify pgbackrest check pgbackrest info vérification des sauvegardes backup verification restore test exercice de restauration date de la dernière sauvegarde verify command sauvegarde saine restore drill vérification mensuelle Contrôles en lecture seule et exercice de restauration dans un répertoire séparé, sans toucher au s"},{"k":"purpose","v":"retention","l":"Modifier la durée de rétention et purger les anciennes sauvegardes (expire)","m":"Contrôle et maintenance No space left on device WARN: option 'repo1-retention-full' is not set repo1-retention-full expire retention rétention dépôt plein disque de sauvegarde plein supprimer les anciennes sauvegardes repo plein bucket plein disque de sauvegarde saturé Modifie les paramètres repo1-retention-full/diff, montre d'abord avec --dry-run ce qui sera supprimé, puis lance expire. Une sauve"}]},{"id":"pg-pitr-basebackup","file":"install/guides/pg-pitr-basebackup.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"Sauvegarde et PITR avec pg_basebackup + archivage WAL (sans pgBackRest)","summary":"Sauvegarde physique avec les outils de PostgreSQL, sans logiciel supplémentaire : archivage continu des WAL avec archive_command, base backup nocturne avec pg_basebackup, rétention avec pg_archivecleanup. Restauration à ","level":"Intermédiaire","duration":"45–90 min","topology":"1 serveur ; en streaming ou avec Patroni, le répertoire d'archive est partagé par tous les nodes (NFS)","tags":["postgresql","pg_basebackup","archive_command","restore_command","pitr","point in time recovery","recovery.signal","recovery_target_time","pg_archivecleanup","archive wal","timeline","base backup","sauvegarde","restauration","pg_verifybackup","cron","exercice de restauration"],"order":12,"steps":14,"scenarios":[{"k":"op","v":"kur","l":"Mettre en place l'archivage WAL et le base backup nocturne","m":"Mise en place archive_mode archive_command archive_timeout pg_stat_archiver wal_level Archivage WAL WAL archiving continuous archiving archivage continu pg_basebackup cron base backup sauvegarde nocturne nightly backup configurer l'archivage wal Mise en place initiale : archivage continu des WAL avec archive_command, script pg_basebackup, planification dans /etc/cron.d, rétention et nettoyage des "},{"k":"op","v":"pitr","l":"Revenir à un point précis (PITR : heure, xid, LSN, restore point)","m":"Restauration recovery ended before configured recovery target was reached requested recovery stop point is before consistent recovery point PITR point in time recovery point in time recovery.signal restore_command recovery_target_time recovery_target_xid recovery_target_lsn recovery_target_name pg_create_restore_point DROP TABLE TRUNCATE supprimé par erreur accidental delete retour à un instant do"},{"k":"op","v":"drill","l":"Exercice de restauration (dans un répertoire distinct, sans toucher à la production)","m":"Contrôle et maintenance backup successfully verified pg_verifybackup pg_amcheck test de restauration restore drill exercice de restauration vérification de sauvegarde backup verify RTO la sauvegarde se restaure-t-elle Pour prouver que la sauvegarde se restaure réellement (une fois par mois recommandé) : le dernier base backup est vérifié (pg_verifybackup), extrait dans un répertoire distinct et dé"},{"k":"op","v":"cleanup","l":"Nettoyer les anciens base backups et l'archive WAL","m":"Contrôle et maintenance No space left on device could not write to file archive command failed pg_archivecleanup disque d'archive plein archive pleine disk full supprimer les anciennes sauvegardes rétention des sauvegardes retention pg_wal grossit archive_status .ready Le disque d'archive ou de sauvegarde se remplit, ou la rétention a changé : les anciens base backups sont supprimés et pg_archivec"}]},{"id":"pg-logical-repl","file":"install/guides/pg-logical-repl.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"Réplication logique PostgreSQL (publication / abonnement)","summary":"Réplication logique au niveau table entre deux instances PostgreSQL : wal_level=logical, contrôle de la replica identity, publication et abonnement, synchronisation initiale, supervision, gestion des conflits, synchronis","level":"Avancé","duration":"45–90 min (+ durée de la copie initiale)","topology":"2 serveurs : publieur (publisher) + abonné (subscriber) ; le publieur peut être un serveur unique, une streaming replication ou un cluster Patroni","tags":["postgresql","logical replication","réplication logique","publication","abonnement","cdc","migration de données","migration","montée de version","conflit","replica identity","séquence","patroni","failover slot","permanent slots","sync_replication_slots"],"order":15,"steps":25,"scenarios":[{"k":"purpose","v":"sync","l":"Réplication continue (reporting, intégration, copie partielle)","m":"logical replication publication abonnement CREATE SUBSCRIPTION réplication de tables réplication partielle Les tables sélectionnées sont répliquées en continu vers une autre instance (serveur de reporting, alimentation d'un entrepôt de données, intégration entre services). L'abonné reste accessible en écriture."},{"k":"purpose","v":"migrate","l":"Migration : nouveau serveur / nouvelle version majeure + bascule finale (cutover)","m":"montée de version sans interruption near zero downtime cutover migration de serveur logical upgrade Passage vers un nouveau serveur ou une nouvelle version majeure avec quelques secondes d'interruption : copie initiale et synchronisation continue, puis courte suspension des écritures et bascule finale (cutover)."}]},{"id":"pg-dump-restore","file":"install/guides/pg-dump-restore.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"Sauvegarde logique et restauration PostgreSQL (pg_dump / pg_restore)","summary":"Sauvegarde logique et restauration avec pg_dump, pg_dumpall et pg_restore au niveau d'une table, d'un schéma, d'une base ou de tout le cluster ; structure seule (DDL) ou données seules, migration vers un autre serveur ou","level":"Intermédiaire","duration":"20–90 min (selon le volume de données)","topology":"1 serveur, streaming replication (primary + standby) ou cluster Patroni ; pour une migration, source + cible ; pour une sauvegarde planifiée distante, un serveur de sauvegarde en plus","tags":["postgresql","postgres","pg_dump","pg_restore","pg_dumpall","sauvegarde logique","logical backup","export","import","dump","restore","restauration","migration de base","migration","upgrade","globals","systemd timer","cron","pgpass","sauvegarde","patroni","streaming replication","replica","standby","haproxy","hot standby","pg_wrapper","cron.d","flock"],"order":15,"steps":9,"scenarios":[{"k":"mode","v":"tables","l":"Export / import d'une ou plusieurs tables","m":"Export / import pg_dump -t export de table import de table table export sauvegarde d'une table no matching tables were found La structure et les données des tables choisies (index, contraintes, triggers compris) sont extraites puis chargées dans une autre base."},{"k":"mode","v":"schema","l":"Export / import d'un schéma","m":"Export / import pg_dump -n export de schéma schema export no matching schemas were found Tous les objets du schéma (tables, vues, fonctions, séquences) sont extraits."},{"k":"mode","v":"db","l":"Sauvegarde / restauration d'une base","m":"Export / import pg_dump pg_restore sauvegarde de base database backup database restore errors ignored on restore Une base complète (structure + données) est extraite dans un seul dump puis restaurée dans la même base ou dans une autre. Les rôles (globals) ne sont pas inclus."},{"k":"mode","v":"cluster","l":"Cluster complet (rôles + tablespaces + chaque base)","m":"Export / import pg_dumpall pg_dumpall -g globals sauvegarde du cluster complet sauvegarde des rôles Les globals avec pg_dumpall -g, un pg_dump distinct par base ; la restauration est répétée sur un cluster de test temporaire."},{"k":"mode","v":"ddl","l":"Structure du schéma seule (DDL)","m":"Structure et données séparées pg_dump -s --schema-only export DDL structure seule Définitions des tables, index, vues et fonctions sans les données ; pour revue, gestion de versions ou création d'un environnement vide."},{"k":"mode","v":"data","l":"Données seules","m":"Structure et données séparées pg_dump -a --data-only données seules --disable-triggers circular foreign-key constraints Seules les lignes (COPY) sont transférées, la structure étant déjà en place sur la cible."},{"k":"mode","v":"migrate","l":"Migration vers un autre serveur / une autre version","m":"Migration et récupération migration de serveur migration dump restore upgrade aborting because of server version mismatch migrer vers une nouvelle version Sur le serveur cible, le pg_dump de la nouvelle version se connecte à la source par le réseau ; le transfert se fait par pipe ou par fichier."},{"k":"mode","v":"partial","l":"Restaurer une seule table / un seul objet depuis un dump","m":"Migration et récupération restaurer une table pg_restore -L pg_restore --list DROP TABLE table supprimée lignes supprimées supprimé par erreur table restore À partir d'un dump custom/directory existant, seule une table avec ses index, contraintes et données est restaurée à l'aide d'un fichier liste (-L)."},{"k":"mode","v":"sched","l":"Sauvegarde planifiée (systemd timer / cron)","m":"Sauvegarde planifiée pg_dump cron systemd timer sauvegarde planifiée scheduled backup sauvegarde quotidienne rétention des sauvegardes Sauvegarde quotidienne des globals et des bases, contrôle de lisibilité, durée de rétention, journalisation et notification d'échec."}]},{"id":"pg-minor-update","file":"install/guides/pg-minor-update.json","group":"PostgreSQL","engineId":"postgres","category":"patch","title":"Mise à jour mineure de PostgreSQL (patch)","summary":"Mise à jour mineure au sein d'une même version majeure (par ex. 17.4 → 17.x) : contrôle des release notes, sauvegarde et conservation des RPM/deb actuels pour le retour arrière. Sur un serveur unique, avec une courte int","level":"Intermédiaire","duration":"20–60 min","topology":"1 serveur, primary + standbys ou cluster Patroni","tags":["postgresql","mineure","patch","mise à jour","update","upgrade","rolling","switchover","dnf","apt","downgrade","rollback","extension","patroni","patronictl","failover"],"order":20,"steps":17,"scenarios":[{"k":"purpose","v":"upd","l":"Mise à jour mineure","m":"minor update mise à jour mineure patch CVE mise à jour de sécurité dnf upgrade postgresql apt upgrade postgresql Passage à une nouvelle version mineure au sein de la même version majeure (par ex. 17.4 → 17.6) : correctifs de sécurité et de bugs. Courte interruption sur un serveur unique, rolling dans un cluster."},{"k":"purpose","v":"rb","l":"Retour arrière : revenir à la version mineure précédente (downgrade)","m":"downgrade retour arrière rollback revenir à la version précédente undefined symbol Une erreur ou une régression est apparue dans la nouvelle version mineure : on revient en arrière avec les RPM/deb de la version précédente conservés. Le répertoire de données ne change pas."}]},{"id":"pg-major-upgrade","file":"install/guides/pg-major-upgrade.json","group":"PostgreSQL","engineId":"postgres","category":"upgrade","title":"Montée de version majeure PostgreSQL (pg_upgrade / logical replication)","summary":"Montée de version majeure (par ex. 16 → 17) : choix de la méthode (pg_upgrade --link / --copy / --clone, ou migration quasi sans interruption par logical replication), pg_upgrade --check, compatibilité des extensions, re","level":"Avancé","duration":"1 à 3 heures (+ selon la volumétrie)","topology":"Sur place, sur le même serveur (pg_upgrade ; serveur unique, primary + standbys ou cluster Patroni) ou vers un nouveau serveur (logical)","tags":["postgresql","majeure","upgrade","montée de version","pg_upgrade","pg_upgradecluster","link","clone","logical replication","migration","migration","rsync","rollback","patroni","patronictl remove","replica","streaming replication"],"order":30,"steps":24,"scenarios":[{"k":"purpose","v":"upg","l":"Montée de version majeure","m":"montée de version majeure pg_upgrade pg_upgradecluster montée de version version majeure upgrade postgresql 17 upgrade postgresql 18 Montée de version majeure (par ex. 16 → 17) : contrôles préalables, sauvegarde, pg_upgrade ou logical replication, vérification et nettoyage."},{"k":"purpose","v":"rb","l":"Retour arrière (upgrade échoué / abandonné)","m":"annuler upgrade rollback pg_control.old database files are incompatible with server échec upgrade L'upgrade s'est arrêté en cours de route ou la nouvelle version pose problème : retour à l'ancienne version. Avec --link, si le nouveau cluster a été démarré, le retour n'est possible qu'à partir de la sauvegarde."}]},{"id":"pg-security","file":"install/guides/pg-security.json","group":"PostgreSQL","engineId":"postgres","category":"security","title":"Durcissement de la sécurité PostgreSQL (audit d'état, réseau, TLS, authentification, privilèges, audit, chiffrement)","summary":"Ce n'est pas un guide d'installation : il trouve et corrige les failles de sécurité d'un serveur PostgreSQL en service. D'abord, l'Audit d'état en lecture seule liste les failles par gravité et indique vers quelle carte ","level":"Intermédiaire","duration":"5 min (audit) – 3 heures (durcissement complet)","topology":"Un serveur PostgreSQL + serveur applicatif (vérification côté client) ; environnements avec replica/Patroni traités dans les notes","tags":["postgresql","postgres","sécurité","security","hardening","durcissement","cis benchmark","pg_hba","tls","ssl","sslmode","verify-full","scram-sha-256","md5","pgaudit","audit","audit de sécurité","public schema","privilèges","rôle","luks","pgcrypto","chiffrement","encryption","firewall"],"order":40,"steps":3,"scenarios":[{"k":"scenario","v":"scan","l":"Audit d'état : quelles sont mes failles ? (lecture seule)","m":"Point de départ audit de sécurité security audit hardening check CIS faille de sécurité vulnérabilité vulnerability constat de pentest rapport d'audit requête d'audit contrôle CIS scan de sécurité security scan Le premier contrôle à lancer : pg_hba, adresse d'écoute, TLS, mots de passe md5, superutilisateurs, privilèges PUBLIC, audit, permissions des fichiers et firewall ; il ne modifie rien."},{"k":"scenario","v":"all","l":"Durcissement complet (tous les domaines dans l'ordre)","m":"Point de départ hardening durcissement sécurité complète full hardening appliquer le CIS benchmark STIG Serveur fraîchement installé ou jamais durci : tous les domaines sont appliqués dans un ordre sûr ; PostgreSQL est redémarré une ou deux fois (une fenêtre de maintenance est nécessaire)."},{"k":"scenario","v":"net","l":"Réseau et connexions : qui peut se connecter d'où ? (listen, firewall, pg_hba)","m":"Réseau et identité pg_hba.conf 0.0.0.0/0 trust listen_addresses firewall no pg_hba.conf entry pg_hba.conf rejects connection port 5432 ouvert FATAL: no pg_hba.conf entry for host 28000 Connection refused listen_addresses = * host all all 0.0.0.0/0 accès depuis l'extérieur restreindre l'accès PostgreSQL écoute sur toutes les interfaces, pg_hba contient 0.0.0.0/0 ou trust, le port est ouvert à tous."},{"k":"scenario","v":"tls","l":"Chiffrer les connexions (TLS, sslmode=verify-full)","m":"Réseau et identité ssl tls sslmode verify-full hostssl certificate verify failed SSL off no encryption server.crt certificat expiré certificate expired SSL error: certificate verify failed does not match host name root certificate file does not exist server does not support SSL, but SSL was required self-signed certificate chaîne de certificats certificate chain hostnossl ssl = off ou les clients "},{"k":"scenario","v":"auth","l":"Authentification et mots de passe (scram-sha-256, connexion distante de postgres)","m":"Réseau et identité md5 scram-sha-256 password_encryption password authentication failed authentication method 10 not supported connexion distante postgres politique de mots de passe password policy 28P01 FATAL: password authentication failed for user SCRAM authentication requires libpq version 10 or above pass-the-hash VALID UNTIL expiration du mot de passe Méthode md5/password, mots de passe hach"},{"k":"scenario","v":"priv","l":"Moindre privilège : rôles, PUBLIC et compte applicatif","m":"Privilèges et traçabilité public schema PUBLIC GRANT REVOKE superuser least privilege moindre privilège permission denied for schema conception des rôles DEFAULT PRIVILEGES 42501 permission denied for table must be owner of table pg_read_server_files pg_execute_server_program SECURITY DEFINER REASSIGN OWNED rapport des privilèges Le compte applicatif est propriétaire des tables ou superutilisateur"},{"k":"scenario","v":"audit","l":"Audit : qui a fait quoi ? (pgaudit, journalisation centralisée)","m":"Privilèges et traçabilité pgaudit audit piste d'audit audit log AUDIT: SESSION log_connections syslog SIEM RGPD PCI shared_preload_libraries could not access file \"pgaudit\" pgaudit must be loaded via shared_preload_libraries log_statement qui a fait quoi who changed L'auditeur ou l'équipe sécurité demande une trace des modifications DDL et des rôles ; installation de pgaudit et envoi des journaux "},{"k":"scenario","v":"tde","l":"Chiffrer les données au repos (LUKS / pgcrypto)","m":"Données et sauvegardes TDE transparent data encryption chiffrement de disque encryption at rest LUKS dm-crypt pgcrypto pg_tde cryptsetup luksFormat pgp_sym_encrypt at-rest encryption Une protection contre le vol d'un disque ou d'une sauvegarde est exigée. PostgreSQL n'a pas de TDE intégré : LUKS au niveau disque (interruption pour le déplacement) ou pgcrypto au niveau colonne."},{"k":"scenario","v":"backup","l":"Sécurité des sauvegardes (sauvegardes chiffrées, permissions)","m":"Données et sauvegardes chiffrement des sauvegardes backup encryption repo-cipher-type pgbackrest cipher gpg .pgpass permissions des sauvegardes cipher: none repo-cipher-pass sauvegarde non chiffrée unencrypted backup Les fichiers de sauvegarde ne sont pas chiffrés ou se trouvent dans un répertoire lisible par tous ; chiffrement du dépôt pgBackRest ou pg_dump + GPG, .pgpass et permissions des réper"},{"k":"scenario","v":"os","l":"Côté système d'exploitation (compte de service, permissions, core dump, SELinux)","m":"Serveur et version SELinux permissive chmod 700 data directory has invalid permissions core dump compte postgres sudo avc: denied LimitCORE core dumped passwd -l Le compte postgres a un mot de passe utilisable, les permissions du répertoire de données sont trop larges, SELinux est en Permissive/Disabled. Le réglage des core dumps prend effet au prochain redémarrage."},{"k":"scenario","v":"patch","l":"Correctifs et version : suis-je sur une version supportée ?","m":"Serveur et version EOL end of life fin de support version mineure CVE correctif de sécurité security release out of support server_version calendrier des correctifs minor update pgsql-announce Contrôle de la version, de la date de fin de support et des mises à jour mineures en attente ; renvoie au guide de patch pour les appliquer. Lecture seule."}]},{"id":"mysql-install","file":"install/guides/mysql-install.json","group":"MySQL","engineId":"mysql","category":"install","title":"Installation de MySQL sur un serveur unique (prête pour la production)","summary":"Installe de zéro un serveur MySQL 8.4 LTS (ou 8.0) depuis le dépôt officiel, prêt pour la production : répertoires séparés pour les données, les binlogs et les logs, un my.cnf calculé selon la RAM, le CPU et le type de d","level":"Débutant","duration":"30–45 min","topology":"1 serveur","tags":["mysql","installation","install","8.4","lts","innodb","my.cnf","tuning","gtid","binlog","rhel","ubuntu"],"order":1,"steps":24,"scenarios":[]},{"id":"mysql-replication","file":"install/guides/mysql-replication.json","group":"MySQL","engineId":"mysql","category":"ha","title":"Réplication MySQL GTID (asynchrone / semi-synchrone)","summary":"Met en place, à partir de serveurs MySQL installés, une réplication basée sur les GTID avec 1 source + N replicas : utilisateur de réplication, transfert des données existantes par le clone plugin ou mysqldump, SOURCE_AU","level":"Intermédiaire","duration":"45–90 min","topology":"1 source + 1 à 5 replicas","tags":["mysql","réplication","replication","gtid","semi-sync","semisync","replica","source","clone","failover","read_only"],"order":2,"steps":21,"scenarios":[]},{"id":"mysql-innodb-cluster","file":"install/guides/mysql-innodb-cluster.json","group":"MySQL","engineId":"mysql","category":"ha","title":"MySQL InnoDB Cluster (Group Replication + MySQL Router)","summary":"Construit un InnoDB Cluster single-primary à partir de 3 (ou 5 à 9) serveurs MySQL installés dans le même datacenter : dba.configureInstance, createCluster et addInstance par clone via l'AdminAPI de MySQL Shell ; bootstr","level":"Avancé","duration":"90–120 min","topology":"3 à 9 nœuds MySQL + 1 serveur Router","tags":["mysql","innodb cluster","group replication","mysql shell","mysqlsh","adminapi","mysql router","haute disponibilité","failover","quorum","clone","single-primary"],"order":3,"steps":39,"scenarios":[]},{"id":"mysql-xtrabackup","file":"install/guides/mysql-xtrabackup.json","group":"MySQL","engineId":"mysql","category":"backup","title":"Sauvegarde, restauration et PITR avec Percona XtraBackup","summary":"Sauvegarde physique avec Percona XtraBackup : installation de la version d'XtraBackup compatible, utilisateur de sauvegarde avec auth_socket, script de sauvegarde full + incrémentale compressée/chiffrée, planification pa","level":"Intermédiaire","duration":"45–90 min","topology":"1 serveur (autonome, source, replica ou secondary d'InnoDB Cluster) ; stockage des sauvegardes sur disque local ou NFS","tags":["mysql","xtrabackup","percona","sauvegarde","backup","restore","incremental","pitr","binlog","récupération","zstd","chiffrement","replica","slave-info","safe-slave-backup","innodb cluster","cron","systemd timer","flock"],"order":4,"steps":26,"scenarios":[{"k":"scenario","v":"setup","l":"Mettre en place les sauvegardes, les planifier et tester la restauration (flux complet)","m":"Installation et test installation xtrabackup script de sauvegarde backup script systemd timer cron test de restauration sauvegarde xtrabackup sauvegarde physique physical backup hot backup incremental backup full backup percona-xtrabackup Installation d'XtraBackup, utilisateur de sauvegarde, script de sauvegarde full + incrémentale, planification et test de restauration (PITR compris). Choisissez "},{"k":"scenario","v":"r_full","l":"Restauration complète : revenir à la dernière sauvegarde full","m":"Restauration restore restaurer copy-back datadir perdu InnoDB: corruption MY-011971 MY-012592 Database page corruption mysqld ne démarre pas full restore ERROR 2002 Can't connect to local MySQL server through socket MY-012574 Unable to lock ./ibdata1 datadir supprimé panne disque disk failure perte de données Le répertoire de données est perdu/corrompu ou le serveur ne démarre pas ; revenir à la s"},{"k":"scenario","v":"r_inc","l":"Restaurer depuis une chaîne full + incrémentale (jusqu'à la dernière incrémentale)","m":"Restauration restauration incrémentale apply-log-only incremental-dir to_lsn from_lsn This incremental backup seems not to be proper for the target chaîne incrémentale incremental chain xtrabackup_checkpoints applying incremental Vous voulez revenir à l'instant de la dernière sauvegarde incrémentale (ou d'une incrémentale de votre choix) ; les incrémentales sont appliquées dans l'ordre sur la full"},{"k":"scenario","v":"r_pitr","l":"Revenir à un instant précis (PITR) : avant un DELETE / DROP erroné","m":"Retour dans le temps PITR point in time supprimé par erreur DROP TABLE DELETE erroné TRUNCATE mysqlbinlog stop-datetime stop-position exclude-gtids binlog replay UPDATE erroné DELETE sans WHERE accidental delete accidental drop DROP DATABASE annuler undo delete récupération avec binlog Données supprimées ou corrompues par erreur : la dernière sauvegarde est restaurée et les binlogs sont appliqués "},{"k":"scenario","v":"r_table","l":"Restaurer une seule table (--export + IMPORT TABLESPACE)","m":"Retour dans le temps une seule table single table restore IMPORT TABLESPACE DISCARD TABLESPACE transportable tablespace --export .cfg ERROR 1808 ERROR 1812 Schema mismatch ERROR 1813 Tablespace exists ERROR 1146 Table doesn't exist tablespace is missing restaurer une table table restore fichier ibd Vous avez seulement besoin de l'état sauvegardé d'une table ; sans restaurer tout le serveur, la tab"},{"k":"scenario","v":"r_replica","l":"Créer un nouveau replica à partir d'une sauvegarde","m":"Migration et clonage nouveau replica créer un replica xtrabackup_binlog_info xtrabackup_slave_info gtid_purged slave-info replication from backup 1236 créer le replica depuis la sauvegarde seed replica Could not find first log file name in binary log index file purged required binary logs 1236 reconstruire le replica Vous voulez préparer un replica neuf (ou à reconstruire) à partir d'une sauvegard"},{"k":"scenario","v":"r_other","l":"Restaurer la sauvegarde sur un autre serveur (migration, test, clone)","m":"Migration et clonage autre serveur serveur de test restore test migration migration clone clone to test rsync server_uuid charger en environnement de test vérification de sauvegarde restore drill ouvrir la sauvegarde migration de serveur Vous voulez ouvrir une sauvegarde de production sur un serveur de test/développement, migrer un serveur ou vérifier sur un serveur séparé que la sauvegarde se res"}]},{"id":"mysql-upgrade","file":"install/guides/mysql-upgrade.json","group":"MySQL","engineId":"mysql","category":"upgrade","title":"Mise à niveau MySQL 8.0 → 8.4 LTS (in-place)","summary":"Met à niveau un MySQL 8.0 installé vers la 8.4 LTS sur le même serveur (in-place) : serveur unique, source + replicas (replicas d'abord, source en dernier) ou InnoDB Cluster (Router → Shell → metadata → secondaries un pa","level":"Intermédiaire","duration":"60–120 min (selon le volume de données)","topology":"1 serveur, source + replicas ou InnoDB Cluster (3+ membres + serveurs MySQL Router)","tags":["mysql","upgrade","8.0","8.4","lts","mise à niveau","checkForServerUpgrade","mysql_native_password","authentication_policy","rollback","retour arrière","innodb cluster","group replication","mysql router","upgradeMetadata","setPrimaryInstance","rolling upgrade"],"order":5,"steps":18,"scenarios":[]},{"id":"mysql-repl-ops","file":"install/guides/mysql-repl-ops.json","group":"MySQL","engineId":"mysql","category":"ops","title":"Réplication MySQL : exploitation courante et scénarios de panne (8.0 / 8.4)","summary":"Runbook d'exploitation courante et de reprise après panne pour une topologie source + replica existante (async / semi-sync, GTID ou position binlog) : contrôle de santé et du retard, switchover planifié, failover, réinté","level":"Avancé","duration":"5 min (contrôle) – 2 heures (reconstruction)","topology":"1 source + 1..5 replicas (async / semi-sync)","tags":["mysql","réplication","replication","gtid","errant gtid","errant transaction","switchover","failover","replica","source","super_read_only","semi-sync","show replica status","seconds_behind_source","retard de réplication","lag","1062","1032","1236","duplicate entry","can't find record","gtid_next","sql_replica_skip_counter","source_delay","replica retardé","delayed replica","clone","xtrabackup","mysqldump","purge binary logs","binlog_expire_logs_seconds","wait_for_executed_gtid_set","change replication source to","runbook","8.0","8.4"],"order":12,"steps":5,"scenarios":[{"k":"scenario","v":"health","l":"Contrôle de l'état et du retard (la réplication est-elle saine ?)","m":"Supervision et contrôles SHOW REPLICA STATUS SHOW SLAVE STATUS Seconds_Behind_Source Seconds_Behind_Master Replica_IO_Running Slave_IO_Running Replica_SQL_Running: No retard de réplication replication lag replica lag Rpl_semi_sync_source_status 1045 2003 2026 Error connecting to source état de la réplication Replica_IO_Running: No Replica_IO_Running: Connecting Slave_IO_Running: No Slave_SQL_Runni"},{"k":"scenario","v":"switchover","l":"Switchover planifié (transférer le rôle de source à un replica)","m":"Changement de rôle switchover bascule planifiée changement de rôle role change promote replica changer de source maintenance maintenance planifiée graceful takeover graceful-master-takeover remplacer la source changer de primaire change primary nouvelle source La source et les replicas sont sains ; vous transférez de façon planifiée le rôle de source à un replica pour une maintenance, un remplacem"},{"k":"scenario","v":"failover","l":"Source perdue : failover vers le replica le plus avancé","m":"Changement de rôle failover source tombée source down master down primary down Error connecting to source Can't connect to MySQL server 2003 2013 Lost connection replica le plus avancé most advanced replica Replica_IO_Running: Connecting Slave_IO_Running: Connecting 2005 Unknown MySQL server host error reconnecting to source source injoignable source arrêtée primaire tombé serveur planté server cr"},{"k":"scenario","v":"rejoin","l":"Réintégrer l'ancienne source comme replica après un failover","m":"Changement de rôle ancienne source old source old master rejoin réintégrer errant split brain 1236 ancien primaire old primary ancien master revenu split-brain Replica has more GTIDs than the source has Slave has more GTIDs than the master has réintégration reintegrate Un failover a eu lieu et l'ancien serveur source est revenu. Il est d'abord fermé aux écritures et ses binlogs sont copiés, puis o"},{"k":"scenario","v":"rebuild","l":"Reconstruire un replica de zéro (clone / XtraBackup / mysqldump)","m":"Reprise 1236 Cannot replicate because the source purged required binary logs purged required binary logs reconstruire rebuild replica reseed clone instance nouveau replica divergence des données data drift Could not find first log file name in binary log index file the source has purged binary logs containing GTIDs that the replica requires the master has purged binary logs containing GTIDs that t"},{"k":"scenario","v":"errant","l":"GTID errant : le replica contient des transactions absentes de la source","m":"Reprise errant gtid errant transaction gtid_subtract extra gtid gtid en trop Having extra GTID events incompatible GTID set 1236 GTID_SUBTRACT écart gtid_executed écrit sur le replica write on replica super_read_only désactivé Replica has more GTIDs than the source has Le replica contient des transactions écrites directement sur lui (pendant que super_read_only était désactivé) ou héritées d'une a"},{"k":"scenario","v":"sqlerr","l":"Réplication arrêtée : 1062 Duplicate entry / 1032 Can't find record","m":"Reprise 1062 1032 1452 1146 1007 1008 Duplicate entry Can't find record HA_ERR_KEY_NOT_FOUND HA_ERR_FOUND_DUPP_KEY Could not execute Write_rows event Could not execute Update_rows event Could not execute Delete_rows event Replica_SQL_Running: No Slave_SQL_Running: No Coordinator stopped because there were error(s) in the worker(s) sql_slave_skip_counter sql_replica_skip_counter gtid_next MY-010584"},{"k":"scenario","v":"delayed","l":"Replica retardé (SOURCE_DELAY) : le mettre en place ou récupérer des données supprimées","m":"Exploitation courante SOURCE_DELAY MASTER_DELAY delayed replica replica retardé supprimé par erreur DROP TABLE DELETE erroné SQL_Delay SQL_BEFORE_GTIDS SQL_Remaining_Delay DELETE accidentel DELETE sans WHERE accidental delete accidental drop dropped table table supprimée récupération de données UNTIL SQL_BEFORE_GTIDS Vous voulez maintenir volontairement un replica N secondes en retard (définir / s"},{"k":"scenario","v":"repoint","l":"Déplacer un replica vers une autre source (chaînage / reconnexion)","m":"Exploitation courante repoint change source réplication chaînée chained replication intermediate replica intermédiaire relay replica CHANGE MASTER TO CHANGE REPLICATION SOURCE TO cascading replication réplication en cascade connecter le replica à une autre source changer SOURCE_HOST changer MASTER_HOST log_replica_updates Vous voulez rattacher un replica sous un autre replica (chaîne / replica int"},{"k":"scenario","v":"binlog","l":"Purge des binlogs et durée de conservation (disque qui se remplit)","m":"Exploitation courante PURGE BINARY LOGS PURGE MASTER LOGS binlog_expire_logs_seconds expire_logs_days disque binlog plein No space left on device errno 28 purge des binlogs binlog retention Disk is full writing OS errno 28 1867 was not purged because it was being read 1373 Target log not found in binlog index binlog qui grossit binlog_expire_logs_auto_purge binlog_transaction_compression Les binlo"}]},{"id":"mysql-ic-ops","file":"install/guides/mysql-ic-ops.json","group":"MySQL","engineId":"mysql","category":"ops","title":"MySQL InnoDB Cluster : exploitation courante et scénarios de panne (MySQL Shell 8.4)","summary":"Pas une installation : runbook par scénarios pour l'exploitation courante et la reprise après panne d'un InnoDB Cluster déjà en service (Group Replication + MySQL Router, mis en place avec l'AdminAPI de MySQL Shell). Cho","level":"Avancé","duration":"5 min (contrôle) – 1 heure (ajout de membre)","topology":"3–9 nœuds MySQL (single-primary) + MySQL Router","tags":["mysql","innodb cluster","group replication","mysql shell","mysqlsh","adminapi","cluster.status","addinstance","removeinstance","rejoininstance","rebootclusterfromcompleteoutage","forcequorumusingpartitionof","setprimaryinstance","setoption","setinstanceoption","rescan","mysql router","bootstrap","quorum","no_quorum","split brain","expelTimeout","memberWeight","autoRejoinTries","exitStateAction","consistency","clone","MISSING","UNREACHABLE","RECOVERING","ERROR","runbook","8.4"],"order":13,"steps":5,"scenarios":[{"k":"scenario","v":"health","l":"État du cluster et contrôle de santé (tout va-t-il bien ?)","m":"Supervision et contrôle cluster.status status() OK_PARTIAL OK_NO_TOLERANCE OK_NO_TOLERANCE_PARTIAL instanceErrors replicationLag flow control RECOVERING applier queue état du cluster Cluster is ONLINE and can tolerate can tolerate up to ONE failure contrôle de santé health check file qui grossit replication_group_members group_replication_member_stats état de santé du cluster Lecture seule : `clus"},{"k":"scenario","v":"add","l":"Ajouter un nouveau membre (addInstance : clone / incremental)","m":"Membres addInstance ajouter un membre ajouter un nœud add instance recoveryMethod clone incremental 3092 Having extra GTID events MYSQLSH 51166 The target instance must be either cloned or fully provisioned Clone Donor Error augmenter la capacité augmenter la tolérance OK_NO_TOLERANCE Vous voulez ajouter un nouveau nœud MySQL au cluster, augmenter la tolérance (3 → 5) ou réintégrer un nœud retiré."},{"k":"scenario","v":"remove","l":"Retirer un membre (removeInstance, force si nécessaire)","m":"Membres removeInstance retirer un membre retirer un nœud remove instance force MISSING decommission Cluster.removeInstance is not reachable and cannot be safely removed supprimer un nœud réduire restitution du serveur Vous voulez retirer définitivement un nœud du cluster (restitution de matériel, réduction), ou le nœud est perdu et doit être supprimé des métadonnées (`force`). Le retrait d'un seco"},{"k":"scenario","v":"rejoin","l":"Réintégrer un membre tombé (MISSING / ERROR / OFFLINE)","m":"Panne et reprise rejoinInstance MISSING ERROR OFFLINE UNREACHABLE expelled exclu du groupe member was expelled MY-011735 MY-011526 incompatible GTID set (MISSING) This member has more executed transactions than those present in the group has an incompatible GTID set with the seed instance Error connecting to all peers Member join failed group_replication is stopped membre tombé membre OFFLINE memb"},{"k":"scenario","v":"reboot","l":"Tout le cluster est arrêté : le redémarrer (rebootClusterFromCompleteOutage)","m":"Panne et reprise rebootClusterFromCompleteOutage complete outage arrêt complet tous les nœuds arrêtés coupure électrique Group Replication is not active OFFLINE Unable to find a cluster PRIMARY member The cluster is offline Dba.rebootClusterFromCompleteOutage Dba.getCluster: This function is not available through a session to a standalone instance metadata exists, instance belongs to that metadata"},{"k":"scenario","v":"quorum","l":"Perte de la majorité (NO_QUORUM) : forcer le quorum avec les membres restants","m":"Panne et reprise NO_QUORUM forceQuorumUsingPartitionOf quorum perte de majorité split-brain split brain UNREACHABLE majority partition 3100 Cluster has no quorum cannot process write transactions split-brain! Instance is not part of the majority group écritures bloquées writes hang forceQuorum La plupart des membres ont disparu en même temps (p. ex. 2 sur 3), les membres restants ont arrêté les éc"},{"k":"scenario","v":"switch","l":"Changement planifié de primary (setPrimaryInstance)","m":"Rôles et routage setPrimaryInstance changer de primary switchover transférer le primary The appointed primary member has a version that is greater group_replication_set_as_primary Cluster.setPrimaryInstance runningTransactionsTimeout changement planifié de primary déplacer le primary planned switchover Pour une maintenance (patch OS, matériel), l'équilibrage de charge ou un exercice de DR, vous vo"},{"k":"scenario","v":"router","l":"Ajouter un Router ou refaire son bootstrap","m":"Rôles et routage mysqlrouter MySQL Router bootstrap --force routerErrors re-bootstrap Router listRouters removeRouterMetadata 6446 6447 previously configured in this host Unable to connect to the metadata server Unable to fetch all configuration options The provided server is currently not in a InnoDB cluster group with quorum mysqlrouter.service failed Router ne se connecte pas installation de Ro"},{"k":"scenario","v":"options","l":"Modifier les options du cluster (consistency, expelTimeout, memberWeight…)","m":"Configuration setOption setInstanceOption consistency BEFORE_ON_PRIMARY_FAILOVER expelTimeout group_replication_member_expel_timeout memberWeight autoRejoinTries exitStateAction OFFLINE_MODE READ_ONLY options() group_replication_consistency group_replication_member_weight group_replication_autorejoin_tries group_replication_exit_state_action membre souvent exclu priorité de failover Vous voulez ré"},{"k":"scenario","v":"rescan","l":"Aligner les métadonnées sur le groupe réel (rescan)","m":"Configuration rescan metadata is not managed not registered in the metadata newlyDiscoveredInstances unavailableInstances instanceErrors obsolete Use cluster.rescan() Instance is not managed by InnoDB cluster Use cluster.rescan() to repair métadonnées incohérentes métadonnées divergentes `cluster.status()` mentionne un membre absent des métadonnées ou un ancien enregistrement qui ne fait plus part"}]},{"id":"mysql-logical-backup","file":"install/guides/mysql-logical-backup.json","group":"MySQL","engineId":"mysql","category":"backup","title":"Sauvegarde logique et restauration MySQL (mysqldump, MySQL Shell)","summary":"Sauvegarde logique et restauration avec mysqldump ou MySQL Shell (util.dumpInstance / dumpSchemas / dumpTables + util.loadDump) : une table, un schéma ou toute l'instance ; structure seule ou données seules ; migration v","level":"Intermédiaire","duration":"20 à 90 min (selon le volume de données)","topology":"1 serveur (+ un serveur cible pour la migration et la restauration d'instance)","tags":["mysql","mysqldump","mysql shell","mysqlsh","dumpInstance","dumpSchemas","dumpTables","loadDump","sauvegarde logique","logical backup","export","import","restauration","restore","migration","migration","login-path","mysql_config_editor","systemd timer","cron","sauvegarde","replica","dump-replica","source-data","innodb cluster","cron","systemd timer","flock","login-path"],"order":15,"steps":8,"scenarios":[{"k":"mode","v":"table","l":"Export / import de table(s)","m":"Sauvegarde et restauration sauvegarde de table table export copier une table copy table mysqldump table Sauvegarder une ou plusieurs tables, les copier vers un autre schéma/serveur ou les restaurer. La source reste disponible ; aucun verrou n'est pris sur les tables InnoDB."},{"k":"mode","v":"schema","l":"Sauvegarder / restaurer un schéma (base de données)","m":"Sauvegarde et restauration sauvegarde de schéma sauvegarde de base de données database backup dumpSchemas mysqldump database Sauvegarder une base de données (schéma) entière ou la charger dans un autre schéma/serveur. La source reste disponible ; sur la cible, les tables de même nom sont supprimées puis recréées."},{"k":"mode","v":"instance","l":"Instance complète (utilisateurs + tous les schémas)","m":"Sauvegarde et restauration toutes les bases de données all databases dumpInstance migrer les utilisateurs full logical backup La sauvegarde est prise sur ce serveur et la restauration se fait sur un serveur vide : utilisateurs, privilèges et tous les schémas. Pour reconstruire un serveur ou changer de version/plateforme."},{"k":"mode","v":"ddl","l":"Structure seule (DDL)","m":"Structure et données séparément structure seule schema only no-data ddlOnly CREATE TABLE export Définitions des tables, vues, triggers, procédures et events ; pas de données. Pour comparer des schémas, monter un environnement vide ou mettre la structure sous gestion de versions."},{"k":"mode","v":"data","l":"Données seules","m":"Structure et données séparément données seules data only no-create-info dataOnly transfert de données Seules les lignes sont transférées, la structure existant déjà sur la cible (par ex. pour rafraîchir des données de test). Les triggers de la cible peuvent se déclencher pendant le chargement ; le guide le vérifie."},{"k":"mode","v":"migrate","l":"Migration vers un autre serveur","m":"Migration migration migration remplacement de serveur migrer vers un nouveau serveur 8.0 to 8.4 migration dump and load migrer vers HeatWave Vous travaillez sur le serveur cible et vous vous connectez à la source par le réseau avec un login-path. Au moment de la bascule, l'application cesse d'écrire ; l'interruption dure le temps du dump + du chargement. Pour changer de serveur, de système d'explo"},{"k":"mode","v":"extract","l":"Extraire une seule table d'un dump complet","m":"Récupération restaurer une seule table extraire une table d'un dump extract table from dump annuler un DROP TABLE lignes supprimées ERROR 1146 Table doesn't exist Vous disposez d'un dump complet et n'avez besoin que d'une table (lignes supprimées, table supprimée par DROP). Elle est extraite dans un schéma séparé sans toucher au schéma d'origine, ou la table supprimée est remise en place."},{"k":"mode","v":"sched","l":"Sauvegarde planifiée (systemd timer / cron)","m":"Sauvegarde planifiée sauvegarde planifiée scheduled backup cron mysqldump systemd timer sauvegarde automatique sauvegarde de nuit Sauvegarde logique automatique chaque jour à heure fixe : utilisateur de sauvegarde dédié, login-path, script, systemd timer ou cron, durée de rétention et notification d'erreur. La source reste disponible."}]},{"id":"mysql-security","file":"install/guides/mysql-security.json","group":"MySQL","engineId":"mysql","category":"security","title":"Durcissement de la sécurité MySQL (audit d'état, réseau, TLS, authentification, privilèges, audit, chiffrement)","summary":"Ce n'est pas un guide d'installation : il détecte et corrige les failles d'un serveur MySQL 8.4 / 8.0 en service. D'abord, l'audit d'état, en lecture seule, liste les failles par ordre de gravité et indique pour chaque c","level":"Intermédiaire","duration":"5 min (audit) – 3 heures (durcissement complet)","topology":"Un serveur MySQL + un serveur d'application (vérification côté client) ; replica/InnoDB Cluster dans les notes","tags":["mysql","sécurité","security","hardening","durcissement","cis benchmark","tls","ssl","require_secure_transport","verify_identity","caching_sha2_password","mysql_native_password","validate_password","audit_log","audit","percona","tde","keyring","innodb encryption","root","utilisateur anonyme","privilèges","rôle","login-path"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"Audit d'état : quelles sont mes failles ? (lecture seule)","m":"Pour commencer contrôle de sécurité security audit hardening check CIS faille de sécurité vulnérabilité vulnerability constat de pentest mysql_secure_installation requête d'audit évaluation de sécurité security scan CRITIQUE Le scan à lancer en premier : comptes anonymes, sans mot de passe ou root distant, accès depuis n'importe où (%), plugin d'authentification faible, privilèges globaux, TLS, au"},{"k":"scenario","v":"all","l":"Durcissement complet (tout dans l'ordre)","m":"Pour commencer hardening durcissement sécurité complète full hardening appliquer le CIS benchmark STIG Serveur fraîchement installé ou jamais durci : tous les domaines sont appliqués dans un ordre sûr ; MySQL est redémarré plusieurs fois (une fenêtre de maintenance est nécessaire)."},{"k":"scenario","v":"net","l":"Réseau et connexions : qui peut se connecter et d'où ? (bind_address, firewall, restriction d'hôte)","m":"Réseau et identité bind_address 0.0.0.0 '%' host '%' firewall 3306 33060 Host is not allowed to connect ERROR 1130 ERROR 2003 Can't connect to MySQL server skip_name_resolve accès externe restreindre l'accès MySQL écoute sur toutes les interfaces, les comptes ont l'hôte '%', le port est ouvert à tous. Modifier bind_address nécessite un redémarrage."},{"k":"scenario","v":"tls","l":"Chiffrer les connexions (TLS, require_secure_transport, VERIFY_IDENTITY)","m":"Réseau et identité ssl tls require_secure_transport ssl-mode VERIFY_IDENTITY VERIFY_CA ERROR 3159 Connections using insecure transport are prohibited server-cert.pem expiration du certificat ERROR 2026 SSL connection error certificate verify failed ERROR 3888 chaîne de certificats certificate chain Le certificat auto-signé généré automatiquement est utilisé, les connexions non chiffrées sont accep"},{"k":"scenario","v":"auth","l":"Authentification et mots de passe (caching_sha2, politique de mots de passe, root)","m":"Réseau et identité mysql_native_password caching_sha2_password validate_password password_history FAILED_LOGIN_ATTEMPTS root distant anonymous user utilisateur anonyme ERROR 1045 Access denied Plugin 'mysql_native_password' is not loaded ERROR 1524 ERROR 1819 ERROR 3955 ERROR 3118 Account is locked ERROR 2061 Authentication requires secure connection auth_socket Comptes mysql_native_password, aucu"},{"k":"scenario","v":"priv","l":"Moindre privilège : rôles et compte applicatif sans DDL","m":"Privilèges et traçabilité GRANT REVOKE ALL PRIVILEGES SUPER FILE least privilege moindre privilège CREATE ROLE ERROR 1142 command denied ERROR 3530 ERROR 1227 you need (at least one of) the SUPER privilege DEFAULT ROLE rapport des privilèges Privilèges CREATE/ALTER/DROP ou *.* sur le compte applicatif, comptes avec FILE/SUPER ; rôles lecture/écriture/DDL et rapport des privilèges. Sans interruptio"},{"k":"scenario","v":"audit","l":"Audit : qui a fait quoi ?","m":"Privilèges et traçabilité audit audit_log audit_log_filter piste d'audit general_log Enterprise Audit Percona audit SIEM RGPD PCI audit_log_filter_set_filter ERROR 1126 licence MySQL Enterprise qui a fait quoi L'auditeur demande la journalisation des sessions et des requêtes. MySQL Community n'a pas d'audit intégré : Enterprise Audit, Audit Log Filter de Percona Server, ou des options limitées pou"},{"k":"scenario","v":"tde","l":"Chiffrer les données au repos (InnoDB tablespace encryption, keyring)","m":"Données et sauvegardes TDE chiffrement au repos ENCRYPTION='Y' keyring component_keyring_file ERROR 3185 Can't find master key from keyring binlog_encryption innodb_redo_log_encrypt ALTER INSTANCE ROTATE INNODB MASTER KEY mysqld.my keyring_component_status master key Chiffrement des tables, du redo, de l'undo et des binlogs contre le vol d'un disque ou d'une copie de sauvegarde ; un redémarrage po"},{"k":"scenario","v":"backup","l":"Sécurité des sauvegardes (sauvegarde chiffrée, login-path, droits)","m":"Données et sauvegardes sauvegarde chiffrée backup encryption mysqldump gpg xtrabackup --encrypt login-path mysql_config_editor mot de passe .my.cnf xbcrypt --encrypt-key-file sauvegarde non chiffrée unencrypted backup Sauvegardes non chiffrées ou dans un répertoire lisible par tous, mot de passe en clair dans les scripts ; mysqldump + GPG ou XtraBackup --encrypt, compte de sauvegarde et login-path"},{"k":"scenario","v":"os","l":"Côté système d'exploitation (compte de service, droits, core dump, SELinux)","m":"Serveur et version SELinux permissive secure_file_priv local_infile core dump compte mysql .mysql_history ERROR 1290 secure-file-priv option ERROR 3948 Loading local data is disabled avc: denied Répertoire de données ou fichiers de certificats accessibles aux autres, secure_file_priv vide, local_infile activé, SELinux désactivé. Certains paramètres ne prennent effet qu'au redémarrage."},{"k":"scenario","v":"patch","l":"Correctifs et versions : suis-je sur une version supportée ?","m":"Serveur et version EOL end of life fin de support MySQL 8.0 EOL CPU Critical Patch Update CVE correctif de sécurité calendrier des correctifs MySQL 8.0 end of life 8.4 LTS Version, fin de support (8.0 s'est terminée en avril 2026) et mises à jour en attente ; renvoie au guide d'upgrade pour leur application. Lecture seule."}]},{"id":"mssql-linux-install","file":"install/guides/mssql-linux-install.json","group":"SQL Server","engineId":"mssql","category":"install","title":"Installation de SQL Server sous Linux (RHEL / Ubuntu, prête pour la production)","summary":"Installe SQL Server 2022 ou 2025 depuis le dépôt Microsoft sur un serveur Linux unique (famille RHEL ou Ubuntu) : choix de l'édition, saisie sécurisée du mot de passe sa, mssql-tools18 (sqlcmd/bcp), répertoires séparés p","level":"Débutant","duration":"40–60 min","topology":"1 serveur","tags":["sql server","mssql","linux","installation","install","mssql-conf","sqlcmd","tempdb","maxdop","rhel","ubuntu","2022","2025"],"order":1,"steps":22,"scenarios":[]},{"id":"mssql-windows-install","file":"install/guides/mssql-windows-install.json","group":"SQL Server","engineId":"mssql","category":"install","title":"Installation de SQL Server 2022 sous Windows (unattended, prête pour la production)","summary":"Installe SQL Server 2022 en mode silencieux (unattended) sur un serveur Windows unique, avec un ConfigurationFile.ini généré à partir de vos choix (fonctionnalités, instance, comptes de service/gMSA, collation, répertoir","level":"Intermédiaire","duration":"45–75 min","topology":"1 Windows Server","tags":["sql server","mssql","windows","installation","install","unattended","configurationfile.ini","setup.exe","gmsa","tempdb","maxdop","ifi","lock pages in memory","2022"],"order":2,"steps":18,"scenarios":[]},{"id":"mssql-ag-windows","file":"install/guides/mssql-ag-windows.json","group":"SQL Server","engineId":"mssql","category":"ha","title":"SQL Server Always On Availability Group (Windows, WSFC)","summary":"Met en place un groupe de disponibilité Always On sur des serveurs Windows où SQL Server 2022 est installé : Windows Server Failover Cluster (WSFC) et témoin de quorum (partage de fichiers / cloud witness), activation d'","level":"Avancé","duration":"90–150 min","topology":"2 à 9 nœuds Windows (WSFC) + témoin (witness)","tags":["sql server","mssql","always on","groupe de disponibilité","ag","wsfc","cluster de basculement","listener","témoin","automatic seeding","hadr","windows","haute disponibilité"],"order":3,"steps":25,"scenarios":[]},{"id":"mssql-ag-linux","file":"install/guides/mssql-ag-linux.json","group":"SQL Server","engineId":"mssql","category":"ha","title":"SQL Server Always On Availability Group (Linux, Pacemaker)","summary":"Met en place, à partir de trois serveurs Linux (RHEL 9 ou Ubuntu 22.04) sur lesquels SQL Server 2022 est installé, un AG Always On géré par Pacemaker : mssql-server-ha, hadr.hadrenabled, points de terminaison de mise en ","level":"Avancé","duration":"120–180 min","topology":"3 nœuds Linux : 3 réplicas synchrones ou 2 synchrones + un réplica configuration-only","tags":["sql server","mssql","always on","groupe de disponibilité","ag","linux","pacemaker","corosync","pcs","stonith","fencing","mssql-server-ha","ocf:mssql:ag","cluster_type external","configuration only","listener","ip virtuelle","rhel","ubuntu","haute disponibilité"],"order":4,"steps":30,"scenarios":[]},{"id":"mssql-cu-patch","file":"install/guides/mssql-cu-patch.json","group":"SQL Server","engineId":"mssql","category":"patch","title":"Appliquer une mise à jour cumulative (CU) SQL Server","summary":"Applique une mise à jour cumulative (CU) à une instance SQL Server 2022/2025 installée, au sein de la même version majeure : avec dnf/apt sous Linux, en installation silencieuse (/quiet) avec le fichier d'installation du","level":"Intermédiaire","duration":"30–90 min","topology":"1 serveur ou un AG (2 réplicas ou plus)","tags":["sql server","mssql","cu","cumulative update","patch","mise à jour","rolling","groupe de disponibilité","always on","yum","apt","kb"],"order":4,"steps":12,"scenarios":[]},{"id":"mssql-ag-ops","file":"install/guides/mssql-ag-ops.json","group":"SQL Server","engineId":"mssql","category":"ops","title":"Exploitation d'un groupe de disponibilité Always On SQL Server (Windows et Linux)","summary":"Pas une installation : exploitation courante et scénarios de panne d'un Always On Availability Group déjà installé : contrôle de santé, diagnostic du retard et de la croissance du journal, failover planifié, failover for","level":"Avancé","duration":"15–90 min (selon le scénario)","topology":"AG de 2 à 9 réplicas (Windows WSFC ou Linux Pacemaker)","tags":["sql server","mssql","always on","availability group","ag","failover","forced failover","force_failover_allow_data_loss","suspend","resume","automatic seeding","read-only routing","listener","pacemaker","pcs","wsfc","secondary lag","log_send_queue","redo_queue","required_synchronized_secondaries_to_commit"],"order":5,"steps":3,"scenarios":[{"k":"op","v":"health","l":"Contrôle de santé de l'AG (requêtes du tableau de bord)","m":"Supervision et diagnostic NOT SYNCHRONIZING NOT_HEALTHY DISCONNECTED RESOLVING PARTIALLY_HEALTHY santé de l'AG availability group health sys.dm_hadr dashboard état du réplica Msg 35206 connection timeout AG dashboard Show Dashboard Tout va-t-il bien ? État des réplicas / bases, préparation au failover, cluster et derniers changements de rôle. Ne modifie rien."},{"k":"op","v":"lag","l":"Le secondaire prend du retard / le fichier journal grossit (diagnostic du retard)","m":"Supervision et diagnostic AVAILABILITY_REPLICA log_reuse_wait log_send_queue redo_queue HADR_SYNC_COMMIT secondary lag secondary_lag_seconds Msg 9002 9002 transaction log is full retard redo blocked journal qui grossit La file d'envoi/de redo grossit, les données du secondaire sont anciennes, le journal du primaire est presque plein (log_reuse_wait = AVAILABILITY_REPLICA) ou les commits sont lents"},{"k":"op","v":"failover","l":"Failover planifié (switchover, sans perte de données)","m":"Changement de rôle planned failover manual failover switchover ALTER AVAILABILITY GROUP FAILOVER pcs resource move Msg 41142 41142 Msg 19407 changer de rôle déplacer le primaire Failover Cluster Manager Move failover de maintenance Déplace le primaire vers un secondaire synchrone et SYNCHRONIZED pour une maintenance, un patch ou un test ; quelques secondes d'interruption, aucune perte de données."},{"k":"op","v":"forced","l":"Primaire perdu : failover forcé (FORCE_FAILOVER_ALLOW_DATA_LOSS)","m":"Changement de rôle FORCE_FAILOVER_ALLOW_DATA_LOSS forced failover failover forcé quorum lost perte de quorum FixQuorum forcequorum disaster recovery DR Msg 41131 Msg 35250 perte de site primary down Msg 47104 ForceQuorum Cluster is running in ForceQuorum state RESOLVING_NORMAL primaire planté perte de données Le primaire (ou tout le site / le quorum du cluster) est perdu et il n'est pas possible d"},{"k":"op","v":"suspend","l":"Suspendre / reprendre le déplacement des données (SUSPEND / RESUME)","m":"Maintenance SUSPENDED SET HADR SUSPEND SET HADR RESUME is_suspended suspend_reason data movement déplacement des données suspendu Msg 35264 35264 Msg 35265 SUSPEND_FROM_REDO SUSPEND_FROM_USER SUSPEND_FROM_PARTNER suspendu suspendre la base Arrêter temporairement l'envoi de données vers un secondaire (maintenance réseau, traitement de masse) ou reprendre une base restée suspendue (SUSPENDED)."},{"k":"op","v":"mode","l":"Changer le mode synchrone / asynchrone","m":"Maintenance SYNCHRONOUS_COMMIT ASYNCHRONOUS_COMMIT availability mode REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT required_synchronized_secondaries_to_commit synchrone asynchrone Msg 35211 35211 Msg 35212 35212 FAILOVER_MODE Passer un réplica de SYNCHRONOUS_COMMIT à ASYNCHRONOUS_COMMIT ou l'inverse (site distant, réseau lent, exercice DR) et régler la protection des commits (REQUIRED_SYNCHRONIZED_S"},{"k":"op","v":"adddb","l":"Ajouter une base à l'AG (automatic seeding ou sauvegarde/restauration)","m":"Modification ADD DATABASE automatic seeding SET HADR AVAILABILITY GROUP Msg 35250 Msg 1478 1478 seeding failed ajouter une base Msg 1475 1475 might contain bulk logged changes manual seeding Intégrer une base nouvelle ou existante à l'AG : automatic seeding pour les bases petites/moyennes, à partir d'une sauvegarde pour les grosses bases ou sur un réseau lent."},{"k":"op","v":"addrep","l":"Ajouter un réplica (nœud)","m":"Modification ADD REPLICA Add-ClusterNode pcs cluster node add nouveau réplica ajouter un réplica Msg 41158 41158 Failed to join local availability replica JOIN Ajouter un nouveau secondaire à l'AG : le nœud rejoint le cluster, l'endpoint est préparé, ADD REPLICA + JOIN, puis les bases arrivent par seeding."},{"k":"op","v":"removerep","l":"Retirer un réplica (nœud)","m":"Modification REMOVE REPLICA Remove-ClusterNode pcs cluster node remove retirer un réplica decommission Msg 35223 35223 retirer un nœud Retirer de l'AG et du cluster un secondaire qui sera définitivement arrêté ou reconstruit."},{"k":"op","v":"routing","l":"Routage en lecture seule (read-only routing)","m":"Connectivité read-only routing READ_ONLY_ROUTING_URL READ_ONLY_ROUTING_LIST ApplicationIntent=ReadOnly readable secondary secondaire lisible Msg 978 978 Msg 976 976 not accessible for queries Msg 10786 ApplicationIntent Router les connexions ApplicationIntent=ReadOnly via le listener vers les secondaires accessibles en lecture (décharger le primaire du reporting)."},{"k":"op","v":"listener","l":"Créer ou modifier le listener (IP, port)","m":"Connectivité listener ADD LISTENER MODIFY LISTENER MultiSubnetFailover RegisterAllProvidersIP Msg 19471 19471 Msg 41009 virtualip Msg 19456 19456 HostRecordTTL port du listener enregistrement DNS Créer le listener s'il n'existe pas, changer son port ou (sous Windows) ajouter une IP pour un second sous-réseau."}]},{"id":"mssql-backup","file":"install/guides/mssql-backup.json","group":"SQL Server","engineId":"mssql","category":"backup","title":"SQL Server : stratégie de sauvegarde, automatisation et restauration","summary":"Met en place les sauvegardes d'une instance SQL Server installée (Linux ou Windows ; instance unique, FCI ou réplicas d'AG) et restaure à partir de ces sauvegardes. Sauvegarde : choix du mode de récupération, full hebdom","level":"Intermédiaire","duration":"30–90 min","topology":"1 serveur, Failover Cluster Instance ou réplicas d'AG Always On (restauration sur un autre serveur : source + cible)","tags":["sql server","mssql","backup","sauvegarde","sauvegarder base","restore","restauration","pitr","stopat","point in time","differential","transaction log","copy_only","compression","checksum","encryption","chiffrement","sql server agent","job","ola hallengren","maintenance solution","retention","verifyonly","headeronly","filelistonly","norecovery","standby","with move","task scheduler","cron","availability group","always on","backup preference","automated_backup_preference","fn_hadr_backup_is_preferred_replica","copy_only","sql agent","sysjobhistory","cron","task scheduler","page restore","suspect_pages","824","823","33111","18456","orphaned user","sp_help_revlogin","master restore","single user","msdb restore","model restore","stopatmark","tail-log","no_truncate","recovery_pending","suspect","log chain","lsn"],"order":15,"steps":11,"scenarios":[{"k":"mode","v":"strategy","l":"Mettre en place une stratégie de sauvegarde (sauvegardes planifiées + rétention)","m":"Sauvegarde plan de sauvegarde backup strategy fichier journal qui grossit LOG_BACKUP Msg 9002 transaction log is full sql agent job Msg 3013 3013 BACKUP DATABASE is terminating abnormally sauvegardes non exécutées job de sauvegarde Nouveau serveur ou base de données sans sauvegardes régulières : mise en place des jobs full / différentielle / journal, de la rétention et de la supervision."},{"k":"mode","v":"oneoff","l":"Prendre une sauvegarde ponctuelle (p. ex. COPY_ONLY avant un changement)","m":"Sauvegarde copy_only sauvegarde ponctuelle sauvegarde avant changement sauvegarde manuelle Msg 4214 4214 Msg 4208 4208 BACKUP LOG is not allowed while the recovery model is SIMPLE Une seule sauvegarde avant un changement, un patch ou un upgrade, ou pour une copie vers un environnement de test ; COPY_ONLY ne casse pas la chaîne."},{"k":"mode","v":"verify","l":"Vérifier les sauvegardes (historique, VERIFYONLY, restauration de test)","m":"Répétition et contrôles VERIFYONLY HEADERONLY FILELISTONLY test restore sauvegarde corrompue Msg 3241 Msg 3183 Msg 3203 Pour prouver que les sauvegardes sont réellement restaurables : historique des sauvegardes, VERIFYONLY, HEADERONLY / FILELISTONLY et restauration de test dans une base temporaire + CHECKDB."},{"k":"mode","v":"chain","l":"La chaîne de sauvegarde est-elle intacte ? (trou de LSN, fichier manquant)","m":"Répétition et contrôles Msg 4305 4305 too recent to apply LSN log chain chaîne rompue broken log chain Msg 3136 last_log_backup_lsn chaîne de sauvegarde Avant une restauration ou lors d'un audit régulier : reconstitue la chaîne à partir de l'historique msdb ; détecte les trous de LSN, les fichiers absents du disque et les sauvegardes intercalées. Ne modifie rien."},{"k":"mode","v":"restore","l":"Restaurer une base de données à partir d'une sauvegarde (chaîne, STOPAT / STOPATMARK, MOVE / REPLACE, autre serveur)","m":"Restauration et retour dans le temps STOPAT STOPATMARK STOPBEFOREMARK point in time PITR supprimé par erreur annuler un DELETE WITH MOVE REPLACE tail-log Msg 3154 Msg 3159 Msg 5133 Msg 1834 Msg 3156 The tail of the log for the database Msg 3101 3101 Exclusive access could not be obtained Msg 4335 4335 RESTORE DATABASE Des données ont été supprimées ou mal mises à jour, une copie de la base est néc"},{"k":"mode","v":"damaged","l":"La base de données ne s'ouvre pas : fichier de données manquant ou endommagé (tail-log + restauration)","m":"Perte / corruption Msg 945 945 RECOVERY_PENDING recovery pending SUSPECT Msg 5120 5120 Msg 17204 Msg 17207 Msg 5181 FCB::Open failed Operating system error 2 mdf supprimé fichier de données manquant database cannot be opened Msg 823 NO_TRUNCATE CONTINUE_AFTER_ERROR EMERGENCY La base est RECOVERY_PENDING ou SUSPECT, le .mdf/.ndf est illisible mais l'instance fonctionne : prenez d'abord une sauvegar"},{"k":"mode","v":"page","l":"Quelques pages corrompues (Msg 823 / 824) : page restore","m":"Perte / corruption Msg 824 824 Msg 823 823 Msg 829 829 suspect_pages torn page incorrect checksum page corrompue page corruption page restore CHECKDB found Msg 8928 Msg 8939 Msg 8976 repair_allow_data_loss Des requêtes ou CHECKDB échouent sur des pages précises (msdb.dbo.suspect_pages) : seules les pages endommagées sont restaurées depuis la sauvegarde. En Enterprise/Developer la base reste en lig"},{"k":"mode","v":"master","l":"Restaurer la base de données master (mode mono-utilisateur)","m":"Bases de données système master Msg 3108 3108 single user mode single-user mode mono-utilisateur -m Msg 3417 3417 Msg 17113 Msg 18461 master corrompue force-setup REBUILDDATABASE master endommagée ou perte de logins / linked servers / paramètres serveur : l'instance est démarrée en mode mono-utilisateur (-m), master est restaurée, puis SQL Server s'arrête de lui-même. Une interruption de service e"},{"k":"mode","v":"sysdb","l":"Restaurer la base de données msdb ou model","m":"Bases de données système msdb model Msg 3168 3168 jobs disparus msdb suspect Msg 1807 msdb corrompue model corrompue Jobs, historique des sauvegardes, Database Mail (msdb) ou modèle des nouvelles bases (model) endommagés / perdus : restauration WITH REPLACE depuis une sauvegarde prise sur le même build ; Agent est arrêté brièvement."},{"k":"mode","v":"orphan","l":"Les utilisateurs ne peuvent plus se connecter après une restauration (orphaned users, mappage des logins)","m":"Migration et accès Msg 18456 18456 Login failed for user orphaned user orphan utilisateur orphelin sp_help_revlogin sp_change_users_login Msg 15023 15023 Msg 916 Msg 4060 mappage des logins SID La base a été déplacée ou restaurée sur un autre serveur et l'application reçoit 'Login failed' : les logins sont transférés depuis la source avec leur SID et le hash du mot de passe, les utilisateurs resta"},{"k":"mode","v":"enc","l":"La sauvegarde chiffrée ne se restaure pas (certificat absent, Msg 33111)","m":"Migration et accès Msg 33111 33111 Cannot find server certificate with thumbprint sauvegarde chiffrée encrypted backup certificat certificate TDE Msg 15581 15581 master key La sauvegarde (ou la sauvegarde d'une base TDE) est chiffrée par un certificat absent de ce serveur : identifier le thumbprint, charger le certificat avec sa clé privée, vérifier la sauvegarde. Les bases existantes ne sont pas "}]},{"id":"mssql-bcp-export","file":"install/guides/mssql-bcp-export.json","group":"SQL Server","engineId":"mssql","category":"backup","title":"Export/import table par table sous SQL Server (bcp, sqlpackage)","summary":"Ce n'est pas une sauvegarde mais un transfert logique de données : export de tables avec bcp et chargement dans une autre base (format natif ou caractère, fichier de format, batch, TABLOCK, colonne d'identité, UTF-8), éc","level":"Intermédiaire","duration":"20–60 min","topology":"1 serveur (les commandes s'exécutent sur le SQL Server lui-même ou sur un serveur d'administration)","tags":["sql server","mssql","bcp","bulk copy","export","import","queryout","fichier de format","natif","csv","utf-8","sqlpackage","bacpac","dacpac","extract","publish","azure sql","transfert de données","migration","copier une table"],"order":16,"steps":8,"scenarios":[{"k":"op","v":"table","l":"Déplacer des tables : bcp out → bcp in","m":"Transfert de données bcp bcp out bcp in bulk copy copier une table déplacer une table Unexpected EOF encountered in BCP data-file Unable to open BCP host data-file Copier une ou plusieurs tables vers une autre base SQL Server ; les tables doivent déjà exister sur la cible."},{"k":"op","v":"query","l":"Écrire le résultat d'une requête dans un fichier : bcp queryout (CSV/texte)","m":"Export queryout csv export csv résultat de requête vers fichier export vers fichier Écrire le résultat d'une requête dans un fichier CSV/texte destiné à un autre système (Excel, ETL, reporting)."},{"k":"op","v":"bacpac","l":"Schéma + données : sqlpackage Export / Import (.bacpac)","m":"Transfert de données bacpac sqlpackage SqlPackage Export SqlPackage Import migration Azure SQL Database SQL71564 SQL71501 Transférer schéma et données dans un seul fichier ; en particulier pour une migration vers Azure SQL Database ou pour des bases petites/moyennes."},{"k":"op","v":"dacpac","l":"Schéma seul : sqlpackage Extract / Script / Publish (.dacpac)","m":"Schéma dacpac Extract Publish comparaison de schéma schema compare BlockOnPossibleDataLoss The schema update is terminating because data loss might occur Extraire uniquement le schéma, le comparer à la cible et générer un script de modification, ou déployer un schéma vide dans un nouvel environnement."}]},{"id":"mssql-security","file":"install/guides/mssql-security.json","group":"SQL Server","engineId":"mssql","category":"security","title":"Durcissement de la sécurité SQL Server (Linux et Windows : évaluation, réseau, TLS, authentification, autorisations, audit, TDE)","summary":"Ce n'est pas un guide d'installation : il trouve et corrige les failles d'une instance SQL Server 2019 / 2022 / 2025 en service (Linux ou Windows). D'abord, une Évaluation de sécurité en lecture seule liste les failles p","level":"Intermédiaire","duration":"5 min (évaluation) – 3 heures (durcissement complet)","topology":"Instance SQL Server unique (Linux ou Windows) + serveur d'application ; Always On AG dans les notes","tags":["sql server","mssql","sécurité","security","hardening","durcissement","cis benchmark","stig","force encryption","tls","certificat","sa","check_policy","xp_cmdshell","trustworthy","server audit","audit","tde","transparent data encryption","backup encryption","mssql-conf","windows","linux"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"Évaluation de sécurité : quelles sont mes failles ? (lecture seule)","m":"Débutant évaluation de sécurité security audit hardening check CIS STIG faille de sécurité vulnérabilité vulnerability constat de pentest SQL Vulnerability Assessment requête d'audit contrôle de sécurité security scan CRITIQUE Le contrôle à lancer en premier : sa, stratégie de mot de passe, mots de passe vides, membres sysadmin, autorisations de public/guest, xp_cmdshell, connexions non chiffrées,"},{"k":"scenario","v":"all","l":"Durcissement complet (tout dans l'ordre)","m":"Débutant hardening durcissement sécurité complète full hardening appliquer le CIS benchmark STIG Instance nouvellement installée ou jamais durcie : tous les domaines sont appliqués dans un ordre sûr ; SQL Server est redémarré une ou deux fois (fenêtre de maintenance nécessaire)."},{"k":"scenario","v":"net","l":"Réseau et connectivité : qui peut se connecter, et d'où ? (écoute, firewall, Browser)","m":"Réseau et identité 1433 1434 SQL Browser firewall ListenOnAllIPs network.ipaddress Error 53 Error 40 TCP/IP Error 26 Error locating Server/Instance Specified Login timeout expired A network-related or instance-specific error accès externe restreindre l'accès SQL Server écoute sur toutes les interfaces, 1433 est ouvert à tous, SQL Browser tourne sans nécessité. Changer l'adresse d'écoute impose un "},{"k":"scenario","v":"tls","l":"Chiffrer les connexions (Force Encryption, certificat, validation côté client)","m":"Réseau et identité Force Encryption TrustServerCertificate Encrypt=Mandatory certificate chain was issued by an authority that is not trusted SSL Provider error 0x80090325 tlscert expiration du certificat TDS 8.0 strict The target principal name is incorrect certificate verify failed Encrypt=Strict Force Strict Encryption Unable to load user-specified certificate chaîne de certificats certificate "},{"k":"scenario","v":"auth","l":"Authentification et mots de passe (sa, CHECK_POLICY, authentification Windows)","m":"Réseau et identité sa CHECK_POLICY CHECK_EXPIRATION mixed mode Login failed for user Error 18456 Error 18486 account is locked out stratégie de mot de passe password policy PWDCOMPARE Error 18452 Login is from an untrusted domain Error 18470 The account is disabled Msg 15118 Msg 33062 sa_disabled Windows Authentication sa activé, pas de stratégie de mot de passe sur les logins SQL, mots de passe v"},{"k":"scenario","v":"priv","l":"Moindre privilège et surface d'attaque (rôles, public, guest, xp_cmdshell)","m":"Autorisations et traçabilité db_owner sysadmin public guest xp_cmdshell TRUSTWORTHY CONTROL SERVER least privilege moindre privilège Msg 262 Msg 229 permission was denied Msg 15247 Msg 916 db_ddladmin EXECUTE AS rapport des autorisations L'utilisateur applicatif est db_owner ou sysadmin, autorisations en trop sur public/guest, xp_cmdshell/TRUSTWORTHY activés ; conception des rôles et rapport des a"},{"k":"scenario","v":"audit","l":"Audit : qui a fait quoi ? (Server Audit, Database Audit Specification)","m":"Autorisations et traçabilité SQL Server Audit server audit audit specification fn_get_audit_file C2 audit login auditing piste d'audit SIEM RGPD PCI sqlaudit Server Audit Specification Database Audit Specification Msg 33072 qui a fait quoi L'auditeur veut une trace des connexions, des changements d'autorisations et de schéma ; Server Audit vers un fichier (ou le journal des événements Windows), tr"},{"k":"scenario","v":"tde","l":"Chiffrer les données au repos (TDE)","m":"Données et sauvegardes TDE transparent data encryption database encryption key DEK service master key Msg 33111 Cannot find server certificate with thumbprint encryption at rest Msg 15208 ENCRYPTION_IN_PROGRESS percent_complete Msg 15581 sauvegarde du certificat TDE contre l'ouverture sur un autre serveur d'un disque, d'un fichier de données ou d'une sauvegarde : DMK, certificat, DEK. Se fait en l"},{"k":"scenario","v":"backup","l":"Sécurité des sauvegardes (sauvegarde chiffrée, certificat, droits)","m":"Données et sauvegardes backup encryption chiffrement des sauvegardes WITH ENCRYPTION BACKUP CERTIFICATE droits sur les sauvegardes Msg 33111 Msg 15151 RESTORE VERIFYONLY key_algorithm Msg 15240 Sauvegardes non chiffrées, répertoire de sauvegarde ouvert à tous, mot de passe en clair dans les scripts ; BACKUP … WITH ENCRYPTION, sauvegarde du certificat et droits sur le répertoire. Pas d'interruption"},{"k":"scenario","v":"os","l":"Côté système d'exploitation (compte de service, droits sur les fichiers, SELinux)","m":"Serveur et version service account compte de service sql server LocalSystem gMSA virtual account icacls SELinux utilisateur mssql NT Service\\MSSQLSERVER mssql-server-selinux unconfined_service_t Configuration Manager Log On Le service tourne sous LocalSystem ou un compte administrateur, les répertoires de données/sauvegardes sont ouverts à tous, SELinux est désactivé ; changer de compte de service"},{"k":"scenario","v":"patch","l":"Correctifs et version : suis-je sur une version prise en charge et sur le dernier CU ?","m":"Serveur et version CU cumulative update GDR EOL end of support fin de support CVE mise à jour de sécurité ProductUpdateLevel ProductUpdateReference Latest updates calendrier des correctifs Version, niveau de CU et fin de support (2016 terminé en juillet 2026) ; renvoie au guide de patch CU pour l'application. Lecture seule."}]},{"id":"mongo-replset","file":"install/guides/mongo-replset.json","group":"MongoDB","engineId":"mongodb","category":"ha","title":"Installation d'un replica set MongoDB (3 membres, keyfile et autorisation)","summary":"Construit de zéro un replica set MongoDB 8.0 (ou 7.0) à trois membres : dépôt officiel, répertoire de données XFS, cache WiredTiger dimensionné selon la RAM, authentification entre membres par keyfile, autorisation, util","level":"Intermédiaire","duration":"60–90 min","topology":"3 serveurs (1 primary + 2 secondaries), nombre impair de membres porteurs de données","tags":["mongodb","mongo","replica set","replset","rs.initiate","keyfile","failover","stepdown","write concern","wiredtiger","haute disponibilité","ha","rhel","ubuntu"],"order":1,"steps":36,"scenarios":[]},{"id":"mongo-sharded","file":"install/guides/mongo-sharded.json","group":"MongoDB","engineId":"mongodb","category":"ha","title":"Installation d'un sharded cluster MongoDB (config servers + shards + mongos)","summary":"Le sharded cluster se met en place quand les données ou la charge en écriture dépassent un seul replica set : un replica set de config servers à trois membres, deux ou trois replica sets de shards et deux routeurs mongos","level":"Avancé","duration":"2 à 3 heures","topology":"3 config servers + 2×3 membres de shards + 2 mongos (au moins 11 serveurs ; moins pour un test)","tags":["mongodb","sharding","sharded cluster","mongos","config server","shard key","addShard","balancer","hashed","montée en charge","rhel","ubuntu"],"order":2,"steps":60,"scenarios":[]},{"id":"mongo-upgrade","file":"install/guides/mongo-upgrade.json","group":"MongoDB","engineId":"mongodb","category":"upgrade","title":"Rolling upgrade MongoDB (7.0 → 8.0) : replica set et sharded cluster","summary":"Met à niveau sans interruption (rolling) vers la 8.0 un replica set ou un sharded cluster MongoDB 7.0 en service : pré-vérifications (version, FCV, état des membres, compatibilité des pilotes), sauvegarde, secondaries d'","level":"Avancé","duration":"60–120 min (indépendamment du volume de données ; quelques minutes par membre)","topology":"Replica set (3 membres ou plus) ou sharded cluster (config servers + shards + mongos)","tags":["mongodb","mise à niveau","mise à niveau","rolling upgrade","8.0","7.0","featureCompatibilityVersion","fcv","stepdown","replica set","sharded","balancer","config server","mongos","fcv","featureCompatibilityVersion","rolling upgrade"],"order":3,"steps":30,"scenarios":[]},{"id":"mongo-backup","file":"install/guides/mongo-backup.json","group":"MongoDB","engineId":"mongodb","category":"backup","title":"Sauvegarde et restauration MongoDB (mongodump, mongorestore, snapshot)","summary":"Met en place la sauvegarde d'un MongoDB déjà installé et décrit, scénario par scénario, la restauration depuis une sauvegarde. Sauvegarde : mongodump d'une collection, d'une base ou de tout le déploiement (--archive --gz","level":"Intermédiaire","duration":"45–90 min","topology":"Serveur de sauvegarde ou un membre + replica set / instance unique / sharded cluster (mongos)","tags":["mongodb","mongo","sauvegarde","backup","restore","restauration","mongodump","mongorestore","oplog","oplogReplay","nsFrom","nsTo","mongoexport","mongoimport","fsyncLock","lvm snapshot","systemd timer","cron","rétention","balancer","activeWindow","config server","cron","systemd timer","flock","oplog","scénarios de restauration","point in time recovery","pitr","oplogLimit","nsInclude","dryRun","restauration sharded","clone","collection supprimée","supprimé par erreur"],"order":15,"steps":20,"scenarios":[{"k":"scenario","v":"setup","l":"Mettre en place la sauvegarde : mongodump, planification et restauration de test","m":"Sauvegarde mongodump mettre en place la sauvegarde backup setup systemd timer cron rétention mongorestore backup user backup.yaml Failed: can't create session oplog overflow mongodump was unable to capture all new oplog entries sauvegarder Utilisateur de sauvegarde, fichier d'identifiants, mongodump manuel et planifié, restauration de test sous un autre nom ; snapshot et mongoexport en option. Com"},{"k":"scenario","v":"r_dry","l":"La sauvegarde est-elle vraiment restaurable ? (--dryRun et répétition sur une instance temporaire)","m":"Répétition et contrôles dryRun --dryRun restore test répétition de restauration vérification de sauvegarde backup verify gzip: invalid compressed data unexpected EOF archive is corrupt archive corrompue gzip -t la sauvegarde est-elle restaurable Sans toucher aux données de production : intégrité de l'archive, namespaces et oplog qu'elle contient et, en option, restauration complète dans un mongod "},{"k":"scenario","v":"r_full","l":"Restaurer toutes les données depuis la sauvegarde (restauration complète, --drop)","m":"Restauration full restore restauration complète restauration complète de la base --drop mongorestore --drop perte de données data loss ransomware rançongiciel toutes les données supprimées dropDatabase no oplog file to replay oplogReplay applyOps not authorized … applyOps admin.tempusers Les données sont en grande partie perdues ou corrompues et le déploiement doit revenir à l'instant de la sauveg"},{"k":"scenario","v":"r_ns","l":"Récupérer une seule base ou collection (--nsInclude, sous un nouveau nom ou en place)","m":"Restauration --nsInclude --nsFrom --nsTo collection unique single collection restore collection effacée collection dropped drop collection documents supprimés deleted documents deleteMany namespace restore E11000 duplicate key error $merge orders_restored restaurer une seule base Une collection ou une base a été supprimée ou corrompue, le reste est intact : seul ce namespace est repris de la sauve"},{"k":"scenario","v":"r_pitr","l":"Revenir à un instant précis : sauvegarde + oplog (drop accidentel, update/delete erroné)","m":"Retour à un instant donné point in time PITR restauration à un instant donné --oplogReplay --oplogLimit oplog replay oplog.rs supprimé par erreur accidental drop update erroné wrong update avant le drop Timestamp oplog écrasé rollback to time revenir à une heure précise L'opération erronée a eu lieu après la sauvegarde et les écritures jusqu'à cet instant sont aussi nécessaires : la dernière sauve"},{"k":"scenario","v":"r_other","l":"Restaurer la sauvegarde sur un autre cluster / serveur (clone, environnement de test, migration)","m":"Migration et clone clone clone restauration vers un autre serveur restore to another cluster migration migration de serveur environnement de test refresh nsExclude admin.system.users Invalid feature compatibility version cannot use --oplogReplay with excludes specified copie vers l'environnement de test Le déploiement source n'est pas touché : la sauvegarde est restaurée sur un autre replica set o"},{"k":"scenario","v":"r_shard","l":"Restaurer un sharded cluster : les limites de mongodump et la bonne méthode","m":"Migration et clone restauration sharded sharded cluster restore mongos restore shard key shardCollection config server restore balancer inconsistent sauvegarde incohérente PBM Percona Backup Ops Manager --oplog mode only supported on full dumps sauvegarde via mongos La topologie sauvegardée est un sharded cluster : restauration logique via mongos (recréation des shard keys, balancer) ou reconstruc"}]},{"id":"mongo-ops","file":"install/guides/mongo-ops.json","group":"MongoDB","engineId":"mongodb","category":"ops","title":"Exploitation courante et scénarios de panne MongoDB (replica set et sharded cluster)","summary":"Pas une installation : runbook par scénario pour exploiter au quotidien un replica set et un sharded cluster MongoDB déjà installés, et les remettre en état quand ils tombent en panne. Contrôle de santé, ajout/retrait de","level":"Avancé","duration":"5 min (contrôle) – plusieurs heures (initial sync, drain, reshard)","topology":"Replica set (3 membres ou plus) ou sharded cluster (config server + replica sets des shards + mongos)","tags":["mongodb","mongo","replica set","sharded cluster","rs.status","rs.add","rs.remove","rs.reconfig","force reconfig","stepDown","priority","hidden","delayed","secondaryDelaySecs","arbiter","resync","initial sync","too stale","oplog","replSetResizeOplog","FCV","setFeatureCompatibilityVersion","addShard","removeShard","movePrimary","balancer","activeWindow","jumbo","chunk","reshardCollection","shard key","exploitation","panne"],"order":20,"steps":3,"scenarios":[{"k":"scenario","v":"health","l":"Contrôle de santé : membres, retard, fenêtre d'oplog (shards et balancer en sharded)","m":"Supervision et contrôles rs.status rs.status() replication lag retard de réplication replLag printSecondaryReplicationInfo sh.status health contrôle de santé NotYetInitialized RECOVERING STARTUP2 UNKNOWN (not reachable/healthy) DOWN ReplicaSetNoPrimary Server selection timed out lastHeartbeatMessage Lecture seule : état des membres, retard de réplication, fenêtre d'oplog, configuration ; sur un sh"},{"k":"scenario","v":"add","l":"Ajouter un nouveau membre au replica set (initial sync)","m":"Gestion des membres rs.add ajouter un membre add member initial sync nouveau secondary newlyAdded STARTUP2 scale out rs.add() Quorum check failed Found two member configurations with same host field votes: 0 Un nouveau serveur est prêt (même version, keyfile, replSetName) ; il est ajouté avec priority 0 / votes 0, puis reçoit son vote et sa priorité une fois l'initial sync terminée et l'état SECON"},{"k":"scenario","v":"remove","l":"Retirer un membre du replica set","m":"Gestion des membres rs.remove retirer un membre remove member decommission retirer un serveur REMOVED Our replica set config is invalid or we are not a member of it rs.remove() Le serveur est mis hors service ou définitivement perdu : le membre est retiré de la configuration et on vérifie que les votes restants forment toujours une majorité. S'il est primary, faites d'abord un stepDown."},{"k":"scenario","v":"member_cfg","l":"Modifier le rôle d'un membre : priority, membre hidden, membre delayed (différé)","m":"Gestion des membres priority hidden delayed secondaryDelaySecs slaveDelay membre caché membre différé votes rs.reconfig priority must be 0 when hidden=true only one voting member can be added or removed at a time rs.reconfig() Modifier la priorité d'un membre pour devenir primary, en faire un membre caché (hidden) pour le reporting/la sauvegarde ou un membre différé (delayed) contre les suppressio"},{"k":"scenario","v":"arbiter","l":"Remplacer l'arbiter par un membre porteur de données (PSA → PSS)","m":"Gestion des membres arbiter arbiterOnly PSA rs.addArb majority write concern cache pressure w: majority en attente Rejecting reconfig where the new config has a PSA topology implicit default write concern majority commit point Replica set primary + secondary + arbiter : quand un membre de données tombe, les écritures majority et la pression sur le cache deviennent un problème. Un nouveau membre de"},{"k":"scenario","v":"resync","l":"Resynchroniser un membre en retard ou corrompu (resync)","m":"Panne et reprise too stale too stale to catch up RS102 resync resynchroniser initial sync RECOVERING WiredTiger error WT_PANIC fichier de données corrompu data corruption OplogStartMissing maintenance mode InitialSyncFailure fassert checksum mismatch Le membre est bloqué en RECOVERING et affiche 'too stale to catch up', ou ses fichiers de données sont corrompus : le répertoire de données du membre"},{"k":"scenario","v":"force","l":"Perte de la majorité : reconfigurer de force avec les membres restants (force reconfig)","m":"Panne et reprise force reconfig force: true no primary pas de primary perte de la majorité majority lost NotWritablePrimary not master no replset config split brain DR site perte de site ReplicaSetNoPrimary Server selection timed out rs.reconfig force La plupart des membres sont définitivement perdus (perte d'un site/de serveurs) et les survivants ne peuvent pas élire de primary : rs.reconfig(cfg,"},{"k":"scenario","v":"stepdown","l":"Changer de primary de façon planifiée (rs.stepDown, désigner un membre précis comme primary)","m":"Maintenance stepDown rs.stepDown replSetStepDown changer de primary switchover maintenance planifiée maintenance rs.freeze No electable secondaries caught up InterruptedDueToReplStateChange PrimarySteppedDown changement de primary Une maintenance/un patch est prévu sur le serveur primary : le primary se retire et un secondary à jour devient primary. L'application peut recevoir des erreurs d'écritu"},{"k":"scenario","v":"oplog","l":"Modifier la taille de l'oplog (replSetResizeOplog)","m":"Maintenance oplog oplog window fenêtre d'oplog replSetResizeOplog oplogSizeMB oplogMinRetentionHours too stale oplog overflow rs.printReplicationInfo getReplicationInfo log length start to end La fenêtre d'oplog est courte : les secondaries deviennent 'too stale' après une maintenance, la sauvegarde ne parvient pas à capturer l'oplog, ou elle ne suffit pas pour une restauration à un instant donné."},{"k":"scenario","v":"fcv","l":"Vérifier et finaliser la Feature Compatibility Version (FCV)","m":"Maintenance FCV featureCompatibilityVersion setFeatureCompatibilityVersion Invalid feature compatibility version IncompatibleServerVersion après montée de version UPGRADE PROBLEM requires confirm: true getParameter featureCompatibilityVersion Après une montée de version, la FCV est restée à l'ancienne version ou les membres n'ont pas la même version : la version binaire et la FCV de chaque membre "},{"k":"scenario","v":"addshard","l":"Ajouter un shard au cluster (sh.addShard)","m":"Sharded cluster addShard sh.addShard ajouter un shard add shard scale out nouveau shard shardAdded is not running with --shardsvr in use by another shard La capacité ne suffit plus : un nouveau replica set de shard (shardsvr) a été monté et est ajouté au cluster ; le balancer y migre les chunks progressivement."},{"k":"scenario","v":"rmshard","l":"Retirer un shard (removeShard : drain et movePrimary)","m":"Sharded cluster removeShard remove shard retirer un shard drain draining movePrimary dbsToMove jumboChunks moveCollection collectionsToMove ShardNotFound draining started successfully Un shard est mis hors service : ses chunks sont vidés vers les autres shards (drain), les bases dont il est le primary shard sont déplacées (movePrimary), puis il est retiré du cluster."},{"k":"scenario","v":"balancer","l":"Balancer : fenêtre horaire, arrêt / démarrage, désactivation par collection","m":"Sharded cluster balancer activeWindow stopBalancer startBalancer sh.stopBalancer disableBalancing fenêtre du balancer chunk migration moveChunk isBalancerRunning getBalancerState Balancer timed out Les migrations de chunks chargent le système aux heures de pointe, elles doivent s'arrêter pendant une maintenance/sauvegarde, ou une collection donnée ne doit pas être équilibrée."},{"k":"scenario","v":"chunks","l":"Répartition déséquilibrée ou jumbo chunks (diagnostic et correction)","m":"Sharded cluster jumbo jumbo chunk chunk too big ChunkTooBig déséquilibré imbalance hot shard balancerCollectionStatus clearJumboFlag moveChunk failed getShardDistribution low cardinality splitFind Cannot split on the boundary jumboChunks Un shard est bien plus rempli que les autres, le balancer ne migre pas, des chunks sont marqués 'jumbo' ou moveChunk échoue : on examine la répartition, les jumbo"},{"k":"scenario","v":"reshard","l":"Changer la shard key (reshardCollection)","m":"Sharded cluster reshardCollection reshard changer la shard key change shard key refineCollectionShardKey abortReshardCollection commitReshardCollection forceRedistribution ReshardCollectionInProgress ReshardCollectionAborted unshardCollection La shard key actuelle provoque un shard chaud, des jumbo chunks ou des requêtes scatter-gather : la collection est redistribuée en ligne avec une nouvelle sh"},{"k":"mcfg","v":"priority","l":"Modifier la priority (préférence pour devenir primary)","m":"Modifie la priorité du membre pour devenir primary (par ex. privilégier le membre du data center principal, mettre le membre DR à 0). Une élection peut avoir lieu ; courte interruption des écritures possible."},{"k":"mcfg","v":"hidden","l":"Membre caché (hidden) : invisible pour les applications, pour sauvegarde/reporting","m":"Le membre est invisible pour les applications (aucune lecture n'y est routée) ; il est réservé au reporting, à l'analytique ou à la sauvegarde. Il passe en priority 0 et ne peut pas devenir primary."},{"k":"mcfg","v":"delayed","l":"Membre différé (delayed) : protection contre les suppressions accidentelles","m":"Le membre applique l'oplog avec le retard indiqué ; les données supprimées par erreur y restent présentes pendant ce délai. Il devient hidden, priority 0 et votes 0."},{"k":"mcfg","v":"normal","l":"Revenir à la normale (priority 1, visible, sans retard, votant)","m":"Annule le rôle précédent : priority 1, visible, sans retard, votant."},{"k":"bal_op","v":"window","l":"Définir une fenêtre horaire (activeWindow)","m":"Les migrations ne s'exécutent qu'à certaines heures (par ex. la nuit) ; aucune charge en journée."},{"k":"bal_op","v":"unwindow","l":"Supprimer la fenêtre horaire (exécution à toute heure)","m":"La fenêtre horaire est supprimée ; le balancer migre à toute heure si nécessaire (par ex. pour rééquilibrer rapidement après l'ajout d'un shard)."},{"k":"bal_op","v":"stop","l":"Arrêter le balancer (maintenance/sauvegarde)","m":"Avant une sauvegarde, une montée de version ou une maintenance : la migration en cours se termine et aucune nouvelle ne démarre. Redémarrez-le impérativement une fois le travail terminé."},{"k":"bal_op","v":"start","l":"Démarrer le balancer","m":"Active un balancer arrêté ; l'équilibrage reprend là où il s'était arrêté."},{"k":"bal_op","v":"coll_off","l":"Désactiver l'équilibrage pour une seule collection","m":"Seuls les chunks d'une collection ne sont pas migrés (placement volontaire, collection problématique). Les autres continuent d'être équilibrées."},{"k":"bal_op","v":"coll_on","l":"Activer l'équilibrage pour une seule collection","m":"Réactive l'équilibrage désactivé pour la collection."}]},{"id":"mongo-security","file":"install/guides/mongo-security.json","group":"MongoDB","engineId":"mongodb","category":"security","title":"Durcissement de la sécurité MongoDB (autorisation, TLS, rôles, audit)","summary":"Runbook par scénarios pour durcir sans interruption un MongoDB en service (serveur standalone, replica set ou sharded cluster) : audit de sécurité en lecture seule, activation rolling de l'autorisation avec keyFile + tra","level":"Avancé","duration":"10 min (audit) – 2 heures (durcissement complet)","topology":"Serveur standalone, replica set de 3 membres ou plus, ou sharded cluster","tags":["sécurité mongodb","mongodb security","hardening","durcissement sécurité","CIS","authorization","keyFile","transitionToAuth","TLS","requireTLS","x.509","SCRAM-SHA-256","custom role","least privilege","bindIp","audit log","auditLog","encryption at rest","LUKS","Percona"],"order":20,"steps":8,"scenarios":[{"k":"op","v":"audit","l":"Audit de l'état de sécurité (lecture seule)","m":"Audit audit de sécurité security audit checklist de sécurité CIS MongoDB hardening durcissement sécurité Access control is not enabled for the database auth désactivée bindIp 0.0.0.0 mongodb exposé mongodb exposé sur internet rançon ransomware getCmdLineOpts checklist sécurité MongoDB rapport d'audit Ne modifie rien. L'accès sans authentification, les adresses d'écoute, TLS, les mécanismes d'authe"},{"k":"op","v":"all","l":"Durcissement complet (tout, dans l'ordre)","m":"Audit durcissement complet full hardening benchmark CIS projet sécurité constat de pentest constats d'audit Audit → authentification interne + autorisation (sans interruption grâce à transitionToAuth) → utilisateurs au moindre privilège → SCRAM-SHA-256 → bindIp/firewall → TLS (allowTLS → preferTLS → requireTLS) → audit (Enterprise/Percona) → audit final. En rolling dans un replica set ; chaque éta"},{"k":"op","v":"auth","l":"Autorisation désactivée : activer l'autorisation (keyFile, sans interruption)","m":"Identité et accès Access control is not enabled for the database Read and write access to data and configuration is unrestricted authorization disabled authorization: enabled auth désactivée no authentication transitionToAuth keyFile internal authentication authentification entre membres accès sans authentification permissions on /etc/mongod.keyfile are too open Authentication failed __system loca"},{"k":"op","v":"users","l":"Utilisateurs au moindre privilège et custom roles (application, sauvegarde, supervision)","m":"Identité et accès least privilege moindre privilège custom role createRole utilisateur root application connectée en root not authorized on Unauthorized command find requires authentication requires authentication readWrite clusterMonitor rôle backup createUser grantRolesToUser monitor_user backup_user Les applications se connectent avec root ou un compte d'administration partagé. On crée un custo"},{"k":"op","v":"scram","l":"Désactiver SCRAM-SHA-1, uniquement SCRAM-SHA-256","m":"Identité et accès SCRAM-SHA-1 SCRAM-SHA-256 authenticationMechanisms Received authentication for mechanism SCRAM-SHA-1 which is not enabled Unable to use SCRAM-SHA-256 based authentication for user without any SCRAM-SHA-256 credentials registered MD5 mechanism Les identifiants des anciens utilisateurs sont stockés en SHA-1, ou l'audit a trouvé SCRAM-SHA-1 activé. Les utilisateurs sont migrés vers "},{"k":"op","v":"bind","l":"Restreindre l'accès réseau (bindIp 0.0.0.0, firewall)","m":"Réseau et chiffrement bindIp 0.0.0.0 bindIpAll bind_ip_all 0.0.0.0:27017 port 27017 ouvert shodan exposé sur internet firewall javascriptEnabled noscripting net.bindIp ufw allow 27017 firewall-cmd 27017 mongod écoute sur toutes les interfaces (0.0.0.0, `bindIpAll`) ou le port est ouvert à tous. bindIp est réduit à localhost + l'IP interne du serveur, et le firewall n'autorise que les membres et le"},{"k":"op","v":"tls","l":"Chiffrer les connexions : TLS (allowTLS → preferTLS → requireTLS)","m":"Réseau et chiffrement TLS SSL net.tls.mode requireTLS preferTLS allowTLS connexion non chiffrée cleartext no SSL certificate provided by peer SSL peer certificate validation failed certificate verify failed x.509 x509 clusterAuthMode Hostname/IP does not match certificate's altnames self-signed certificate in certificate chain rotateCertificates allowConnectionsWithoutCertificates Le trafic entre "},{"k":"op","v":"auditlog","l":"Activer l'audit log (Enterprise / Percona)","m":"Traçabilité et protection des données audit log auditLog piste d'audit qui a supprimé who dropped Unrecognized option: auditLog unrecognised option '--auditDestination' RGPD PCI DSS journalisation Qui s'est connecté et quand, les modifications d'utilisateurs/rôles et les opérations drop doivent être tracés. Dans MongoDB Enterprise et Percona Server for MongoDB, cela s'active avec `auditLog` ; Comm"},{"k":"op","v":"ear","l":"Encryption at rest (chiffrer les données sur disque)","m":"Traçabilité et protection des données encryption at rest chiffrement disque enableEncryption encryptionKeyFile KMIP Vault LUKS dm-crypt chiffrement des données Unrecognized option: security.enableEncryption Unable to retrieve key Unable to use encryption at rest cryptsetup luksFormat Si un disque ou une sauvegarde est volé, les données doivent rester illisibles. Sur Enterprise/Percona, le chiffrem"}]},{"id":"redis-sentinel","file":"install/guides/redis-sentinel.json","group":"Redis","engineId":"redis","category":"ha","title":"Haute disponibilité avec Redis Sentinel (master + replicas + 3 Sentinels)","summary":"Met en place de zéro une architecture haute disponibilité Redis 7.2 / 8.2 ou Valkey sur trois serveurs : un master, deux replicas et un Sentinel sur chaque serveur. maxmemory dimensionné selon la RAM, persistance (RDB/AO","level":"Intermédiaire","duration":"45–60 min","topology":"3 serveurs : Redis (1 master + 2 replicas) et Sentinel sur chacun","tags":["redis","valkey","sentinel","replica","réplication","failover","quorum","haute disponibilité","ha","cache","rhel","ubuntu"],"order":1,"steps":27,"scenarios":[]},{"id":"redis-cluster","file":"install/guides/redis-cluster.json","group":"Redis","engineId":"redis","category":"ha","title":"Installation d'un Redis Cluster (3 masters + 3 replicas)","summary":"Construit un Redis Cluster de zéro (Redis 7.2 / 8.2 ou Valkey) : les 16384 hash slots sont répartis sur trois masters (ou plus), et chaque master a un replica sur un autre serveur. Architecture à une instance par serveur","level":"Intermédiaire","duration":"45–60 min","topology":"6 instances : 6 serveurs × 1 ou 3 serveurs × 2","tags":["redis","valkey","cluster","redis cluster","sharding","hash slot","reshard","failover","replica","montée en charge","rhel","ubuntu"],"order":2,"steps":33,"scenarios":[]},{"id":"redis-backup","file":"install/guides/redis-backup.json","group":"Redis","engineId":"redis","category":"backup","title":"Sauvegarde et restauration Redis (RDB, AOF, replica et cluster)","summary":"Met en place la sauvegarde d'un Redis ou Valkey installé (instance unique, master/replica avec Sentinel, ou Redis Cluster) et décrit la restauration à partir d'une sauvegarde. Étape par étape : sauvegardes RDB (règle sav","level":"Intermédiaire","duration":"30–60 min","topology":"Instance unique, replica ou serveur Redis Cluster (Redis 7.2 / 8.x, Valkey)","tags":["redis","valkey","sauvegarde","backup","restore","restauration","rdb","dump.rdb","bgsave","lastsave","aof","appendonly","bgrewriteaof","appendonlydir","redis cluster","systemd timer","cron","rétention","cron","systemd timer","flock","REDISCLI_AUTH","sentinel"],"order":15,"steps":19,"scenarios":[]},{"id":"redis-ops","file":"install/guides/redis-ops.json","group":"Redis","engineId":"redis","category":"ops","title":"Redis / Valkey : exploitation courante et scénarios de panne (Cluster et Sentinel)","summary":"Ce n'est pas une installation : runbook par scénario pour administrer au quotidien un Redis Cluster existant ou une architecture master/replica avec Sentinel, et la remettre en état en cas de panne (Redis 7.2, Redis 8 et","level":"Avancé","duration":"5 min (contrôle) – 1 heure (reshard)","topology":"Redis Cluster (3+ masters + replicas) ou Sentinel (1 master + replicas + 3 Sentinel)","tags":["redis","valkey","redis cluster","sentinel","redis-cli --cluster","cluster check","add-node","del-node","reshard","rebalance","CLUSTER FAILOVER","FORCE","TAKEOVER","CLUSTER FORGET","cluster fix","SENTINEL FAILOVER","SENTINEL RESET","quorum","CKQUORUM","INFO memory","MEMORY DOCTOR","eviction","maxmemory","bigkeys","memkeys","hotkeys","exploitation","panne"],"order":20,"steps":3,"scenarios":[{"k":"scenario","v":"c_health","l":"Contrôle de santé du cluster (--cluster check, CLUSTER INFO / NODES)","m":"Redis Cluster : supervision cluster_state:fail CLUSTERDOWN CLUSTERDOWN Hash slot not served --cluster check CLUSTER INFO CLUSTER NODES fail? pfail slots not covered Not all 16384 slots are covered [ERR] Nodes don't agree about configuration MOVED ASK CLUSTERDOWN The cluster is down cluster_slots_fail état du cluster cluster health cluster_state Lecture seule : tous les slots sont-ils couverts, les"},{"k":"scenario","v":"c_add","l":"Ajouter un nœud au cluster (master ou replica)","m":"Redis Cluster : gestion des nœuds add-node --cluster add-node ajouter un nœud add node scale out --cluster-slave --cluster-replica cluster-master-id nouveau master nouvelle replica Node is not empty. Either the node already knows other nodes New node added correctly augmenter la capacité Une nouvelle instance (cluster-enabled yes, vide) est prête : elle rejoint le cluster comme master et reçoit de"},{"k":"scenario","v":"c_remove","l":"Retirer un nœud du cluster (vider ses slots + del-node)","m":"Redis Cluster : gestion des nœuds del-node --cluster del-node retirer un nœud remove node scale in vider les slots cluster-weight Node … is not empty is not empty! Reshard data away and try again CLUSTER RESET SOFT décommissionner un nœud Un master ou une replica est mis hors service : les slots du master sont déplacés vers les autres masters, puis le nœud est retiré du cluster. Aucune interruptio"},{"k":"scenario","v":"c_reshard","l":"Redistribuer les slots (rebalance / reshard)","m":"Redis Cluster : gestion des nœuds rebalance reshard --cluster rebalance --cluster reshard répartition des slots déséquilibré imbalance use-empty-masters MIGRATE importing migrating BUSYKEY IOERR error or timeout reading to target instance has slots in importing state has slots in migrating state Calling MIGRATE No rebalancing needed déplacer des slots Les slots ou la mémoire sont déséquilibrés ent"},{"k":"scenario","v":"c_failover","l":"Failover manuel : promouvoir une replica en master (CLUSTER FAILOVER, FORCE, TAKEOVER)","m":"Redis Cluster : pannes et maintenance CLUSTER FAILOVER failover FORCE TAKEOVER manual failover maintenance du master promouvoir une replica en master Manual failover timed out ERR Master is down or failed, please use CLUSTER FAILOVER FORCE ERR You should send CLUSTER FAILOVER to a replica changer de master Une maintenance est prévue sur le serveur du master (planifiée, sans perte de données), ou l"},{"k":"scenario","v":"c_replace","l":"Remplacer un master / nœud en panne (FORGET, nouvelle replica, --cluster fix)","m":"Redis Cluster : pannes et maintenance CLUSTER FORGET --cluster fix nœud perdu failed node fail noaddr slots not covered CLUSTERDOWN replace node master en panne handshake ERR Can't forget my master! CLUSTERDOWN Hash slot not served serveur perdu panne disque Le serveur d'un nœud est définitivement perdu : si sa replica est devenue master, l'ancien nœud est oublié et une nouvelle replica est ajouté"},{"k":"scenario","v":"s_health","l":"État de Sentinel : master, replicas, Sentinel, quorum","m":"Sentinel SENTINEL MASTERS SENTINEL master get-master-addr-by-name CKQUORUM +sdown +odown -odown +switch-master NOQUORUM NOGOODSLAVE état de sentinel +tilt -failover-abort-not-elected s_down o_down master_link_status:down sentinel health Lecture seule : l'adresse du master vue par les Sentinel, les replicas, les autres Sentinel, CKQUORUM et les derniers événements de failover. Commencez ici si vous"},{"k":"scenario","v":"s_failover","l":"Failover manuel avec Sentinel (SENTINEL FAILOVER)","m":"Sentinel SENTINEL FAILOVER sentinel failover switch-master failover planifié INPROG NOGOODSLAVE -failover-abort-no-good-slave NOGOODSLAVE No suitable replica to promote maintenance sentinel Une maintenance est prévue sur le serveur du master : Sentinel promeut la replica la plus adaptée en master et redirige les autres replicas et les clients vers le nouveau master. Interruption de 1 à 2 s ; aucun"},{"k":"scenario","v":"s_replace","l":"Ajouter / retirer / remplacer un nœud dans une architecture Sentinel (SENTINEL RESET)","m":"Sentinel SENTINEL RESET sentinel reset replicaof nouvelle replica nouveau sentinel ajouter un sentinel retirer un sentinel ancienne replica toujours listée num-other-sentinels known-replica known-sentinel ancien sentinel toujours listé Une replica ou un Sentinel a été retiré définitivement ou vient d'être ajouté : la nouvelle replica se connecte avec REPLICAOF, et les entrées obsolètes des Sentine"},{"k":"scenario","v":"s_quorum","l":"Modifier le quorum et les paramètres de Sentinel (SENTINEL SET)","m":"Sentinel SENTINEL SET quorum down-after-milliseconds failover-timeout parallel-syncs CKQUORUM NOQUORUM failover trop tardif failover intempestif Le nombre de Sentinel a changé, ou le failover se déclenche trop tard/trop tôt : quorum, down-after-milliseconds, failover-timeout et parallel-syncs sont modifiés sur tous les Sentinel."},{"k":"scenario","v":"memory","l":"Pression mémoire : diagnostic de l'utilisation, de la fragmentation et de l'eviction","m":"Mémoire et clés OOM command not allowed when used memory > 'maxmemory' OOM maxmemory used_memory evicted_keys eviction maxmemory-policy MEMORY DOCTOR mem_fragmentation_ratio fragmentation activedefrag client-output-buffer-limit mémoire pleine Out of memory: Killed process oom-killer used_memory_rss mem_clients_normal omem used_memory approche de maxmemory, erreurs 'OOM command not allowed', l'evic"},{"k":"scenario","v":"bigkeys","l":"Trouver les clés volumineuses et très sollicitées (--bigkeys, --memkeys, --hotkeys)","m":"Mémoire et clés --bigkeys --memkeys --hotkeys big key clé volumineuse hot key clé très sollicitée MEMORY USAGE UNLINK slowlog latency SCAN HGETALL KEYS * --keystats pics de latence latency spike La mémoire ou le CPU d'un nœud est plus élevé que celui des autres, il y a des pics de latence, DEL est long : les clés les plus volumineuses et les plus sollicitées sont identifiées puis nettoyées sans ri"},{"k":"add_role","v":"master","l":"Master (reçoit des slots : rebalance après l'ajout)","m":"Rejoint le cluster comme master vide, puis reçoit des slots par un rebalance. Augmente la capacité ; une replica doit aussi être ajoutée pour le nouveau master."},{"k":"add_role","v":"replica","l":"Replica (copie d'un master)","m":"Devient la copie d'un master et prend le relais si le master tombe. Un volume égal à la mémoire du master transite par le réseau (synchronisation complète)."},{"k":"rs_mode","v":"rebalance","l":"Rebalance : répartir les slots équitablement entre les masters (masters vides compris)","m":"Tous les masters reçoivent une part égale (y compris un nouveau master sans slot). Une simulation est affichée d'abord. C'est l'opération la plus courante après l'ajout d'un master."},{"k":"rs_mode","v":"move","l":"Reshard : déplacer un nombre donné de slots d'un master vers un autre","m":"Un nombre donné de slots est déplacé d'un master vers un autre : pour soulager un master très sollicité/volumineux ou pour vider progressivement un master."},{"k":"fo_mode","v":"default","l":"Planifié (par défaut) : se synchronise avec le master puis prend le relais, sans perte de données","m":"Le master doit être en service."},{"k":"fo_mode","v":"force","l":"FORCE : le master est injoignable, prendre le relais sans attendre la synchronisation","m":"Les dernières écritures du master peuvent être perdues ; une majorité de masters doit quand même voter."},{"k":"fo_mode","v":"takeover","l":"TAKEOVER : la majorité des masters a aussi disparu, prendre le relais sans vote","m":"Dernier recours : risque de split brain et de collision d'epoch."},{"k":"srep_op","v":"add_replica","l":"Ajouter une nouvelle replica","m":"Le Redis du nouveau serveur se connecte au master actuel ; les Sentinel le découvrent d'eux-mêmes."},{"k":"srep_op","v":"rm_replica","l":"Retirer définitivement une replica","m":"La replica est arrêtée et son entrée est nettoyée sur tous les Sentinel."},{"k":"srep_op","v":"add_sentinel","l":"Ajouter un nouveau Sentinel","m":"Un Sentinel est ajouté sur un nouveau serveur (le nombre total de Sentinel doit rester impair : 3 → 5)."},{"k":"srep_op","v":"rm_sentinel","l":"Retirer définitivement un Sentinel","m":"Le Sentinel est arrêté et son entrée est nettoyée sur les autres ; le quorum est ensuite réexaminé."}]},{"id":"redis-security","file":"install/guides/redis-security.json","group":"Redis","engineId":"redis","category":"security","title":"Durcissement de la sécurité Redis / Valkey (ACL, TLS, commandes dangereuses)","summary":"Ni une installation ni un guide d'exploitation courante : runbook par scénarios pour durcir sans interruption un Redis ou un Valkey en service (serveur unique, replica + Sentinel ou Cluster). Audit de sécurité en lecture","level":"Avancé","duration":"5 min (audit) – 90 min (durcissement complet)","topology":"Serveur unique, master + replica + Sentinel, ou Redis Cluster","tags":["sécurité redis","redis security","valkey","hardening","durcissement","ACL","ACL SETUSER","aclfile","requirepass","masterauth","protected-mode","bind","rename-command","enable-protected-configs","TLS","tls-replication","mot de passe sentinel","CIS"],"order":20,"steps":7,"scenarios":[{"k":"op","v":"audit","l":"Audit de l'état de sécurité (lecture seule)","m":"Audit audit sécurité redis redis security audit CIS Redis hardening durcissement redis exposé exposed redis unauthorized redis redis sans mot de passe -NOAUTH crackit redis cryptominer kinsing NOAUTH Authentication required contrôle de sécurité redis redis pentest Ne modifie rien. L'accès sans mot de passe, l'utilisateur default, les adresses d'écoute, protected-mode, les commandes dangereuses, TL"},{"k":"op","v":"all","l":"Durcissement complet (tout, dans l'ordre)","m":"Audit durcissement complet full hardening projet sécurité redis constat de pentest Audit → utilisateurs ACL (administrateur, application, réplication, Sentinel) et désactivation de l'utilisateur default → bind/protected-mode/firewall → commandes dangereuses et protected configs → TLS → droits sur les fichiers → audit final. En rolling sur les replicas/Sentinel ; les étapes risquées demandent confi"},{"k":"op","v":"acl","l":"Utilisateurs ACL et désactivation de l'utilisateur default (Redis 6+)","m":"Identité et privilèges ACL ACL SETUSER aclfile default user utilisateur default NOPERM WRONGPASS invalid username-password pair -NOAUTH Authentication required user default on nopass masteruser least privilege moindre privilège NOPERM this user has no permissions to run the NOPERM this user has no permissions to access one of the keys used as arguments ACL LOG mot de passe par utilisateur ACL SAVE"},{"k":"op","v":"auth","l":"Redis sans mot de passe : requirepass / masterauth (mesure rapide)","m":"Identité et privilèges requirepass masterauth auth-pass redis sans mot de passe no password MASTER aborted replication with an error: NOAUTH Unable to AUTH to MASTER -NOAUTH ERR AUTH <password> called without any password configured for the default user Client sent AUTH, but no password is set définir un mot de passe Aucun mot de passe n'est défini et le temps manque pour passer aux ACL. Un mot de"},{"k":"op","v":"net","l":"Restreindre l'accès réseau (bind 0.0.0.0, protected-mode, firewall)","m":"Réseau et chiffrement bind 0.0.0.0 protected-mode no DENIED Redis is running in protected mode 0.0.0.0:6379 port 6379 ouvert shodan firewall DENIED Redis is running in protected mode because protected mode is enabled and no password is set for the default user *:6379 Could not create server TCP listening socket Redis écoute sur toutes les interfaces ou tourne avec `protected-mode no`. bind est réd"},{"k":"op","v":"cmds","l":"Commandes dangereuses et abus de CONFIG (FLUSHALL, CONFIG, DEBUG, MODULE)","m":"Réseau et chiffrement CONFIG SET dir dbfilename authorized_keys crontab FLUSHALL FLUSHDB DEBUG MODULE LOAD enable-protected-configs enable-debug-command enable-module-command rename-command @dangerous ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config ERR DEBUG command not allowed CONFIG SET dbfilename rename-command CONFIG module malveillant exploit SLAVEOF Co"},{"k":"op","v":"tls","l":"Chiffrer les connexions : TLS (clients, réplication, Sentinel, cluster bus)","m":"Réseau et chiffrement TLS SSL tls-port tls-replication tls-cluster tls-auth-clients connexion non chiffrée Error accepting a client connection: error:0A00010B:SSL routines::wrong version number SSL_connect certificate verify failed rediss:// Connection reset by peer tls-cert-file connexion chiffrée chiffrement en transit Le trafic n'est pas chiffré. Le port TLS est ouvert à chaud à côté du port ex"},{"k":"op","v":"perms","l":"Droits sur les fichiers et utilisateur de service","m":"Traces et fichiers droits redis.conf world readable chmod dump.rdb appendonly tourne en root running as root droits sur les fichiers Permission denied 640 User=redis redis root redis.conf (qui contient des mots de passe), le fichier ACL, les fichiers RDB/AOF et les logs sont lisibles par d'autres utilisateurs, ou Redis tourne en root. Les droits sont ramenés à 640/750 et le propriétaire devient l'"}]},{"id":"es-cluster","file":"install/guides/es-cluster.json","group":"Elasticsearch","engineId":"elasticsearch","category":"install","title":"Installation d'un cluster Elasticsearch (3+ nœuds, TLS, Kibana)","summary":"Construit à partir de zéro un cluster Elasticsearch 8.x / 9.x de 3 nœuds ou plus : dépôt Elastic, heap dimensionné selon la RAM, memory lock, vm.max_map_count, sécurité automatique sur le premier nœud (TLS + mot de passe","level":"Intermédiaire","duration":"60–90 min","topology":"3+ nœuds Elasticsearch (+ serveur Kibana facultatif)","tags":["elasticsearch","elastic","kibana","cluster","cluster elasticsearch","installation","install","enrollment","tls","heap","snapshot","rhel","ubuntu"],"order":1,"steps":54,"scenarios":[]},{"id":"es-rolling-upgrade","file":"install/guides/es-rolling-upgrade.json","group":"Elasticsearch","engineId":"elasticsearch","category":"upgrade","title":"Rolling upgrade d'Elasticsearch (8.x minor ou 8.19 → 9.x)","summary":"Il ne s'agit pas d'une installation : ce guide migre un cluster Elasticsearch en service vers une nouvelle version sans interruption, en mettant à jour les nœuds un par un (version mineure au sein de 8.x, ou version maje","level":"Avancé","duration":"15–30 min par nœud","topology":"Cluster existant de 3+ nœuds (+ Kibana)","tags":["elasticsearch","mise à niveau","rolling upgrade","version mineure","version majeure","9.x","8.19","kibana","upgrade assistant","dépréciation","snapshot","retour arrière","rolling upgrade","rôles des nœuds","master-eligible","deprecation api","versionlock"],"order":2,"steps":34,"scenarios":[{"k":"path","v":"minor","l":"Au sein de la même version majeure (ex. 8.18 → 8.19, 9.1 → 9.2)","m":"Au sein de la même version majeure (8.18 → 8.19, 9.1 → 9.2) : le dépôt ne change pas, le contrôle des dépréciations est quand même effectué. Sauter des versions mineures intermédiaires est supporté."},{"k":"path","v":"major","l":"Version majeure : 8.19.x → 9.x","m":"Le cluster source doit d'abord être mis à jour en 8.19.x (dernier patch). Les contrôles préalables incluent l'Upgrade Assistant / deprecation API et la system feature migration."}]},{"id":"es-snapshot","file":"install/guides/es-snapshot.json","group":"Elasticsearch","engineId":"elasticsearch","category":"backup","title":"Snapshot et restore Elasticsearch (repository fs / S3, SLM)","summary":"Met en place les snapshots (sauvegardes) d'un cluster Elasticsearch 8.x / 9.x installé et décrit la restauration : repository sur système de fichiers partagé (NFS + path.repo) ou S3 (keystore), activation par rolling res","level":"Intermédiaire","duration":"45–90 min","topology":"Cluster Elasticsearch 8.x / 9.x (1 à N nœuds) + répertoire partagé (NFS) ou stockage objet compatible S3","tags":["elasticsearch","snapshot","restore","sauvegarde","backup","restauration","repository","path.repo","nfs","s3","keystore","slm","snapshot lifecycle","rétention","rename_pattern","feature_states","repository lecture seule","migration","slm","snapshot lifecycle","quartz cron","utc","netrc"],"order":15,"steps":33,"scenarios":[]},{"id":"es-ops","file":"install/guides/es-ops.json","group":"Elasticsearch","engineId":"elasticsearch","category":"ops","title":"Exploitation courante d'Elasticsearch et scénarios de panne (8.x / 9.x)","summary":"Il ne s'agit pas d'une installation : runbook par scénarios pour exploiter au quotidien un cluster Elasticsearch 8.x / 9.x installé et le rétablir en cas de panne. Santé et allocation explain, résolution des shards non a","level":"Avancé","duration":"5 min (contrôle) – plusieurs heures (reindex, vidage d'un nœud)","topology":"Cluster Elasticsearch 8.x / 9.x (1–N nœuds), TLS et authentification activés","tags":["elasticsearch","elastic","cluster health","red","yellow","unassigned","allocation explain","reroute","retry_failed","allocate_stale_primary","watermark","flood stage","read_only_allow_delete","exclude","voting_config_exclusions","enrollment token","reindex","alias","ILM","rollover","shrink","split","snapshot restore","exploitation","dépannage"],"order":20,"steps":6,"scenarios":[{"k":"scenario","v":"health","l":"Santé du cluster : status, nœuds, shards non assignés, disque","m":"Supervision et diagnostic red yellow _cluster/health _cat/shards UNASSIGNED allocation explain _cluster/allocation/explain status red status yellow santé du cluster pending tasks master_not_discovered_exception NoNodeAvailableException circuit_breaking_exception heap contrôle de santé du cluster Lecture seule : green/yellow/red, liste des nœuds, shards non assignés (UNASSIGNED) et leur cause (allo"},{"k":"scenario","v":"unassigned","l":"Résoudre un shard non assigné (UNASSIGNED) (selon explain)","m":"Supervision et diagnostic UNASSIGNED unassigned shards NODE_LEFT ALLOCATION_FAILED max_retry shard has exceeded the maximum number of retries retry_failed no_valid_shard_copy cannot allocate because a previous copy of the primary shard existed allocate_stale_primary allocate_empty_primary the node is above the high watermark same_shard awareness filter NO_VALID_SHARD_COPY CLUSTER_RECOVERED REPLICA"},{"k":"scenario","v":"watermark","l":"Afficher / modifier les paramètres de disk watermark","m":"Disque et blocs watermark disk.watermark low disk watermark high disk watermark flood stage disk watermark disque plein disk full cluster.routing.allocation.disk cluster.routing.allocation.disk.watermark.high disk usage exceeded seuil disque Des shards sont déplacés ou ne sont pas alloués à cause du remplissage disque ; les seuils de watermark (low / high / flood_stage) doivent être modifiés tempo"},{"k":"scenario","v":"readonly","l":"Index passé en lecture seule : lever le bloc read_only_allow_delete","m":"Disque et blocs read_only_allow_delete FORBIDDEN/12/index read-only / allow delete (api) TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark cluster_block_exception index.blocks.read_only_allow_delete flood stage lecture seule read-only blocked by: [FORBIDDEN/12/index read-only / allow delete (api)] index read-only écriture rejetée Les écritures sont rejetées avec 'FORBIDDEN/12/index re"},{"k":"scenario","v":"add_node","l":"Ajouter un nœud au cluster (enrollment token)","m":"Gestion des nœuds ajouter un nœud add node enrollment token elasticsearch-create-enrollment-token elasticsearch-reconfigure-node scale out join cluster master not discovered master not discovered yet, this node has not previously joined a bootstrapped cluster augmenter la capacité nouveau nœud Besoin de capacité : Elasticsearch a été installé sur un nouveau serveur, qui rejoint le cluster avec un "},{"k":"scenario","v":"remove_node","l":"Retirer un nœud en toute sécurité (exclude, vidage, arrêt)","m":"Gestion des nœuds retirer un nœud remove node decommission cluster.routing.allocation.exclude._name exclude voting_config_exclusions relocating vider un nœud voting configuration nœud mis hors service remplacement de serveur Un nœud est mis hors service ou son matériel va changer : ses shards sont déplacés vers les autres nœuds avec un allocation exclude, on vérifie qu'il est vide, on lui applique"},{"k":"scenario","v":"reindex","l":"Reindex : bascule sans interruption vers un nouvel index via un alias pour un changement de mapping/paramètres","m":"Gestion des index _reindex reindex modifier le mapping mapper_parsing_exception illegal_argument_exception: mapper alias _aliases bascule sans interruption zero downtime task version_conflict_engine_exception cannot be changed from type mapper [ modifier l'analyzer Le type d'un champ du mapping, l'analyzer ou le nombre de shards doit changer : un nouvel index est créé, les données sont copiées ave"},{"k":"scenario","v":"ilm","l":"Mettre en place / dépanner l'index lifecycle (ILM) et le rollover","m":"Gestion des index ILM index lifecycle rollover _rollover _ilm/explain is_write_index rollover_alias ilm policy index.lifecycle.name ERROR step illegal_argument_exception: index.lifecycle.rollover_alias rollover_alias does not point to index ILM ERROR index non supprimé index de logs qui grossit Les index de logs/séries temporelles grossissent ou ne sont pas supprimés : une politique ILM (rollover "},{"k":"scenario","v":"resize","l":"Corriger le nombre de shards : _shrink (réduire) / _split (augmenter)","m":"Gestion des index _shrink _split shrink split nombre de shards number_of_shards too many shards trop de shards oversharding index.blocks.write must be read-only to resize index must have all shards allocated on the same node to shrink index number_of_routing_shards Le nombre de shards de l'index a été mal choisi : les shards trop petits (oversharding) sont fusionnés avec _shrink, les shards trop g"},{"k":"scenario","v":"snaprestore","l":"Restaurer un seul index depuis un snapshot sous un nouveau nom","m":"Gestion des index snapshot restore _restore restauration d'un seul index rename_pattern rename_replacement index supprimé deleted index index_not_found_exception cannot restore index because an open index with same name already exists snapshot_missing_exception supprimé par erreur récupérer un index Un index a été supprimé ou corrompu : seul cet index est restauré depuis le snapshot repository sou"},{"k":"un_fix","v":"retry","l":"Limite de tentatives dépassée : relancer (_cluster/reroute?retry_failed)","m":"max_retry shard has exceeded the maximum number of retries ALLOCATION_FAILED explain : 'shard has exceeded the maximum number of retries'. Corrigez d'abord l'erreur de fond (disque, mapping, mémoire)."},{"k":"un_fix","v":"replicas","l":"Plus de replicas que de nœuds : réduire les replicas","m":"same_shard a copy of this shard is already allocated to this node explain : same_shard (une copie ne peut pas être placée sur le même nœud) ; replicas à 0 sur un cluster à un seul nœud."},{"k":"un_fix","v":"filter","l":"Allocation filtering / exclude bloque : supprimer le filtre","m":"filter node does not match index setting [index.routing.allocation node matches cluster setting [cluster.routing.allocation.exclude explain : filter decider ('node does not match index setting [index.routing.allocation…]' ou exclude au niveau cluster)."},{"k":"un_fix","v":"disk","l":"Le disk watermark bloque","m":"disk_threshold the node is above the low watermark the node is above the high watermark explain : disk_threshold decider. Renvoie au scénario watermark."},{"k":"un_fix","v":"stale","l":"Dernier recours : allouer une primaire depuis une ancienne copie (stale)","m":"no_valid_shard_copy all the copies of its data in the cluster are stale or corrupt allocate_stale_primary La copie à jour de la primaire est perdue, seule une ancienne copie subsiste : les dernières écritures absentes de cette copie sont perdues."},{"k":"un_fix","v":"empty","l":"Ultime recours : allouer une primaire vide (toutes les données du shard sont perdues)","m":"no_valid_shard_copy there are no copies of its data in the cluster allocate_empty_primary Aucune copie n'existe ; si l'index ne peut pas être restauré depuis un snapshot."},{"k":"wm_op","v":"show","l":"Afficher seulement (occupation disque et seuils actuels)","m":"Lecture seule : disque par nœud et seuils en vigueur."},{"k":"wm_op","v":"set","l":"Modifier les seuils","m":"Relever temporairement les seuils (ex. `50gb` d'espace libre au lieu d'un pourcentage sur un gros disque). Revenez aux valeurs par défaut une fois le problème résolu."},{"k":"wm_op","v":"reset","l":"Revenir aux valeurs par défaut (85% / 90% / 95%)","m":"Supprime les seuils modifiés précédemment et revient aux valeurs par défaut (85% / 90% / 95%)."},{"k":"ilm_op","v":"setup","l":"Nouvelle politique + template + index initial (write alias)","m":"Crée la politique (rollover + suppression), l'index template et l'index initial (`-000001`, alias d'écriture). Pour un nouveau flux de logs."},{"k":"ilm_op","v":"debug","l":"Pourquoi les index existants n'avancent-ils pas ? (explain, retry)","m":"Les index ne font pas de rollover ou ne sont pas supprimés : `_ilm/explain` montre l'étape en erreur ; une fois la cause corrigée, on fait un retry."},{"k":"ilm_op","v":"rollover","l":"Faire un rollover manuel","m":"Un nouvel index d'écriture est créé sans attendre les conditions (après un changement de mapping ou pour un index trop gros)."},{"k":"rs_kind","v":"shrink","l":"_shrink : réduire le nombre de shards (la cible doit être un diviseur du nombre actuel)","m":""},{"k":"rs_kind","v":"split","l":"_split : augmenter le nombre de shards (la cible doit être un multiple du nombre actuel)","m":""}]},{"id":"es-security","file":"install/guides/es-security.json","group":"Elasticsearch","engineId":"elasticsearch","category":"security","title":"Durcissement de la sécurité Elasticsearch (security, TLS, rôles, audit)","summary":"Ni une installation ni l'exploitation courante : un runbook par scénarios pour durcir un cluster Elasticsearch en service. Audit de sécurité en lecture seule, activation de la sécurité désactivée avec le TLS transport (r","level":"Avancé","duration":"10 min (audit) – 2 heures (durcissement complet)","topology":"Cluster Elasticsearch de 1 à 12 nodes (+ Kibana)","tags":["sécurité elasticsearch","elasticsearch security","hardening","durcissement","xpack.security","TLS","elasticsearch-certutil","elasticsearch-users","API key","role mapping","elastic superuser","anonymous","audit logging","IP filtering","kibana_system","service account token","CIS"],"order":20,"steps":4,"scenarios":[{"k":"op","v":"audit","l":"Audit de l'état de sécurité (lecture seule)","m":"Audit audit de sécurité elasticsearch elasticsearch security audit CIS Elasticsearch hardening durcissement elasticsearch exposé exposed elasticsearch xpack.security.enabled missing authentication credentials for REST request security_exception contrôle de sécurité elasticsearch elasticsearch pentest RISK Ne modifie rien. Sécurité activée ou non, accès sans authentification, TLS HTTP/transport, ac"},{"k":"op","v":"all","l":"Durcissement complet (tout, dans l'ordre)","m":"Audit durcissement complet full hardening projet sécurité elastic constat de pentest Audit → security + TLS transport/HTTP (si désactivée ; redémarrage complet du cluster) → utilisateurs/rôles/API keys, désactivation de l'accès anonyme, retrait d'elastic de l'usage quotidien → réseau et filtrage IP → audit logging (si la licence le permet) → accès au dépôt de snapshots → connexion Kibana → audit f"},{"k":"op","v":"enable","l":"Sécurité désactivée : activer la sécurité et le TLS transport","m":"Identité et chiffrement xpack.security.enabled: false security disabled accès sans authentification Elasticsearch built-in security features are not enabled Transport SSL must be enabled if security is enabled invalid configuration for xpack.security.transport.ssl sécurité mise à niveau 7.x Security is disabled xpack.security.transport.ssl pas d'authentification Cluster avec `xpack.security.enable"},{"k":"op","v":"httptls","l":"Passer le trafic HTTP (REST) en TLS","m":"Identité et chiffrement http.ssl xpack.security.http.ssl.enabled received plaintext http traffic on an https channel plaintext http https 9200 elasticsearch-certutil http SSLHandshakeException curl: (52) Empty reply from server http:// 9200 mot de passe en clair sur le réseau La sécurité est activée mais le port 9200 est en HTTP simple : les mots de passe sont lisibles sur le réseau. Les certifica"},{"k":"op","v":"users","l":"Utilisateurs, rôles, API key ; accès elastic et anonyme","m":"Identité et chiffrement utilisateur elastic elastic superuser superuser role mapping API key api_key elasticsearch-users anonymous xpack.security.authc.anonymous action [indices:data/write/bulk[s]] is unauthorized is unauthorized for user unable to authenticate user action [indices:data/read/search] is unauthorized for user expiration api key api key sans expiration break-glass Les applications se"},{"k":"op","v":"auditlog","l":"Activer l'audit logging","m":"Traces et accès audit log xpack.security.audit.enabled audit logging _audit.json piste d'audit qui a supprimé RGPD PCI DSS xpack.security.audit.logfile.events.include journal d'accès Qui a accédé à quel index, les tentatives de connexion échouées, les refus d'autorisation et les modifications des paramètres de sécurité doivent être enregistrés. `xpack.security.audit.enabled` (statique, redémarrage"},{"k":"op","v":"net","l":"Accès réseau et filtrage IP","m":"Traces et accès network.host 0.0.0.0 0.0.0.0:9200 port 9200 ouvert xpack.security.transport.filter xpack.security.http.filter ip filtering shodan firewall 0.0.0.0:9300 network.host: 0.0.0.0 _site_ IP publique 9200/9300 écoutent sur toutes les interfaces ou sont ouverts à tous. `network.host` est restreint à l'IP interne ; avec le firewall et le filtrage IP d'Elasticsearch, seuls les nodes, Kibana "},{"k":"op","v":"snapshot","l":"Sécuriser l'accès au dépôt de snapshots","m":"Traces et accès snapshot repo path.repo repository_exception s3.client access_key readonly repository sécurité des snapshots snapshot_operator Object Lock sécurité des sauvegardes Le répertoire des snapshots est lisible par tous, le privilège de gestion du dépôt est trop large, ou les clés d'accès cloud sont dans elasticsearch.yml. Permissions du répertoire, rôle d'opérateur snapshot, keystore et "},{"k":"op","v":"kibana","l":"Sécuriser la connexion Kibana","m":"Traces et accès kibana_system elasticsearch.username: elastic mot de passe kibana.yml service account token elasticsearch.serviceAccountToken xpack.encryptedSavedObjects.encryptionKey Kibana server is not ready yet unable to retrieve version information from Elasticsearch nodes value of \"elastic\" is forbidden serviceAccountToken kibana.keystore Kibana se connecte avec l'utilisateur `elastic` ou sa"}]},{"id":"mon-prometheus-grafana","file":"install/guides/mon-prometheus-grafana.json","group":"Supervision","engineId":null,"category":"monitoring","title":"Supervision des bases de données avec Prometheus + Grafana","summary":"Met en place un serveur de supervision central (Prometheus, Alertmanager, Grafana) et ajoute sur les serveurs supervisés node_exporter ainsi que les exporters des bases de données choisies : PostgreSQL, MySQL, Oracle, SQ","level":"Intermédiaire","duration":"60–120 min","topology":"1 serveur de supervision + serveurs supervisés","tags":["prometheus","grafana","alertmanager","node_exporter","postgres_exporter","mysqld_exporter","oracledb_exporter","sql_exporter","mongodb_exporter","redis_exporter","elasticsearch_exporter","supervision","monitoring","alerte","dashboard"],"order":1,"steps":44,"scenarios":[]},{"id":"ora19-install","file":"install/guides/ora19-install.json","group":"Oracle","engineId":"oracle","category":"install","title":"Installation d'Oracle Database 19c en instance unique (EE / SE2)","summary":"Installe Oracle Database 19c de zéro sur un serveur unique sous Oracle Linux / RHEL 7.9, 8 ou 9 : préparation du système d'exploitation (preinstall RPM ou à la main), répertoires OFA, installation silencieuse (silent) du","level":"Intermédiaire","duration":"90 à 150 min","topology":"1 serveur","tags":["oracle","19c","installation","install","runInstaller","dbca","netca","silent","release update","applyRU","opatch","cdb","pdb","hugepages","rman","oracle linux","rhel","asm","ol7","multipath","oracle restart"],"order":1,"steps":38,"scenarios":[]},{"id":"ora23-install","file":"install/guides/ora23-install.json","group":"Oracle","engineId":"oracle","category":"install","title":"Installation d'Oracle AI Database 26ai / 23ai (Linux)","summary":"Installe Oracle AI Database 26ai (la version qui remplace 23ai, numéro de version 23.26.x) sur un seul serveur ; vous choisissez l'une des trois méthodes officielles : installation OFA à partir de la gold image ZIP pour ","level":"Intermédiaire","duration":"45–120 min","topology":"1 serveur","tags":["oracle","26ai","23ai","ai database","installation","install","rpm","free","gold image","setupDBHome","dbca","cdb","pdb","oracle linux","rhel","asm","oracle restart"],"order":2,"steps":30,"scenarios":[]},{"id":"ora-storage-prep","file":"install/guides/ora-storage-prep.json","group":"Oracle","engineId":"oracle","category":"install","title":"Préparation du stockage pour Oracle : multipath, noms de périphériques persistants, disques ASM","summary":"À appliquer une seule fois, avant l'installation d'Oracle Grid Infrastructure, d'Oracle Restart ou de la base de données : les LUN SAN sont identifiés par leur WWID, les alias DM-Multipath sont configurés si nécessaire, ","level":"Intermédiaire","duration":"30–90 min","topology":"1 serveur, ou 2–8 nœuds RAC + disques SAN/iSCSI/VM","tags":["oracle","asm","multipath","device-mapper-multipath","dm-multipath","multipath.conf","wwid","udev","règle udev","asmlib","oracleasm","afd","asm filter driver","lvm","xfs","fstab","san","lun","préparation du stockage","stockage","disque","rac","oracle restart"],"order":3,"steps":10,"scenarios":[]},{"id":"ora-grid-asm","file":"install/guides/ora-grid-asm.json","group":"Oracle","engineId":"oracle","category":"install","title":"Oracle Restart : Grid Infrastructure 19c + ASM (serveur unique)","summary":"Installation d'Oracle Restart (Grid Infrastructure 19c standalone, HA_CONFIG) et d'ASM sur un serveur unique : utilisateur grid et groupes ASM, noms de disques persistants (udev, ASMLib ou ASM Filter Driver ; disques à c","level":"Avancé","duration":"90–150 min","topology":"1 serveur + disques ASM non partagés","tags":["oracle","19c","grid infrastructure","oracle restart","asm","gridSetup","HA_CONFIG","udev","asmlib","oracleasm","afd","asm filter driver","srvctl","asmcmd","installation","multipath","ol7"],"order":3,"steps":30,"scenarios":[]},{"id":"ora19-patch-ru","file":"install/guides/ora19-patch-ru.json","group":"Oracle","engineId":"oracle","category":"patch","title":"Appliquer une Release Update (RU) Oracle Database 19c","summary":"Application d'une Release Update trimestrielle sur une base 19c mono-instance ou Oracle Restart (Grid Infrastructure + ASM) : mise à jour d'OPatch, contrôles préalables de conflits et d'espace, sauvegarde de l'ORACLE_HOM","level":"Intermédiaire","duration":"60–120 min (interruption : in-place 30–60 min, out-of-place 10–20 min)","topology":"1 serveur (mono-instance ou Oracle Restart) ; pour RAC, le guide de rolling patch","tags":["oracle","19c","patch","release update","ru","opatch","opatchauto","datapatch","ojvm","out-of-place","rollback","restore point","oracle restart","rac","rolling","standby-first","data guard","standby","1265700.1"],"order":10,"steps":25,"scenarios":[]},{"id":"ora19-rac","file":"install/guides/ora19-rac.json","group":"Oracle","engineId":"oracle","category":"ha","title":"Installation d'Oracle RAC 19c (Grid Infrastructure + ASM + base de données RAC)","summary":"Installe à partir de zéro Oracle Grid Infrastructure 19c (Flex ASM, SCAN) et une base de données Oracle RAC 19c sur deux nœuds ou plus : plan réseau et DNS, préparation des disques partagés (udev ou ASMLib ; avec multipa","level":"Avancé","duration":"4 à 6 heures","topology":"2 à 8 nœuds + disque partagé (SAN/iSCSI) + SCAN dans le DNS","tags":["oracle","rac","19c","grid infrastructure","clusterware","asm","scan","gridsetup","dbca","srvctl","crsctl","cluster","haute disponibilité","udev","asmlib","multipath","ol7","scan","ocr","voting disk"],"order":10,"steps":68,"scenarios":[]},{"id":"ora-rac-patch","file":"install/guides/ora-rac-patch.json","group":"Oracle","engineId":"oracle","category":"patch","title":"Oracle RAC 19c : Release Update en rolling (opatchauto)","summary":"Applique une Release Update GI trimestrielle à un cluster RAC 19c de 2 à 8 nœuds en fonctionnement, sans interruption (rolling) : mise à jour d'OPatch, contrôle des conflits avec opatchauto analyze, sauvegarde de l'OCR e","level":"Avancé","duration":"45 à 90 min par nœud","topology":"RAC 19c existant de 2 à 8 nœuds","tags":["oracle","rac","19c","patch","release update","ru","opatchauto","opatch","datapatch","rolling","grid infrastructure","rollback","scan","service","standby-first","srvctl"],"order":11,"steps":38,"scenarios":[]},{"id":"ora-rac-ops","file":"install/guides/ora-rac-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle RAC : exploitation courante et scénarios de panne","summary":"Exploitation courante et intervention sur incident d'un cluster Oracle RAC (Grid Infrastructure 19c) installé et en fonctionnement : contrôle de santé, diagnostic d'éviction de nœud, mise en maintenance d'un nœud, arrêt ","level":"Avancé","duration":"15 min à 3 heures (selon le scénario)","topology":"RAC existant de 2 à 8 nœuds (Grid Infrastructure 19c ; différences 23ai/26ai dans les notes)","tags":["oracle","rac","grid infrastructure","clusterware","crsctl","srvctl","olsnodes","cluvfy","addnode","ajout de nœud","suppression de nœud","delete node","ocr","olr","voting disk","votedisk","ocrconfig","ocrcheck","scan","vip","oifcfg","interconnect","eviction","ocssd","misscount","service","relocate","application continuity","taf","fan","exploitation courante","panne"],"order":12,"steps":10,"scenarios":[{"k":"scenario","v":"health","l":"Contrôle de santé du cluster (quotidien / avant maintenance)","m":"Contrôle et diagnostic crsctl check cluster crsctl stat res -t cluvfy ressource OFFLINE CRS-4535 CRS-4530 CRS-4639 health check contrôle de santé INTERMEDIATE CRS-4537 CRS-2765 CRS-2878 CRS-4700 CRS-4701 crsctl check crs état du cluster cluster status ressource arrêtée resource offline orachk tfactl contrôle avant maintenance Ne modifie rien : contrôle de la pile Clusterware, des ressources, de l'"},{"k":"scenario","v":"eviction","l":"Un nœud a été évincé du cluster ou a redémarré tout seul (diagnostic d'éviction)","m":"Contrôle et diagnostic CRS-1609 CRS-1610 CRS-1611 CRS-1612 CRS-1615 CRS-1625 CRS-1632 CRS-1656 CRS-8011 node eviction evicted node reboot split brain ocssd.trc misscount disktimeout nœud évincé CRS-1601 CRS-1606 CRS-1607 CRS-1608 CRS-1613 CRS-1614 CRS-8013 ORA-29740 ORA-29770 IPC Send timeout network heartbeat disk heartbeat nœud redémarré le serveur a redémarré tout seul rebootless restart instan"},{"k":"scenario","v":"stopnode","l":"Arrêter un nœud pour maintenance et le redémarrer","m":"Démarrer / arrêter crsctl stop crs crsctl start crs maintenance de nœud OS patch reboot CRS-4133 CRS-4123 drain_timeout srvctl stop instance CRS-2673 CRS-2677 CRS-2675 crsctl disable crs crsctl enable crs mode maintenance maintenance kernel update mise à jour du noyau firmware À choisir pour un patch du système d'exploitation, une maintenance du noyau ou du matériel sur un nœud. Les autres nœuds c"},{"k":"scenario","v":"stopall","l":"Arrêter et redémarrer tout le cluster de façon planifiée","m":"Démarrer / arrêter crsctl stop cluster -all crsctl start cluster -all arrêt complet cluster shutdown interruption planifiée srvctl stop database CRS-2677 CRS-4133 CRS-4123 crsctl stop crs crsctl start crs maintenance SAN maintenance datacenter datacenter maintenance power off arrêter tous les serveurs À choisir lorsque tous les nœuds doivent être arrêtés en même temps, par exemple pour une mainten"},{"k":"scenario","v":"addnode","l":"Ajouter un nouveau nœud au cluster","m":"Ajouter / supprimer un nœud addnode.sh addNode gridSetup.sh -addNode ajout de nœud add node cluvfy stage -pre nodeadd dbca -addInstance INS-06006 PRVG- PRVF- INS-13013 INS-40912 CLSRSC-184 PRVG-11050 PRVF-4664 PRVF-7617 cvuqdisk new node ajouter un nouveau serveur extension du cluster À choisir lorsqu'un nouveau serveur rejoint le cluster (augmentation de capacité, renouvellement matériel). Sans i"},{"k":"scenario","v":"delnode","l":"Supprimer un nœud du cluster","m":"Ajouter / supprimer un nœud crsctl delete node deinstall -local dbca -deleteInstance suppression de nœud delete node remove node cluvfy stage -post nodedel crsctl unpin css CRS-4658 CRS-4661 runInstaller -updateNodeList srvctl remove instance decommission retirer le serveur du cluster démonter l'ancien serveur À choisir lorsqu'un serveur quitte définitivement le cluster (restitution, panne matérie"},{"k":"scenario","v":"service","l":"Ajouter, modifier, déplacer ou supprimer un service","m":"Services et réseau srvctl add service srvctl modify service srvctl relocate service srvctl remove service ORA-12514 PRCD-1026 preferred available TAF Application Continuity TAC FAN déplacement de service PRKO-2017 PRCR-1090 ORA-12514: TNS:listener does not currently know of service srvctl start service srvctl status service failback service non revenu drain_timeout ajout de service create service "},{"k":"scenario","v":"net","l":"Changement de SCAN, de VIP, de port du listener ou d'interconnect","m":"Services et réseau srvctl modify scan srvctl modify scan_listener srvctl modify nodeapps srvctl modify listener oifcfg setif oifcfg delif oifcfg getif changement d'IP SCAN IP changement de VIP interconnect private network ORA-12545 listener port PRCS-1034 PRKO-2310 PRCR-1079 oifcfg HAIP 169.254 changement de port changement de SCAN VIP change remote_listener ASMNET1LSNR_ASM À choisir si le nom ou "},{"k":"scenario","v":"ocr_backup","l":"Sauvegarder l'OCR et l'OLR, contrôler les sauvegardes","m":"OCR, OLR et voting ocrconfig -manualbackup ocrconfig -showbackup ocrconfig -local -manualbackup sauvegarde OLR sauvegarde OCR OCR backup ocrcheck ocrdump PROT- ocrconfig -copy ocrconfig -showbackuploc OLR backup où est la sauvegarde OCR sauvegarde manuelle de l'OCR Sans interruption : sauvegarde manuelle de l'OCR, sauvegarde de l'OLR sur chaque nœud et copie des sauvegardes hors des disques du clu"},{"k":"scenario","v":"ocr_restore","l":"OCR corrompu ou disk group CRS perdu : restaurer depuis la sauvegarde","m":"OCR, OLR et voting PROT-602 PROC-26 CRS-4535 CRS-4000 CRS-1606 ocrconfig -restore crsctl start crs -excl -nocrs OCR corruption OCR corrompu disk group CRS perdu voting disk perdu ORA-15017 ORA-15063 PROT-35 PROT-19 ORA-15018 ORA-15033 ocrdump OCR restore CRSD ne démarre pas Logical corruption check failed À choisir si `ocrcheck` signale une corruption, si CRSD ne démarre pas (erreurs PROT-/PROC-) "},{"k":"scenario","v":"olr_restore","l":"Clusterware ne démarre pas sur un nœud : OLR corrompu","m":"OCR, OLR et voting ocrcheck -local ocrconfig -local -restore OLR corrompu OLR corrupt ohasd ne démarre pas CRS-4124 CRS-4639 PROCL- CRS-4123 ohasd.trc olr.loc ocrconfig -local -showbackup Local registry integrity check failed OHASD ne démarre pas un seul nœud ne démarre pas À choisir si, alors que les autres nœuds fonctionnent, `crsctl start crs` ne parvient pas à démarrer OHASD sur un seul nœud e"},{"k":"scenario","v":"ocrvote_move","l":"Déplacer l'OCR et les voting files vers un autre disk group","m":"OCR, OLR et voting crsctl replace votedisk crsctl query css votedisk ocrconfig -add ocrconfig -delete déplacement du voting disk votedisk déplacement de l'OCR storage migration ORA-15274 ocrconfig -backuploc asmcmd spmove asmcmd pwmove déplacement du disk group CRS déplacement des voting files OCR move changement de stockage Sans interruption : ocrconfig -add/-delete et crsctl replace votedisk. À "}]},{"id":"ora-asm-ops","file":"install/guides/ora-asm-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle ASM : exploitation courante et scénarios de panne","summary":"Exploitation courante des disk groups ASM sur un Oracle Restart ou un RAC installé, et conduite à tenir en cas de panne : contrôle de capacité (usable_file_mb), suivi du rebalance, ajout/remplacement/retrait de disques, ","level":"Avancé","duration":"10 min – quelques heures (selon la durée du rebalance)","topology":"Oracle Restart (serveur unique) ou RAC de 2 à 8 nodes, Grid Infrastructure 19c (différences 23ai/26ai dans les notes)","tags":["oracle","asm","disk group","diskgroup","add disk","drop disk","replace disk","rebalance","v$asm_operation","usable_file_mb","disk_repair_time","online disk","offline disk","failgroup","compatible.asm","asmcmd","spbackup","md_backup","asm_diskstring","dsset","udev","asmlib","afd","multipath","rac","oracle restart","exploitation courante","panne"],"order":13,"steps":4,"scenarios":[{"k":"op","v":"capacity","l":"Contrôle de capacité et de santé des disk groups","m":"Contrôle et supervision usable_file_mb required_mirror_free_mb asmcmd lsdg disk group plein occupation ASM free_mb capacité ORA-15041 ORA-01653 ORA-17502 ORA-15041: diskgroup space exhausted diskgroup space exhausted disk group full ASM disk group full usable_file_mb négatif negative usable_file_mb ASM plus d'espace asmcmd du Ne modifie rien : le disk group est-il plein, combien d'espace reste-t-i"},{"k":"op","v":"rebal","l":"Suivre, accélérer ou ralentir un rebalance","m":"Contrôle et supervision v$asm_operation gv$asm_operation rebalance REBALANCE POWER asm_power_limit asmcmd lsop est_minutes resync compact rebalance lent rebalance slow rebalance bloqué rebalance hung ERRS ARB0 fin du rebalance Avancement et temps restant d'un rebalance/resync en cours (v$asm_operation, est_minutes) et modification du POWER. La base reste ouverte ; seule la charge d'E/S change."},{"k":"op","v":"add","l":"Ajouter un nouveau disque à un disk group","m":"Opérations sur les disques ADD DISK ALTER DISKGROUP ADD DISK ajout de disque libérer de l'espace augmentation de capacité ORA-15041 ORA-15031 ORA-15033 ORA-15014 ORA-15410 ORA-15032 ORA-15075 ORA-17502 add disk nouveau LUN new LUN agrandir le disk group extend diskgroup Le disk group manque d'espace ou doit être agrandi : préparer le nouveau LUN sur tous les nodes (multipath/udev/ASMLib/AFD), l'aj"},{"k":"op","v":"replace","l":"Remplacer un disque par un nouveau (REPLACE DISK)","m":"Opérations sur les disques REPLACE DISK remplacement de disque replace disk ORA-15145 storage migration renouvellement de disque ORA-15221 ORA-15033 replace failed disk remplacer un disque en panne disk replacement migration de stockage storage migration ASM Remplace en une seule opération un disque en panne (offline) ou à renouveler par un nouveau ; seules les données de ce disque sont copiées (1"},{"k":"op","v":"drop","l":"Retirer un disque d'un disk group","m":"Opérations sur les disques DROP DISK retrait de disque UNDROP DISKS FORMER ORA-15041 ORA-15067 restitution de LUN drop disk DROPPING détacher un disque remove disk from diskgroup rendre un LUN Pour restituer un LUN ou le retirer de la baie : avec DROP DISK, les données sont déplacées vers les disques restants ; le disque n'est pas détaché tant que le rebalance n'est pas terminé et que le disque n'"},{"k":"op","v":"failed","l":"Un disque est offline ou en panne (redondance NORMAL/HIGH)","m":"Opérations sur les disques ONLINE DISK OFFLINE DISK disk_repair_time repair_timer mode_status OFFLINE ORA-15042 ORA-15040 ORA-15130 ORA-15063 missing disk disk offline fast mirror resync ORA-15080 ORA-15081 ORA-27061 ORA-27072 ORA-15066 WARNING: Write Failed WARNING: Read Failed remaining active paths: 0 disque passé offline SYNCING disque illisible I/O error ASM Dans un disk group NORMAL/HIGH, un"},{"k":"op","v":"create","l":"Créer un nouveau disk group","m":"Gestion des disk groups CREATE DISKGROUP création de disk group au_size compatible.asm compatible.rdbms failgroup ORA-15018 ORA-15221 ORA-15072 ORA-15031 ORA-15033 create diskgroup new disk group nouveau disk group Pour créer un nouveau disk group de données, FRA ou applicatif : redondance, taille d'AU, attributs compatible.asm/rdbms ; en RAC, mount sur tous les nodes et ressource Clusterware. Les"},{"k":"op","v":"mount","l":"Monter ou démonter un disk group (mount/dismount)","m":"Gestion des disk groups srvctl start diskgroup srvctl stop diskgroup ALTER DISKGROUP MOUNT DISMOUNT ORA-15027 ORA-15017 disk group ne se monte pas v$asm_client ORA-15063 ORA-15040 ORA-15130 ORA-15032 ORA-15042 PRCR-1065 CRS-2674 MOUNT FORCE diskgroup dismounted disk group ne s'ouvre pas insufficient number of disks Pour arrêter un disk group de façon planifiée (dismount) ou démarrer un disk group "},{"k":"op","v":"spbackup","l":"Sauvegarde du spfile ASM, du fichier de mots de passe et des métadonnées","m":"Configuration asmcmd spbackup asmcmd spget asmcmd pwget asmcmd pwcopy md_backup md_restore sauvegarde du spfile ASM orapwASM asmcmd spcopy GPnP profile profile.xml ASM password file backup ASM metadata backup sauvegarde ASM Sans interruption : le spfile ASM, le fichier de mots de passe, la structure des disk groups (md_backup) et, en RAC, le profil GPnP sont sauvegardés hors d'ASM, afin de pouvoir"},{"k":"op","v":"dstring","l":"Modifier la chaîne de découverte des disques ASM (asm_diskstring)","m":"Configuration asm_diskstring asmcmd dsset asmcmd dsget discovery string chaîne de découverte ORA-15014 kfod ORA-15020 discovered duplicate ASM disk afd_dsset asm_diskstring change disque introuvable Pour modifier le motif de chemin où ASM cherche les disques (ex. passage au multipath, nouveau répertoire udev, ASMLib/AFD) : tester d'abord avec kfod, puis asmcmd dsset. Tous les disques existants (OC"}]},{"id":"ora-autoupgrade","file":"install/guides/ora-autoupgrade.json","group":"Oracle","engineId":"oracle","category":"upgrade","title":"Mise à niveau 19c → Oracle AI Database 26ai (23ai) : AutoUpgrade","summary":"À utiliser pour une mise à niveau de version majeure (19c → 26ai) ; pour une RU trimestrielle au sein de la même version, utilisez le guide « Appliquer une Release Update (RU) Oracle Database 19c ». Avec AutoUpgrade, l'o","level":"Avancé","duration":"2 à 4 heures (interruption : 30 à 90 min selon la taille de la base et les composants)","topology":"1 serveur, Oracle Restart ou RAC (home 26ai sur tous les nœuds)","tags":["oracle","mise à niveau","autoupgrade","19c","26ai","23ai","noncdb","pdb","target_cdb","point de restauration","restoration","timezone","compatible","rollback","rac","oracle restart","cluster_database","srvctl"],"order":20,"steps":25,"scenarios":[]},{"id":"ora-dataguard","file":"install/guides/ora-dataguard.json","group":"Oracle","engineId":"oracle","category":"ha","title":"Oracle Data Guard 19c physical standby (avec Broker ; instance unique, Oracle Restart, RAC)","summary":"Construit une physical standby d'une base primary 19c en fonctionnement avec RMAN DUPLICATE … FROM ACTIVE DATABASE et l'ajoute au Data Guard Broker. Résultat : une standby qui reçoit et applique le redo en temps réel, un","level":"Avancé","duration":"2 à 4 heures (selon le volume de données)","topology":"Primary (instance unique / Oracle Restart / RAC 2 à 8 nœuds) + physical standby (instance unique / Oracle Restart / RAC) + observer en option","tags":["oracle","data guard","dataguard","standby","physical standby","dgmgrl","broker","rman duplicate","switchover","failover","fast-start failover","observer","19c","pra","rac","oracle restart","srvctl","standby redo log","instance d'apply"],"order":20,"steps":43,"scenarios":[]},{"id":"ora-datapump","file":"install/guides/ora-datapump.json","group":"Oracle","engineId":"oracle","category":"backup","title":"Oracle Data Pump : export / import, migration, rafraîchissement de schéma et transportable","summary":"Export et import de table, de schéma, de tablespace ou complet avec expdp/impdp, migration entre deux bases (fichier dump + scp + checksum, ou NETWORK_LINK) et extraction du DDL d'un dump (SQLFILE). Comprend la préparati","level":"Intermédiaire","duration":"30–90 min (selon le volume de données)","topology":"1 base de données (instance unique, Oracle Restart ou RAC) ; source + cible pour une migration","tags":["oracle","data pump","datapump","expdp","impdp","export","import","dump","parfile","remap_schema","network_link","sqlfile","table_exists_action","flashback_time","migration de données","migration","logical backup","sauvegarde logique","rac","scan","cluster","acfs","cron","wallet","planification","refresh","rafraîchissement de schéma","prod vers test","drop user cascade","transportable tablespace","tts","xtts","full transportable","transportable=always","dbms_tts","transport_set_check","endian","rman convert","cross-platform","11g vers 19c","non-cdb vers pdb","chiffrement","encryption_password","compression","attach","kill_job","start_job","continue_client","orphelin","dba_datapump_jobs","lob","securefile","data_options"],"order":20,"steps":16,"scenarios":[{"k":"op","v":"export","l":"Export : produire un fichier dump à partir de la base","m":"Export / import expdp export faire un dump sauvegarde logique logical backup ORA-39001 ORA-39000 ORA-39070 ORA-31633 ORA-01555 ORA-39095 ORA-39094 ORA-00439 LRM-00123 UDE-00002 ORA-39127 Une table, un schéma, un tablespace ou toute la base est écrit dans un fichier dump (expdp). La base reste ouverte et l'application continue de fonctionner. Il s'agit d'une copie logique : elle ne remplace pas une"},{"k":"op","v":"import","l":"Import : charger un fichier dump dans la base","m":"Export / import impdp import charger un dump REMAP_SCHEMA REMAP_TABLESPACE TABLE_EXISTS_ACTION ORA-39002 ORA-39087 ORA-39142 ORA-39143 ORA-39083 ORA-12899 ORA-39171 ORA-39082 ORA-31684 ORA-31640 ORA-39059 ORA-01917 ORA-39151 ORA-39111 ORA-01653 ORA-30036 Un fichier dump déjà produit est chargé avec impdp ; les noms de schéma, de tablespace et de table peuvent être remappés. La cible reste ouverte "},{"k":"op","v":"move","l":"Migration : transférer les données de la base source vers la cible","m":"Migration et rafraîchissement migration de données migration NETWORK_LINK database link scp checksum ORA-31679 ORA-02085 ORA-39183 Export → copie de fichiers → import, ou directement avec NETWORK_LINK sans fichier dump. Choisissez cette carte si le schéma n'existe pas encore sur la cible (chargement initial) ; si le même schéma existe déjà sur la cible et doit être remplacé par celui de la product"},{"k":"op","v":"sqlfile","l":"Extraire uniquement le DDL d'un fichier dump (SQLFILE)","m":"Export / import SQLFILE extraire le DDL contenu du dump que contient le dump métadonnées DBMS_METADATA Rien n'est chargé ; les ordres CREATE des objets sont écrits dans un fichier .sql. La base n'est pas modifiée."},{"k":"op","v":"refresh","l":"Rafraîchissement de schéma de la production vers le test (refresh)","m":"Migration et rafraîchissement refresh rafraîchissement de schéma rafraîchir le test prod vers test production vers test rafraîchissement UAT DROP USER CASCADE ORA-01940 ORA-02380 ORA-01917 ORA-31684 ORA-00031 TABLE_EXISTS_ACTION=REPLACE Un schéma qui existe déjà en test/UAT est remplacé par sa version actuelle de production : sauvegarde du schéma cible, fermeture des sessions, DROP USER … CASCADE "},{"k":"op","v":"tts","l":"Déplacer un tablespace en copiant les datafiles (transportable tablespace)","m":"Transportable (copie de datafiles) transportable tablespace TTS XTTS TRANSPORT_TABLESPACES TRANSPORT_DATAFILES DBMS_TTS TRANSPORT_SET_CHECK endian cross-platform RMAN CONVERT ORA-29335 ORA-29341 ORA-29342 ORA-29345 ORA-29349 ORA-19722 ORA-01647 Les gros tablespaces (plusieurs centaines de Go et plus) sont déplacés en copiant les datafiles et non ligne par ligne ; plateforme/endian identique ou dif"},{"k":"op","v":"fulltts","l":"Déplacer toute la base : full transportable (11g/12c → 19c, non-CDB → PDB)","m":"Transportable (copie de datafiles) full transportable TRANSPORTABLE=ALWAYS FULL=Y VERSION=12 11g vers 19c de 11g vers 19c non-CDB vers PDB migration vers PDB migration upgrade by migration ORA-39123 ORA-01647 ORA-29349 ORA-39127 Avec FULL=Y TRANSPORTABLE=ALWAYS, les tablespaces utilisateur sont déplacés vers une nouvelle PDB sous forme de datafiles, et tout le reste (utilisateurs, PL/SQL, privilèg"},{"k":"op","v":"jobfix","l":"Job Data Pump interrompu / suspendu : surveiller, reprendre, arrêter","m":"Surveillance et incidents ATTACH START_JOB CONTINUE_CLIENT KILL_JOB STOP_JOB dba_datapump_jobs orphelin master table NOT RUNNING IDLING suspendu resumable ORA-39171 ORA-31626 ORA-31633 ORA-39002 ORA-31634 ORA-39097 ORA-39126 Un job expdp/impdp est suspendu (resumable wait), arrêté ou interrompu ; vous voulez vous y rattacher avec ATTACH et le reprendre, le terminer avec KILL_JOB ou nettoyer une ma"}]},{"id":"ora-dataguard-ops","file":"install/guides/ora-dataguard-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle Data Guard : exploitation courante et scénarios de panne (19c / 23ai)","summary":"Runbook d'exploitation courante et de reprise après panne pour une configuration Data Guard (physical standby) existante ; pour la mise en place initiale, utilisez le guide d'installation Data Guard. Si vous ne savez pas","level":"Avancé","duration":"5 min (contrôle) – 4 heures (reconstruction)","topology":"Primary (instance unique / Oracle Restart / RAC) + physical standby (instance unique / Oracle Restart / RAC) + observer facultatif","tags":["oracle","data guard","dataguard","standby","physical standby","dgmgrl","broker","switchover","failover","reinstate","archive gap","fal","recover from service","roll forward","snapshot standby","active data guard","redo apply","mrp","apply lag","transport lag","protection mode","fast-start failover","fsfo","observer","standby_file_management","standby redo log","unnamed","rman","runbook","dr","rac","19c","23ai"],"order":21,"steps":5,"scenarios":[{"k":"scenario","v":"health","l":"Contrôle de l'état et du retard (tout va-t-il bien ?)","m":"Supervision et contrôles ORA-16809 ORA-16810 ORA-16853 ORA-16855 ORA-16857 ORA-16766 ORA-16825 apply lag transport lag retard lag SHOW CONFIGURATION WARNING état Data Guard ORA-16854 ORA-16856 ORA-16191 ORA-16198 ORA-03135 ORA-16714 ORA-16792 ORA-16047 standby en retard transport lag élevé contrôle de santé Data Guard health check Lecture seule : configuration, transport/apply lag, trou d'archives"},{"k":"scenario","v":"switchover","l":"Switchover planifié (permuter les rôles)","m":"Changement de rôle switchover changement de rôle role transition exercice PRA ORA-16475 ORA-16470 ORA-16467 Ready for Switchover ORA-16541 ORA-16516 switchover_status TO STANDBY SESSIONS ACTIVE switchback revenir aux rôles d'origine maintenance planifiée bascule de site Le primary et le standby sont sains et vous voulez permuter les rôles de façon planifiée (maintenance, exercice PRA, retour aux r"},{"k":"scenario","v":"failover","l":"Primary perdu : failover vers le standby","m":"Panne et reprise failover primary planté primary down perte de site disaster ORA-16472 ORA-01034 ORA-12541 FAILOVER TO ACTIVATE PHYSICAL STANDBY ORA-12514 ORA-12170 ORA-03113 ORA-16625 ORA-16778 primary injoignable perte du datacenter FAILOVER TO IMMEDIATE urgence Le primary est injoignable et ne reviendra pas (perte du serveur, du stockage ou du site) ; le standby devient primary. Irréversible : "},{"k":"scenario","v":"reinstate","l":"Après un failover, réintégrer l'ancien primary comme standby (reinstate)","m":"Panne et reprise reinstate ORA-16661 ORA-16795 ORA-38729 ORA-16653 needs to be reinstated ancien primary flashback ORA-16649 split-brain l'ancien primary ne s'ouvre pas FastStartFailoverAutoReinstate REINSTATE DATABASE standby_became_primary_scn Un failover a eu lieu et le serveur de l'ancien primary est revenu ; si Flashback Database est activé, il devient standby du nouveau primary sans reconstr"},{"k":"scenario","v":"gap","l":"Trou d'archives (gap) : le standby attend des archive logs manquants","m":"Panne et reprise archive gap trou d'archives v$archive_gap FAL Fetching gap ORA-00308 ORA-16401 ORA-27037 ORA-16055 gap sequence waiting for log WAIT_FOR_GAP FAL[client] Media Recovery Waiting for thread archive manquante missing archive log ORA-16089 `v$archive_gap` renvoie des lignes ou l'alert log contient un message FAL/gap ; l'apply est bloqué sur une séquence. Les bases restent ouvertes. Si "},{"k":"scenario","v":"rollfwd","l":"Standby très en retard ou archives absentes : mise à niveau (RECOVER FROM SERVICE)","m":"Panne et reprise roll forward mise à niveau incrémentale RECOVER STANDBY DATABASE FROM SERVICE nologging ORA-01578 ORA-26040 standby en retard resync resynchroniser ORA-10567 BACKUP INCREMENTAL FROM SCN standby en retard de plusieurs jours NONLOGGED BLOCK incremental roll forward Les archives du gap n'existent plus nulle part, des opérations NOLOGGING ont corrompu le standby ou le standby a plusie"},{"k":"scenario","v":"rebuild","l":"Reconstruire le standby depuis zéro","m":"Panne et reprise rebuild reconstruire re-create standby ORA-16795 DUPLICATE standby perdu controlfile perdu ORA-00205 ORA-01157 fichiers du standby supprimés DROP DATABASE perte de disque du standby Le reinstate (pas de flashback) et la mise à niveau incrémentale sont impossibles, ou les fichiers/le fichier de contrôle du standby sont perdus ; le standby est supprimé puis recréé par DUPLICATE. Le "},{"k":"scenario","v":"snapshot","l":"Convertir en snapshot standby / revenir (copie modifiable pour tests)","m":"Tests et lecture snapshot standby CONVERT TO SNAPSHOT STANDBY environnement de test read write standby ORA-16000 ORA-38784 ORA-16433 CONVERT TO PHYSICAL STANDBY SNAPSHOT STANDBY guaranteed restore point ouvrir le standby pour des tests Vous voulez ouvrir temporairement le standby en lecture-écriture pour des tests, puis abandonner les modifications et revenir en physical standby ; le primary n'est"},{"k":"scenario","v":"apply","l":"Arrêter / démarrer le Redo Apply, changer l'instance d'apply, ouvrir en read-only","m":"Exploitation courante MRP redo apply APPLY-OFF APPLY-ON apply instance read only Active Data Guard ORA-16766 ORA-16136 ORA-01153 real-time query WAIT_FOR_LOG APPLYING_LOG READ ONLY WITH APPLY OPEN READ ONLY PreferredApplyInstance intended state apply arrêté MRP ne tourne pas Vous voulez arrêter le MRP pour une maintenance ou le redémarrer, déplacer l'apply vers une autre instance d'un standby RAC,"},{"k":"scenario","v":"prot","l":"Mode de protection, Fast-Start Failover et observer","m":"Exploitation courante protection mode MaxAvailability MaxPerformance MaxProtection SYNC ASYNC FSFO fast-start failover observer ORA-16627 ORA-16651 ORA-16820 ORA-16819 ORA-16072 ORA-16647 NetTimeout LogXptMode FASTSYNC protection_level mode de protection Vous voulez passer d'un mode à l'autre entre MaxPerformance / MaxAvailability / MaxProtection, activer ou désactiver le FSFO, ou gérer l'observer"},{"k":"scenario","v":"files","l":"Ajout de datafile, redo log et standby redo log / fichier UNNAMED","m":"Exploitation courante standby_file_management UNNAMED ORA-01111 ORA-01110 ORA-01157 ORA-01274 ajouter un datafile add datafile ajouter un redo log standby redo log SRL ORA-16789 db_file_name_convert ADD STANDBY LOGFILE ORA-01136 datafile ajouté standby Un datafile ou un redo log doit être ajouté au primary, un fichier UNNAMED est apparu sur le standby (ORA-01111/ORA-01274) ou des standby redo logs"},{"k":"scenario","v":"backup","l":"Sauvegarder avec RMAN depuis le standby","m":"Exploitation courante rman standby backup sauvegarde depuis le standby archivelog deletion policy RMAN-08137 recovery catalog offload backup RMAN-06820 BACKED UP 1 TIMES CONFIGURE DB_UNIQUE_NAME sauvegarde standby Vous voulez exécuter la sauvegarde RMAN sur le standby pour décharger le primary et adapter la politique de suppression des archives en conséquence."}]},{"id":"ora-backup-strategy","file":"install/guides/ora-backup-strategy.json","group":"Oracle","engineId":"oracle","category":"backup","title":"Stratégie de sauvegarde Oracle et automatisation (RMAN + Data Pump)","summary":"Met en place un plan de sauvegarde permanent et automatisé pour une base de données installée (la restauration après une panne fait l'objet d'un guide distinct : \"Restauration et récupération avec RMAN\") : level 0 hebdom","level":"Intermédiaire","duration":"60–120 min (hors durée du premier level 0)","topology":"1 serveur (instance unique ou Oracle Restart) ou RAC (2–8 nœuds)","tags":["oracle","rman","sauvegarde","backup","stratégie","level 0","level 1","incrémentale","archivelog","fra","rétention","block change tracking","data pump","expdp","crontab","cron","systemd timer","dbms_scheduler","automatisation","wallet","mkstore","rac","oracle restart","snapshot controlfile","asm","/etc/cron.d","ol7","catalogue de récupération","create catalog","register database","test de restauration","restore drill","restore validate","preview"],"order":21,"steps":33,"scenarios":[]},{"id":"ora-rman-restore","file":"install/guides/ora-rman-restore.json","group":"Oracle","engineId":"oracle","category":"backup","title":"Restauration et récupération avec RMAN","summary":"Guide pas à pas pour exécuter la bonne récupération RMAN au moment de la panne : 19 scénarios — perte de datafile, de SYSTEM/UNDO, de tempfile, blocs corrompus, perte de online redo log, de controlfile ou de spfile ; res","level":"Avancé","duration":"30 min – quelques heures (selon la taille de la base)","topology":"1 serveur (instance unique ou Oracle Restart) ou RAC ; pour le scénario nouvel hôte, source + cible","tags":["oracle","rman","restore","recovery","restauration","pitr","point in time","resetlogs","recover table","controlfile","spfile","autobackup","datafile","preview","validate","dbid","nouveau serveur","disaster recovery","rac","oracle restart","cluster_database","srvctl","asm","block corruption","bloc corrompu","ORA-01578","redo log","ORA-00313","clear logfile","tempfile","tspitr","flashback database","restore point","dropped pdb","unplug","from service","data guard","restore archivelog","catalog start with","set newname","undo","system tablespace"],"order":22,"steps":10,"scenarios":[{"k":"scenario","v":"preview","l":"Répétition : RESTORE … PREVIEW / VALIDATE (ne modifie rien)","m":"Répétition et enregistrements de sauvegarde RESTORE PREVIEW RESTORE VALIDATE répétition de restauration test de restauration est-ce que la sauvegarde fonctionne backup test restore drill exercice de restauration RMAN VALIDATE sauvegarde intègre Rien n'est cassé ; vous voulez vérifier, sans écrire de données, que les sauvegardes suffisent réellement à un retour arrière (la base reste ouverte). C'es"},{"k":"scenario","v":"catalog","l":"Sauvegardes déplacées dans un autre répertoire / RMAN ne trouve pas la sauvegarde (CATALOG)","m":"Répétition et enregistrements de sauvegarde CATALOG START WITH RMAN-06023 RMAN-06025 RMAN-06026 ORA-19505 ORA-27037 EXPIRED sauvegarde déplacée backup moved no backup found CROSSCHECK NFS modifié sauvegarde introuvable Les pièces de sauvegarde existent mais RMAN ne les connaît pas (déplacées, NFS modifié, copiées depuis un autre serveur) : vous obtenez RMAN-06023/06025. La base reste ouverte."},{"k":"scenario","v":"arch","l":"Restaurer uniquement les archive logs (RESTORE ARCHIVELOG)","m":"Répétition et enregistrements de sauvegarde RESTORE ARCHIVELOG ORA-00308 restaurer archive log archive log restore archive gap LogMiner SET ARCHIVELOG DESTINATION Les archives d'une plage de séquences donnée sont nécessaires sur disque pour LogMiner, un gap de standby ou une recovery manuelle ; la base reste ouverte, aucun fichier n'est écrasé."},{"k":"scenario","v":"datafile","l":"Perte/corruption de datafile ou de tablespace (la base reste ouverte)","m":"Perte / corruption ORA-01157 ORA-01110 ORA-01116 ORA-27041 ORA-00376 ORA-01113 datafile perdu datafile supprimé missing datafile lost datafile tablespace perdu ORA-27037 ORA-01135 ORA-01115 ORA-01114 datafile corrompu La base est ouverte, seul un datafile ou un tablespace est illisible (ORA-01157/01110/01116) ; tout le reste fonctionne. Si le fichier appartient à SYSTEM/UNDO, choisissez la carte \""},{"k":"scenario","v":"block","l":"Blocs corrompus (ORA-01578, block media recovery)","m":"Perte / corruption ORA-01578 ORA-26040 ORA-01110 bloc corrompu block corruption corrupt block v$database_block_corruption RECOVER CORRUPTION LIST block media recovery nologging ORA-19566 Marked Corrupt Corrupt block relative dba DBVERIFY Une requête renvoie ORA-01578/ORA-26040, la sauvegarde s'arrête sur ORA-19566 ou VALIDATE a trouvé des blocs corrompus ; le reste du fichier est sain. La base res"},{"k":"scenario","v":"sysdf","l":"Datafile SYSTEM ou UNDO perdu (la base ne s'ouvre pas)","m":"Perte / corruption ORA-01157 ORA-01110 ORA-01116 ORA-01147 ORA-00376 ORA-01541 SYSTEM datafile UNDO datafile tablespace undo perdu system tablespace lost la base ne s'ouvre pas ORA-01113 system01.dbf undotbs Le fichier perdu ou corrompu appartient au tablespace SYSTEM ou UNDO : il ne peut pas être mis offline, la base (ou seulement la PDB s'il s'agit de fichiers de PDB) est arrêtée et le fichier e"},{"k":"scenario","v":"temp","l":"Tempfile perdu ou corrompu (tablespace TEMP)","m":"Perte / corruption ORA-01157 ORA-01187 ORA-25153 ORA-01116 ORA-01122 ORA-01110 tempfile temp tablespace tablespace temporaire TEMP perdu temp01.dbf dba_temp_files Les tris/hash renvoient ORA-01157/ORA-01187/ORA-25153 et le fichier est un tempfile (le numéro dans le message d'erreur est supérieur à la valeur de `db_files`, p. ex. `data file 201`). Aucune restauration n'est nécessaire ; un nouveau t"},{"k":"scenario","v":"redo","l":"Online redo log perdu ou corrompu","m":"Perte / corruption ORA-00313 ORA-00312 ORA-00327 ORA-00366 ORA-00367 ORA-00353 ORA-00354 ORA-00316 ORA-01624 redo log perdu online redo lost redo log CLEAR LOGFILE ORA-00314 ORA-00320 ORA-00321 ORA-00340 ORA-00350 ORA-00470 CLEAR UNARCHIVED ORA-00313/00312 (ouverture impossible) ou ORA-00327/00366/00367 (corrompu) dans l'alert log. Si un membre est perdu ou si le groupe est INACTIVE, la base reste"},{"k":"scenario","v":"controlfile","l":"Toutes les copies du controlfile sont perdues","m":"Perte / corruption ORA-00205 ORA-00202 ORA-00210 ORA-27037 controlfile perdu control file lost RESTORE CONTROLFILE autobackup ORA-00206 ORA-00214 ORA-00221 ORA-00227 controlfile corrompu En phase MOUNT, l'instance ne trouve pas ou ne peut pas lire le controlfile (ORA-00205/00202/00210) ; datafiles et redo sont sains. Si l'une des copies est saine, aucune restauration n'est nécessaire (l'étape de d"},{"k":"scenario","v":"spfile","l":"spfile perdu","m":"Perte / corruption ORA-01078 LRM-00109 ORA-01565 spfile perdu spfile lost parameter file RESTORE SPFILE CRS-5017 PRCR-1079 spfile supprimé Le spfile a été supprimé ou corrompu ; l'instance ne démarre pas (ORA-01078/LRM-00109), ou bien l'instance tourne mais le fichier n'existe plus. Les datafiles ne sont pas touchés, aucune perte de données ; si l'instance tourne encore, il n'y a pas non plus d'in"},{"k":"scenario","v":"full","l":"Restauration complète, même serveur (datafiles perdus ; controlfile et redo sains)","m":"Perte / corruption tous les datafiles perdus all datafiles lost full restore RESTORE DATABASE panne disque storage failure disque perdu LUN perdu datafiles supprimés Tous les datafiles ou la plupart ont disparu, le controlfile et les online redo logs sont sains ; la base est arrêtée, aucune perte de données. Si l'ancien disque/répertoire des fichiers n'est plus utilisable, choisissez la carte \"aut"},{"k":"scenario","v":"relocate","l":"Disque perdu : restaurer la base dans un autre répertoire / dans ASM (SET NEWNAME)","m":"Perte / corruption SET NEWNAME SWITCH DATAFILE ALL restauration vers un autre répertoire restauration dans ASM new location disque remplacé point de montage perdu relocate ORA-15001 ORA-15040 disk group dismounted Le disque ou le disk group qui contient les datafiles est inutilisable ; controlfile et redo sont sains. Les fichiers sont restaurés dans un nouveau répertoire ou un disk group ASM, la b"},{"k":"scenario","v":"pitr","l":"Retour à une date / un SCN (PITR, RESETLOGS)","m":"Retour dans le temps et annulation PITR point in time recovery retour dans le temps retour à un SCN DELETE erroné UPDATE erroné TRUNCATE erreur logique logical error UNTIL TIME données supprimées DROP USER UNTIL SCN RESETLOGS DELETE/UPDATE/TRUNCATE erroné ou version applicative défectueuse : ramène la base (ou seulement la PDB) à un instant passé ; toutes les modifications postérieures sont perdue"},{"k":"scenario","v":"flashback","l":"Rembobiner avec Flashback Database (restore point / SCN / heure)","m":"Retour dans le temps et annulation FLASHBACK DATABASE FLASHBACK PLUGGABLE DATABASE restore point guaranteed restore point rembobiner la base retour arrière upgrade revenir après un test FLASHBACK_ON FLASHBACK TO RESTORE POINT Flashback est activé ou un guaranteed restore point existe, et aucun fichier n'est perdu : la base est rembobinée en quelques minutes sans restauration (Enterprise Edition). "},{"k":"scenario","v":"tspitr","l":"Ramener un tablespace à un point dans le passé (TSPITR)","m":"Retour dans le temps et annulation TSPITR tablespace point in time RECOVER TABLESPACE AUXILIARY DESTINATION DBMS_TTS récupérer tablespace ORA-29308 L'opération erronée ne concerne que des objets situés dans un seul tablespace ; le reste de la base doit rester dans le présent (Enterprise Edition). Le tablespace est offline pendant toute l'opération et tous ses objets reviennent au point cible ; si "},{"k":"scenario","v":"table","l":"Récupérer une seule table (RECOVER TABLE, 12c+ Enterprise Edition)","m":"Retour dans le temps et annulation RECOVER TABLE table supprimée DROP TABLE récupérer table table recovery ORA-00942 REMAP TABLE ORA-38305 FLASHBACK TABLE recyclebin PURGE Une seule table a été supprimée ou endommagée par erreur ; le reste de la base doit rester tel quel (12c+ Enterprise Edition). Si la table a été supprimée par DROP sans PURGE, regardez d'abord la recyclebin : `FLASHBACK TABLE … "},{"k":"scenario","v":"pdbdrop","l":"Récupérer une PDB supprimée (DROP PLUGGABLE DATABASE)","m":"Retour dans le temps et annulation DROP PLUGGABLE DATABASE PDB supprimée dropped PDB RMAN-06813 ORA-65011 ORA-65179 KEEP DATAFILES récupérer PDB unplug DBMS_PDB.RECOVER Une PDB a été supprimée avec `DROP PLUGGABLE DATABASE`. Si les fichiers sont toujours là, la PDB est rebranchée avec le XML ; s'ils ont été supprimés, la PDB est récupérée depuis la sauvegarde (21c+ en une seule commande, 19c via u"},{"k":"scenario","v":"newhost","l":"Restauration sur un autre serveur (nouvel hôte, depuis l'autobackup du controlfile)","m":"Autre serveur et standby nouveau serveur autre serveur perte de serveur disaster recovery DR new host clonage clone environnement de test serveur détruit server lost restore to another host Le serveur est entièrement perdu, ou un environnement de copie/test doit être monté : la sauvegarde est restaurée sur un autre serveur (instance unique sur système de fichiers seulement). La récupération va jus"},{"k":"scenario","v":"fromsvc","l":"Data Guard : récupérer le fichier depuis la standby/primaire par le réseau (FROM SERVICE)","m":"Autre serveur et standby RESTORE FROM SERVICE FROM SERVICE RECOVER STANDBY DATABASE restauration depuis la standby Data Guard standby en retard archive gap standby controlfile ORA-01110 v$archive_gap apply lag Data Guard est en place : vous récupérez un datafile perdu sur la primaire depuis la standby, et une standby en retard ou un controlfile de standby perdu depuis la primaire, sans utiliser de"}]},{"id":"ora-goldengate","file":"install/guides/ora-goldengate.json","group":"Oracle","engineId":"oracle","category":"ha","title":"Oracle GoldenGate Microservices : réplication unidirectionnelle Oracle → Oracle","summary":"Réplique en continu (avec quelques secondes de latence) un schéma de PDB vers une autre base de données avec GoldenGate 21c ou 23ai/26ai Microservices. À la fin, un Integrated Extract et un Distribution Path tournent sur","level":"Avancé","duration":"3 à 5 heures","topology":"Serveur de base source (deployment OGG source) + serveur de base cible (deployment OGG cible) ; en RAC, GoldenGate sur le nœud 1 (ou en tant que ressource cluster avec XAG)","tags":["oracle","goldengate","ogg","microservices","réplication","replication","cdc","integrated extract","parallel replicat","distribution path","adminclient","oggca","21c","23ai","26ai","rac","scan","xag","acfs","tranlogoptions"],"order":30,"steps":42,"scenarios":[]},{"id":"ora-goldengate-ops","file":"install/guides/ora-goldengate-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle GoldenGate : exploitation courante et scénarios d'incident (Microservices 21c / 23ai)","summary":"Runbook d'exploitation courante et de reprise après incident d'une réplication GoldenGate Microservices en place (pour l'installation, voir le guide d'installation GoldenGate). Si vous ne savez pas quoi faire, commencez ","level":"Avancé","duration":"5 min (contrôle) – 2 heures (montée de version, ajout de table)","topology":"Serveur source (déploiement source, Integrated Extract, Distribution Path) + serveur cible (déploiement cible, Parallel Replicat)","tags":["oracle","goldengate","ogg","microservices","adminclient","runbook","abend","extract","replicat","lag","stats","info all","heartbeat","report","discard","reperror","handlecollisions","cdr","conflit","add trandata","schematrandata","aftercsn","atcsn","mapexclude","tableexclude","resync","purge trail","purgeoldextracts","trail","alter extract scn","etrollover","montée de version","patch","opatch","oggHome","data guard","switchover","failover","handledlfailover","OGG-00664","OGG-01004","OGG-01296","OGG-02912","21c","23ai","26ai"],"order":31,"steps":8,"scenarios":[{"k":"scenario","v":"health","l":"Contrôle de santé et du décalage (tout va-t-il bien ?)","m":"Supervision et contrôles INFO ALL lag décalage STATS heartbeat GG_LAG Service Manager ggserr.log OGG-01027 OGG-01738 Long Running Transaction STOPPED RUNNING Distribution Path DISTPATH Receiver INFO DISTPATH réplication en retard replication lag Lecture seule : Service Manager et microservices, INFO ALL, lag, STATS, décalage heartbeat, état de la capture dans la base et ggserr.log ; rien n'est mod"},{"k":"scenario","v":"ext_abend","l":"Extract ABENDED (arrêté sur erreur)","m":"Incident (ABEND) Extract ABENDED ABEND OGG-00664 OGG-00446 OGG-02171 OGG-02912 OGG-02022 OGG-02091 OGG-01044 OGG-01668 ORA-01291 ORA-01017 ORA-28001 ORA-04031 ORA-01031 log manquant archived log not found logmining OGG-00868 Extract STARTING Extract arrêté capture ABORTED L'Extract de la source est ABENDED ou redémarre en boucle : la cause (connexion, archived log manquant, log mining, privilèges,"},{"k":"scenario","v":"rep_abend","l":"Replicat ABENDED (application impossible sur la cible)","m":"Incident (ABEND) Replicat ABENDED OGG-01004 OGG-01296 OGG-01154 OGG-00869 OGG-01161 OGG-01169 OGG-01431 OGG-00199 OGG-00423 ORA-01403 ORA-00001 ORA-02291 ORA-00942 ORA-01653 no data found unique constraint discard REPERROR HANDLECOLLISIONS CDR conflit Replicat arrêté ORA-01400 ORA-12899 value too large Le Replicat de la cible n'a pas pu appliquer une transaction : la table, l'opération et l'erreur"},{"k":"scenario","v":"add_table","l":"Ajouter une table déjà remplie à la réplication (chargement initial + AFTERCSN)","m":"Gestion des tables ajouter une table add table ADD TRANDATA ADD SCHEMATRANDATA AFTERCSN ATCSN initial load chargement initial flashback_scn MAPEXCLUDE nouvelle table Ajoute sans interruption une table existante et déjà remplie à une réplication en cours : ADD TRANDATA, expdp/impdp à un SCN cohérent, et un Replicat distinct qui n'applique que les modifications de cette table postérieures à ce SCN ("},{"k":"scenario","v":"remove_table","l":"Retirer une table de la réplication","m":"Gestion des tables retirer une table remove table TABLEEXCLUDE MAPEXCLUDE DELETE TRANDATA exclude La table ne sera plus répliquée : TABLEEXCLUDE dans l'Extract (ou suppression de la ligne TABLE), MAPEXCLUDE dans le Replicat et, si besoin, DELETE TRANDATA. Les processus sont brièvement redémarrés."},{"k":"scenario","v":"resync","l":"Resynchroniser une seule table (resync)","m":"Gestion des tables resync resynchroniser out of sync écart de données désynchronisée Veridata OGG-01004 ORA-01403 ORA-00001 row count nombre de lignes différent Les données d'une table sur la cible ont divergé de la source (modification manuelle, erreur ignorée, chargement initial incomplet) : la table est rechargée depuis la source à un SCN cohérent et un Replicat temporaire applique les modifica"},{"k":"scenario","v":"purge","l":"Les fichiers trail remplissent le disque : purge (Purge Trail)","m":"Maintenance trail purge PURGEOLDEXTRACTS Purge Trail disque plein No space left on device purge des trails var/lib/data répertoire trail plein ENOSPC Le répertoire des trails grossit ou le disque est presque plein : on identifie les fichiers en cours de lecture, une tâche Purge Trail (équivalent de PURGEOLDEXTRACTS en Classic) est définie avec USECHECKPOINTS ; en urgence, les fichiers déjà lus son"},{"k":"scenario","v":"reposition","l":"Repositionner l'Extract sur un SCN précis","m":"Maintenance repositionner ALTER EXTRACT SCN BEGIN SCN ETROLLOVER démarrer depuis un SCN perte de trail first_scn rembobiner Vous voulez que l'Extract reparte d'un SCN donné (perte de trail, mauvais point de départ, retour arrière sur la cible) : il est redémarré sur un nouveau fichier trail avec ALTER EXTRACT … SCN et ETROLLOVER. Les mêmes données peuvent arriver une seconde fois sur la cible."},{"k":"scenario","v":"upgrade","l":"Montée de version / patch GoldenGate (Microservices)","m":"Maintenance montée de version patch montée de version OPatch out-of-place oggHome Service Manager upgrade bundle patch 21c 23ai 26ai release update GoldenGate patch Service Manager PATCH Passage à une nouvelle version ou à un patch GoldenGate : out-of-place (installer un nouveau home, basculer Service Manager et les déploiements sur le nouveau home via REST) ou in-place avec OPatch. Extract/Replic"},{"k":"scenario","v":"dg_role","l":"Maintenir l'Extract après un switchover/failover Data Guard côté source","m":"Changement de rôle Data Guard switchover failover role transition HANDLEDLFAILOVER ORA-01033 ORA-16000 ORA-01109 nouvelle primary standby FSFO ORA-12514 changement de rôle Extract nouvelle primary FAILOVERTARGETDESTID Un switchover ou un failover a eu lieu sur la base source ; l'Extract doit se connecter à la nouvelle primary et reprendre depuis son checkpoint : la connexion (credential alias/serv"}]},{"id":"ora-security","file":"install/guides/ora-security.json","group":"Oracle","engineId":"oracle","category":"security","title":"Durcissement de la sécurité Oracle Database (évaluation, réseau, TLS, authentification, privilèges, Unified Auditing, TDE)","summary":"Ce n'est pas un guide d'installation : il repère et corrige les failles d'une base Oracle 19c / 23ai (26ai) en fonctionnement (instance unique, Oracle Restart ou RAC ; CDB/PDB). L'Évaluation de sécurité, en lecture seule","level":"Avancé","duration":"5 min (évaluation) – 4 heures (durcissement complet)","topology":"Instance unique / Oracle Restart / RAC (2 à 8 nœuds), CDB + PDB ; serveur d'application (vérification côté client)","tags":["oracle","sécurité","security","hardening","durcissement base de données","cis benchmark","stig","tcps","wallet","orapki","sqlnet.ora","native network encryption","TCP.VALIDNODE_CHECKING","ALLOWED_LOGON_VERSION_SERVER","profile","ORA12C_STRONG_VERIFY_FUNCTION","unified auditing","AUDSYS","DBMS_AUDIT_MGMT","TDE","keystore","ADMINISTER KEY MANAGEMENT","UTL_FILE","PUBLIC","mot de passe par défaut"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"Évaluation de sécurité : quelles sont mes failles ? (lecture seule)","m":"Pour commencer audit de sécurité base de données security audit hardening check CIS STIG DBSAT faille de sécurité vulnérabilité de sécurité vulnerability constat de pentest dba_users_with_defpwd requête d'audit évaluation de sécurité security scan CRITIQUE Le premier contrôle à lancer : comptes avec mot de passe par défaut et comptes ouverts, anciens vérificateurs de mot de passe, profils, privilè"},{"k":"scenario","v":"all","l":"Durcissement complet (tous les domaines dans l'ordre)","m":"Pour commencer hardening durcissement base de données sécurité complète full hardening appliquer le CIS benchmark STIG Base nouvellement installée ou jamais durcie : tous les domaines sont appliqués dans un ordre sûr ; le listener et la base sont redémarrés une ou deux fois (une fenêtre de maintenance est nécessaire)."},{"k":"scenario","v":"net","l":"Réseau et connexions : qui peut se connecter, et d'où ? (listener, TCP.VALIDNODE_CHECKING, pare-feu)","m":"Réseau et authentification TCP.VALIDNODE_CHECKING TCP.INVITED_NODES listener 1521 5500 ORA-12537 TNS-12537 ORA-12541 ADMIN_RESTRICTIONS pare-feu TNS-12546 ORA-12170 invited nodes listener.log accès externe restreindre l'accès Le listener est ouvert à toutes les adresses, les invited nodes ne sont pas définis, 1521/5500 sont ouverts à tous. Le listener est redémarré (les sessions existantes ne sont"},{"k":"scenario","v":"tls","l":"Chiffrer les connexions (wallet TCPS ou native network encryption)","m":"Réseau et authentification TCPS SSL TLS wallet orapki SQLNET.ENCRYPTION_SERVER ORA-28860 ORA-29024 ORA-28759 ORA-29106 ORA-12696 ORA-12660 SSL_SERVER_DN_MATCH expiration du certificat ORA-29003 ORA-29002 TNS-12560 chaîne de certificats TLS certificate chain native network encryption Le trafic réseau n'est pas chiffré ; TLS (TCPS, wallet, certificat, SSL_SERVER_DN_MATCH côté client) ou native encry"},{"k":"scenario","v":"auth","l":"Authentification et mots de passe (vérificateur 12C, profils, comptes par défaut)","m":"Réseau et authentification ALLOWED_LOGON_VERSION_SERVER ORA-28040 ORA-01017 ORA-28000 ORA-28001 ORA-28003 ORA-28007 password_versions 10G PROFILE ORA12C_STRONG_VERIFY_FUNCTION mot de passe par défaut default password SCOTT ORA-65066 ORA-00988 password_versions 10G profil de mot de passe password policy FAILED_LOGIN_ATTEMPTS Vérificateurs de mot de passe 10G/11G, pas de profil de mot de passe, comp"},{"k":"scenario","v":"priv","l":"Moindre privilège : rôles, PUBLIC et compte applicatif","m":"Privilèges et piste d'audit PUBLIC UTL_FILE UTL_TCP UTL_HTTP UTL_SMTP DBA role RESOURCE ANY privilege least privilege moindre privilège ORA-01031 ORA-00942 schema-only account PLS-00201 ORA-04021 GRANT ANY UNLIMITED TABLESPACE rapport des privilèges Privilèges DBA/RESOURCE/ANY sur le compte applicatif, UTL_FILE/UTL_TCP/UTL_HTTP accordés à PUBLIC ; propriétaire de schéma, rôles de lecture/écriture "},{"k":"scenario","v":"audit","l":"Audit : qui a fait quoi ? (Unified Auditing, purge d'AUDSYS)","m":"Privilèges et piste d'audit unified auditing AUDIT POLICY AUDSYS DBMS_AUDIT_MGMT UNIFIED_AUDIT_TRAIL ORA_LOGON_FAILURES ORA_SECURECONFIG UNIFIED_AUDIT_SYSTEMLOG SYSAUX plein piste d'audit SIEM ORA-46267 unified audit policy qui a fait quoi who changed L'auditeur demande la trace des connexions, des modifications de comptes et de schémas ; politiques d'audit unifié, purge d'AUDSYS selon une durée d"},{"k":"scenario","v":"tde","l":"Chiffrer les données au repos (TDE)","m":"Données et sauvegardes TDE transparent data encryption keystore wallet ADMINISTER KEY MANAGEMENT WALLET_ROOT ORA-28365 ORA-28374 ORA-46665 ENCRYPT_NEW_TABLESPACES encryption at rest ORA-28367 ORA-46658 ORA-28353 Advanced Security master key TDE_CONFIGURATION TDE contre la lecture des datafiles, des sauvegardes ou des copies de disque : WALLET_ROOT, keystore, master key, chiffrement des tablespaces"},{"k":"scenario","v":"backup","l":"Sécurité des sauvegardes (chiffrement RMAN, automatisation sans mot de passe, droits)","m":"Données et sauvegardes RMAN encryption CONFIGURE ENCRYPTION ORA-19913 expdp ENCRYPTION secure external password store mkstore SEPS droits des sauvegardes ORA-19914 licence Advanced Security sauvegarde non chiffrée unencrypted backup ENCRYPTION_PWD_PROMPT Sauvegardes RMAN/Data Pump non chiffrées, mots de passe en clair dans les scripts, répertoire de sauvegarde lisible par tous ; chiffrement RMAN, "},{"k":"scenario","v":"os","l":"Côté système d'exploitation (compte oracle, droits sur les fichiers, core dumps, SELinux)","m":"Serveur et version SELinux orapw compte oracle droits sur les fichiers core dump droits ORACLE_HOME sudo avc: denied passwd -l oracle setuid 6751 limits.d Le compte oracle a un mot de passe et peut se connecter, les droits sur le fichier de mots de passe / network admin / wallet sont trop larges, SELinux est désactivé. La limite des core dumps s'applique dans une nouvelle session / après un redéma"},{"k":"scenario","v":"patch","l":"Patchs et version : suis-je sur la dernière RU ?","m":"Serveur et version RU release update CPU Critical Patch Update opatch lspatches datapatch dba_registry_sqlpatch CVE fin de support opatch Doc ID 742060.1 calendrier des patchs RU installée, état de datapatch et calendrier de support ; renvoie au guide de patch RU pour l'application. Lecture seule."}]},{"id":"ora-oem","file":"install/guides/ora-oem.json","group":"Oracle","engineId":"oracle","category":"monitoring","title":"Installation d'Oracle Enterprise Manager Cloud Control 13c Release 5 (13.5)","summary":"Installe Oracle Enterprise Manager Cloud Control 13.5 : une base 19c existante est préparée comme Management Repository (paramètres documentés, réinitialisation des paramètres de l'optimiseur), une installation silencieu","level":"Avancé","duration":"3 à 5 heures","topology":"Serveur OMS + serveur de la base repository (+ serveurs cibles à superviser)","tags":["oracle","enterprise manager","oem","em13c","13.5","cloud control","oms","agent","emctl","emcli","monitoring","supervision","rac","cluster","discover_db","data guard","exportconfig"],"order":40,"steps":32,"scenarios":[]}]}}