{"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 & Activity","categoryId":"sessions","order":1,"concepts":[{"id":"active-sessions","title":"Show active sessions / running queries","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 gives the currently active sessions. For the full SQL text, join v$sql on sql_id.","danger":"safe"},"postgres":{"command":"SELECT pid, usename, state, wait_event_type, query\nFROM pg_stat_activity\nWHERE state <> 'idle';","note":"state = 'active' shows those running a query at that moment. Compute durations from 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":"On MySQL 8 / MariaDB, performance_schema.processlist holds fewer locks.","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":"For a quick look: EXEC sp_who2 'active';","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"active\": true })","note":"To filter long-running operations: db.currentOp({ active: true, secs_running: { $gt: 5 } })","danger":"safe"},"redis":{"command":"CLIENT LIST\n# For a summary: INFO clients","note":"Because Redis is single-threaded, connected clients are listed instead of 'running queries'. For slow commands use SLOWLOG GET.","danger":"safe"}}},{"id":"blocking-sessions","title":"Find blocking / lock-waiting sessions","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":"A session with blocking_session populated is blocked by the SID in the blocking_session value.","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":"The blocked_by array gives the PIDs blocking this pid.","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.innodb_lock_waits\\G\n-- MySQL 8: SELECT * FROM performance_schema.data_lock_waits;","note":"The sys schema presents the waiting and blocking trx_id / thread in readable form.","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":"Requests with blocking_session_id <> 0 are blocked. A recursive query can be built to follow the chain.","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"waitingForLock\": true })","note":"WiredTiger locks at document/collection level. You can terminate long operations with 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":"Blocking usually stems from a single long-running command (e.g. KEYS *, a large SORT); SLOWLOG shows them.","danger":"safe"}}},{"id":"kill-session","title":"Terminate (kill) a session / query","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":"Get the sid and serial# values from v$session. IMMEDIATE starts the rollback right away.","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":"Try pg_cancel_backend first; if it does not work, use pg_terminate_backend.","danger":"danger"},"mysql":{"command":"-- Kill only the query:\nKILL QUERY <id>;\n-- Close the connection entirely:\nKILL CONNECTION <id>;","note":"The <id> value is the Id column in the processlist.","danger":"danger"},"mssql":{"command":"KILL <session_id>;\n-- To see rollback progress:\n-- KILL <session_id> WITH STATUSONLY;","note":"KILL rolls back the open transaction; for large transactions the rollback can take a long time.","danger":"danger"},"mongodb":{"command":"db.killOp(<opid>)","note":"Take the opid value from the opid field in the db.currentOp() output.","danger":"danger"},"redis":{"command":"CLIENT KILL ID <client-id>\n# or by address: CLIENT KILL ADDR 10.0.0.5:6379","note":"Use CLIENT LIST for the client-id. A single command already running cannot be interrupted (except a Lua script: SCRIPT KILL); you must wait for it to finish. To release a client waiting on a blocking command such as BLPOP/XREAD BLOCK without dropping its connection, use CLIENT UNBLOCK <client-id> [TIMEOUT|ERROR]. CLIENT UNPAUSE only resumes clients paused with CLIENT PAUSE.","danger":"caution"}}},{"id":"connection-count","title":"View connection count and connection limit","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":"The limits are the processes/sessions parameters. For utilization you can also check the resource_limit view.","danger":"safe"},"postgres":{"command":"SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';\nSHOW max_connections;","note":"Because pg_stat_activity also includes background processes, only client connections (backend_type = 'client backend') are counted. superuser_reserved_connections slots are reserved for admins.","danger":"safe"},"mysql":{"command":"SHOW STATUS LIKE 'Threads_connected';\nSHOW VARIABLES LIKE 'max_connections';","note":"The Max_used_connections status shows the peak.","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 is the theoretical ceiling; the practical limit depends on memory and worker threads.","danger":"safe"},"mongodb":{"command":"db.serverStatus().connections","note":"Returns the current, available and totalCreated fields. The limit is set by net.maxIncomingConnections.","danger":"safe"},"redis":{"command":"INFO clients\nCONFIG GET maxclients","note":"connected_clients is the current count; maxclients is the configured ceiling (default 10000).","danger":"safe"}}}]},"rosetta/storage.json":{"category":"Space & Storage","categoryId":"storage","order":2,"concepts":[{"id":"database-size","title":"Show the total database size","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":"For used segments only, sum dba_segments. Add dba_temp_files for TEMP.","danger":"safe"},"postgres":{"command":"SELECT pg_size_pretty(pg_database_size(current_database()));","note":"For all databases: 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":"For InnoDB this value is an estimate; for the exact file size check ibdata/the files at the OS level.","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":"The size column is in 8 KB pages; * 8 / 1024 converts it to MB.","danger":"safe"},"mongodb":{"command":"db.stats(1024*1024)\n// returns the dataSize, storageSize, indexSize fields in MB","note":"storageSize is the compressed on-disk size; dataSize is the uncompressed logical size.","danger":"safe"},"redis":{"command":"INFO memory\n# the used_memory_human line gives the total size in memory\nDBSIZE  # number of keys","note":"Redis is memory-based; 'size' means RAM usage. The RDB/AOF file size is the persisted state on disk.","danger":"safe"}}},{"id":"largest-tables","title":"List the largest 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":"Remove the segment_type filter to see indexes as well.","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 is the sum of table + indexes + TOAST; use pg_relation_size for the table only.","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":"For a specific schema, use 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 covers all reserved pages (data + indexes).","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 is the compressed on-disk size. The cost of stats() is low even on large collections.","danger":"safe"},"redis":{"command":"redis-cli --bigkeys\n# To rank by memory (bytes): redis-cli --memkeys","note":"Both scan the whole keyspace with SCAN (they do not block the server but add load); the difference is the metric: --bigkeys reports the largest keys by element count/length per type, --memkeys by memory consumption via MEMORY USAGE. For a single key use MEMORY USAGE <key>.","danger":"caution"}}},{"id":"free-space","title":"Check free space / usage ratio","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":"For files with AUTOEXTEND on, the real limit is maxbytes; check dba_data_files.maxbytes for 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 relies on the OS for free disk space; operating system monitoring (df) is essential for critical thresholds.","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 is reclaimable (fragmented) space inside the table; free disk space is at the OS level.","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 is in pages; dividing by 128 gives MB (8 KB pages).","danger":"safe"},"mongodb":{"command":"var s = db.stats();\n({ fsTotalGB: +(s.fsTotalSize/1073741824).toFixed(1),\n   fsUsedGB:  +(s.fsUsedSize/1073741824).toFixed(1) })","note":"With WiredTiger, fsTotalSize / fsUsedSize give the total and used size of the disk at the OS level.","danger":"safe"},"redis":{"command":"INFO memory\nCONFIG GET maxmemory\n# Usage = used_memory / maxmemory","note":"If maxmemory is 0 there is no limit, and when RAM fills up the OS OOM killer steps in. maxmemory-policy determines eviction behavior.","danger":"safe"}}},{"id":"row-count","title":"Get the row / document count (fast)","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 reflects the last ANALYZE/DBMS_STATS run; use COUNT(*) for an exact value.","danger":"safe"},"postgres":{"command":"-- Estimated (instant):\nSELECT reltuples::bigint FROM pg_class WHERE relname = 'customer';\n-- Exact: SELECT count(*) FROM customer;","note":"reltuples is an estimate as of the last ANALYZE/VACUUM; a full COUNT is expensive on large 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":"In InnoDB, table_rows is an estimate and can be off by up to 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) or 1 (clustered) gives the row count; much faster than a COUNT(*) scan.","danger":"safe"},"mongodb":{"command":"// Fast (from metadata):\ndb.customer.estimatedDocumentCount()\n// Exact (can count with a filter): db.customer.countDocuments({})","note":"estimatedDocumentCount reads metadata (instant); countDocuments performs an actual scan.","danger":"safe"},"redis":{"command":"DBSIZE\n# Specific pattern: count roughly with SCAN (do not use KEYS * in production)","note":"DBSIZE returns the total number of keys in the selected database in O(1).","danger":"safe"}}}]},"rosetta/performance.json":{"category":"Performance","categoryId":"performance","order":3,"concepts":[{"id":"top-slow-queries","title":"Find the slowest / most expensive queries","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":"For historical analysis use AWR: dba_hist_sqlstat. With the sql_id, DBMS_XPLAN.DISPLAY_CURSOR shows the 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":"Requires the pg_stat_statements extension (shared_preload_libraries). Reset with 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: the slow query log with slow_query_log = ON and 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":"If Query Store is enabled, use sys.query_store_runtime_stats for richer historical analysis.","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":"The profiler adds write overhead; turn it off with db.setProfilingLevel(0) after diagnostics.","danger":"caution"},"redis":{"command":"SLOWLOG GET 10\n# Set the threshold (microseconds): CONFIG SET slowlog-log-slower-than 10000","note":"slowlog-log-slower-than is in microseconds (10000 = 10 ms). Clear it with SLOWLOG RESET.","danger":"safe"}}},{"id":"explain-plan","title":"Get a query's execution plan (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 gives the estimated plan; for actual row counts use DISPLAY_CURSOR + STATISTICS_LEVEL=ALL.","danger":"safe"},"postgres":{"command":"EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT * FROM customer WHERE city = 'Ankara';","note":"ANALYZE ACTUALLY executes the query (be careful with INSERT/UPDATE/DELETE; try it inside a transaction and 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+) executes the query; FORMAT=JSON gives details without executing it.","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":"In SSMS, 'Include Actual Execution Plan' (Ctrl+M) shows the graphical plan.","danger":"caution"},"mongodb":{"command":"db.customer.find({ city: \"Ankara\" }).explain(\"executionStats\")","note":"executionStats shows the actual number of documents returned and index usage (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":"Performance comes from command complexity and key/data structure size; measure it with SLOWLOG.","danger":"safe"}}},{"id":"unused-indexes","title":"Detect unused indexes","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":"If USED = NO, the index was not used during the monitoring period. Turn monitoring off with NOMONITORING USAGE. The old v$object_usage shows only the indexes of the session user and is deprecated since 12c. In 12.2+, index usage is tracked automatically in 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 means the index has not been used since statistics were last reset; do not drop UNIQUE/PK indexes.","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.schema_unused_indexes;","note":"performance_schema must be enabled and the server must have been running for a while; UNIQUE constraints are excluded.","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 is reset when the server restarts; allow a sufficient observation window.","danger":"safe"},"mongodb":{"command":"db.customer.aggregate([{ $indexStats: {} }])\n// Indexes with low/0 accesses.ops are candidates","note":"accesses.ops counts accesses since the accesses.since timestamp; the _id index cannot be dropped.","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":"Since the data model is key-value, there is no traditional index concept.","danger":"safe"}}},{"id":"cache-hit-ratio","title":"Measure the cache hit ratio","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":"A low ratio is not always bad; interpret it in the context of the workload, not in isolation.","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":"99%+ is usually the target. If it is low, review shared_buffers and the OS cache.","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 are from disk, read_requests are total reads. If it is low, increase 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":"On modern SQL Server, Page Life Expectancy (PLE) is a more meaningful indicator of memory pressure.","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":"High 'pages read into cache' and eviction indicate that the WiredTiger cache is too small.","danger":"safe"},"redis":{"command":"INFO stats\n# keyspace_hits / (keyspace_hits + keyspace_misses) = hit ratio","note":"A persistently low hit ratio indicates that TTL/eviction settings or the access pattern need review.","danger":"safe"}}}]},"rosetta/backup.json":{"category":"Backup & Recovery","categoryId":"backup","order":4,"concepts":[{"id":"logical-dump","title":"Take a logical backup (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":"Uses Data Pump. The DIRECTORY object must be defined beforehand with CREATE DIRECTORY. Use RMAN for physical backups.","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 allows flexible restores (selective tables/parallel). Use pg_dumpall for the whole cluster (including roles).","danger":"safe"},"mysql":{"command":"mysqldump -u root -p --single-transaction \\\n  --routines --triggers --events mydb > db.sql","note":"--single-transaction gives a lock-free consistent backup on InnoDB. Parallel options for large data: mydumper/myloader or MySQL Shell util.dumpInstance()/dumpSchemas() (mysqlpump was deprecated in 8.0.34 and removed in 8.4).","danger":"safe"},"mssql":{"command":"BACKUP DATABASE [MyDB]\nTO DISK = N'C:\\backup\\MyDB_full.bak'\nWITH COMPRESSION, CHECKSUM, INIT;","note":"INIT overwrites the existing file; use NOINIT to append. For log backups use BACKUP LOG (in the FULL recovery model).","danger":"safe"},"mongodb":{"command":"mongodump --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --db=mydb","note":"On a sharded cluster, take it through mongos with the balancer stopped for consistency.","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 runs in the background via fork; SAVE blocks the server (do not use in production). With AOF enabled, less data is lost.","danger":"safe"}}},{"id":"restore-dump","title":"Restore from backup","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":"Use REMAP_SCHEMA to load into a different schema and REMAP_TABLESPACE for a different 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 drops existing objects. Use -j <n> for a parallel restore.","danger":"danger"},"mysql":{"command":"mysql -u root -p target_db < db.sql","note":"The target database must already exist (CREATE DATABASE). For large files the source command is preferred.","danger":"danger"},"mssql":{"command":"RESTORE DATABASE [MyDB]\nFROM DISK = N'C:\\backup\\MyDB_full.bak'\nWITH REPLACE, RECOVERY;","note":"For point-in-time, restore the full backup WITH NORECOVERY, then apply log backups with STOPAT.","danger":"danger"},"mongodb":{"command":"mongorestore --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --drop","note":"--drop deletes the target collections before restoring. Use --nsInclude for a selective restore.","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":"With AOF enabled, the AOF is loaded instead of the RDB; to restore from the RDB, start with appendonly no or rebuild the AOF.","danger":"danger"}}},{"id":"last-backup-status","title":"Check last backup status / time","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":"RMAN history can also be viewed with 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":"Logical (pg_dump) backups are not recorded inside the DB; check the logs of the backup tool/job.","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 shows the available binlog files (the PITR window); backup history is in the tool's (Xtrabackup, etc.) log.","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 records every backup taken by 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":"The oplog window determines how far back you can do PITR from the replica set; backup history is in the tool's log.","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 gives the time of the last successful RDB save (Unix epoch); rdb_changes_since_last_save shows how many writes are not yet backed up.","danger":"safe"}}}]},"rosetta/security.json":{"category":"Users & Privileges","categoryId":"security","order":5,"concepts":[{"id":"list-users","title":"List users / roles","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 shows LOCKED/EXPIRED states. For roles, use dba_roles.","danger":"safe"},"postgres":{"command":"SELECT rolname, rolsuper, rolcanlogin, rolvaliduntil\nFROM pg_roles\nORDER BY rolname;\n-- Shortcut (psql): \\du","note":"In PostgreSQL a user is a role with the LOGIN attribute; rolcanlogin distinguishes it.","danger":"safe"},"mysql":{"command":"SELECT user, host, account_locked, password_expired\nFROM mysql.user\nORDER BY user;","note":"In MySQL an identity is a user + host pair ('app'@'%' and 'app'@'localhost' are different).","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":"A login is server-level, a user is database-level; the two are mapped by 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":"Users are defined in the database where they were created (authenticationDatabase).","danger":"safe"},"redis":{"command":"ACL LIST\n# Names only: ACL USERS\n# Your own identity: ACL WHOAMI","note":"The Redis 6+ ACL system. Before that there was a single 'default' user and requirepass.","danger":"safe"}}},{"id":"create-user-grant","title":"Create a user and grant privileges","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":"Without CREATE SESSION the user cannot connect. For a 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":"For tables created in the future: 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":"'%' allows connections from any host; for security, specify a particular IP/host where possible. CREATE USER/GRANT take effect immediately; FLUSH PRIVILEGES is needed only if DML was run directly against the grant tables.","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":"A login is server-wide; a user is created separately in each database. For a role: 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":"The db in which the user is created becomes its auth db. Built-in roles: 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 = enabled, >password adds a password, ~app:* is the key pattern, +@read/+@write are command categories. If not persisted, it is lost on restart: ACL SAVE works only when an aclfile is configured and returns an error otherwise; in that case use CONFIG REWRITE.","danger":"caution"}}},{"id":"show-privileges","title":"Show a user's privileges","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":"The grantee is stored in uppercase. For indirect privileges granted through roles, also walk 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":"Role memberships (inheritance) are stored in pg_auth_members; \\du/\\drg show only direct memberships, use pg_has_role('app', '<role>', 'MEMBER') for indirect membership.","danger":"safe"},"mysql":{"command":"SHOW GRANTS FOR 'app'@'%';","note":"For roles (MySQL 8), first SET ROLE ALL, or use 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":"For permissions granted through roles, also check memberships in sys.database_role_members.","danger":"safe"},"mongodb":{"command":"db.getUser(\"app\", { showPrivileges: true, showCredentials: false })","note":"showPrivileges also lists the expanded (effective) privileges of the roles.","danger":"safe"},"redis":{"command":"ACL GETUSER app","note":"Returns the allowed commands, key patterns (~), pub/sub channels (&) and password hashes.","danger":"safe"}}},{"id":"change-password","title":"Change a user's password","aliases":["change password","reset password","change password","reset password","alter password"],"engines":{"oracle":{"command":"ALTER USER app IDENTIFIED BY \"New#Pass2\";","note":"To unlock a locked account: ALTER USER app ACCOUNT UNLOCK; If the password has expired, the same command resets it.","danger":"caution"},"postgres":{"command":"ALTER ROLE app WITH PASSWORD 'New#Pass2';","note":"To keep the password out of the logs, the \\password app (psql) meta-command hashes the password on the client side.","danger":"caution"},"mysql":{"command":"ALTER USER 'app'@'%' IDENTIFIED BY 'New#Pass2';","note":"Make sure you pick the right host; 'app'@'localhost' is a separate account. ALTER USER takes effect immediately; FLUSH PRIVILEGES is not needed.","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 can force a change at first login: WITH PASSWORD='...' MUST_CHANGE, CHECK_POLICY=ON.","danger":"caution"},"mongodb":{"command":"db.changeUserPassword(\"app\", \"New#Pass2\")","note":"Make sure you have switched to the user's auth database (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 removes all existing passwords; > only adds one. For persistence, run ACL SAVE if an aclfile is used, or CONFIG REWRITE if users are defined in redis.conf (ACL SAVE returns an error without an aclfile).","danger":"caution"}}}]},"rosetta/maintenance.json":{"category":"Maintenance & Administration","categoryId":"maintenance","order":6,"concepts":[{"id":"update-statistics","title":"Update statistics (for the optimizer)","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":"The AUTO task usually gathers them at night; gather them manually when a problem occurs. ESTIMATE_PERCENT=>AUTO_SAMPLE_SIZE is recommended.","danger":"safe"},"postgres":{"command":"ANALYZE customer;\n-- Both cleanup and analyze: VACUUM ANALYZE customer;","note":"Autovacuum does this automatically; a manual ANALYZE after a sudden data load gives accurate plans.","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer;","note":"Takes a brief read lock on InnoDB; on large tables run it during low activity.","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Whole database: EXEC sp_updatestats;","note":"FULLSCAN is the most accurate but the most expensive; on large tables sampling (SAMPLE) may be preferred.","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":"Instead of cost estimation, MongoDB runs actual trials (plan racing); manual statistics are not needed.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no query optimizer / statistics.","note":"Command complexity is fixed; performance depends on the choice of data structure.","danger":"safe"}}},{"id":"rebuild-index","title":"Rebuild / reorganize an index","aliases":["rebuild index","reindex","refresh index","reorganize","index maintenance","defrag index"],"engines":{"oracle":{"command":"ALTER INDEX customer_city_idx REBUILD ONLINE;","note":"ONLINE rebuilds without locking the table (Enterprise Edition). To move it to another 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+) rebuilds without blocking writes to the table; slower but safe.","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 allows concurrent writes; INPLACE may not be possible for every 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":"Common threshold: 5-30% fragmentation REORGANIZE, >30% REBUILD. ONLINE is an Enterprise feature.","danger":"caution"},"mongodb":{"command":"db.customer.reIndex()","note":"Rebuilds all indexes in the foreground and locks the collection. In 6.0+ it runs only on a standalone (not allowed on replica set members); on a replica set, rebuild a single index with dropIndex + createIndex, node by node (rolling).","danger":"danger"},"redis":{"command":"# Not applicable (core). If RediSearch is used:\n# FT.ALTER / rebuilding is done per the module documentation.","note":"The key-value model has no traditional index maintenance.","danger":"safe"}}},{"id":"reclaim-space","title":"Reclaim free space (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 lowers the high water mark (HWM). To shrink indexes too, use 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 takes an AccessExclusiveLock and rewrites the table; do not run it at peak hours. Alternative: pg_repack.","danger":"caution"},"mysql":{"command":"OPTIMIZE TABLE customer;","note":"On InnoDB it rebuilds the table and returns the data_free space; takes a long time on large tables.","danger":"caution"},"mssql":{"command":"-- Generally NOT RECOMMENDED (causes fragmentation). If really needed:\nDBCC SHRINKFILE (N'MyDB_data', 1024);  -- target 1024 MB","note":"Shrinking a data file causes index fragmentation; REBUILD the indexes afterwards. For the log, take a backup first.","danger":"danger"},"mongodb":{"command":"db.runCommand({ compact: \"customer\" })","note":"On WiredTiger it tries to return free space to the operating system (not always possible). In 4.4+ it does not block reads/writes, only certain DDL operations on the same collection; because it generates heavy IO, running it on a secondary and in a maintenance window is safer.","danger":"danger"},"redis":{"command":"MEMORY PURGE\n# Tries to return jemalloc fragmentation to the OS","note":"The activedefrag yes setting performs online automatic defragmentation; MEMORY DOCTOR gives recommendations.","danger":"caution"}}},{"id":"version-uptime","title":"Show version and 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 also provides instance details such as status and 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 is handy for numeric comparison (e.g. 160002 = 16.2).","danger":"safe"},"mysql":{"command":"SELECT VERSION();\nSHOW GLOBAL STATUS LIKE 'Uptime';  -- seconds","note":"Uptime is in seconds; divide by 86400 to get days.","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') and ('Edition') give version/edition details.","danger":"safe"},"mongodb":{"command":"db.version()\ndb.serverStatus().uptime   // seconds","note":"serverStatus() also includes host, process and connections information.","danger":"safe"},"redis":{"command":"INFO server\n# redis_version and uptime_in_seconds / uptime_in_days lines","note":"INFO server also provides os, run_id and tcp_port information.","danger":"safe"}}},{"id":"show-config-param","title":"View a configuration parameter","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":"To change it: ALTER SYSTEM SET ... SCOPE=BOTH; (some only with SCOPE=SPFILE + restart).","danger":"safe"},"postgres":{"command":"SHOW shared_buffers;\n-- Details: SELECT name, setting, unit, context FROM pg_settings WHERE name = 'shared_buffers';","note":"The context column tells whether the change requires a restart or a reload (SELECT pg_reload_conf()).","danger":"safe"},"mysql":{"command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","note":"Mind the GLOBAL vs SESSION distinction: SHOW GLOBAL VARIABLES shows the server-wide value.","danger":"safe"},"mssql":{"command":"SELECT name, value, value_in_use, is_dynamic\nFROM sys.configurations\nWHERE name LIKE '%max server memory%';","note":"To change it: sp_configure + RECONFIGURE; options with is_dynamic=0 require a restart.","danger":"safe"},"mongodb":{"command":"db.adminCommand({ getParameter: 1, cursorTimeoutMillis: 1 })\n// All startup options: db.adminCommand({ getCmdLineOpts: 1 })","note":"Use setParameter for those changeable at runtime; persistent settings live in the mongod.conf file.","danger":"safe"},"redis":{"command":"CONFIG GET maxmemory\n# Wildcard: CONFIG GET max*","note":"Use CONFIG SET to change at runtime; CONFIG REWRITE for persistence (writes to redis.conf).","danger":"safe"}}}]},"rosetta/schema.json":{"category":"Schema & Objects","categoryId":"schema","order":7,"concepts":[{"id":"list-tables","title":"List 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 returns tables in the current schema, all_tables those that are accessible.","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":"For the public schema only: WHERE table_schema = 'public'.","danger":"safe"},"mysql":{"command":"SHOW TABLES;\n-- Another schema: SHOW TABLES FROM mydb;","note":"For details: 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":"Shortcut: SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';","danger":"safe"},"mongodb":{"command":"show collections\n// or: db.getCollectionNames()","note":"To also see system collections: db.getCollectionInfos().","danger":"safe"},"redis":{"command":"SCAN 0 MATCH * COUNT 100\n# Continue with SCAN <cursor> ... using the returned cursor","note":"Redis has no 'tables'; keys are listed. NEVER use KEYS * in production (it blocks); SCAN is cursor-based.","danger":"caution"}}},{"id":"describe-table","title":"Show a table's columns / structure (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":"Table names are stored in uppercase (if created without quotes).","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 also shows indexes, constraints and foreign keys.","danger":"safe"},"mysql":{"command":"DESCRIBE customer;\n-- or: SHOW COLUMNS FROM customer;","note":"For the full definition use SHOW CREATE TABLE customer; (see the 'Generate DDL' concept below).","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 lists indexes and constraints in addition to columns.","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 has no fixed schema; fields can vary from document to document. If a $jsonSchema validator is defined, it can be seen with db.getCollectionInfos.","danger":"safe"},"redis":{"command":"TYPE mykey        # string | list | set | hash | zset | stream\nOBJECT ENCODING mykey   # internal encoding","note":"There is no fixed schema; the type and encoding of each key are queried separately. Use HGETALL for hash fields.","danger":"safe"}}},{"id":"list-indexes","title":"List a table's indexes","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":"Join with user_indexes for index type/uniqueness.","danger":"safe"},"postgres":{"command":"SELECT indexname, indexdef\nFROM pg_indexes\nWHERE tablename = 'customer';","note":"indexdef gives the full CREATE INDEX statement; \\d customer in psql also lists them.","danger":"safe"},"mysql":{"command":"SHOW INDEX FROM customer;","note":"The Non_unique, Seq_in_index and Cardinality columns show index structure and selectivity.","danger":"safe"},"mssql":{"command":"EXEC sp_helpindex 'dbo.customer';\n-- Details: join sys.indexes + sys.index_columns","note":"sp_helpindex returns index name, type and columns; use the sys views for filtered/included columns.","danger":"safe"},"mongodb":{"command":"db.customer.getIndexes()","note":"Returns each index's keys and options such as uniqueness and 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":"The key-value model has no secondary indexes; they come only with modules such as RediSearch/RedisJSON.","danger":"safe"}}},{"id":"create-index","title":"Create an 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 builds it without locking the table. Use CREATE UNIQUE INDEX for unique; (a, b) for composite.","danger":"caution"},"postgres":{"command":"CREATE INDEX CONCURRENTLY customer_city_idx ON customer(city);","note":"CONCURRENTLY does not block writes to the table, but it cannot run inside a transaction block and is slower.","danger":"caution"},"mysql":{"command":"CREATE INDEX customer_city_idx ON customer(city);\n-- Lock-free: ... ALGORITHM=INPLACE, LOCK=NONE;","note":"InnoDB adds most indexes online; on large tables allow writes with LOCK=NONE.","danger":"caution"},"mssql":{"command":"CREATE NONCLUSTERED INDEX IX_customer_city\nON dbo.customer (city)\nWITH (ONLINE = ON);","note":"ONLINE is an Enterprise feature. For covering columns use 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 is ascending, -1 descending order. On large collections, building on a secondary first reduces load.","danger":"caution"},"redis":{"command":"# Not applicable (core). If RediSearch is present:\n# FT.CREATE idx ON HASH PREFIX 1 customer: SCHEMA city TAG","note":"Secondary indexes come only with the RediSearch module; core Redis access is by key.","danger":"safe"}}},{"id":"show-ddl","title":"Generate an object's DDL (CREATE statement)","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":"Use 'INDEX' for indexes, 'PACKAGE' for packages, etc. Prevent long output truncation with 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":"In psql, \\d customer shows the structure readably; for full DDL use pg_dump --schema-only.","danger":"safe"},"mysql":{"command":"SHOW CREATE TABLE customer\\G","note":"Returns the full CREATE TABLE (columns, indexes, engine, charset) in a single command.","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":"For a procedure/view body: 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":"If options.validator ($jsonSchema) is defined, it is the only 'schema definition'; indexes are retrieved separately with getIndexes().","danger":"safe"},"redis":{"command":"# Not applicable: there is no schema/DDL concept.","note":"Data structures are defined in application code; persistence is in RDB/AOF files.","danger":"safe"}}}]},"rosetta/replication.json":{"category":"Replication & HA","categoryId":"replication","order":8,"concepts":[{"id":"replication-status","title":"Show replication status and lag","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":"Apply lag and transport lag are the main Data Guard lag metrics; run this on the 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 is the replay delay; sync_state = 'sync' indicates a synchronous replica.","danger":"safe"},"mysql":{"command":"SHOW REPLICA STATUS\\G\n-- Before 8.0.22: SHOW SLAVE STATUS\\G","note":"Seconds_Behind_Source gives the lag; Replica_IO_Running and Replica_SQL_Running must be 'Yes'. SHOW SLAVE STATUS is an alias in 8.0.22–8.3 and was removed in 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":"For Always On. A large redo_queue_size means the secondary replica is falling behind.","danger":"safe"},"mongodb":{"command":"rs.printSecondaryReplicationInfo()\n// Detailed status: rs.status()","note":"Shows how many seconds each secondary is behind the primary; optimeDate values are compared.","danger":"safe"},"redis":{"command":"INFO replication\n# Check the role, connected_slaves, master_repl_offset,\n# and slaveN:...,offset=... lines","note":"The difference between master_repl_offset and the slave offset gives the lag; master_link_status must be 'up'.","danger":"safe"}}},{"id":"list-replicas","title":"List replicas / cluster nodes","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":"The Data Guard configuration can also be viewed with DGMGRL> SHOW CONFIGURATION.","danger":"safe"},"postgres":{"command":"SELECT application_name, client_addr, state, sync_state\nFROM pg_stat_replication;","note":"Lists connected replicas only on the PRIMARY; each row is a standby.","danger":"safe"},"mysql":{"command":"SHOW REPLICAS;\n-- Before 8.0.22: SHOW SLAVE HOSTS;","note":"Run on the source server; shows replicas that have report-host set. SHOW SLAVE HOSTS is an alias in 8.0.22–8.3 and was removed in 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 distinguishes PRIMARY/SECONDARY; synchronization_health_desc gives overall health.","danger":"safe"},"mongodb":{"command":"rs.status().members.map(m => ({ name: m.name, state: m.stateStr, health: m.health }))","note":"stateStr returns PRIMARY/SECONDARY/ARBITER; health 1 means healthy.","danger":"safe"},"redis":{"command":"INFO replication\n# If Sentinel is used:\n# SENTINEL replicas <master-name>","note":"The slave0, slave1... lines give the ip/port/offset of each replica.","danger":"safe"}}},{"id":"promote-failover","title":"Promote a replica / fail over (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 is a planned role transition; FAILOVER is performed while the primary is unreachable and carries a risk of data loss.","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":"Before promoting, make sure the old primary is really stopped (split-brain risk). After promotion, rejoining the old primary as a replica of the new primary requires pg_rewind; for that, wal_log_hints = on or data checksums must be enabled.","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":"If RESET REPLICA ALL is run before the relay log is fully applied, transactions that were received but not applied are lost. Replicas usually run with read_only/super_read_only enabled; writes are not accepted until they are turned off. If the old source is still writable, there is a split-brain risk. The syntax is 8.0.22+ (before that STOP SLAVE / RESET SLAVE ALL; the old form was removed in 8.4). For automatic and safe failover in production, prefer InnoDB Cluster/Group Replication, Orchestrator or 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":"A planned failover to a synchronous replica has no data loss; FORCE is used only in a disaster scenario.","danger":"danger"},"mongodb":{"command":"// Trigger an election on the current primary:\nrs.stepDown(60)   // step down from primary for 60 s","note":"stepDown starts a new election; to favor a specific node, adjust the members' priority values.","danger":"danger"},"redis":{"command":"# Make the replica an independent primary:\nREPLICAOF NO ONE\n# With Sentinel (recommended): SENTINEL FAILOVER <master-name>","note":"If you use Sentinel/Cluster, use SENTINEL FAILOVER instead of a manual REPLICAOF; otherwise there is a split-brain risk.","danger":"danger"}}},{"id":"open-transactions","title":"Find open / long-running transactions","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":"A high used_ublk means a large transaction is consuming a lot of undo; it may be waiting for a 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":"Long transactions in the 'idle in transaction' state block VACUUM and tuple cleanup; monitor them.","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":"Use trx_mysql_thread_id to correlate with PROCESSLIST and KILL it if necessary.","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":"Long open transactions prevent log truncation and bloat the log file.","danger":"safe"},"mongodb":{"command":"db.adminCommand({\n  currentOp: 1,\n  \"transaction\": { $exists: true }\n})","note":"Long multi-document transactions are aborted automatically after transactionLifetimeLimitSeconds (default 60 s).","danger":"safe"},"redis":{"command":"# Not applicable: MULTI/EXEC transactions are short and atomic; they do not stay open.","note":"Redis transactions hold no locks; for long-running single commands, check SLOWLOG.","danger":"safe"}}}]},"rosetta/connect.json":{"category":"Connection & CLI","categoryId":"connect","order":9,"concepts":[{"id":"connect-cli","title":"Connect from the command line (CLI / connection string)","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 is the 'Easy Connect' format; alternatively, use a TNS alias from 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":"To avoid typing the password on the command line, use a ~/.pgpass file or the PGPASSWORD environment variable.","danger":"safe"},"mysql":{"command":"mysql -h host -P 3306 -u root -p mydb\n# Require TLS: --ssl-mode=REQUIRED","note":"Pass -p on its own (you are prompted for the password); the 'mysql -pPassword' form exposes the password in the process list.","danger":"safe"},"mssql":{"command":"sqlcmd -S host,1433 -U sa -P password -d mydb\n# Windows authentication: sqlcmd -S host -E -d mydb","note":"In the newer go-sqlcmd, the -C (trust server certificate) and -N (encrypt) flags may be needed for 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 is usually the database where the user is defined (most often 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 passes the password on the command line (prints a warning); for secure login use the REDISCLI_AUTH environment variable.","danger":"safe"}}},{"id":"list-databases","title":"List databases / schemas","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":"In Oracle a 'database' corresponds to the instance; the separation is at the schema (user) level or the PDB level inside a CDB.","danger":"safe"},"postgres":{"command":"SELECT datname FROM pg_database WHERE datistemplate = false;\n-- psql shortcut: \\l","note":"For schemas: SELECT schema_name FROM information_schema.schemata;","danger":"safe"},"mysql":{"command":"SHOW DATABASES;","note":"In MySQL, 'database' and 'schema' are synonyms.","danger":"safe"},"mssql":{"command":"SELECT name, state_desc FROM sys.databases ORDER BY name;","note":"state_desc shows the database state, such as ONLINE/OFFLINE/RESTORING.","danger":"safe"},"mongodb":{"command":"show dbs\n// or: db.adminCommand({ listDatabases: 1 })","note":"Empty databases (no data written yet) do not appear in the list.","danger":"safe"},"redis":{"command":"INFO keyspace\n# db0:keys=..., db1:keys=... lines show the logical DBs in use","note":"Redis has numbered logical DBs (default 0-15); in Cluster mode only DB 0 is used.","danger":"safe"}}},{"id":"switch-database","title":"Change the active database / schema","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 changes only object name resolution, not privileges.","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":"In PostgreSQL a connection is bound to a single database; changing the DB means reconnecting.","danger":"safe"},"mysql":{"command":"USE mydb;","note":"Changes the active schema without dropping the connection.","danger":"safe"},"mssql":{"command":"USE mydb;","note":"Changes the batch context; subsequent commands run in this database.","danger":"safe"},"mongodb":{"command":"use mydb","note":"Switches even if the database does not exist; it is actually created when the first document is written.","danger":"safe"},"redis":{"command":"SELECT 1\n# Switches to a logical DB index between 0 and 15","note":"In Cluster mode SELECT supports only 0; use key prefixes instead of logical DB separation.","danger":"safe"}}}]},"rosetta/objects.json":{"category":"Object Management","categoryId":"objects","order":10,"concepts":[{"id":"create-partition","title":"Create / split / drop a 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":"Supports RANGE/LIST/HASH partitioning. DROP PARTITION deletes all rows in that partition; with UPDATE INDEXES, global indexes stay valid. Interval partitioning can add partitions automatically.","danger":"caution","priv":"ALTER on table","minver":"Partitioning Option"},"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":"Core PostgreSQL has no SPLIT/MERGE PARTITION (as of PG17); to split, DETACH the partition, create two partitions with the new bounds, move the data and ATTACH them. DETACH ... CONCURRENTLY (PG14+) reduces lock duration.","danger":"caution","priv":"table owner","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":"The partitioning column must be part of every PRIMARY KEY / UNIQUE key. For RANGE, splitting/merging is done with REORGANIZE PARTITION. DROP PARTITION also deletes the rows (RANGE/LIST).","danger":"caution","priv":"ALTER, DROP on 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":"A partition function + scheme are defined, and the table is created on that scheme with CREATE TABLE ... ON ps_date(created_at). The NEXT USED filegroup must be set before SPLIT. To remove data, switch it out to a staging table with SWITCH PARTITION.","danger":"caution","priv":"ALTER on table, ALTER ANY DATASPACE","minver":"2016 SP1 (all editions)"},"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":"There is no partitioning in the classic sense. Horizontal splitting is done via chunks with sharding; for time-based data, time-series collections provide automatic bucketing.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no table/partition concept; scaling is done with Redis Cluster hash slots (16384).","note":"Redis Cluster distributes keys across 16384 hash slots; this is application-level splitting, not table partitioning. There is no manual partition command.","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":"In Elasticsearch, splitting is achieved by dividing an index into shards and, for time series, by creating new backing indices with ILM + data streams (rollover). Old indices can be deleted with ILM.","danger":"safe"}}},{"id":"sequence","title":"Create a sequence / auto-increment value","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":"The classic method is CREATE SEQUENCE + NEXTVAL/CURRVAL. Since 12c, GENERATED AS IDENTITY columns are also supported. A CACHE value improves performance but gaps can occur.","danger":"safe","priv":"CREATE SEQUENCE","minver":"12c for 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 is SQL standard and recommended; serial is the legacy shorthand. Access manually with nextval/currval. The value can be reset with setval.","danger":"safe","priv":"CREATE on schema","minver":"PG10 for 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 has no standalone (standard) sequence object; an AUTO_INCREMENT column is used (one per table, must be indexed). MariaDB 10.3+ supports CREATE SEQUENCE.","danger":"safe","priv":"CREATE, ALTER on 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) is bound to a table column. CREATE SEQUENCE (2012+) provides a counter independent of tables that can be shared across tables; values are fetched with NEXT VALUE FOR.","danger":"safe","priv":"CREATE SEQUENCE / ALTER on table","minver":"2012 for 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":"The default _id is an ObjectId (already unique and roughly increasing). If a sequential integer is needed, generate it atomically with $inc in a separate counters collection.","danger":"safe"},"redis":{"command":"# Atomic counter\nINCR customer:id\n# or with a specific step\nINCRBY customer:id 10","note":"INCR/INCRBY are atomic and ideal for generating unique increasing IDs in distributed environments. If the key does not exist, it starts from 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":"If you POST without an ID, ES assigns a random unique _id (not increasing). _seq_no is an internal sequence number that increases with each write, but it is for optimistic concurrency, not an application counter.","danger":"safe"}}},{"id":"add-constraint","title":"Add or disable a constraint (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":"Oracle constraints can be temporarily turned off with DISABLE/ENABLE (useful for bulk loads). ENABLE VALIDATE checks existing rows; NOVALIDATE does not.","danger":"caution","priv":"ALTER on table, REFERENCES on target"},"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 has no constraint DISABLE. To reduce locking on a large table, add the constraint with NOT VALID and VALIDATE it later. ALTER TABLE ... DISABLE TRIGGER ALL also disables the FK's internal triggers and requires superuser; data inserted while they are off is never validated, and re-enabling does not check retroactively (risk of silent integrity loss). Instead, DROP the constraint, re-add it with NOT VALID, then run VALIDATE CONSTRAINT.","danger":"caution","priv":"table owner, REFERENCES on target"},"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":"Requires InnoDB. Instead of disabling a single constraint, the session-level SET FOREIGN_KEY_CHECKS=0 is used. CHECK constraints are actually enforced starting with MySQL 8.0.16 (they were ignored before).","danger":"caution","priv":"ALTER, REFERENCES on table","minver":"8.0.16 for 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":"In SQL Server, FK/CHECK constraints are disabled with NOCHECK CONSTRAINT. If WITH CHECK CHECK CONSTRAINT is not used when re-enabling, the constraint stays 'not trusted' and the query optimizer cannot use it.","danger":"caution","priv":"ALTER on table, REFERENCES on target"},"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":"There are no foreign keys (enforced in the application layer). The UNIQUE equivalent is a unique index. Instead of CHECK, use $jsonSchema validation; validationAction: 'warn' achieves the effect of disabling it.","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":"There is no concept of relational integrity or constraints. Uniqueness is managed on the application side with SET ... NX (write only if absent) or by checking a separate SET/HASH.","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":"Relations and constraints do not exist in the engine. Type checking is done with the mapping. For a UNIQUE effect, use the natural key as the _id (writes to the same _id overwrite) or reject conflicts with op_type=create.","danger":"safe"}}},{"id":"create-view","title":"Create / modify a view","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 both creates and modifies. For views that are updatable under certain conditions, WITH CHECK OPTION can be added.","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":"With CREATE OR REPLACE, new columns can only be added at the end; changing a column's type/order requires DROP first. Simple views are automatically updatable.","danger":"safe","readonly":true,"priv":"CREATE on schema"},"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":"The definition is updated with CREATE OR REPLACE VIEW or ALTER VIEW. The execution method can be set with ALGORITHM = MERGE/TEMPTABLE; WITH CHECK OPTION protects updatable views.","danger":"safe","readonly":true,"priv":"CREATE VIEW on 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+) creates/modifies in a single statement. In older versions, use CREATE first, then ALTER VIEW. The view statement must be the first statement in the batch.","danger":"safe","readonly":true,"priv":"CREATE VIEW","minver":"2016 SP1 for 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 creates a read-only view over a source collection and an aggregation pipeline. Views cannot be written to; to change the definition, drop and recreate it (or use 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":"There is no support for views defined over queries. For a similar need, keep precomputed keys or build a searchable index with 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":"There is no classic SQL view; a filtered alias is the closest equivalent and applies the filter to queries automatically. For column selection, use _source filtering in the query.","danger":"safe","readonly":true}}},{"id":"materialized-view","title":"Create and refresh a materialized view","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 requires a materialized view log; COMPLETE ('C') recomputes fully, FAST ('F') is incremental. ON COMMIT/ON DEMAND determines when the refresh happens.","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 recomputes everything (it is not incremental). CONCURRENTLY does not block reads but requires a unique index and is slower.","danger":"caution","priv":"table owner","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":"There is no native materialized view support. A summary table is maintained and repopulated either instantly with triggers or periodically with the EVENT SCHEDULER.","danger":"caution","priv":"CREATE, EVENT on schema"},"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 uses indexed views: WITH SCHEMABINDING is mandatory; once a unique clustered index is created, the data is physically stored and maintained automatically (no separate REFRESH needed). With GROUP BY, COUNT_BIG(*) is required; if the column inside SUM is nullable, write SUM(ISNULL(amount, 0)) (otherwise error 8662). The view must be created with QUOTED_IDENTIFIER and ANSI_NULLS ON. Automatic view matching by the query optimizer is available only in Enterprise (and Developer) edition; in Standard, reference the view with the WITH (NOEXPAND) hint.","danger":"caution","priv":"CREATE VIEW, ALTER on schema"},"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":"An on-demand materialized view is obtained by writing the aggregation result to a persistent collection with $out or $merge. To refresh, rerun the pipeline (manually or with a scheduler).","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":"There is no materialized view concept; the closest approach is to keep precomputed values in keys (String/Hash/Sorted Set) and cache them for a limited time with 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":"A transform pivots the source index and materializes it into a summary destination index. This example is a batch transform: it runs once with _start and stops. For a continuous transform that updates automatically as new data arrives, add \"sync\": { \"time\": { \"field\": \"<time_field>\", \"delay\": \"60s\" } } with a time field to the configuration (there is no continuous: true setting).","danger":"caution"}}},{"id":"rename-object","title":"Rename a table / object","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 or ALTER ... RENAME TO is used for tables/views/sequences. Dependent objects (views, synonyms) may become invalid and need to be recompiled.","danger":"caution","priv":"ALTER on object"},"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 works for most object types. Foreign key and index references are preserved automatically even when the name changes (OID-based).","danger":"caution","priv":"table owner"},"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 is atomic and can move multiple tables in a single statement. For renaming columns use RENAME COLUMN in 8.0+; before that, CHANGE must be used and the type specified as well.","danger":"caution","priv":"ALTER, DROP on old; CREATE, INSERT on new"},"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 has no RENAME TABLE statement; the system procedure sp_rename is used. The schema prefix is given in the table name but not in the new name. Dependent views/procedures must be updated manually.","danger":"caution","priv":"ALTER on object, 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 changes the collection name. To rename a field, use the $rename operator with updateMany. Sharded collections can be renamed in 5.0+; older versions do not support it.","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 renames the key; if the target already exists, it is overwritten. RENAMENX works only if the target does not exist. An error is returned if the source key does not exist.","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":"There is no index rename command. Reindex to the new name with _reindex and delete the old one. Running behind a stable alias is recommended to keep the application independent of index names.","danger":"caution"}}}]},"rosetta/datamove.json":{"category":"Data Movement","categoryId":"datamove","order":11,"concepts":[{"id":"bulk-import","title":"Bulk load data (CSV / from file)","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":"The most common tool is SQL*Loader (with a control file). To read the file like a query, use an external table. For large volumes, direct path load (DIRECT=TRUE) is fast.","danger":"caution","priv":"INSERT on the table, a DIRECTORY object"},"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 reads a file on the server and requires privileges; psql \\copy reads a client file and needs no extra privileges. It is the fastest way to load CSV.","danger":"caution","priv":"INSERT on the table (pg_read_server_files for 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 is the fastest way to bulk load. LOCAL reads a client file and must be enabled both on the server (local_infile=ON) and on the client. secure_file_priv restricts the server path.","danger":"caution","priv":"FILE (INSERT for 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 reads the file from a path accessible to the SQL Server service account. bcp is a command-line tool; -c is character mode, -t the field terminator. For maximum speed, aim for minimal logging with 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 loads CSV/TSV/JSON; --headerline treats the first line as field names. To update existing documents, use --mode=upsert --upsertFields. For large data, --numInsertionWorkers speeds it up.","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":"For bulk loading, use redis-cli --pipe (mass insert); it is very fast because it streams commands in RESP protocol format over a single connection. Alternatively, commands are grouped with 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":"The _bulk API takes NDJSON (an action line + a data line per document; the file must end with \\n). For CSV, use Logstash, Filebeat or elasticdump. Disabling refresh_interval during the load speeds it up.","danger":"caution","priv":"index / create index privilege"}}},{"id":"bulk-export","title":"Export data (CSV / to file)","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":"For CSV, use SQL*Plus SET MARKUP CSV ON + SPOOL (12.2+). To move schemas/tables, Data Pump (expdp) produces a binary .dmp that is imported into another Oracle database with impdp.","danger":"safe","readonly":true,"priv":"SELECT on the table","minver":"12.2 for 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 writes a query result to a file (server side, requires privileges); psql \\copy writes to a client file. HEADER true adds a header line.","danger":"safe","readonly":true,"priv":"SELECT on the 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 creates the file on the server (requires the secure_file_priv path and the FILE privilege; it cannot overwrite). For client-side output, redirect with 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 writes data to a file: -c character mode, -t, field terminator, -n native binary format. Use queryout for a query result. 'Results to File' in SSMS is also an option.","danger":"safe","readonly":true,"priv":"SELECT on the 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 produces CSV/JSON; for CSV, the columns must be given explicitly with --fields. Use --query to filter. For a full binary backup, mongodump is preferred.","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":"Structured CSV export is not built in. Use --scan (non-blocking) for a key list and an RDB file for the full data: redis-cli --rdb downloads a snapshot generated in the background on the server to a local file; if you want the file on the server, use BGSAVE. SAVE blocks the main thread; do not use it in production. To get the values too, read the scan result in a loop.","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":"There is no direct CSV export. To get all data, use elasticdump or page through _search with scroll/PIT (point-in-time). Converting to CSV requires the Logstash csv output or an external tool.","danger":"safe","readonly":true,"priv":"read on index"}}},{"id":"copy-between","title":"Copy data between tables / environments","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":"Within the same database, use INSERT ... SELECT or CREATE TABLE AS SELECT (CTAS). For copies between environments, a database link (@remote_db) or Data Pump is preferred. The APPEND hint speeds it up with direct-path.","danger":"caution","priv":"SELECT on source, INSERT/CREATE on target"},"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":"Within the same DB, use INSERT ... SELECT / CTAS. Between servers, use a pg_dump | psql pipeline or copy through a foreign table with postgres_fdw.","danger":"caution","priv":"SELECT on source, INSERT/CREATE on target"},"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":"Within the same DB, INSERT ... SELECT or CREATE TABLE ... AS SELECT. Between different servers, piping mysqldump output into the mysql client is common. CTAS does not copy indexes and constraints.","danger":"caution","priv":"SELECT on source, INSERT/CREATE on target"},"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":"In SQL Server, copying into a new table is done with SELECT ... INTO (the CTAS equivalent). Between servers, use a linked server (four-part name), bcp out/in or SSIS.","danger":"caution","priv":"SELECT on source, INSERT / CREATE TABLE on target"},"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 replaces the target collection entirely; $merge merges with/inserts into existing documents (same or another DB). For moves between servers, use mongodump | mongorestore or mongoexport/mongoimport.","danger":"caution","priv":"find on source, insert on target"},"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 atomically moves the key to the target instance and deletes it from the source (with the COPY option it does not delete). To copy within the same instance, use the COPY command (6.0+) or 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 copies between indexes; it can be filtered with a query and transformed with a script. Between clusters, use remote reindex (reindex.remote.whitelist must be defined on the target). Large jobs are parallelized with slices.","danger":"caution","priv":"read on source, write on target"}}},{"id":"generate-insert","title":"Generate data as an INSERT script","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 offers no SQL command that converts data directly into a ready INSERT script. For DDL use DBMS_METADATA.GET_DDL; for row INSERTs use SQLcl (SET SQLFORMAT INSERT) or the SQL Developer Export wizard.","danger":"safe","readonly":true,"priv":"SELECT on the 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 writes each row as a separate INSERT; --column-inserts also adds column names (robust to schema differences). The default (no flag) produces COPY format, which loads much faster.","danger":"safe","readonly":true,"priv":"SELECT on the 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 produces INSERT statements by default; --no-create-info skips CREATE TABLE and outputs data only. --skip-extended-insert makes each row a separate INSERT (the default combines many rows into one statement).","danger":"safe","readonly":true,"priv":"SELECT on the 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 has no built-in T-SQL command for this. The most common way is to choose Data only in the SSMS 'Generate Scripts' wizard. On the command line, Microsoft's mssql-scripter tool generates INSERTs with --data-only.","danger":"safe","readonly":true,"priv":"SELECT on the 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":"There is no SQL INSERT concept. For a portable backup use mongodump (BSON, restored with mongorestore). For an insertMany embedded in code, the output of mongoexport --jsonArray can be used.","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":"No SQL INSERT script is produced. Use DUMP + RESTORE serialization per key and an RDB (--rdb) file for all data. If a command list is needed, iterate with SCAN and build the appropriate SET/HSET commands for each 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":"No SQL INSERT is produced; in Elasticsearch the unit of loading is _bulk NDJSON. To extract existing data in a reloadable form, use elasticdump (type=data); _search + PIT output can be converted to NDJSON with an external script.","danger":"safe","readonly":true,"priv":"read on index"}}}]},"rosetta/monitoring.json":{"category":"Monitoring & Diagnostics","categoryId":"monitoring","order":12,"concepts":[{"id":"current-locks","title":"List current locks","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":"Shows active locks and the blocking session (blocking_session) via v$lock and v$locked_object.","danger":"safe","readonly":true,"priv":"SELECT on 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":"Joins pg_locks with pg_stat_activity; rows with granted=false show waiting (blocked) locks.","danger":"safe","readonly":true,"priv":"pg_monitor role (to see all queries)"},"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 lists current InnoDB locks, and data_lock_waits lists the wait relationships.","danger":"safe","readonly":true,"priv":"SELECT on 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 shows current lock requests; those with request_status = WAIT are blocked requests.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"$or\": [ { \"waitingForLock\": true }, { \"locks\": { \"$exists\": true } } ] })","note":"db.currentOp shows operations waiting for a lock (waitingForLock) and operations holding locks; MongoDB locks at the document/collection level, not the row level.","danger":"safe","priv":"inprog / clusterMonitor role"},"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":"Because Redis is single-threaded, there is no traditional lock list.","danger":"safe"},"elasticsearch":{"command":"GET _cluster/pending_tasks","note":"Elasticsearch has no row locks; this lists the cluster change tasks queued on the master (pending tasks).","danger":"safe","readonly":true}}},{"id":"long-operations","title":"View progress of long-running operations","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 shows the percentage progress and remaining time (time_remaining) of operations running longer than 6 seconds.","danger":"safe","readonly":true,"priv":"SELECT on 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":"The pg_stat_progress_* views report the progress of VACUUM, CREATE INDEX, CLUSTER, ANALYZE, and COPY operations.","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":"When stage instrumentation is enabled, events_stages_current shows progress via work_completed/work_estimated (e.g. ALTER TABLE).","danger":"safe","readonly":true,"priv":"SELECT on 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 gives the percentage for operations such as BACKUP, RESTORE, DBCC, and index rebuilds.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"active\": true, \"secs_running\": { \"$gte\": 3 } })","note":"In the db.currentOp output, long-running operations and, for index builds, the 'progress' field (done/total) show progress.","danger":"safe","priv":"inprog / clusterMonitor role"},"redis":{"command":"INFO persistence\n# Keys: rdb_bgsave_in_progress, aof_rewrite_in_progress, rdb_last_bgsave_status, loading, loading_loaded_perc","note":"The INFO persistence section reports whether RDB/AOF save or rewrite operations are in progress and the loading percentage.","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 shows the status of long-running tasks (reindex, update_by_query); _cat/recovery shows active shard recovery progress.","danger":"safe","readonly":true}}},{"id":"session-trace","title":"Trace a session / query (enable 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 produces a 10046 trace file for the session; always disable it when done (tracing adds overhead and disk usage).","danger":"caution","priv":"EXECUTE on 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 writes query plans, and log_min_duration_statement writes slow/all queries, to the log file; a value of 0 logs every query, so watch the log volume.","danger":"caution","priv":"SUPERUSER (for LOAD) or session-level SET"},"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":"Query tracing is done with performance_schema events_statements; the SET profiling method is old (deprecated) but gives a quick per-session measurement.","danger":"caution","priv":"SELECT/UPDATE on 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) is the modern tracing method; create the session and start it with START, and stop it with STOP/DROP when done.","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) records all operations in the system.profile collection; in production 1 (slow only) is preferred, as level 2 adds overhead.","danger":"caution","priv":"dbAdmin / enableProfiler"},"redis":{"command":"MONITOR\n# Streams all commands arriving at the server in real time. Exit: Ctrl-C","note":"MONITOR shows all commands live but causes a serious performance drop; use it briefly and carefully in 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 gives the query/aggregation timing breakdown of a single search; for continuous monitoring, configure slowlog thresholds.","danger":"safe","readonly":true}}},{"id":"top-wait-events","title":"View top wait events","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 gives cumulative wait events; v$active_session_history gives active session sampling for the last few minutes (requires the Diagnostics Pack license).","danger":"safe","readonly":true,"priv":"SELECT on 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 shows the current wait state; sampling it repeatedly (polling) reveals the most frequent wait events.","danger":"safe","readonly":true,"priv":"pg_monitor role"},"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 gives total wait times per event (in picoseconds); wait instruments must be enabled.","danger":"safe","readonly":true,"priv":"SELECT on 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 shows cumulative wait types since instance startup; it can be reset with 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 has no classic wait events; exhaustion of concurrentTransactions (tickets), globalLock.currentQueue, and cache pressure indicate congestion.","danger":"safe","priv":"clusterMonitor role"},"redis":{"command":"CONFIG SET latency-monitor-threshold 100\nLATENCY LATEST\nLATENCY HISTORY command\n# For the slowest commands: SLOWLOG GET 10","note":"Once the latency monitor threshold is set, LATENCY LATEST/HISTORY reports the events causing the most latency.","danger":"safe"},"elasticsearch":{"command":"GET _nodes/hot_threads","note":"_nodes/hot_threads reveals bottlenecks by giving stack traces of the threads consuming the most CPU.","danger":"safe","readonly":true}}},{"id":"os-resource","title":"View server resource usage from inside the 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 gives OS CPU/memory counters; v$sysmetric gives real-time DB metrics (CPU, IOPS) for the latest interval.","danger":"safe","readonly":true,"priv":"SELECT on 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 gives read/write/cache hit counters; PostgreSQL does not expose CPU usage, so operating system tools are required.","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":"The sys schema summary views (host_summary) give an IO/latency breakdown; SHOW ENGINE INNODB STATUS shows buffer pool and IO activity.","danger":"safe","readonly":true,"priv":"SELECT on 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 gives DB performance counters; dm_os_ring_buffers gives the SQL/other process CPU ratio for the last ~256 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() gives memory, network, opcounters, and WiredTiger cache metrics; for detailed OS CPU/IO use mongostat or operating system tools.","danger":"safe","priv":"clusterMonitor role"},"redis":{"command":"INFO cpu\nINFO stats\n# used_cpu_sys, used_cpu_user, instantaneous_ops_per_sec, total_net_input_bytes","note":"INFO cpu reports the Redis process's CPU consumption, and INFO stats reports per-second operations and network traffic.","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 gives CPU percentage, memory, and process metrics per node; use the fs section for disk.","danger":"safe","readonly":true}}}]},"rosetta/automation.json":{"category":"Automation & Auditing","categoryId":"automation","order":13,"concepts":[{"id":"schedule-job","title":"Create a scheduled job","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 defines a scheduled job; repeat_interval sets the recurrence with a calendaring expression.","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":"The pg_cron extension defines jobs in standard cron syntax with cron.schedule; if the extension is not installed, use pgAgent or the operating system's cron.","danger":"caution","priv":"pg_cron installed; usually superuser/extension owner"},"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 runs via the event scheduler; the event_scheduler global variable must be ON, otherwise events do not fire.","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":"SQL Server Agent jobs are created with msdb procedures: the job, step and schedule are defined and attached to the server. The SQL Server Agent service must be running.","danger":"caution","priv":"SQLAgentUserRole or 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":"The Community edition has no scheduled jobs; an external scheduler or an Atlas Trigger is required.","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 does not run scheduled jobs; this is handled with TTL/keyspace notifications and external cron.","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) takes scheduled snapshots; ILM is used for data lifecycle and Watcher for advanced automation.","danger":"caution","priv":"manage_slm cluster privilege"}}},{"id":"list-jobs","title":"List scheduled jobs / view their status","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 shows the defined jobs and their states; dba_scheduler_job_run_details shows the success/failure results of past runs.","danger":"safe","readonly":true,"priv":"SELECT on 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":"With pg_cron, cron.job lists the defined jobs and cron.job_run_details gives the status and return message of each run.","danger":"safe","readonly":true,"priv":"pg_cron installed; access to the cron schema"},"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 lists the defined events, their status (ENABLED/DISABLED) and last execution time.","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 gives the defined Agent jobs, and sysjobhistory gives the run history and outcome (run_status).","danger":"safe","readonly":true,"priv":"SQLAgentReaderRole / msdb access"},"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":"Community MongoDB has no job table; Atlas Triggers are managed in the Atlas interface.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of scheduled jobs, so there are no jobs to list.","note":"Redis includes no job scheduler; there are no jobs to list.","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 lists snapshot policies, _ilm/policy lists lifecycle policies, and _watcher/stats shows Watcher execution state.","danger":"safe","readonly":true,"priv":"read_slm / read_ilm privilege"}}},{"id":"enable-audit","title":"Enable auditing / logging","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":"With Unified Auditing, a policy is created and enabled with AUDIT; records are kept in the unified_audit_trail view.","danger":"caution","priv":"AUDIT SYSTEM / AUDIT_ADMIN role","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 provides detailed audit logging; set it with ALTER SYSTEM to apply server-wide (a session-level SET audits only that session). Without the extension, basic logging is enabled with log_statement ('ddl','mod','all'). Watch the log volume.","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":"The audit_log plugin (Enterprise, or server_audit in MariaDB) keeps a detailed audit trail; general_log logs all queries but degrades performance.","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":"First create and enable a SERVER AUDIT target (file/event log), then define which events to track with a server or database AUDIT SPECIFICATION.","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":"Auditing is available only in Enterprise and Atlas; it is enabled with the auditLog section of the mongod configuration. It does not exist in the Community edition.","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 automatically records security events (failed authentication, unauthorized commands/keys); full command auditing is not built in.","danger":"safe","priv":"ACL permission (admin by default)"},"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":"Audit logging is enabled with xpack.security.audit.enabled; it is set in elasticsearch.yml and requires a node restart (it is not dynamic). Requires a commercial license.","danger":"caution"}}},{"id":"current-config-diff","title":"List non-default settings","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":"Rows in v$parameter with isdefault = 'FALSE' show parameters that differ from the default (set explicitly).","danger":"safe","readonly":true,"priv":"SELECT on v$parameter"},"postgres":{"command":"SELECT name, setting, unit, source\nFROM pg_settings\nWHERE source NOT IN ('default', 'override')\nORDER BY name;","note":"If pg_settings.source is anything other than 'default' and 'override', the setting was changed by a configuration file, the command line or the 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 returns all settings; it does not show the difference from defaults directly. The VARIABLE_SOURCE column of performance_schema.variables_info shows changes other than the compiled default (file, command line, dynamic).","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 returns server settings; if value and value_in_use differ, there is a change not applied with RECONFIGURE (or awaiting a restart). sys.configurations does not store the default value (minimum/maximum is only the allowed range); to see differences from defaults, compare values with the documented defaults.","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":"The 'parsed' section of the getCmdLineOpts command lists only explicitly specified (non-default) settings.","danger":"safe","priv":"clusterMonitor / hostInfo privilege"},"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 * returns all running settings; it has no built-in diff against defaults, so compare the output with the default redis.conf values of the relevant version.","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 returns non-default (persistent/transient) cluster settings; with include_defaults=true, defaults are also shown for comparison.","danger":"safe","readonly":true}}}]},"rosetta/lifecycle.json":{"category":"Installation & Upgrade","categoryId":"lifecycle","order":14,"concepts":[{"id":"check-version","title":"Show installed version and patch level","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":"The v$version banner gives the version. dba_registry_sqlpatch shows whether applied Release Updates (RU) and one-off patches have been registered in the SQL layer, while 'opatch lspatches' shows the binary patches in the home.","danger":"safe","readonly":true,"priv":"SELECT on v$version, dba_registry_sqlpatch"},"postgres":{"command":"SELECT version();\n-- Numeric version only:\nSHOW server_version;\nSELECT current_setting('server_version_num');","note":"version() returns the full text including build information. PostgreSQL has no separate patch number; the minor version (e.g. 16.3) represents the fix level.","danger":"safe","readonly":true},"mysql":{"command":"SELECT VERSION();\n-- Detailed build information:\nSHOW VARIABLES LIKE 'version%';","note":"VERSION() returns e.g. '8.0.36'; the last component is the patch (release) level. The version_comment variable indicates whether the build is Community/Enterprise or 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 gives the applied Cumulative Update (e.g. CU12), and ProductUpdateReference gives the related KB number. ProductLevel shows 'RTM' or, on older 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() returns the version string (e.g. '7.0.11'). In MongoDB, fixes are released as a new patch version; there is no separate 'patch level' concept.","danger":"safe"},"redis":{"command":"INFO server\n# redis-cli one-liner: redis-cli INFO server | grep redis_version","note":"The redis_version field in the INFO server output gives the installed version. In Redis, fixes ship as a new patch version (e.g. 7.2.5); the redis_git_sha1 field also shows the build identifier.","danger":"safe"},"elasticsearch":{"command":"GET /\n# curl: curl -s http://localhost:9200/","note":"The root endpoint returns version.number (e.g. '8.14.1'), lucene_version, and build_flavor. To see the versions of all nodes: GET _cat/nodes?v&h=name,version","danger":"safe","readonly":true}}},{"id":"check-upgrade-readiness","title":"Pre-upgrade compatibility/readiness check","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":"AutoUpgrade 'analyze' mode produces a pre-upgrade report without changing data; it lists warnings that must be fixed and items that will be resolved automatically. Always run it before the actual upgrade.","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 verifies that the upgrade is feasible without changing any data (reports missing extensions, incompatible data types, etc.). --check can also be run while the old cluster is running; the actual upgrade is done with both clusters stopped.","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":"MySQL Shell's util.checkForServerUpgrade() reports removed features, reserved word conflicts, and incompatible definitions. From 8.0 on, the server performs the dictionary upgrade itself at startup; a separate mysql_upgrade is no longer needed.","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) replaced the old Upgrade Advisor; it reports deprecated features, breaking changes, and blockers per database. Run it before the upgrade and resolve the findings.","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":"Before upgrading the binary version, the FCV must equal the current version; otherwise the upgrade is blocked. MongoDB has no automatic 'deprecation scanner'; release compatibility notes are reviewed manually.","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":"Redis upgrades are generally forward compatible; since there is no official readiness scanner, check the RDB/AOF file format, removed configuration directives, and command changes manually from the release notes.","danger":"safe"},"elasticsearch":{"command":"GET _migration/deprecations\n# For a specific component: GET <index>/_migration/deprecations","note":"The Deprecation Info API lists cluster, node, index, and ML settings that will cause problems when moving to the next major version as 'critical' / 'warning'. Kibana Upgrade Assistant visualizes the same data.","danger":"safe","readonly":true}}},{"id":"apply-patch","title":"Apply a 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 updates the binaries in the Oracle Home; datapatch then applies dictionary/SQL changes on the open database and records them in dba_registry_sqlpatch. On RAC, rolling-installable patches can be applied node by node.","danger":"caution","priv":"OS: ORACLE_HOME owner; 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":"Minor patches within the same major version series (16.3 -> 16.4) do not change the data format: updating the package and restarting the service is enough; no pg_upgrade or dump/restore is needed.","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+ upgrades the data dictionary automatically at startup; no separate mysql_upgrade step is needed. In replication, patch the replicas first, then the 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":"CU (Cumulative Update) and GDR (General Distribution Release) updates are applied with the setup installer and require an instance restart. In an Always On group, patch the secondary replicas first and fail over to minimize downtime.","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":"Patch updates within the same major version (7.0.x) are done in a rolling fashion, secondaries first. The primary is patched after handing over with rs.stepDown(), minimizing write downtime.","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":"Patch versions (7.2.x) are applied with a rolling restart: replicas are upgraded and resynced first, then a replica is promoted to master and the old master is patched. Sentinel/Cluster automates the 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":"Patch/minor upgrades are done with a rolling upgrade: shard allocation is restricted, nodes are updated and restarted one at a time, then allocation is re-enabled and you wait for the cluster to return to green.","danger":"caution"}}},{"id":"upgrade-major","title":"Major version upgrade","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 runs analysis, pre-fixups, the upgrade, and post-steps with a single command; it can process multiple databases in sequence to shorten downtime. DBUA does the same with a guided interface. A full backup beforehand is mandatory.","danger":"danger","priv":"SYSDBA + OS: new ORACLE_HOME owner"},"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 performs the major version migration (16 -> 17) in place; --link mode shortens the time by creating hard links instead of copying files, but invalidates the old cluster. Statistics must be regathered after the 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":"From MySQL 8.x on, in-place major upgrades are supported: a server started with the new binaries automatically converts the data dictionary and system tables (the old separate mysql_upgrade step has been removed). Upgrade one major version at a time.","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 upgrades the instance in place to the new major version. The safer method is a side-by-side installation, then moving the databases with backup/restore and raising the compatibility level in a separate step.","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":"A major upgrade in MongoDB has two phases: first all nodes' binaries are rolled to the new version, then, once the cluster is stable, the FCV is raised to the new version. You can move up only one major version at a time; FCV is managed separately for downgrade capability.","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":"A Redis major version migration (6.x -> 7.x) is done by setting up a replica on the new version, letting it sync, and then promoting it to master; write downtime is thus reduced to almost zero. RDB files are compatible with later versions.","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 does not support skipping major versions with any method: both rolling upgrade and full-cluster restart only move to the next major (n -> n+1) and require the source to be on the last minor (e.g. 7.17). Indices created two majors earlier (e.g. in 6.x) cannot be opened in the new version; they must be rebuilt with _reindex before the migration (8.x can read them only in a limited way as read-only archive indices). Take a snapshot and resolve deprecation warnings before the migration.","danger":"danger"}}},{"id":"set-compatibility","title":"Set compatibility level / feature version","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":"The COMPATIBLE parameter determines which version features and file formats are enabled. Once raised and the instance restarted, it CANNOT BE REVERTED (downgrade is blocked); therefore raise it gradually until the upgrade's success is confirmed.","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 has no 'compatibility level' concept like SQL Server/Oracle; version behavior is tied directly to the binary version. Migration between versions is done with pg_dump/pg_restore or 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 has no version-based compatibility level; SQL behavior depends mostly on the binary version and the sql_mode setting. Fine-tune sql_mode to preserve legacy application behavior.","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 determines which version the query optimizer and language features behave like (150=2019, 160=2022). After upgrading the instance, databases can stay at the old level; raising it gradually and monitoring plan regressions is recommended. It can be lowered again.","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":"FCV controls when persistent data format changes and new features that come with a version are enabled. Raise it after the binary upgrade while the cluster is stable; lowering it is only possible if the same binary version still supports the lower FCV.","danger":"caution","priv":"clusterManager / root role"},"redis":{"command":"# Not applicable: Redis has no compatibility level / feature version concept.\n# Behavior depends directly on the running binary version.","note":"Redis has no configurable compatibility level; the feature set depends entirely on the running version. Compatibility is preserved only at the RDB/AOF file format level, in a forward direction.","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 has no compatibility level setting; each index carries the format of the major version it was created in. Indices created in the previous major version can be read, while older ones must be moved to the new version with _reindex.","danger":"safe"}}},{"id":"post-upgrade-tasks","title":"Post-upgrade tasks (recompile/analyze)","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 recompiles packages/views/triggers that became INVALID after the upgrade. Gathering dictionary and fixed object statistics afterwards lets the optimizer produce sound plans in the new 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 and earlier) does not carry over planner statistics; analyze-in-stages makes the system usable quickly by gathering coarse statistics first, then full ones. Extensions must be refreshed with ALTER EXTENSION ... UPDATE, and indexes with REINDEX if the collation changed.","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":"In MySQL 8.0.16 and later, the mysql_upgrade function has moved into the server, so it is not run separately. After the upgrade, refreshing statistics with ANALYZE TABLE on large/critical tables preserves plan quality.","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":"After the upgrade, DBCC UPDATEUSAGE corrects inaccurate space/row counters carried over from older versions, and sp_updatestats refreshes statistics. When raising the compatibility level, monitor plan regressions that the new Cardinality Estimator (CE) may cause.","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":"After a major upgrade, verify that the FCV has reached the target version. Rebuilding indexes is not a routine upgrade step; if an index is called out in the release notes or produces a warning in the log, recreate it with dropIndex + createIndex. On 6.0+, reIndex works only on a standalone and is not allowed on replica set members.","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":"After a Redis upgrade there is no schema compile/analyze task; the job is to verify that replication has been re-established, RDB/AOF persistence is healthy, and the key count is preserved.","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":"After the upgrade, verify that the cluster status is 'green' and all shards are allocated. Indices in a format older than the previous major version cannot be read in the new version, so they must be moved to the current format with _reindex.","danger":"caution"}}}]},"rosetta/migration.json":{"category":"Migration & Transfer","categoryId":"migration","order":15,"concepts":[{"id":"compare-schema","title":"Diff two schemas/environments (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 has no built-in single-command schema diff. The common method is to generate deterministic DDL from both environments with DBMS_METADATA.GET_DDL (with STORAGE/segment attributes turned off) and take a text diff. For visual/automated comparison, use SQL Developer's Database Diff wizard or SQLcl.","danger":"safe","readonly":true,"priv":"SELECT_CATALOG_ROLE or read access on the schema"},"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":"The simplest way is to take pg_dump --schema-only output in both environments and compare it with diff. apgdiff and migra are tools that turn the difference directly into executable ALTER statements. Add --no-owner --no-privileges for deterministic output.","danger":"safe","readonly":true,"priv":"SELECT / schema read (object access for 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":"The mysqldbcompare tool from MySQL Utilities reports object/schema differences between two servers (as applicable statements with difftype=sql). The tool-free method is to take mysqldump --no-data (schema only) or SHOW CREATE TABLE output from both environments and compare with diff. mysqldbcompare is now deprecated; for MariaDB/8.x, Liquibase or Skeema is preferred.","danger":"safe","readonly":true,"priv":"SELECT, SHOW VIEW on both schemas"},"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":"For schema differences, SqlPackage /Action:DeployReport returns the changes to be applied to the target as an XML report; /Action:Script generates an executable diff script. Visual Studio / Azure Data Studio 'Schema Compare' uses the same engine. For row-level data differences there is the tablediff command-line utility.","danger":"safe","readonly":true,"priv":"VIEW DEFINITION (read access on the target for 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 is schemaless; there is no fixed DDL. Comparison is done in two ways: compare the $jsonSchema validator rules defined on the collection (via getCollectionInfos), or generate an inferred schema from the data with mongodb-schema / Compass and compare field and type distributions. For indexes, diff the db.collection.getIndexes() output.","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":"Not applicable: Redis has no schema concept (table/column/DDL), so a classic schema diff does not apply. If a structural comparison is needed, only the key naming pattern (namespace) can be listed with SCAN and compared.","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":"In Elasticsearch the equivalent of a schema is the index mapping. The standard method is to take the GET <index>/_mapping output (and _settings if needed) from both clusters and compare with diff. Because mapping fields are mostly immutable, a detected difference may require a new index + _reindex.","danger":"safe","readonly":true,"priv":"view_index_metadata / monitor"}}},{"id":"export-for-migration","title":"Consistent export for 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) is the standard logical export tool for migration. For read consistency, pass FLASHBACK_SCN=<scn> (or FLASHBACK_TIME) so all tables are exported consistently at a single SCN; this is critical on a live system. PARALLEL speeds it up, and %U produces multiple files.","danger":"caution","readonly":true,"priv":"DATAPUMP_EXP_FULL_DATABASE, DIRECTORY object","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":"The pg_dump -Fc (custom) or -Fd (directory) format is preferred for migration; both allow selective/parallel loading with pg_restore. Because pg_dump runs inside a single transaction, its output is automatically a consistent snapshot. Cluster-wide objects such as roles and tablespaces are dumped separately with pg_dumpall --globals-only.","danger":"safe","readonly":true,"priv":"SELECT (on all objects to be dumped)"},"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":"On InnoDB, --single-transaction provides a single consistent snapshot and does not lock tables. --source-data=2 (formerly --master-data=2) writes the binlog position/GTID information needed to start replication/CDC as a comment at the top of the output; this is essential for a zero-downtime cutover. For large data, mydumper runs in parallel.","danger":"safe","readonly":true,"priv":"SELECT, LOCK TABLES, RELOAD, REPLICATION CLIENT (for --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":"When migrating to the same or a newer SQL Server, BACKUP ... RESTORE is the most reliable path (integrity is verified with CHECKSUM). For version/platform-independent migration or a move to Azure SQL, exporting schema+data into a single .bacpac file with SqlPackage /Action:Export is preferred. bcp is used for individual tables.","danger":"safe","readonly":true,"priv":"BACKUP DATABASE (db_backupoperator) or read access for 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 is the standard binary (BSON) export for migration. On a replica set, the --oplog option captures changes written while the dump is running from the oplog, providing a consistent point in time (paired with mongorestore --oplogReplay); this option is valid only for a full dump. For cluster-level migration of a large cluster, mongosync/live tools are more suitable.","danger":"safe","readonly":true,"priv":"read (read on the local db for the 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":"In Redis the consistent export is the RDB snapshot. BGSAVE produces a consistent dump.rdb in the background via fork without blocking the main process (SAVE blocks and should not be used). The file path is found with CONFIG GET dir and dbfilename; migrate by copying this dump.rdb into the target server's data directory.","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":"In Elasticsearch the consistent export method for migration is the snapshot. First a repository (fs, S3, GCS, etc.) is registered, then the snapshot is taken; snapshots are incremental and capture a consistent state as of the moment they are taken. elasticdump can also be used for small data/a single index, but snapshot is the standard.","danger":"safe","readonly":true,"priv":"manage / create_snapshot"}}},{"id":"import-for-migration","title":"Import into the target","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 loads the .dmp file taken with expdp into the target. REMAP_SCHEMA moves the source into a different schema and REMAP_TABLESPACE into a different tablespace; these are used often when migrating between environments. Behavior for existing tables is set with TABLE_EXISTS_ACTION, and speed with PARALLEL.","danger":"caution","priv":"DATAPUMP_IMP_FULL_DATABASE, quota on the target tablespace"},"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":"With pg_restore -j, custom/directory format dumps are loaded in parallel (multiple processes); this speeds up loading significantly for large databases. --clean --if-exists drops objects in the target first, and --no-owner adapts to a different role structure. Plain SQL dumps are loaded with psql -f (not parallel).","danger":"caution","priv":"CREATE / owner privileges on the target database"},"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":"A mysqldump SQL dump is loaded by redirecting it to the mysql client (mysql shop < dump.sql). For parallel and much faster loading, a directory taken with mydumper is restored with myloader --threads. Disabling FOREIGN_KEY_CHECKS and UNIQUE_CHECKS during the load improves performance.","danger":"caution","priv":"INSERT, CREATE (and DROP, for --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":"A .bak backup is loaded with RESTORE DATABASE; when moving to a different disk layout, WITH MOVE redirects the data/log files to new paths. For a version-independent/Azure SQL target, SqlPackage /Action:Import creates the .bacpac file (schema + data) as a new database.","danger":"caution","priv":"dbcreator/sysadmin for RESTORE; CREATE DATABASE for 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 loads mongodump BSON output into the target. --oplogReplay replays the oplog changes in a backup taken with --oplog, bringing the dump to a single consistent point in time. Database/collection names are changed with --nsFrom/--nsTo, and existing data can be cleared with --drop.","danger":"caution","priv":"readWrite (restore/clusterAdmin for 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 loads the RDB only at startup. Therefore the target server is stopped, the migrated dump.rdb is placed in the data directory (CONFIG GET dir), and the server is restarted; at startup memory is populated from the RDB. To load without stopping the server, stream the keys in RESP format with 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":"On the target cluster, register the same snapshot repository as the source (the files must be accessible), then restore the indices with _snapshot/.../_restore. Index names can be changed with rename_pattern/rename_replacement. Existing indices cannot be restored while open; close or rename them first.","danger":"caution","priv":"manage (create_index, restore)"}}},{"id":"heterogeneous-migration","title":"Migrate to a different database (heterogeneous)","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":"For heterogeneous migration from Oracle (especially to PostgreSQL), the most common open-source tool is Ora2Pg: it converts tables, constraints, PL/SQL and data into the target dialect; --estimate_cost estimates the migration effort. For online/low-downtime migration, AWS DMS or Oracle GoldenGate (with CDC) is used.","danger":"caution","readonly":true,"priv":"SELECT on the source schema (read access to DBA views)"},"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 is the standard tool for migrating to PostgreSQL from other engines (MySQL, SQLite, MS SQL): with connection strings it creates the schema in a single command, maps types and copies data quickly; custom transformations/type mappings are defined in a .load script. If the source is Oracle, Ora2Pg is preferred; for online migration, AWS DMS.","danger":"caution","priv":"CREATE on the target; SELECT on the 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":"There is no single dominant open tool for migrating to MySQL from other engines; the most common solution is AWS DMS (managed, CDC-capable) or adapting the source dump for syntax/types and loading it with the mysql client. MySQL and MariaDB are largely compatible; mysqldump output usually migrates directly.","danger":"caution","priv":"CREATE, INSERT on the target"},"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 (mssql:// source) is used to migrate from SQL Server to PostgreSQL; if you want to keep T-SQL without changing the application, Babelfish for Aurora PostgreSQL offers a compatibility layer. Microsoft's SSMA tool automates migration from other engines (Oracle, MySQL, DB2) to SQL Server. For online migration, AWS DMS.","danger":"caution","priv":"SELECT on the source; CREATE on the target"},"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":"Migration between MongoDB and SQL engines requires a row<->document model transformation (ETL); there is no automatic single command. From SQL to MongoDB, MongoDB Relational Migrator maps tables to a document schema; in simple cases, generate CSV/JSON from the source and load it with mongoimport. In the reverse direction, mongoexport output is loaded into the target relational table with COPY/LOAD DATA.","danger":"caution","priv":"read on the source, write on the target"},"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":"In heterogeneous migration Redis is usually the cache layer as target/source; to load data into Redis from relational or file sources, Redis's official RIOT tool is used (db-import maps an SQL result to HSET/SET structures). For Redis to Redis or to a file there are the riot replicate / file-export commands.","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":"The most common tool for migrating from other databases to Elasticsearch is Logstash (JDBC/file input -> elasticsearch output); Elastic's own connectors/Beats are also used. When moving from an old Elasticsearch cluster to a new one, remote reindex (_reindex with a 'remote' source) is common; reindex.remote.whitelist must be defined on the target.","danger":"caution","priv":"write/create_index on the target"}}},{"id":"setup-cdc-replication","title":"Set up continuous replication / CDC (zero-downtime cutover)","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":"In Oracle, the standard tool for zero-downtime/heterogeneous CDC is Oracle GoldenGate: the Extract process captures changes from the redo logs and Replicat applies them to the target; supplemental logging must be enabled. Typical migration: the initial load is taken with Data Pump, and from then on GoldenGate streams changes to the target, enabling a cutover with near-zero downtime.","danger":"caution","priv":"GoldenGate administrator privileges, 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 provides CDC with built-in logical replication: CREATE PUBLICATION on the source, CREATE SUBSCRIPTION on the target. When the subscription starts it copies the existing data (initial copy), then continuously streams changes from WAL; wal_level=logical is required. For large/complex scenarios, pglogical or Debezium is preferred.","danger":"caution","priv":"REPLICATION role on the source; superuser on the target for CREATE SUBSCRIPTION (in PG16+ the pg_create_subscription role is also sufficient)","minver":"10 (logical replication)"},"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":"In MySQL, a zero-downtime cutover is done with binlog-based replication. The initial load is taken with mysqldump --source-data and the binlog position/GTID is recorded; on the target, replication is started from that position with CHANGE REPLICATION SOURCE TO (8.0.23+ syntax; formerly CHANGE MASTER TO). START REPLICA applies changes continuously. A GTID-based setup is safer.","danger":"caution","priv":"REPLICATION SLAVE (repl user), 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 has two main paths for continuous replication: if you want a direct stream to the target, set up Transactional Replication (publisher/subscriber); if you will move changes with your own ETL, enable Change Data Capture (sp_cdc_enable_db + sp_cdc_enable_table) and read from the _CT tables. For heterogeneous targets, CDC is combined with AWS DMS/Debezium.","danger":"caution","priv":"sysadmin (to enable CDC); SQL Server Agent must be running"},"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":"In MongoDB, Change Streams are used for CDC: db.collection.watch() streams insert/update/delete events live from the oplog, to be applied to the target (a replica set is required). For full cluster-to-cluster continuous migration/replication, MongoDB's official Cluster-to-Cluster Sync tool, mongosync (not Atlas-specific; it also works with self-managed clusters), manages the initial load + continuous synchronization.","danger":"caution","priv":"changeStream (read); clusterAdmin for 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":"In Redis, a zero-downtime cutover is done with native replication: run REPLICAOF <source host port> (formerly SLAVEOF) on the target server; the target first receives the full RDB, then stays continuously in sync via the command stream. Verify master_link_status:up with INFO replication, and once the cutover is complete, make the target standalone with 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":"The real continuous replication feature in Elasticsearch is Cross-Cluster Replication (CCR): the target cluster follows the leader index on the source and pulls changes continuously (requires a commercial/Platinum license). Without a license, near-continuous sync is achieved with periodic remote reindex (incremental, based on a time/sequence field).","danger":"caution","priv":"manage/read_ccr for CCR; whitelist for remote reindex"}}},{"id":"verify-migration","title":"Post-migration verification (row count/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":"The quickest check is to compare COUNT(*) per table on both sides. To find real row-level content differences, use the DBMS_COMPARISON package: it compares the source and target tables row by row over a database link and can report/converge the differences. For checksums, a column digest can be taken with ORA_HASH or STANDARD_HASH.","danger":"safe","readonly":true,"priv":"SELECT on both sides; EXECUTE on 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":"The first step is to compare count(*) on each table. For content verification, md5 the text representation of each row, concatenate them in sorted order and hash again; this gives an order-independent checksum across the two sides. For large data, take the digest over only the mutable/key columns; ordering by PK matters for repeatability.","danger":"safe","readonly":true,"priv":"SELECT on the 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":"In MySQL, CHECKSUM TABLE gives a single checksum of the table content; run it on source and target and compare (engine/version must be the same). Use COUNT(*) for the row count. In live production and for replication consistency, the industry standard is Percona Toolkit's pt-table-checksum; it compares chunk by chunk, online and with low impact.","danger":"safe","readonly":true,"priv":"SELECT on the 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":"In SQL Server, the official command-line tool that finds row-level differences is tablediff (it reports which rows were added/changed, and with the optional -f writes a fix script). For a quick overall check, CHECKSUM_AGG(BINARY_CHECKSUM(*)) gives a single table digest; compare both sides together with COUNT_BIG(*) (independent of row order).","danger":"safe","readonly":true,"priv":"SELECT on both 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":"The first check is to compare document counts on both sides with countDocuments() (estimatedDocumentCount is fast but relies on metadata; use countDocuments for an exact count). For content verification, the dbHash command produces an md5 digest at collection/database level; compare the hashes of the two environments. Check indexes separately with getIndexes().","danger":"safe","readonly":true,"priv":"read (access for 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":"In Redis, basic verification is comparing the total key count with DBSIZE; the per-database distribution is shown by INFO keyspace. Redis has no built-in table checksum; if exact content verification is needed, take the DUMP serialization per key and compare both sides (keep TTL differences in mind). The riot compare tool can also be used.","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":"In Elasticsearch, basic verification is comparing document counts on both clusters with GET <index>/_count (_cat/indices docs.count also gives a quick summary). Base the comparison on _count: _cat/indices docs.count counts at the Lucene level (including nested sub-documents, e.g. one document with two nested items counts as 3), so it can differ from _count. For content verification, compare by sampling documents over the same set of _ids or via a hash field.","danger":"safe","readonly":true,"priv":"read / monitor on index"}}}]},"rosetta/tuning.json":{"category":"Performance Tuning","categoryId":"tuning","order":16,"concepts":[{"id":"plan-baseline","title":"Pin / force a query plan (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 locks in the plan; the strongest protection against regressions. /*+ ... */ hints can also be used.","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: steer at session level with settings such as enable_seqscan; for persistent pinning, use pg_hint_plan.","danger":"caution"},"mysql":{"command":"SELECT /*+ INDEX(customer customer_city_idx) */ * FROM customer WHERE city='Ankara';","note":"Optimizer hints are embedded in the query. For persistent rewriting, use the query rewrite plugin.","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 must be enabled. Alternatively OPTION (USE PLAN N'...') or a plan guide.","danger":"caution","priv":"ALTER on database"},"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 forces a specific index; clear a stale plan with planCacheClear.","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner.","note":"Performance comes from the data structure and command complexity.","danger":"safe"},"elasticsearch":{"command":"# There is no plan pinning; optimize the query/index and routing.\nGET <index>/_search { \"profile\": true, \"query\": {...} }","note":"Use profile to see which query is expensive; steer with shards/routing.","danger":"safe"}}},{"id":"gather-stats-advanced","title":"Gather statistics with advanced options (histograms, 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 controls histograms and degree controls parallelism. Use LOCK_TABLE_STATS to lock statistics.","danger":"safe"},"postgres":{"command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500;\nANALYZE customer;","note":"Per-column sampling target; default_statistics_target sets the global default.","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer UPDATE HISTOGRAM ON city WITH 32 BUCKETS;","note":"Histogram support in MySQL 8.0; useful on columns that are filtered on but not selective.","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Filtered statistics: CREATE STATISTICS ... WHERE ...","note":"FULLSCAN is the most accurate; use SAMPLE on large tables. AUTO_UPDATE_STATISTICS for automatic updates.","danger":"safe"},"mongodb":{"command":"// There are no manual statistics; the planner measures at runtime.\ndb.customer.getPlanCache().clear()","note":"Apart from clearing a stale plan cache, no manual statistics are needed.","danger":"safe"},"redis":{"command":"# Not applicable.","note":"There are no statistics/optimizer.","danger":"safe"},"elasticsearch":{"command":"# There is no statistics gathering; refresh searchability:\nPOST <index>/_refresh","note":"Field data and doc values are used at query time; force merge reduces segments.","danger":"safe"}}},{"id":"perf-report","title":"Generate a performance report (AWR / ASH and equivalents)","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 require the Diagnostics Pack license. Statspack is the unlicensed equivalent.","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":"For an AWR equivalent, use the pg_profile extension (periodic snapshots) or pgBadger log analysis.","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":"The sys schema turns performance_schema into readable reports; Enterprise Monitor is more comprehensive.","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 keeps plans/performance over time; sp_BlitzFirst (community) gives quick diagnostics.","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 show instantaneous load; the profiler records slow operations.","danger":"safe"},"redis":{"command":"INFO stats\nredis-cli --latency-history\nSLOWLOG GET 20","note":"--latency and --latency-history give the latency profile; INFO gives all counters.","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 shows the most expensive threads; _nodes/stats gives holistic metrics. Monitoring (Kibana) is historical.","danger":"safe"}}},{"id":"optimizer-params","title":"View/change optimizer / parallelism settings","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":"Settings such as optimizer_index_cost_adj and parallel_degree_policy affect plan selection; change them carefully.","danger":"caution"},"postgres":{"command":"SHOW random_page_cost;\nSHOW max_parallel_workers_per_gather;\n-- Session: SET enable_seqscan = off;","note":"On SSDs, random_page_cost ~1.1 is recommended. enable_* flags are for diagnosis, not a permanent fix.","danger":"caution"},"mysql":{"command":"SELECT @@optimizer_switch;\nSET SESSION optimizer_switch='index_merge=off';","note":"optimizer_switch turns individual optimizations on/off; test at session level.","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 is unlimited; usually 4-8 for OLTP. The 'cost threshold for parallelism' default of 5 is too low for most.","danger":"caution"},"mongodb":{"command":"# There are no optimizer parameters; indexes and query shape are decisive.\ndb.customer.find({...}).explain('allPlansExecution')","note":"allPlansExecution shows all plans evaluated by the planner.","danger":"safe"},"redis":{"command":"# Not applicable.","note":"There is no planner; io-threads and data structure choice determine performance.","danger":"safe"},"elasticsearch":{"command":"GET <index>/_settings?include_defaults=true\n# search limits such as search.max_buckets, index.max_result_window","note":"There is no optimizer; shard count, routing and query structure determine performance.","danger":"safe"}}},{"id":"sql-monitor","title":"Monitor running/recent SQL in real time","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 shows long/parallel queries live (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":"With auto_explain, plans of slow queries can be logged automatically.","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 shows the statements currently running.","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' in SSMS shows plan progress live.","danger":"safe"},"mongodb":{"command":"db.currentOp({ active: true, secs_running: { $gt: 1 } })","note":"Shows long-running operations live; they can be terminated with killOp.","danger":"safe"},"redis":{"command":"MONITOR","note":"Streams ALL commands live — affects performance in production, use it only briefly. Prefer SLOWLOG instead.","danger":"caution"},"elasticsearch":{"command":"GET _tasks?detailed=true&group_by=parents","note":"Shows running tasks live; a long task is cancelled with POST _tasks/<id>/_cancel.","danger":"safe"}}}]},"rosetta/tuning-adv.json":{"category":"Advanced Tuning","categoryId":"tuning-adv","order":17,"concepts":[{"id":"parallel-query","title":"Run a parallel query / set the degree of parallelism","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":"The PARALLEL hint applies a parallel scan to the table; degree=DOP. An excessive DOP exhausts server resources, choose it carefully. parallel_degree_policy=AUTO manages it automatically.","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":"If Gather/Parallel Seq Scan appears in the plan, parallelism is in effect. max_parallel_workers and min_parallel_table_scan_size also play a role.","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 does not offer general parallel query execution; innodb_parallel_read_threads only parallelizes clustered index scans (e.g. COUNT(*)). Complex JOINs are single-threaded.","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 limits the number of parallel threads; MAXDOP 1 disables parallelism. Once the 'cost threshold for parallelism' is exceeded, the plan goes parallel.","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":"In a sharded cluster the query runs on each shard in parallel; on a single node aggregation stages are mostly sequential. allowDiskUse enables operations that exceed the memory limit.","danger":"safe"},"redis":{"command":"# Not applicable: there is no concept of query parallelism.","note":"Command execution is single-threaded; scale with Cluster (sharding) or use io-threads for network I/O parallelism.","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":"A search runs concurrently on all relevant shards; the number of primary shards sets the baseline level of parallelism. slices split large scroll/reindex operations.","danger":"safe"}}},{"id":"bind-variables","title":"Manage bind variables / 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":"Bind variables reduce hard parses. Adaptive Cursor Sharing mitigates sniffing problems by keeping multiple plans for bind values with skewed distributions. Use cursor_sharing=FORCE with care.","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 chooses between generic and custom plans; force_custom_plan produces a parameter-specific plan on every execution (a fix for sniffing-like problems).","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":"Prepared statements prevent SQL injection and lower parsing cost. MySQL also supports parameterization at the driver level.","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) produces a parameter-specific plan on every execution (CPU-expensive). OPTIMIZE FOR pins the plan to a specific/typical value; OPTIMIZE FOR UNKNOWN uses average selectivity.","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":"Queries with the same shape (same fields/operators) share the cached plan; different literal values do not produce a new plan. If the value distribution is highly skewed, reset it with planCacheClear.","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner or parameter sniffing.","note":"Commands execute directly; key design, not parameterization, determines performance.","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":"A search template (Mustache) parameterizes the query body; the application calls a fixed template with different values, improving query consistency and cache usage.","danger":"safe"}}},{"id":"partition-pruning","title":"Verify 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 show that only the relevant partitions are read (a number rather than KEY is ideal). Applying a function/implicit conversion to the partition key prevents 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":"With enable_partition_pruning on, only matching partitions appear in the plan; the others show as 'never executed'. Constant values are pruned at plan time, parameters at execution time.","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":"In MySQL 5.7+ the 'partitions' column of plain EXPLAIN output lists the partitions to be scanned (the EXPLAIN PARTITIONS syntax was removed in 8.0); only the relevant partition(s) should appear. If all partitions are listed, pruning is not happening.","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":"The $PARTITION function returns a value's partition number. If the number of partitions accessed in the actual execution plan is close to 1, pruning is in effect; if all partitions are scanned, the predicate does not match.","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":"If the query contains the shard key, mongos routes it only to the relevant shard (targeted); otherwise it fans out to all shards (scatter-gather). Verify the shards accessed in the explain output.","danger":"safe"},"redis":{"command":"# Not applicable: there is no table/partition concept.","note":"Data is distributed via Cluster hash slots; instead of query pruning, key access is 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":"Custom routing narrows the query to a single shard (similar to pruning). For time-series data, a date-patterned index name (sale-2026-01) ensures only the relevant indices are searched; use a narrow pattern instead of a wildcard.","danger":"safe"}}},{"id":"result-cache","title":"Use a result / query cache","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":"The Server Result Cache keeps the result in the SGA; it is invalidated automatically when the underlying tables change. RESULT_CACHE_MODE=FORCE applies it to all eligible queries, MANUAL only via the hint. Little benefit on frequently changing tables.","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 does not offer a query result cache; use a materialized view (manual/scheduled REFRESH) or an application-level cache (Redis). Note: the Memoize node provides row-level caching inside a nested loop, not caching of query results.","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":"The old query_cache_type was removed entirely in MySQL 8.0 (due to scalability problems). Use the InnoDB buffer pool, summary tables or an external cache (Redis/Memcached) instead.","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 does not keep a result cache (it keeps a plan cache). For repeated aggregations an indexed view is maintained automatically; automatic matching in plans exists only in Enterprise (and Developer) edition, while on Standard the view must be queried with the WITH (NOEXPAND) hint; in addition, the buffer pool keeps frequently read pages in memory.","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 does not offer a result cache; WiredTiger keeps frequently accessed data in memory. For expensive aggregations, use a precomputed summary collection via $merge/$out or an application cache (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":"The common cache-aside pattern: if the key exists, return it quickly with GET; otherwise compute from the source and SET it with EX (TTL). Redis usually serves as the result cache for other databases.","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":"The shard request cache caches per-shard results of size:0 (aggregation-only) requests; it is invalidated when the index refreshes. Queries with size>0 (returning hits) are not cached by default.","danger":"safe"}}}]},"rosetta/advanced-features.json":{"category":"Advanced Features","categoryId":"advanced-features","order":18,"concepts":[{"id":"full-text-search","title":"Full-text search","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 (formerly interMedia) provides language-aware full-text search through a CONTEXT index. The CONTAINS operator supports search operators such as AND/OR/NEAR. The index is maintained with CTX_DDL.SYNC_INDEX or automatic SYNC.","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":"PostgreSQL built-in full-text search matches tsvector (document) against tsquery (query) with the @@ operator. A GIN index speeds up searches over large text. The language configuration ('english') performs stemming and stop-word handling.","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":"A MySQL FULLTEXT index is queried with the MATCH ... AGAINST syntax. IN NATURAL LANGUAGE MODE returns a relevance score; IN BOOLEAN MODE gives fine-grained control with the +/-/* operators. InnoDB supports full-text search from 5.6 onward.","since":"MySQL 5.6","xnote":"InnoDB full-text search arrived in MySQL 5.6; before that it existed only in 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":"SQL Server Full-Text Search first needs a Full-Text Catalog, then a Full-Text Index on the table (a unique KEY INDEX is mandatory). CONTAINS supports exact/proximity search and logical operators; FREETEXT supports meaning-based (linguistic) search. The Full-Text Search component must be installed.","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":"A MongoDB text index provides full-text search through the $text operator; a collection can have only one text index. The textScore meta field gives the relevance. For more advanced search, Atlas Search ($search, Lucene-based) is used.","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":"Core Redis does not offer full-text search; the RediSearch module (shipped with Redis Stack) provides indexing with FT.CREATE and searching with FT.SEARCH. Stemming, fuzzy search, filtering and sorting are supported. Without the module installed, the feature is unavailable.","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 is fundamentally a full-text search engine built on Lucene; text fields are analyzed at index time (tokenization, stemming). The match query returns results sorted by relevance score (_score). Language-specific analyzers (e.g. 'english') can be assigned.","danger":"safe"}}},{"id":"json-query","title":"Query JSON data","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+) queries JSON data with SQL/JSON path expressions. JSON_VALUE returns a single scalar value, JSON_QUERY an object/array fragment, and JSON_TABLE converts JSON into a relational row-column structure. 21c+ supports a native JSON data type.","since":"Oracle 12c","xnote":"SQL/JSON queries arrived in Oracle 12c, the native JSON data type in 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":"The PostgreSQL jsonb type offers rich operators: -> (json object/element), ->> (text value), #> (path), @> (containment), ? (key exists). A GIN index speeds up @> and ? queries. SQL/JSON path queries are also possible with jsonb_path_query.","since":"PostgreSQL 9.4","xnote":"The json type arrived in PostgreSQL 9.2, the jsonb type in 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+) queries the native JSON type with JSON_EXTRACT(); the -> operator is shorthand for JSON_EXTRACT, and ->> for JSON_UNQUOTE(JSON_EXTRACT()). Searches on JSON fields can be accelerated with a generated column + index.","since":"MySQL 5.7","xnote":"The native JSON data type arrived in 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+) stores JSON in an NVARCHAR column. JSON_VALUE returns a scalar value, JSON_QUERY an object/array, and OPENJSON converts JSON into tabular form. ISJSON() validates it. Lookups on JSON paths can be accelerated with a computed column + index.","since":"SQL Server 2016","xnote":"The JSON functions (JSON_VALUE/OPENJSON) arrived in 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 stores all data as documents (JSON-like BSON); querying it is native. Dot notation reaches nested fields and array elements. Complex transformations are done with the Aggregation Pipeline, and indexes give fast lookups on nested fields.","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":"Core Redis has no JSON type; the RedisJSON module (Redis Stack) provides JSONPath-based reads and writes with JSON.SET/JSON.GET. Together with RediSearch, JSON documents can be indexed and queried. Without the module, JSON is stored as a string and parsed in the 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 stores and indexes documents natively as JSON. To preserve element boundaries in arrays of nested objects, use the 'nested' field type and a nested query; otherwise arrays of object type are flattened (risk of cross-matching). The object vs. nested distinction is critical for correctness.","danger":"safe"}}},{"id":"spatial-geo","title":"Geographic / spatial query","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 stores geographic data in the SDO_GEOMETRY type; a USER_SDO_GEOM_METADATA entry and a spatial index are required. Operators such as SDO_WITHIN_DISTANCE, SDO_RELATE and SDO_NN answer distance/intersection/nearest-neighbor queries. SRID 4326 is the WGS84 (latitude/longitude) system.","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 turns PostgreSQL into a full-featured spatial database. It provides geometry (planar) and geography (spherical) types and hundreds of ST_ functions such as ST_Distance/ST_DWithin/ST_Intersects/ST_Contains. A GiST/SP-GiST index speeds up spatial queries.","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+) supports OGC-compliant spatial data types (POINT, POLYGON, etc.) and ST_ functions. A SPATIAL index (R-tree) can be created only on InnoDB/MyISAM and on NOT NULL columns; for the optimizer to use it, the column must be defined with an SRID attribute (SRID 4326). With SRID 4326, ST_Distance returns the geodesic distance in meters. With POINT(x, y) + ST_SRID, x is longitude and y is latitude; for WKT input (ST_GeomFromText) with SRID 4326 the order is 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 offers two spatial types: geography (spherical, latitude/longitude) and geometry (planar). Methods such as STDistance, STIntersects and STContains answer the queries. A spatial index (grid-based) improves performance. Mind the argument order of 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 stores geographic data in GeoJSON format and runs spherical queries with a 2dsphere index. The $near (sorted by distance), $geoWithin (within an area) and $geoIntersects operators are supported. Coordinates are in [longitude, latitude] order; $maxDistance is in meters.","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 stores geographic locations in a sorted set with GEOADD (longitude latitude member). GEOSEARCH (Redis 6.2+, replacing the older GEORADIUS) returns the members within a radius or box, sorted by distance. GEODIST gives the distance between two points. This is a core feature; no module is needed.","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 supports the geo_point (point) and geo_shape (polygon/line) field types. Filtering is done with geo_distance (radius), geo_bounding_box (box) and geo_polygon queries; sorting with geo_distance, and aggregation with geohash_grid/geotile_grid. Coordinates are given as lat/lon or as a [lon, lat] array.","danger":"safe"}}},{"id":"window-functions-adv","title":"Window functions (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 pioneered analytic (window) functions; ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM/AVG OVER, NTILE and FIRST_VALUE are fully supported. Inside OVER, PARTITION BY defines the groups, ORDER BY the ordering, and the frame (ROWS/RANGE BETWEEN) the sliding window.","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 fully supports window functions with standard SQL syntax. ROW_NUMBER/RANK/DENSE_RANK, LAG/LEAD, aggregates with OVER, NTILE and FIRST_VALUE/LAST_VALUE are available. Repeated window definitions can be named and shared with the WINDOW clause.","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 added window functions in version 8.0 (MariaDB 10.2+ also supports them). ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, NTILE and aggregates with OVER are available. Versions before 8.0 lack these functions; they used to be emulated with user variables.","since":"MySQL 8.0","xnote":"Before MySQL 8.0 there were no window functions; they were emulated with user variables.","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 supports window functions since 2005 (ROW_NUMBER/RANK) and 2012 (LAG/LEAD, framing, aggregates with OVER). PARTITION BY, ORDER BY and the ROWS/RANGE frame are fully available. OFFSET/FETCH or ROW_NUMBER is common for paging.","since":"SQL Server 2005","xnote":"Ranking functions arrived in 2005, LAG/LEAD and framing in 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":"The $setWindowFields aggregation stage, introduced in MongoDB 5.0, provides window functions: $documentNumber (ROW_NUMBER), $rank/$denseRank, $shift (LAG/LEAD), aggregate functions and sliding windows (window: { documents/range }). partitionBy and sortBy correspond to PARTITION BY/ORDER BY in SQL.","since":"MongoDB 5.0","xnote":"The $setWindowFields stage arrived in MongoDB 5.0.","danger":"safe"},"redis":{"command":"# Not applicable: there are no SQL-like window functions.","note":"Redis is a key-value store; it has no analytic window functions. For ordered results a Sorted Set can be used (rank-like access with ZADD/ZRANGE); however, relational window operations such as PARTITION BY/LAG are done in the application layer.","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 does not offer classic window functions (ROW_NUMBER/RANK/LAG). Grouping and ordering are done with the aggregation framework: a terms aggregation is close to PARTITION BY, top_hits to the top records, and derivative/serial_diff (pipeline aggregations) to LAG-like successive differences.","danger":"safe"}}},{"id":"columnstore-analytics","title":"Columnar storage / analytics","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+) speeds up analytic queries by keeping tables in memory in columnar format (In-Memory Column Store, IM) while preserving the row format on disk (dual format). INMEMORY is a separately licensed option. The inmemory_size parameter sizes the IM area.","since":"Oracle 12.1.0.2","xnote":"The Database In-Memory column store arrived in Oracle 12.1.0.2 (separate license).","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":"Core PostgreSQL offers only row-based (heap) storage. For columnar storage, the Citus 'columnar' access method (USING columnar), Hydra or the older cstore_fdw extensions are used; they provide compression and scan speed for analytics/data warehouse workloads.","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":"Standard MySQL (InnoDB) is row-based and has no built-in columnstore. For analytic acceleration, MySQL HeatWave (an in-memory columnar accelerator in Oracle Cloud) is used; on-premises solutions typically use external columnar engines such as ClickHouse or 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":"SQL Server columnstore indexes (read-only nonclustered in 2012; updatable clustered in 2014; updatable nonclustered in 2016) store data column-wise with high compression; with batch mode they give large speedups for aggregation/scan-heavy analytic queries. Clustered suits data warehouses, nonclustered suits HTAP.","since":"SQL Server 2012","xnote":"The columnstore index arrived in 2012 (read-only), updatable clustered columnstore in 2014, updatable nonclustered columnstore in 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 stores documents as row-like BSON; there is no built-in columnar (columnstore) storage. For analytic workloads, dedicated Analytics Nodes in an Atlas replica set, Atlas Data Federation, or exporting data to a columnar system (e.g. Parquet) are used.","danger":"safe"},"redis":{"command":"-- Not applicable: there is no concept of columnar storage.","note":"Redis is an in-memory key-value store; there is no row/column storage distinction. Analytic aggregations are done with special structures such as Sorted Set, HyperLogLog (approximate counting) or RedisTimeSeries; it is not suitable for large-scale OLAP.","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":"For sorting and aggregations, Elasticsearch keeps fields in a disk-based columnar structure called doc_values (enabled by default on numeric/keyword/date fields). This columnar layout speeds up analytic/aggregation queries; it is disabled by default on text fields.","danger":"safe"}}},{"id":"in-memory","title":"In-memory tables / acceleration","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 speeds up analytics by keeping frequently queried tables in memory in columnar format (separate license). For OLTP, the Buffer Cache and the KEEP pool also keep frequently accessed blocks in memory. The in-memory solution preserves durability through the row format on disk.","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 offers no dedicated in-memory table engine; an UNLOGGED table skips WAL writes to speed up writing (its contents are lost on a crash; not durable). TEMP tables live for the session and are kept in memory where possible via temp_buffers. Hot data is cached in 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":"The MySQL MEMORY (formerly HEAP) storage engine keeps table data entirely in RAM; it is very fast, but data is lost when the server restarts (the structure remains). It does not support BLOB/TEXT and uses a fixed row size. For durable hot data, the InnoDB buffer pool is preferred.","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":"SQL Server In-Memory OLTP (Hekaton) provides highly concurrent OLTP with memory-optimized tables (MEMORY_OPTIMIZED=ON) and latch-free structures. DURABILITY=SCHEMA_AND_DATA keeps the data durable; SCHEMA_ONLY preserves only the schema. Requires a memory-optimized filegroup.","since":"SQL Server 2014","xnote":"In-Memory OLTP (Hekaton) arrived in 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 has no separate in-memory table type (the old In-Memory Storage Engine was Enterprise-only and has been deprecated). The default WiredTiger engine improves read speed by keeping frequently accessed data and indexes in its memory cache; the OS file cache also contributes.","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 keeps all data in RAM; by design this is the source of its speed. Persistence is provided by RDB (snapshots) and/or AOF (command log). The maxmemory limit and the eviction policy (LRU/LFU/random) determine behavior when memory is full.","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 stores data on disk (Lucene segments); it offers no dedicated in-memory table engine. For speed it relies on the OS file cache (page cache), doc_values and a hot-warm-cold architecture. Sufficient RAM and heap settings are critical for performance.","danger":"safe"}}},{"id":"time-series","title":"Time-series data","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":"In Oracle, time-series data is usually managed with RANGE (INTERVAL) partitioning; new partitions are created automatically and old partitions are easily dropped (partition pruning speeds up queries). Compression (ILM/ADO) and materialized views for aggregation are also used.","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 can hold time series with native partitioning (PARTITION BY RANGE); the TimescaleDB extension adds hypertables with automatic time-based chunking, compression, retention policies and continuous aggregates. It is a common solution for large IoT/metrics workloads.","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 offers no dedicated time-series engine; RANGE partitioning (with TO_DAYS/UNIX_TIMESTAMP) allows fast dropping of old data and partition pruning. For high-volume time series, external solutions (TimescaleDB, InfluxDB) or summary tables are usually preferred.","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 manages time series by partitioning with a partition function/scheme; this provides pruning and fast archiving for data spanning large date ranges. In addition, system-versioned temporal tables (SYSTEM_VERSIONING) automatically keep row change history and allow point-in-time querying (FOR SYSTEM_TIME).","since":"SQL Server 2016","xnote":"System-versioned temporal tables arrived in 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":"Time-series collections, introduced in MongoDB 5.0, automatically store data in a compressed, column-like (bucketed) format by time and meta field; they reduce disk and index usage and speed up range queries. timeField is mandatory; metaField (source/tag) and granularity determine performance.","since":"MongoDB 5.0","xnote":"Time-series collections arrived in 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":"Core Redis has no time-series type; the RedisTimeSeries module (Redis Stack) provides high-speed sample ingestion with TS.CREATE/TS.ADD, automatic retention (RETENTION), downsampling (TS.CREATERULE) and label-based range queries with TS.RANGE/TS.MRANGE. Without the module, it is emulated with a 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 manages time-series data with data streams; new backing indexes are created automatically over time (rollover), and ILM moves old data through hot/warm/cold tiers and deletes it. TSDS (index.mode: time_series) provides storage optimizations specific to metrics data; at least one time_series_dimension field, an index.routing_path pointing to it, and a @timestamp field are mandatory (otherwise the template is rejected).","danger":"safe"}}},{"id":"vector-search","title":"Vector search (AI/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 offers a native VECTOR data type and the AI Vector Search feature. The VECTOR_DISTANCE function computes similarity with the COSINE/EUCLIDEAN/DOT metrics; HNSW and IVF vector indexes are supported for approximate search. Embeddings are generated in-database or with external models.","since":"Oracle 23ai","xnote":"The native VECTOR type and AI Vector Search arrived in 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":"The pgvector extension adds a vector type and similarity operators to PostgreSQL: <-> (L2/Euclidean), <=> (cosine), <#> (inner product). HNSW or IVFFlat indexes speed up approximate nearest neighbor (ANN) search. Widely used in RAG and semantic search applications.","xnote":"Core PostgreSQL has no vector type; the pgvector extension is required.","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 added the native VECTOR data type and the STRING_TO_VECTOR/VECTOR_TO_STRING and VECTOR_DIM functions (also in Community). The DISTANCE() function and approximate vector search at scale are available only in MySQL HeatWave in Oracle Cloud; in Community/Enterprise, compute distances in the application or an external vector store. Older on-premises versions require an external vector store.","since":"MySQL 9.0","xnote":"The native VECTOR data type arrived in 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 (and Azure SQL) offer a native vector data type and the VECTOR_DISTANCE function (cosine/euclidean/dot); DiskANN-based vector indexes for approximate nearest neighbor search are under development. Embeddings are generated with external models such as Azure OpenAI and stored.","since":"SQL Server 2025","xnote":"The native vector data type arrived in 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 queries embeddings with the $vectorSearch aggregation stage; under the hood it performs HNSW-based approximate nearest neighbor search. The vector index is defined as an Atlas Search index. This feature is specific to the managed Atlas service (not available in the on-premises Community edition).","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 provides vector search through the RediSearch module (Redis Stack); the VECTOR field type supports FLAT (exact) or HNSW (approximate) indexes and the COSINE/L2/IP metrics. Nearest neighbors are retrieved with the KNN syntax inside FT.SEARCH. Used for low-latency semantic search and 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 supports approximate nearest neighbor (HNSW) queries with the dense_vector field type and built-in knn search; similarity can be cosine/dot_product/l2_norm. Relevance can be improved by combining vector search with a classic text query (hybrid search / RRF).","since":"Elasticsearch 8.0","xnote":"Built-in dense_vector kNN search arrived in Elasticsearch 8.x.","danger":"safe"}}},{"id":"cdc-change-tracking","title":"Change tracking (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 does change tracking based on the redo log. LogMiner translates redo/archived logs into SQL to inspect past DML; the log files to analyze are added with DBMS_LOGMNR.ADD_LOGFILE. For meaningful results, at least minimal supplemental logging must be enabled (ALTER DATABASE ADD SUPPLEMENTAL LOG DATA). For continuous, low-latency CDC and heterogeneous replication, Oracle GoldenGate (a separate product) is the standard; the old Streams/Change Data Capture features are deprecated.","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":"PostgreSQL logical decoding turns WAL records into a readable change stream through an output plugin (wal2json, pgoutput, test_decoding); the replication slot ensures changes are not lost. wal_level must be logical. Debezium uses this mechanism to provide enterprise CDC.","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":"In MySQL the CDC source is the binary log. With binlog_format=ROW every row change is recorded. Tools such as Debezium or Maxwell read the binlog like a replica and produce an event stream into Kafka. Enabling GTID makes reliable position tracking easier.","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 offers two mechanisms: Change Data Capture (CDC) writes all DML (with old/new values) from the transaction log into change tables and requires SQL Server Agent. Change Tracking only lightly tracks which rows changed (net changes) and keeps no full data history.","since":"SQL Server 2008","xnote":"CDC and Change Tracking arrived in 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":"MongoDB Change Streams, built on the oplog, deliver insert, update and delete events at the collection/database/cluster level as a real-time stream. Requires a replica set or sharded cluster. With a resumeToken, a stream can resume where it left off after an interruption; it is the foundation of CDC integrations.","since":"MongoDB 3.6","xnote":"Change Streams arrived in 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":"Redis keyspace notifications publish events on keys (SET, DEL, EXPIRE, etc.) through Pub/Sub channels; they are enabled with the notify-keyspace-events setting (K=keyspace, E=keyevent, A=all events). For a reliable, durable change stream, Redis Streams (XADD/XREAD) are preferred.","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 offers no change data capture (CDC) stream; it does not natively publish when a document changed. The common approach is to add an updated_ts field to every document and run incremental queries by timestamp (range + sort). Usually the source database's CDC is fed into Elasticsearch.","danger":"safe"}}}]},"rosetta/sql-syntax.json":{"category":"SQL Syntax & Keywords","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 defines a named temporary result set (CTE) inside a query; instead of writing a subquery repeatedly, you define it once and refer to it by name. It breaks complex queries into readable pieces. Supported since 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":"A CTE splits complex queries into named blocks. Before PostgreSQL 12 a CTE was an optimization fence (always materialized); from 12 on it can be inlined, and the old behavior can be forced with 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":"A CTE defines a temporary named result set within a query and improves readability. Supported from MySQL 8.0 (and MariaDB 10.2); older versions require a derived table (a subquery in FROM).","since":"MySQL 8.0","xnote":"Before MySQL 8.0 there were no CTEs; the same was done with a derived table (a subquery in 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":"A CTE is a named temporary result set. In SQL Server the statement immediately preceding a CTE must end with a semicolon, which is why WITH is often written as ';WITH'. It is valid for the duration of a single statement (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 has no SQL/CTE; aggregation pipeline stages do the same job. Each stage takes the previous stage's output as input, giving step-by-step processing similar to CTE chains. $facet or $lookup can also be used for intermediate results.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of SQL or CTEs.","note":"Redis is a key-value data store with no query language. You combine intermediate results on the client side, or use a Lua script (EVAL) for server-side logic.","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 (the _sql API) does not support WITH/CTE syntax; grouping and filtering are written in a single SELECT. With the native approach, the same is done with a 'terms' aggregation and a 'bucket_selector' pipeline aggregation.","danger":"safe"}}},{"id":"recursive-cte","title":"Recursive CTE (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":"Used to traverse hierarchical (tree) data: org charts, bills of materials and the like. Oracle's classic way is CONNECT BY / PRIOR and the LEVEL pseudocolumn; START WITH sets the root row. Since 11gR2 the standard recursive CTE is supported as well.","since":"Oracle 11gR2","xnote":"CONNECT BY is the classic method; the standard recursive WITH (without the RECURSIVE keyword) arrived with 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 combines an anchor query and a recursive part with UNION ALL; the recursive part runs repeatedly until the result is empty. Ideal for hierarchies and graph traversal. Guard against infinite loops with UNION (instead of ALL) or cycle protection.","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 (and MariaDB 10.2+) support standard-compliant WITH RECURSIVE syntax. Recursion depth is limited by the cte_max_recursion_depth system variable (default 1000). Older versions required writing a loop in a stored procedure.","since":"MySQL 8.0","xnote":"Before MySQL 8.0 (MariaDB 10.2) there were no recursive queries; a loop was written in a stored procedure.","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":"In SQL Server the RECURSIVE keyword is not written for a recursive CTE; a CTE containing UNION ALL that references itself is treated as recursive automatically. The default recursion limit is 100; change it with OPTION (MAXRECURSION n) (0 = unlimited, use with care).","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 has no recursive CTE; the $graphLookup aggregation stage is used for hierarchical traversal. connectFromField/connectToField set the link fields and maxDepth sets the depth limit. It resolves tree and graph relationships in a single query.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no recursive queries.","note":"Redis offers no query language. Hierarchy/graph traversal is done in application code or server-side with a Lua script; in core Redis the tree walk is written on the client side. (Support for the formerly used RedisGraph module ended in 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 offers no recursive query (no equivalent of WITH RECURSIVE / CONNECT BY). Hierarchical relationships are modeled with the 'join' (parent/child) field type or by storing the tree path (materialized path) in a field; multi-level traversal is usually done in the application.","danger":"safe"}}},{"id":"joins","title":"JOIN types (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":"A JOIN combines two tables according to the given condition. INNER returns only matches; LEFT/RIGHT return all rows from one side (NULL where there is no match); FULL returns rows from both sides; CROSS returns all combinations. Besides standard ANSI JOINs, Oracle also supports the old (+) outer join syntax.","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 fully supports all standard JOIN types (INNER, LEFT/RIGHT/FULL OUTER, CROSS). USING(my_column) is a shorthand for commonly named columns, while NATURAL JOIN joins automatically on all same-named columns (use with care).","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 support INNER, LEFT, RIGHT and CROSS JOIN but do not offer FULL OUTER JOIN directly; it is emulated by combining LEFT and RIGHT results with UNION. The old comma-style join produces a Cartesian product and must be restricted with 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 supports all standard JOIN types. The old *= / =* outer join operators have been removed; ANSI JOIN syntax must be used. APPLY (CROSS/OUTER APPLY) is an additional join form for row-by-row correlated subqueries.","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 has no classic JOIN; the $lookup aggregation stage fetches matching documents from another collection as an array (similar to a left outer join). For an INNER-like result, filter out empty arrays afterwards with $match and expand the array into rows with $unwind.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no tables or JOINs.","note":"Redis offers no relational joins; data is usually denormalized, or relationships are built on the client side by reading multiple keys. RediSearch (FT.AGGREGATE) can be used for limited join-like queries; support for the RedisGraph module ended in 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":"Because of its distributed architecture, Elasticsearch does not offer arbitrary cross-table JOINs. For limited relationships there are the 'nested' field type within a single index and the 'join' (parent/child) field type on the same shard. The most common solution is to denormalize data at indexing time.","danger":"safe"}}},{"id":"window-functions","title":"Window functions (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":"Window functions compute over a 'window' for each row without grouping rows (without reducing the row count). PARTITION BY splits the window, ORDER BY sorts it. Oracle pioneered these analytic functions and offers a rich set including ROW_NUMBER, RANK, DENSE_RANK, LAG/LEAD and 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 supports all standard window functions and lets you name a repeated window definition with WINDOW ... AS. With ROWS/RANGE/GROUPS frame definitions you can compute running totals, moving averages and similar.","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 (and MariaDB 10.2+) support window functions; before that they were emulated with user variables or subqueries. ROW_NUMBER, RANK, DENSE_RANK, NTILE, LAG/LEAD, FIRST_VALUE/LAST_VALUE and the OVER form of aggregate functions are available.","since":"MySQL 8.0","xnote":"Before MySQL 8.0 there were no window functions; they were emulated with variables/self-joins.","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 supports ranking window functions since 2005, and framed aggregates and functions such as LAG/LEAD since 2012. ROW_NUMBER is commonly used for paging (with OFFSET/FETCH) and for finding the top N rows per group.","since":"SQL Server 2005","xnote":"Ranking window functions arrived in 2005, frames and LAG/LEAD in 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":"The MongoDB 5.0+ $setWindowFields stage is the equivalent of SQL window functions: the window is defined with partitionBy and sortBy, producing outputs such as $rank/$denseRank/$documentNumber, $shift (LAG/LEAD) and running totals. In older versions this was solved with $group + code.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no window functions.","note":"Redis offers no analytic window functions. For ordered results and ranking, use the Sorted Set (ZSET) structure: assign scores with ZADD, read the rank with ZRANK/ZREVRANK and ranges with ZRANGE. Complex analytics are done in the 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 has no direct window functions. For the top N records per group use a 'top_hits' sub-aggregation; for moving/cumulative calculations use pipeline aggregations such as 'derivative', 'cumulative_sum' and 'moving_fn'. Sorting and partitioning are done with a terms aggregation.","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 groups rows by the given columns and applies aggregate functions; HAVING filters the grouped result (WHERE filters rows before grouping). ROLLUP produces hierarchical subtotals + a grand total, CUBE all combinations, and GROUPING SETS the selected groupings. Oracle supports all three.","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+ supports ROLLUP, CUBE and GROUPING SETS in a standard-compliant way. The GROUPING() function tells whether a column is in a total row (NULL). HAVING filters after aggregation, WHERE before.","since":"PostgreSQL 9.5","xnote":"ROLLUP, CUBE and GROUPING SETS arrived with 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 produce subtotals/grand totals with GROUP BY ... WITH ROLLUP syntax; CUBE and GROUPING SETS are not supported. SELECT aliases can be used in HAVING (this generally does not work in Oracle/SQL Server). In ROLLUP rows the grouped column appears as 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":"Alongside GROUP BY, SQL Server supports ROLLUP(), CUBE() and GROUPING SETS (2008+) with modern syntax; the old WITH ROLLUP/WITH CUBE form is deprecated. GROUPING() and GROUPING_ID() help distinguish total rows.","since":"SQL Server 2008","xnote":"The modern GROUPING SETS syntax arrived with 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":"In MongoDB $group is the equivalent of SQL GROUP BY; _id sets the grouping key and accumulators ($sum, $avg, $max) define the aggregates. Filtering after grouping is done with $match (the HAVING equivalent). Multi-level totals such as ROLLUP/CUBE are obtained by running multiple pipelines with $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":"Core Redis offers no grouping; the RediSearch module's FT.AGGREGATE command performs SQL-like aggregation with GROUPBY and REDUCE (COUNT, SUM, AVG). The FILTER step filters the grouped result like HAVING. Without the module, aggregation is done in the 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":"In Elasticsearch grouping is done with the 'terms' bucket aggregation and aggregates with metric aggregations (sum, avg, cardinality). For HAVING-like filtering, use the 'bucket_selector' pipeline aggregation. Multi-level subtotals are built with nested aggregations.","danger":"safe"}}},{"id":"case-expr","title":"CASE expression (conditional value)","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 maps an expression to different values according to conditions; it has both a simple form (CASE my_column WHEN value) and a searched form (CASE WHEN condition). Oracle's DECODE does a similar job but compares only for equality and treats NULLs as equal; prefer CASE for portability.","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 fully supports the standard CASE expression, usable anywhere a value is produced (SELECT, WHERE, ORDER BY, GROUP BY). It has no Oracle-style DECODE; use CASE instead, or COALESCE/NULLIF for short conditions.","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 support the standard CASE expression; there is also the IF(condition, if_true, if_false) function for a quick two-branch condition (similar to SQL Server's IIF). CASE can be used in any expression context.","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 supports the standard CASE; since 2012 there are the IIF() shortcut for two-branch conditions and CHOOSE() for multiple matches. IIF is actually translated into CASE. CASE can also be used in ORDER BY and 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":"In MongoDB aggregation, the CASE equivalents are the $switch (multi-branch) and $cond (three-argument if-then-else) operators. They are used inside $project or $addFields to create conditional derived fields.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no in-query conditional expression.","note":"Redis has no query expression like CASE. Conditional logic is written in application code or in a server-side Lua script (EVAL). Limited expression evaluation is possible with APPLY inside RediSearch FT.AGGREGATE.","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":"In Elasticsearch, in-query CASE logic is written as a Painless script (runtime field, script_fields or scripted metric). The if/else flow is defined in the Painless language. The 'range' aggregation can also produce CASE-like buckets for categorization.","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":"Set operators combine the result sets of two queries. UNION removes duplicates (slower), UNION ALL keeps them all (faster), INTERSECT returns common rows, and the difference operator returns rows found only in the first set. For difference Oracle uses MINUS instead of the standard EXCEPT (EXCEPT also exists since 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 fully supports standard UNION, UNION ALL, INTERSECT and EXCEPT (EXCEPT, not MINUS, for difference). The ALL form of each operator (INTERSECT ALL, EXCEPT ALL) preserves duplicate counts. Column counts and types must be compatible.","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":"For a long time MySQL supported only UNION / UNION ALL; INTERSECT and EXCEPT were added only in MySQL 8.0.31 (MariaDB 10.3). On older versions use INNER JOIN/EXISTS for intersection and LEFT JOIN ... IS NULL or NOT EXISTS for difference.","since":"MySQL 8.0.31","xnote":"INTERSECT/EXCEPT arrived with MySQL 8.0.31 (MariaDB 10.3); before that they were solved with 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 supports standard UNION, UNION ALL, INTERSECT and EXCEPT (2005+); the difference operator is EXCEPT (not MINUS). INTERSECT/EXCEPT compare rows treating NULL values as equal.","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":"The MongoDB 4.4+ $unionWith stage combines the results of two collections (like UNION ALL). To remove duplicates, deduplicate afterwards with $group. INTERSECT/EXCEPT have no direct equivalent; they are modeled with $lookup or set operators ($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 has no SQL, but the Set data type offers native set operations: SUNION (union), SINTER (intersection), SDIFF (difference) and their STORE variants that save the result. These operate on pre-built set members, not on query results.","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 does not offer UNION/INTERSECT/EXCEPT. A union effect is achieved by querying multiple comma-separated indices; for intersection and difference use the must/should/must_not clauses of a bool query or set logic on the application side.","danger":"safe"}}},{"id":"exists-in","title":"EXISTS / IN / NOT EXISTS (subquery)","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 checks whether a correlated subquery returns at least one row (it stops at the first match, so it is fast). IN checks whether a value is in a list/subquery result. Caution: if the subquery can produce NULL, NOT IN gives unexpected results; NOT EXISTS is safer. The Oracle optimizer often turns both into the same 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 fully supports EXISTS/IN/NOT EXISTS. With NOT IN, if the subquery contains NULL the whole result can become empty, so NOT EXISTS or LEFT JOIN ... IS NULL is recommended. The planner handles IN and EXISTS equivalently as a semi-join in most cases.","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 support EXISTS/IN/NOT EXISTS. Before MySQL 5.6 correlated subqueries could be slow; 5.6+ sped up IN subqueries with semi-join and materialization optimizations. The NOT IN + NULL trap applies here as well.","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 supports EXISTS/IN/NOT EXISTS, and the optimizer mostly processes them as semi/anti-joins. If a NOT IN subquery contains NULL, the result can unexpectedly become empty; NOT EXISTS is the safe choice. INTERSECT/EXCEPT can also be used for similar membership logic.","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":"In MongoDB value-list membership is done with the $in / $nin operators. The equivalent of a subquery EXISTS is to fetch the array of matching documents with $lookup and filter on whether the array is empty. Checking field existence is a separate { field: { $exists: true } } operator (the presence of a field, not a row relationship).","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 has no subqueries. For value membership, use SISMEMBER on a Set (IN-like). The EXISTS command is not a subquery; it returns whether the given key exists. Relational existence checks are done on the application side by reading multiple keys.","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":"In Elasticsearch the IN equivalent is the 'terms' query (match one of several values), and NOT IN is terms inside bool must_not. The 'exists' query does not correspond to SQL EXISTS; it checks whether a field has a value in the document. Correlated EXISTS across tables usually requires denormalization.","danger":"safe"}}},{"id":"limit-paging","title":"Row limiting / paging (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":"Used to limit/page through a result set. Since Oracle 12c the standard OFFSET n ROWS FETCH NEXT m ROWS ONLY is supported. Before that the ROWNUM pseudocolumn was used; because ROWNUM is assigned before sorting, wrapping an ORDER BY subquery is mandatory.","since":"Oracle 12c","xnote":"Before Oracle 12c, row limiting was done with 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 supports both LIMIT n OFFSET m and the standard OFFSET ... FETCH form. Large OFFSET values are slow; for deep paging, key-based (keyset / 'WHERE id > last_id') pagination is more efficient. LIMIT without ORDER BY does not guarantee result order.","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 use the LIMIT keyword in two forms: LIMIT row_count OFFSET offset or LIMIT offset, row_count. MySQL 8.0.19+ also accepts the standard FETCH syntax. Keyset pagination is recommended for deep OFFSET performance.","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 uses TOP n (with TOP (n) PERCENT / WITH TIES variants) for the first N rows, and since 2012 OFFSET ... FETCH for paging. ORDER BY is mandatory for OFFSET/FETCH to work. SQL Server has no LIMIT keyword.","since":"SQL Server 2012","xnote":"OFFSET/FETCH paging arrived with 2012; before that it was done with TOP or ROW_NUMBER.","danger":"safe"},"mongodb":{"command":"// Paging with skip/limit\ndb.employee.find()\n  .sort({ salary: -1 })\n  .skip(20)\n  .limit(10)","note":"In MongoDB limit() sets the number of documents returned and skip() the number skipped; combined with sort() they provide paging. Large skip values are expensive; for deep paging a range (keyset) query based on the last seen value is preferred.","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 has no SQL LIMIT, but it has range-read commands: ZRANGE/ZREVRANGE start stop (index based) or ZRANGEBYSCORE ... LIMIT offset count for Sorted Sets; LRANGE for Lists. These commands provide paging-like access.","danger":"safe"},"elasticsearch":{"command":"GET employee/_search\n{\n  \"from\": 20,\n  \"size\": 10,\n  \"sort\": [ { \"salary\": \"desc\" } ]\n}","note":"In Elasticsearch 'size' sets the number of hits returned and 'from' the number skipped (the LIMIT/OFFSET equivalent). from+size can be at most index.max_result_window (default 10,000); for deep paging use 'search_after' (keyset) or a PIT (point-in-time).","danger":"safe"}}},{"id":"merge-upsert","title":"MERGE / UPSERT (update if exists, else insert)","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) matches a source against a target, updating matching rows and inserting non-matching ones; ideal for bulk data synchronization. Oracle has strong MERGE support: WHEN MATCHED ... DELETE and conditional updates can also be written. For a single-row source, the dual table is used.","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":"PostgreSQL's classic upsert is INSERT ... ON CONFLICT (unique column) DO UPDATE / DO NOTHING; the new values of the conflicting row are accessed through the EXCLUDED alias. Since PostgreSQL 15 the standard MERGE statement is also supported. ON CONFLICT is atomic on a single table and widely used.","since":"PostgreSQL 15","xnote":"The standard MERGE statement arrived with PostgreSQL 15; before that upserts were done with 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 use INSERT ... ON DUPLICATE KEY UPDATE for upserts; if a unique or primary key conflicts, the row is updated. New values are accessed with VALUES(my_column) (or the 'new.my_column' row alias in 8.0.19+). INSERT IGNORE silently skips the row on conflict. MySQL has no standard MERGE.","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 supports the MERGE statement since 2008; it offers extra branches such as WHEN NOT MATCHED BY SOURCE for deleting surplus rows in the target. A MERGE statement must be terminated with a semicolon. Note: because of some edge-case bugs, writing separate INSERT/UPDATE statements may be preferred for simple upserts.","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":"In MongoDB upsert is a native option: if { upsert: true } is added to updateOne/updateMany/findOneAndUpdate, a matching document is updated, otherwise a new document is created. For bulk synchronization, the aggregation $merge stage (write/update into a target collection) is closer to SQL MERGE.","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":"In Redis write commands are inherently upserts: SET/HSET update the key or field if it exists, otherwise create it. Use the SET ... NX option to write only if absent, and XX only if present. The 'update if exists, else insert' distinction is already the default behavior of most commands.","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":"In Elasticsearch an index (PUT) call with the same _id fully replaces the document if it exists, otherwise creates it (upsert-like). For partial updates with insert-if-absent, use 'doc_as_upsert': true or an 'upsert' block in the _update API. Scripted upserts are also possible.","danger":"caution"}}},{"id":"returning-output","title":"Return the modified row (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 returns the values of rows changed by INSERT/UPDATE/DELETE (e.g. an auto-generated ID) without a separate SELECT. In Oracle, a single row is returned with RETURNING ... INTO a bind variable, and multiple rows with RETURNING ... BULK COLLECT INTO a 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":"PostgreSQL's RETURNING clause is very flexible: it returns any column (including computed expressions) of the rows affected by INSERT, UPDATE, DELETE and MERGE statements directly as a result set. It is the cleanest way to obtain generated serial/identity values.","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 does not support standard RETURNING; the auto-increment key generated by an INSERT is retrieved with LAST_INSERT_ID(), and updated values are read with a separate SELECT. MariaDB offers INSERT ... RETURNING since 10.5 and DELETE ... RETURNING since 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":"SQL Server's equivalent is the OUTPUT clause: it accesses the INSERTED (new values) and DELETED (old values) pseudo-tables; both can be used in an UPDATE. With OUTPUT ... INTO the results can be written to a table/table variable. Ideal for copying changes into an audit table.","since":"SQL Server 2005","xnote":"The OUTPUT clause arrived with 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":"In MongoDB the findOneAndUpdate / findOneAndDelete / findOneAndReplace commands return the affected document in a single atomic operation. returnDocument: 'before' gives the version before the change, 'after' the version after. insertOne returns the _id of the inserted document.","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 has no separate RETURNING, but many write commands return the result directly: INCR/INCRBY return the new value, SET ... GET the old value (GETSET is deprecated since 6.2), and LPOP/RPOP the removed element. For atomic read-write, this behavior substitutes for 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":"In Elasticsearch index/update operations return result metadata (_id, _version, result). To get the body of the updated document, add '_source': true to the _update request; the get field of the response contains the current _source. No separate GET call is needed.","danger":"safe"}}},{"id":"lateral-apply","title":"LATERAL / CROSS APPLY (row-by-row subquery)","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 allows a subquery in FROM to reference columns of the table to its left, so you can write correlated joins such as 'the first N rows for each row'. Oracle 12c supports both ANSI LATERAL and CROSS/OUTER APPLY syntax.","since":"Oracle 12c","xnote":"LATERAL and CROSS/OUTER APPLY arrived with 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":"In PostgreSQL, subqueries that depend on the row to the left are written with the LATERAL keyword (JOIN LATERAL / CROSS JOIN LATERAL / LEFT JOIN LATERAL). Very handy for 'top N per group' and for expanding rows with set-returning functions. There is no CROSS APPLY syntax; LATERAL is used.","since":"PostgreSQL 9.3","xnote":"LATERAL arrived with 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":"Since MySQL 8.0.14, LATERAL derived tables are supported; the subquery can reference columns of the outer table. Before that, and for CROSS APPLY, there was no equivalent; 'top N per group' was solved with window functions or correlated subqueries. MariaDB does not have LATERAL yet.","since":"MySQL 8.0.14","xnote":"LATERAL derived tables arrived with MySQL 8.0.14; MariaDB does not have them.","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":"SQL Server's LATERAL equivalent is the APPLY operator: CROSS APPLY (like INNER JOIN LATERAL, drops non-matches) and OUTER APPLY (like LEFT JOIN LATERAL, NULL for non-matches). Common for calling table-valued functions for each row and for top-N-per-group queries.","since":"SQL Server 2005","xnote":"The CROSS/OUTER APPLY operator arrived with 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":"In MongoDB the LATERAL/APPLY equivalent is the $lookup form with a sub-pipeline and let variables: the outer document's fields are passed into the sub-pipeline via let as $$variable, where matching/sorting/limiting happen. This solves the 'related top N for each document' pattern.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no subqueries / LATERAL.","note":"Redis offers no row-by-row correlated subqueries. The 'related top N for each item' pattern is solved with a loop on the application side (e.g. a separate ZRANGE per key) or with a server-side Lua script.","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 has no LATERAL/APPLY; the 'top N records per group' pattern is achieved with a top_hits sub-aggregation nested inside a terms aggregation. Instead of a correlated subquery, this is grouping plus in-group sampling on a single index.","danger":"safe"}}},{"id":"pivot","title":"PIVOT / UNPIVOT (rows↔columns)","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 turns distinct values in rows into columns (a cross tab), and UNPIVOT turns columns back into rows; used in summary/report tables. Oracle offers built-in PIVOT/UNPIVOT operators since 11g; the column list must be static (dynamic columns require PIVOT XML or code).","since":"Oracle 11g","xnote":"The built-in PIVOT/UNPIVOT operators arrived with 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 has no built-in PIVOT operator. There are two ways: the crosstab() function of the tablefunc extension, or portable conditional aggregation (SUM(...) FILTER (WHERE ...) or with CASE). The FILTER clause is an elegant PostgreSQL-specific pivot technique.","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 have no PIVOT/UNPIVOT operator; a cross tab is built by hand with GROUP BY plus a SUM(CASE WHEN ... THEN ... END) for each target column. If the columns are dynamic, the query is generated as a string with a 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 supports the PIVOT and UNPIVOT operators natively since 2005. The PIVOT column list is given as static bracketed values; for dynamic columns the query is built with dynamic SQL. For small summaries, conditional aggregation (CASE) is an alternative.","since":"SQL Server 2005","xnote":"The PIVOT/UNPIVOT operators arrived with 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 has no PIVOT operator; a cross tab is built in the $group stage with $sum + $cond (conditional aggregation) for each target column. For the reverse direction (UNPIVOT), fields are expanded into rows with $objectToArray and $unwind.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no pivot/cross tab.","note":"Redis has no concept of rotating rows and columns. Summaries/cross tabs are computed on the application side; frequently requested summaries can be precomputed and stored in Hash fields (one field per column). RediSearch FT.AGGREGATE can do grouped aggregation, but it is not a real 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 has no PIVOT operator; a cross summary is built with nested terms aggregations (outer key / inner key), the result comes back as nested buckets rather than columns, and is turned into a table on the client. A transform job can produce a grouped, pivot-like target index.","danger":"safe"}}},{"id":"coalesce-null","title":"NULL handling (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":"For handling NULL (unknown value): COALESCE returns the first non-NULL value in the list (standard, multiple arguments). Oracle's NVL(a,b) is the two-argument equivalent; NVL2 returns different values depending on whether the value is NULL or not. NULLIF(a,b) returns NULL if they are equal. Always test for NULL with IS NULL / IS NOT NULL (= NULL does not work).","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 offers standard COALESCE, NULLIF and IS NULL/IS NOT NULL; use COALESCE instead of Oracle's NVL. Use IS, not =, for NULL comparisons; if two NULLs must be treated as equal, the 'IS NOT DISTINCT FROM' operator does the job.","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 support standard COALESCE and NULLIF; IFNULL(a,b) is a two-argument shortcut (similar to Oracle NVL). The <=> operator provides NULL-safe equality (treats two NULLs as equal). NULL is tested with 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 offers standard COALESCE (multiple arguments, with data type promotion) and the two-argument ISNULL(a,b); the two differ in subtle ways (COALESCE is standard and more flexible with type precedence). NULLIF and IS NULL are also supported. ISNULL is the equivalent of Oracle NVL (the names are similar).","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":"In MongoDB aggregation the COALESCE equivalent is the $ifNull operator (multiple arguments, returns the first that is not null/missing). In a query, { field: null } matches both documents whose value is null and documents that lack the field entirely; make the distinction explicit with { field: { $type: 'null' } } or $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 has no NULL in the SQL sense; the equivalent is a missing key or a missing Hash field, and commands return nil in that case. Default-value logic (like COALESCE) is applied on the application side with a nil check. Field existence is checked with 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":"In Elasticsearch null or missing fields are checked with the 'exists' query (must_not exists = documents without the field). For a COALESCE-like default, define 'null_value' in the mapping, or assign a value to missing entries with the 'missing' parameter in aggregations. Defaults can also be produced with 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":"The Task API returns currently running tasks (search, reindex, bulk). For momentary CPU load, hot_threads shows the most active threads.","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":"Instead of blocking, slowness usually comes from long-running tasks (large searches, reindex). Assess duration with the running_time_in_nanos field.","danger":"safe"},"kill-session":{"command":"POST _tasks/<task_id>/_cancel\n# task_id format: nodeId:taskNumber (e.g. oTUltX4IQMOUUVeiohTt8A:12345)","note":"Take the task_id value from the GET _tasks output. Only cancellable (cancellable:true) tasks can be stopped.","danger":"danger"},"connection-count":{"command":"GET _nodes/stats/http","note":"current_open gives the currently open HTTP connections, total_opened the total connections since node startup. The upper limit on HTTP connections is determined by operating system/socket limits, not 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 gives the total disk usage of the whole cluster via indices.store.size_in_bytes. _cat/indices lists per-index size in human-readable form.","danger":"safe"},"largest-tables":{"command":"GET _cat/indices?v&s=store.size:desc&h=index,docs.count,store.size","note":"In Elasticsearch the equivalent of a \"table\" is an index. s=store.size:desc sorts the largest indices to the top.","danger":"safe"},"free-space":{"command":"GET _cat/allocation?v","note":"The disk.used, disk.avail and disk.percent columns give disk usage per node. When the watermark thresholds (85%/90%/95%) are exceeded, shard allocation stops.","danger":"safe"},"row-count":{"command":"GET <index>/_count\n# Shortcut: GET _cat/count/<index>?v","note":"_count returns the document count quickly. For a filtered count, pass a query in the body: 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 has no central slow query table; the search slowlog writes queries exceeding the threshold to the node log files. Read the logs there.","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 breaks down the time of each query component and aggregation step. _validate/query?explain shows the Lucene equivalent of the query and why it was rejected.","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":"Instead of unused field indexes, identify indices with a low search count (search.query_total) or that are no longer queried via _stats/search, and close/delete them.","danger":"safe"},"cache-hit-ratio":{"command":"GET _nodes/stats/indices/query_cache\n# Ratio = hit_count / (hit_count + miss_count)","note":"query_cache caches results in filter context. Examine request_cache separately with 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":"In Elasticsearch the equivalent of a logical dump is the snapshot API. wait_for_completion=true waits until the operation finishes; for large snapshots leave it false and query the status separately.","danger":"safe"},"restore-dump":{"command":"POST _snapshot/<repo>/<snapshot_1>/_restore\n{\n  \"indices\": \"*\",\n  \"rename_pattern\": \"(.+)\",\n  \"rename_replacement\": \"restored_$1\"\n}","note":"To restore an existing index, close it first (POST <index>/_close) or restore under a new name with rename_pattern. You cannot restore over an open index.","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":"The state field takes the values SUCCESS / IN_PROGRESS / PARTIAL / FAILED. _status gives detailed per-shard progress.","danger":"safe"},"list-users":{"command":"GET _security/user","note":"X-Pack Security must be enabled. For a single user: GET _security/user/<user>. Built-in users also appear in the list.","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":"Authorization is role-based: first define the role containing index/cluster privileges, then assign it to the user. The password must be at least 6 characters.","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":"See a user's roles with _security/user and the privileges a role contains with _security/role. _has_privileges verifies whether a specific action is permitted.","danger":"safe"},"change-password":{"command":"POST _security/user/<user>/_password\n{\n  \"password\": \"NewStrongPassword123\"\n}","note":"To change your own password, POST _security/user/_password (without a username) is sufficient. For the built-in elastic user, the bin/elasticsearch-reset-password tool can also be used.","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 is segment-based; no separate statistics update step is needed. _refresh makes not-yet-searchable documents searchable immediately (by default this happens automatically every 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 is used for mapping changes or to rebuild a broken index. For a seamless switch, point an alias at the new 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":"Deleted/updated documents remain in segments as tombstones; forcemerge reclaims this space. It is expensive on actively written indices; run it during off-peak hours.","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":"The GET / root endpoint returns the version, Lucene version and cluster name. jvm.uptime_in_millis is the node uptime.","danger":"safe"},"show-config-param":{"command":"GET _cluster/settings?include_defaults=true&flat_settings=true","note":"include_defaults=true also shows default values. Persistent and transient settings are returned in separate sections; for node-level settings use GET _nodes/settings.","danger":"safe"},"list-tables":{"command":"GET _cat/indices?v","note":"In Elasticsearch the equivalent of a relational table is an index. Gives a summary with the health, status, docs.count and store.size columns. The default output lists open and closed indices but not hidden ones (e.g. .ds- backing indices, system indices); add &expand_wildcards=all to include them, or &expand_wildcards=open for open indices only.","danger":"safe"},"describe-table":{"command":"GET <index>/_mapping","note":"The mapping gives the field types and analysis settings; it is the equivalent of DESCRIBE TABLE. For a single field: GET <index>/_mapping/field/<field>.","danger":"safe"},"list-indexes":{"command":"GET <index>/_mapping","note":"In ES fields are indexed by default; there is no separate secondary index table. Fields with \"index\": false in the mapping are not searchable. For indices (tables) across the 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":"After an index is created, the primary shard count cannot be changed in place (the replica count is dynamic); if needed, produce a new index with the _split / _shrink APIs or with _reindex. The type of an existing field cannot be changed; new fields can be added.","danger":"caution"},"show-ddl":{"command":"GET <index>","note":"A single call returns settings, mappings and aliases; together these three are the index's \"DDL\" equivalent. You can reuse the same output as the body of a new 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 summarizes shard health as green/yellow/red; yellow = replica unassigned. active_shards_percent gives the completion ratio.","danger":"safe"},"list-replicas":{"command":"GET _cat/shards?v&h=index,shard,prirep,state,node\n# For node roles: GET _cat/nodes?v","note":"The prirep column takes the value p (primary) or r (replica); it shows how shards are distributed across nodes and their state (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":"On node failure, Elasticsearch automatically re-elects the master and reassigns primary shards. Manual takeover is needed only to detach a CCR follower index from its leader and make it writable.","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":"A write to a single document is atomic and versioned (optimistic concurrency: if_seq_no / if_primary_term). There is no concept of open transaction tracking.","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":"It is REST-based; instead of a separate CLI client, curl or Kibana Dev Tools is used. -k skips self-signed TLS certificate verification (use a CA in production).","danger":"safe"},"list-databases":{"command":"GET _cat/indices?v\n# For aliases (logical views): GET _aliases","note":"Elasticsearch has no separate \"database\" layer; the closest concepts are indices and the aliases that group them. For 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":"There is no USE <db>-style command; the target index/alias is given in the path of every request. To search multiple indices, separate them with commas: 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","severity":"caution","meaning":"A row being inserted or updated violates a UNIQUE or PRIMARY KEY constraint. In other words, the same value already exists.","cause":"Re-inserting a row with the same key value, entering duplicate data into a column that must be unique, or processing the same record twice during replication/re-runs.","fix":"Find the violated column from the constraint name in the error message: SELECT column_name FROM all_cons_columns WHERE constraint_name = 'CONSTRAINT_NAME'. Use GROUP BY ... HAVING COUNT(*) > 1 to find duplicate data. In the application, use UPDATE or MERGE instead of INSERT for existing records. If the sequence value has fallen behind, advance it with 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","severity":"caution","meaning":"The resource (row or table) to be locked is locked by another session and could not be acquired immediately because NOWAIT was specified or DDL_LOCK_TIMEOUT expired.","cause":"Usually a DDL (ALTER TABLE, DROP, CREATE INDEX) attempted while the table is locked by another transaction; or a locked row encountered with SELECT ... FOR UPDATE NOWAIT.","fix":"Find the session holding the lock: SELECT * FROM v$locked_object. Join v$session and v$lock to identify the blocking session. Wait for the transaction to finish or, if necessary, terminate it with ALTER SYSTEM KILL SESSION. For DDL you can set a wait time: ALTER SESSION SET DDL_LOCK_TIMEOUT = 60; Run DDL in quiet maintenance windows.","tags":["lock","locked","ddl","nowait","busy","resource busy","timeout"]},{"code":"ORA-00060","title":"Deadlock detected while waiting for resource","severity":"danger","meaning":"Two or more sessions are deadlocked, each waiting for a resource held by the other. Oracle detects the cycle, picks a victim and rolls back that session's statement.","cause":"Row locks acquired in different orders, the same rows updated crosswise by different transactions, table locks on parent table updates caused by unindexed foreign keys, or concurrent DML on a bitmap index.","fix":"Examine the deadlock trace (graph) file referenced in the alert log to find the conflicting SQL statements and rows. In the application, always access tables/rows in the same order. Index foreign key columns (unindexed FKs are a classic deadlock cause). Keep transactions short and review commit frequency. Catch the deadlocked transaction in the application and retry it.","tags":["deadlock","deadlock detected","lock","locking","tx","transaction"]},{"code":"ORA-01017","title":"Invalid username/password; logon denied","severity":"caution","meaning":"Logon was denied because the username or password supplied when connecting to the database is incorrect.","cause":"Wrong password, wrong username, the case-sensitive password setting (SEC_CASE_SENSITIVE_LOGON) differing from what is expected, a changed password, or stale credentials in the application connection string.","fix":"Verify the username and password. If necessary, reset the password as DBA: ALTER USER app_user IDENTIFIED BY new_password. Check that the user exists and is not locked: SELECT username, account_status FROM dba_users. With 12c+ password hash versions, 11g clients may have trouble connecting; review the SQLNET.ALLOWED_LOGON_VERSION_SERVER setting.","tags":["auth","credentials","password","password","logon","login","privilege"]},{"code":"ORA-01555","title":"Snapshot too old: rollback segment number too small","severity":"danger","meaning":"A long-running query could not access the old undo data needed to build the read-consistent image as of its start time, because that undo information was overwritten.","cause":"A small undo tablespace, a short UNDO_RETENTION, long-running queries during very heavy concurrent DML, or a loop that commits on the same table inside a query (fetch across commit).","fix":"Increase UNDO_RETENTION and enlarge the undo tablespace; where possible leave it under automatic management (AUTOEXTEND on). Split long queries into smaller pieces or run them off-peak. Consider RETENTION GUARANTEE: ALTER TABLESPACE undotbs1 RETENTION GUARANTEE. Fix application logic that commits inside a loop.","tags":["undo","snapshot","rollback","read consistency","consistency","long query"]},{"code":"ORA-01652","title":"Unable to extend temp segment by N in tablespace","severity":"danger","meaning":"Oracle could not find enough free space in the tablespace to extend a temporary (temp) segment. Usually occurs during sorts, hash joins or global temporary table operations.","cause":"The TEMP tablespace is full; large ORDER BY, GROUP BY, DISTINCT, hash join or index creation operations do not fit in PGA memory and spill to disk; datafiles have AUTOEXTEND off, or disk space runs out.","fix":"Monitor TEMP usage: SELECT * FROM v$sort_usage / v$tempseg_usage. Add a file to or enlarge the TEMP tablespace: ALTER TABLESPACE temp ADD TEMPFILE '...' SIZE 2G AUTOEXTEND ON. If a permanent tablespace is full, add a datafile. Reduce disk sorts by optimizing the query (appropriate index, larger PGA_AGGREGATE_TARGET).","tags":["temp","tablespace","free space","space","sort","sorting","extend"]},{"code":"ORA-04031","title":"Unable to allocate N bytes of shared memory","severity":"danger","meaning":"Oracle could not allocate contiguous memory of the requested size in a shared memory pool (usually the shared pool or large pool). An SGA component is under memory pressure.","cause":"Shared pool fragmentation due to many unique SQL statements without bind variables (full of literals), insufficient SHARED_POOL_SIZE, too many concurrent connections, large PL/SQL packages, or incorrect ASMM/AMM sizing.","fix":"Check which pool the error message refers to (shared pool, large pool, etc.). Short term: ALTER SYSTEM FLUSH SHARED_POOL (caution: in production this forces all SQL to be re-parsed, i.e. a temporary hard parse storm, and does not fix fragmentation permanently). Permanent fix: enforce bind variable usage (including CURSOR_SHARING), increase SGA_TARGET / SHARED_POOL_SIZE / LARGE_POOL_SIZE. Pin frequently used packages in memory with DBMS_SHARED_POOL.KEEP. Examine memory distribution with V$SGASTAT.","tags":["memory","shared memory","shared pool","sga","ora-4031","bind"]},{"code":"ORA-12154","title":"TNS: could not resolve the connect identifier specified","severity":"caution","meaning":"The service name (connect identifier / TNS alias) used in the connection could not be resolved. Oracle Net cannot map the given name to server/service information.","cause":"The alias is missing from tnsnames.ora, the TNS_ADMIN environment variable points to the wrong directory, the alias name is misspelled, EZConnect/LDAP naming configuration is missing, or the NAMES.DIRECTORY_PATH order in sqlnet.ora is wrong.","fix":"Verify that the alias you use is defined in tnsnames.ora. Test the connection: tnsping service_name. Make sure TNS_ADMIN points to the correct folder. Alternatively try EZConnect: app_user/password@host:1521/service_name. Check the NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT) line in 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","severity":"caution","meaning":"The listener is running but does not recognize the requested service name; that is, the service is not registered with the listener.","cause":"The database instance is not fully open, the service has not yet registered with the listener (LREG in 12c+, PMON registration delay in 11g and earlier), the wrong SERVICE_NAME is used, or SID and service name are confused.","fix":"View the services known to the listener: lsnrctl status and lsnrctl services. Verify that the database is OPEN. Trigger service registration: ALTER SYSTEM REGISTER in SQL. Check the LOCAL_LISTENER parameter and correct SERVICE_NAME usage. In a pluggable database, make sure the PDB is OPEN.","tags":["tns","listener","service","service_name","connection","network","networking"]},{"code":"ORA-00942","title":"Table or view does not exist","severity":"caution","meaning":"The table or view referenced in the query does not exist or is not visible to the accessing user.","cause":"The object really does not exist, it is in a different schema and no schema prefix was given, the user lacks privileges on the object (such as SELECT), a synonym is missing, or the object name is misspelled.","fix":"Check that the object exists and who owns it: SELECT owner, object_type FROM all_objects WHERE object_name = 'TABLE_NAME'. If it is in another schema, fully qualify it as schema.table or grant GRANT SELECT ON app_schema.my_table TO app_user. If needed, create a public/private synonym: CREATE SYNONYM my_table FOR app_schema.my_table. Watch for case and quoted identifiers.","tags":["table","missing table","view","missing view","grant","privilege","synonym","alias"]},{"code":"ORA-01031","title":"Insufficient privileges","severity":"caution","meaning":"The user does not have the privilege (system or object privilege) required for the operation being attempted.","cause":"The required system or object privilege has not been granted, the privilege comes only through a role (roles are not effective inside definer-rights PL/SQL blocks), an administrative operation was attempted without SYSDBA/SYSOPER, or a direct grant is missing for definer-rights procedures.","fix":"Grant the missing privilege, for example: GRANT CREATE TABLE TO app_user or GRANT SELECT ON app_schema.my_table TO app_user. Remember that for named PL/SQL objects privileges must be granted directly, not through a role. For operations requiring SYSDBA, connect with the correct credentials. Check current privileges: 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","severity":"danger","meaning":"The archiver (ARCH) process cannot archive redo logs; usually the archive destination (FRA or archive log destination) is full. The database suspends new transactions.","cause":"The Flash Recovery Area (DB_RECOVERY_FILE_DEST) or archive log destination is full, disk space has run out, RMAN backups are not deleting archive logs, or the DB_RECOVERY_FILE_DEST_SIZE limit has been reached.","fix":"Free up space: back up old archive logs with RMAN to a destination outside the FRA and delete them: RMAN> BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT (writing the backup to the FRA while it is full fails). With Data Guard, do not delete archives not yet shipped to or applied on the standby; otherwise a gap forms on the standby (CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY). View FRA usage: SELECT * FROM v$recovery_file_dest / v$flash_recovery_area_usage. Temporarily increase DB_RECOVERY_FILE_DEST_SIZE or move the destination to a larger disk. Check free space at the disk level. Establish a regular archive cleanup policy.","tags":["archive","archivelog","archiver","fra","disk","space","redo"]},{"code":"ORA-01000","title":"Maximum open cursors exceeded","severity":"caution","meaning":"A session opened more cursors than allowed by the OPEN_CURSORS parameter. No new cursor can be opened.","cause":"The application does not close the Statement/ResultSet objects it opens (cursor leak), OPEN_CURSORS is set too low for the workload, or new prepared statements are continually prepared in a loop and never closed.","fix":"Monitor open cursors: SELECT * FROM v$open_cursor WHERE sid = ...  and see the per-session count via v$sesstat. The real fix is to close every Statement/ResultSet in a finally block in the application (eliminating the cursor leak). As a workaround, increase OPEN_CURSORS: ALTER SYSTEM SET OPEN_CURSORS = 1000. Review the JDBC statement cache size.","tags":["cursor","open cursor","open_cursors","leak","cursor leak","jdbc"]},{"code":"ORA-00018","title":"Maximum number of sessions exceeded","severity":"danger","meaning":"The database has reached the maximum number of sessions allowed by the SESSIONS parameter; no new session can be opened.","cause":"SESSIONS/PROCESSES set below the workload's needs, an oversized connection pool configuration, a connection leak, or accumulation of sessions that are never closed.","fix":"View the current session count: SELECT COUNT(*) FROM v$session. Check the limits: SELECT * FROM v$resource_limit WHERE resource_name IN ('sessions','processes'). Increase PROCESSES and, indirectly, SESSIONS (via SPFILE, restart required): ALTER SYSTEM SET PROCESSES = 500 SCOPE=SPFILE. Review connection pool sizes and leaks in the application.","tags":["session","session limit","sessions","processes","connection","connections","limit"]},{"code":"ORA-28000","title":"The account is locked","severity":"caution","meaning":"Logon fails because the database user account being connected to is locked.","cause":"The FAILED_LOGIN_ATTEMPTS limit in the profile was exceeded (repeated wrong passwords), or the account was locked manually by a DBA (ALTER USER ... ACCOUNT LOCK). A failed-login lock is released automatically after PASSWORD_LOCK_TIME. Note: PASSWORD_LIFE_TIME expiry does not lock the account; the account moves to EXPIRED / EXPIRED(GRACE) and returns ORA-28001/ORA-28002.","fix":"Check the account status: SELECT username, account_status, lock_date FROM dba_users WHERE username = 'APP_USER'. Unlock it: ALTER USER app_user ACCOUNT UNLOCK. To prevent recurrence, find the source of the failed logins and, if necessary, review FAILED_LOGIN_ATTEMPTS in the profile. If the password has expired, assign a new password.","tags":["account","user account","locked","account locked","auth","password","profile"]},{"code":"ORA-01578","title":"Oracle data block corrupted (file #, block #)","severity":"danger","meaning":"A data block in the specified datafile is corrupted; Oracle reports that the block failed its consistency check (checksum). This is physical corruption.","cause":"Disk/hardware failure, storage layer errors, operating system or controller problems, partially written blocks after a power loss, or a corrupt backup/restore.","fix":"Confirm the corruption and its extent: RMAN> VALIDATE DATABASE / VALIDATE CHECK LOGICAL DATAFILE n; also run DBVERIFY (dbv) and check v$database_block_corruption. If a backup exists, recover at the block level: RMAN> RECOVER DATAFILE n BLOCK m; or, for all listed blocks, RMAN> RECOVER CORRUPTION LIST; (the older BLOCKRECOVER command has been deprecated since 11g). If it is an index block, rebuild the index. Always check hardware/disk health; the root cause is usually storage.","tags":["corruption","block corruption","block","corrupt block","datafile","rman","disk"]},{"code":"ORA-01722","title":"Invalid number","severity":"safe","meaning":"A character string that cannot be converted to a number was used where a numeric context is required. An implicit or explicit conversion failed.","cause":"Comparing a numeric column with text (e.g. WHERE num_col = 'ABC'), inserting a value containing letters into a numeric field, an unsuitable format for TO_NUMBER, or a decimal/group separator mismatch (comma vs period).","fix":"Find the expression with the failing conversion; usually a WHERE condition or an INSERT value. Ensure type compatibility in text/number comparisons and use explicit conversion where needed: TO_NUMBER(my_column) or TO_CHAR for the text side. Prevent non-numeric input into numeric columns at the application layer. Watch the NLS_NUMERIC_CHARACTERS setting.","tags":["number","numeric","conversion","type conversion","to_number","datatype","data type"]},{"code":"ORA-02049","title":"Timeout: distributed transaction waiting for lock","severity":"danger","meaning":"A distributed transaction exceeded DISTRIBUTED_LOCK_TIMEOUT while waiting for a lock on the remote database. The transaction timed out.","cause":"A transaction running over a database link waits for a locked row on the remote side; long transactions in the remote session, network latency, or conflicting locks held on both ends.","fix":"Find and resolve the blocking lock and session on the remote database (v$lock, v$session). Keep distributed transactions short and acquire the required locks as late as possible. If necessary, increase DISTRIBUTED_LOCK_TIMEOUT for the workload (SPFILE, restart required). For in-doubt distributed transactions, examine the DBA_2PC_PENDING view and resolve them manually if necessary.","tags":["distributed","distributed transaction","dblink","lock","lock wait","timeout","2pc"]},{"code":"enq: TX - row lock contention","title":"Wait: session blocked on a row-level lock held by another transaction","severity":"caution","meaning":"A session is waiting for a row lock held by another transaction to be released. This is the classic application-level lock wait event.","cause":"Concurrent transactions trying to update the same row, transactions left uncommitted for a long time, broad locking caused by unindexed foreign keys, or concentration on a sequential key (hot block).","fix":"Find the blocking and blocked sessions: SELECT * FROM v$session WHERE blocking_session IS NOT NULL; and correlate with v$lock. If long transactions are the root cause, review the commit strategy. Index foreign key columns. Rethink the design (sequence, hash) for hot rows. Terminate the blocking session when necessary.","tags":["wait","wait event","enq","row lock","row lock wait","tx","contention","lock"]},{"code":"log file sync","title":"Wait: session waiting for LGWR to flush redo to disk on commit","severity":"caution","meaning":"When a session COMMITs, it waits for LGWR to write (flush) the redo records to the redo log files. If this wait is high, there is commit latency.","cause":"Slow redo log disks (high I/O latency), an application that commits too frequently (row-by-row commit), CPU pressure on the LGWR process, or very high redo volume.","fix":"Measure the average wait time with AWR/Statspack. Move the redo log files to fast, low-latency disks (SSD). Reduce unnecessary commit frequency in the application and group batch operations. If necessary, evaluate the COMMIT_LOGGING/COMMIT_WAIT (commit_write) options against business requirements; WARNING: NOWAIT/BATCH reduce durability, and transactions that appear committed can be lost in a crash. Use them only at session or statement level for work where loss is tolerable; do not enable them system-wide. Examine LGWR and storage latency together.","tags":["wait","wait event","log file sync","commit","lgwr","redo","latency"]},{"code":"db file sequential read","title":"Wait: single-block physical read, typically an index or table lookup","severity":"caution","meaning":"A session waits for a single block to be read from disk. Usually seen with index access or single-block reads by ROWID; normally an expected event.","cause":"Blocks not in the buffer cache being read from disk, non-selective or wrong index usage, an insufficient buffer cache, or a slow storage layer.","fix":"If high, find the SQL statements doing the most reads (AWR Top SQL, v$sql). Improve queries with appropriate indexes and correct access paths, and reduce unnecessary nested loops. Evaluate the buffer cache size (DB_CACHE_SIZE). Check storage I/O latency. This event is often normal; if abnormally high, focus on SQL tuning.","tags":["wait","wait event","db file sequential read","io","disk io","index","index access","single block"]},{"code":"db file scattered read","title":"Wait: multi-block physical read, typically a full table or index fast full scan","severity":"caution","meaning":"A session waits for multiple blocks (multiblock) to be read from disk. Usually occurs during a full table scan or index fast full scan.","cause":"Full table scans due to a missing suitable index, a wrong plan due to stale statistics, scans of very large tables, or the chosen access path falling back to a table scan.","fix":"Identify the SQL statements doing full scans and add the required indexes or ensure the existing index is used. Update statistics: DBMS_STATS.GATHER_TABLE_STATS. If a large scan is genuinely needed, consider parallel query or partitioning. Review DB_FILE_MULTIBLOCK_READ_COUNT and storage throughput.","tags":["wait","wait event","db file scattered read","full scan","full table scan","io","disk io","multiblock"]},{"code":"buffer busy waits","title":"Wait: contention for a buffer already being read or modified by another session","severity":"caution","meaning":"A session is waiting to access a buffer block that another session is reading or modifying at the same time. This is in-memory block contention.","cause":"Concentration on hot blocks, contention on the right edge of indexes with monotonically increasing keys, insufficient freelists/non-ASSM segments, or heavy concurrent DML on the same block.","fix":"Find which segment and block class are affected (v$session, v$segment_statistics, ASH). For hot index blocks, consider a reverse key or hash-partitioned index. Use ASSM (Automatic Segment Space Management). Under high concurrency, increase INITRANS/PCTFREE. Spread the application access pattern.","tags":["wait","wait event","buffer busy","hot block","hot blocks","contention","block contention"]},{"code":"gc buffer busy","title":"Wait (RAC): contention for a cache-fusion block being transferred between instances","severity":"caution","meaning":"In a RAC environment, a session is waiting to access a global block that is being transferred via cache fusion to/from another instance or is held busy by that instance.","cause":"The same hot blocks shared between RAC nodes (cross-instance hot block), interconnect latency/bottleneck, cross-node contention on indexes with sequential keys, or unbalanced workload distribution.","fix":"Find the affected segments with v$segment_statistics and GV$ views. Check interconnect health and latency (private, fast network). Use reverse key or hash partitioning for hot indexes. Reduce cross-node contention on the same blocks by separating the workload across nodes by service.","tags":["wait","wait event","rac","gc buffer busy","cache fusion","interconnect","global"]},{"code":"library cache lock","title":"Wait: contention on a library cache object handle (parse/DDL serialization)","severity":"caution","meaning":"A session is waiting for a lock on an object in the library cache (SQL, PL/SQL, table definition). Usually caused by serialization of parse and DDL operations.","cause":"DDL (ALTER/CREATE) on heavily used objects, PL/SQL objects frequently recompiled (invalidation), excessive hard parsing, invalidation of dependent objects, or heavy concurrent connection storms.","fix":"Find the blocking session and object (v$session, ASH, library cache lock diagnostics if needed). Avoid running DDL on heavily used objects during peak hours. Reduce dependency chains and prevent unnecessary recompilations. Lower hard parsing by using bind variables. Smooth out connection storms with a connection pool.","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":"The inserted or updated row violates a UNIQUE constraint or the PRIMARY KEY; the same value already exists in the table.","cause":"Re-inserting with the same unique key, entering duplicate data into a unique column, the sequence value falling behind because of manual inserts, or two concurrent requests inserting the same key.","fix":"The constraint name and key value in the error detail show which row conflicts. To update the existing row, use INSERT ... ON CONFLICT (my_column) DO UPDATE or DO NOTHING. To find duplicates: SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1. If the sequence fell behind: 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":"A FOREIGN KEY constraint was violated. The parent row referenced by the inserted/updated row does not exist, or the parent row being deleted still has dependent child rows.","cause":"An INSERT/UPDATE referencing a non-existent parent key, an attempt to delete/update a parent row that is still referenced, or data loaded in the wrong order (child first, then parent).","fix":"Insert the parent row first, then the child row. If cascading behavior is needed on delete, define the constraint with ON DELETE CASCADE or ON DELETE SET NULL. In bulk loads, load data in parent-child order. Find the referencing rows: 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":"An attempt was made to insert NULL into a column with a NOT NULL constraint, or to set that column to NULL.","cause":"Omitting a required column from the INSERT, explicitly assigning NULL, the column having no DEFAULT, or an empty/missing value coming from the application.","fix":"Supply a valid value for the column or define a sensible DEFAULT on it: ALTER TABLE my_table ALTER COLUMN my_column SET DEFAULT value. If the column does not really need to be mandatory, drop the constraint: ALTER TABLE my_table ALTER COLUMN my_column DROP NOT NULL. Add missing-data validation in the application layer.","tags":["not null","null","constraint","constraint","required","23502","insert"]},{"code":"40P01","title":"deadlock_detected","severity":"danger","meaning":"Two or more transactions entered a deadlock by each waiting for locks held by the other. PostgreSQL detects the cycle and cancels one of the transactions.","cause":"Locks acquired in different orders, cross updates of the same rows, long transactions, or conflicting locks taken during foreign key checks.","fix":"The deadlock detail in the server log shows the conflicting queries. Access tables/rows in the same order in all transactions. Keep transactions short. Catch the cancelled transaction in the application and retry it. Join pg_locks with pg_stat_activity to examine locks. If needed, make the lock order explicit with SELECT ... FOR UPDATE.","tags":["deadlock","deadlock","lock","lock","40P01","tx","transaction"]},{"code":"40001","title":"serialization_failure","severity":"caution","meaning":"Under the SERIALIZABLE or REPEATABLE READ isolation level, the transaction could not be serialized because it conflicted with concurrent changes, and it was cancelled. This is expected behavior that protects data consistency.","cause":"Transactions accessing the same data concurrently cannot be arranged in a serializable order, write conflicts under high concurrency, or dangerous dependencies detected by SSI (Serializable Snapshot Isolation).","fix":"This is not a bug but an expected signal: retry the transaction as is. Add automatic retry logic to the application. To reduce conflicts, shorten transactions and use a lower isolation level (READ COMMITTED) if it is not really needed. Design to reduce contention on hot rows.","tags":["serialization","serialization","isolation","isolation","retry","retry","40001","mvcc"]},{"code":"53300","title":"too_many_connections","severity":"danger","meaning":"The server reached the maximum number of concurrent connections allowed by max_connections; new connections cannot be accepted.","cause":"No connection pool or a wrongly sized one, connection leaks, applications opening many short-lived connections, or max_connections set below the workload.","fix":"Identify the connection sources: SELECT usename, state, COUNT(*) FROM pg_stat_activity GROUP BY 1,2. Terminate long-idle client connections in a targeted way (unfiltered mass termination affects every application and pool): 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(). The real fix is a connection pool such as PgBouncer. Increasing max_connections costs memory; prefer pooling. Connections reserved for superusers keep administrative access available.","tags":["connection","connection","max_connections","too many","53300","pool","pool","pgbouncer"]},{"code":"53100","title":"disk_full","severity":"danger","meaning":"The server could not write to a file because the disk is full. No space is left on the disk holding the data directory, WAL or temporary files.","cause":"Data growth, uncontrolled WAL accumulation (archiving or a replication slot falling behind), large temporary files (huge sorts), bloated log files, or table/index bloat.","fix":"Inspect disk usage and clean up unnecessary files. Check WAL accumulation: pg_stat_replication and pg_replication_slots (drop unused slots; lagging ones retain WAL). Do not run VACUUM FULL or REINDEX while the disk is full: because they rewrite the table they need extra space equal to the table size and can make things worse; plain VACUUM also (almost) never returns space to the operating system. NEVER delete files in the pg_wal directory by hand. First free space by resolving the WAL/slot, log and temp sources; review the placement of logs and temp files. As a permanent fix, increase disk capacity and set up monitoring/alerting.","tags":["disk","space","space","full","full","wal","53100","vacuum"]},{"code":"57014","title":"query_canceled","severity":"safe","meaning":"The query was cancelled. Usually caused by statement_timeout expiring or a cancel from the user or client (pg_cancel_backend, driver cancel).","cause":"statement_timeout exceeded, a cancel with pg_cancel_backend, or a cancel request sent by the application/client. Similar situations have different codes: lock_timeout expiry returns 55P03, idle_in_transaction_session_timeout 25P03 (FATAL), and a cancel caused by a replication conflict on a hot standby 40001 (40P01 in some cases).","fix":"If the query really takes long, tune it (EXPLAIN ANALYZE, indexes, plan). If the timeout is too low, adjust statement_timeout for the relevant session/role. Deliberate cancels need no action. For cancels caused by locks and standby conflicts, see the 55P03 and 40001 entries.","tags":["cancel","cancel","timeout","timeout","statement_timeout","57014","standby"]},{"code":"42P01","title":"undefined_table","severity":"caution","meaning":"The table (or view) referenced in the query does not exist or cannot be found in the search_path.","cause":"The table really does not exist, it is in another schema that is not in the search_path, a typo/case error in the table name, a case mismatch with quoted identifiers, or the object has not been created yet or was dropped.","fix":"Verify the table exists and its schema: SELECT schemaname, tablename FROM pg_tables WHERE tablename = 'my_table'. If it is in another schema, qualify it as schema.table or set the search_path: SET search_path TO app_schema, public. Remember that unquoted identifiers are folded to lowercase; if quotes were used at creation, reference it the same way.","tags":["table","table","undefined","42P01","search_path","schema","schema"]},{"code":"42703","title":"undefined_column","severity":"caution","meaning":"The column referenced in the query does not exist in the table or in the current scope.","cause":"A typo in the column name, the column having been dropped/renamed, a reference to the wrong table, a quoted-identifier case mismatch, or ambiguous/incorrect qualification in a multi-table query.","fix":"List the columns: \\d my_table or SELECT column_name FROM information_schema.columns WHERE table_name = 'my_table'. Correct the name and the table, and qualify columns in JOINs as table.column. If quotes were used at creation, reference it with the same case.","tags":["column","column","undefined","42703","typo","typo","schema"]},{"code":"42501","title":"insufficient_privilege","severity":"caution","meaning":"The role does not have the privilege required for the operation it is attempting.","cause":"The required GRANT on the table/schema/database was not given, USAGE on the schema is missing, the object belongs to a different role, or row-level security (RLS) policies block access.","fix":"Grant the missing privilege: GRANT SELECT, INSERT ON my_table TO app_role; for the schema GRANT USAGE ON SCHEMA app_schema TO app_role. For bulk grants use GRANT ... ON ALL TABLES IN SCHEMA, and ALTER DEFAULT PRIVILEGES for future objects. View current privileges: \\dp my_table. If RLS is active, review the relevant policies.","tags":["privilege","privilege","permission","grant","42501","permission","rls"]},{"code":"28P01","title":"invalid_password","severity":"caution","meaning":"The password supplied during authentication is wrong; login was rejected for the role.","cause":"A wrong password, a changed password, stale credentials in the application connection string, or a mismatch with the authentication method expected in pg_hba.conf (scram-sha-256, md5).","fix":"Verify the password and reset it if needed: ALTER ROLE app_role WITH PASSWORD 'new_password'. Make sure the method in pg_hba.conf is compatible with the client (old clients can have problems with scram-sha-256). After changes, reload the configuration with SELECT pg_reload_conf().","tags":["auth","identity","password","password","28P01","scram","md5","hba"]},{"code":"08006","title":"connection_failure","severity":"danger","meaning":"The connection between client and server could not be established or was dropped unexpectedly.","cause":"The server not running or restarting, network/firewall problems, wrong host/port, a listen_addresses setting that blocks access, an idle connection dropped by the network, or an overloaded server.","fix":"Check that the server is up and listening on the correct host/port (pg_isready host port). Verify listen_addresses in postgresql.conf and the access rules in pg_hba.conf. Check network/firewall and DNS settings. Review TCP keepalive and timeout settings in the connection pool. Look for crash/restart traces in the server log.","tags":["connection","connection","network","network","08006","failure","disconnect"]},{"code":"55P03","title":"lock_not_available","severity":"caution","meaning":"The requested lock could not be acquired immediately. It usually happens when a lock is requested with NOWAIT while someone else holds it, or when lock_timeout expires.","cause":"The row/table being locked while using SELECT ... FOR UPDATE NOWAIT or LOCK TABLE ... NOWAIT, or lock_timeout expiring. Most of the time a long transaction is holding the lock.","fix":"Find the blocking session: join pg_locks and pg_stat_activity (waiting/blocking relationship). Finish the long transaction or, if necessary, terminate it with pg_terminate_backend(pid). Instead of NOWAIT, wait with a reasonable lock_timeout and add retry logic. Shorten transactions to reduce lock hold time.","tags":["lock","lock","nowait","lock_timeout","55P03","not available","busy"]},{"code":"54000","title":"program_limit_exceeded","severity":"caution","meaning":"A PostgreSQL implementation/format limit was exceeded; for example the maximum size of a row or field, the index row size, or the limit on the number of parameters/columns in a query.","cause":"Trying to store a very large value (e.g. a field over 1 GB), an indexed value exceeding the B-tree page limit (about 2704 bytes), or using too many columns or query parameters.","fix":"Identify the limit that was exceeded (the message usually says what). Review or split the structure for very large text/data. Index long text columns with a hash index or an expression/partial index instead of a B-tree. Reduce the number of parameters/columns in the query or split the batch.","tags":["limit","limit","program_limit","54000","index size","size","row size"]},{"code":"22001","title":"string_data_right_truncation","severity":"caution","meaning":"The value would have been truncated because the text length exceeds the defined length of the target column (e.g. VARCHAR(n)); PostgreSQL rejects it and raises an error.","cause":"Inserting or updating text longer than n into a VARCHAR(n)/CHAR(n) column, longer-than-expected data coming from the application, or a column length defined too narrowly for the need.","fix":"Shorten the value so that it fits the column, or increase the column length: ALTER TABLE my_table ALTER COLUMN my_column TYPE VARCHAR(500). If unlimited text is needed, use the TEXT type. Validate length in the application. If you want deliberate truncation, apply LEFT(value, n) explicitly.","tags":["truncation","truncation","varchar","string","text","22001","length","length"]},{"code":"25P02","title":"in_failed_sql_transaction","severity":"caution","meaning":"An error already occurred earlier in the transaction; no further commands can run in it. The transaction is in the aborted state and only ROLLBACK is accepted.","cause":"A previous statement in a transaction block failed (e.g. a constraint violation) and new commands keep being sent in the same transaction anyway. The application does not catch the error and carries on with the transaction.","fix":"Roll back the transaction: ROLLBACK, then start it again. If you want to isolate a block's error and continue, use SAVEPOINT and return to that point with ROLLBACK TO SAVEPOINT. In the application, catch each statement's error and manage the transaction properly (make the commit/rollback decision explicit).","tags":["transaction","transaction","aborted","aborted","25P02","rollback","savepoint"]},{"code":"53200","title":"out_of_memory","severity":"danger","meaning":"The server could not allocate the memory required for an operation. A backend process or the whole server is under memory pressure.","cause":"work_mem/maintenance_work_mem set too high and multiplied by high concurrency, too many hash/sort operations in a single query, an excessive number of connections, a memory leak, or pressure from the operating system OOM killer.","fix":"Check the memory context in the server log and traces of the OOM killer. Set work_mem sensibly, taking query and concurrency counts into account (it is allocated separately for each sort/hash). Limit the number of connections with pooling. Review the operating system memory/overcommit settings. Optimize heavy queries to lower their memory requirement.","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)","severity":"caution","meaning":"Authentication failed for the user name, password or the host you are connecting from; the login was rejected.","cause":"Wrong password, the user has no privileges from the host it connects from (user@host), the account does not exist, an authentication plugin mismatch (caching_sha2_password vs mysql_native_password) or stale credentials in the connection string.","fix":"Check the user and host match: SELECT user, host, plugin FROM mysql.user. Grant privileges for the correct host (e.g. 'app_user'@'%'). Reset the password: ALTER USER 'app_user'@'host' IDENTIFIED BY 'new_password'; (ALTER USER/GRANT take effect immediately; FLUSH PRIVILEGES is needed only if the grant tables were modified directly with DML). If you see 'using password: NO', the client is not sending a password at all. For plugin mismatches, review the authentication plugin: in MySQL 8.4 the mysql_native_password plugin is disabled by default (loaded only with --mysql-native-password=ON); old accounts using this plugin and old clients cannot connect. Migrate the account with ALTER USER ... IDENTIFIED WITH caching_sha2_password BY '...'; and upgrade the client driver.","tags":["auth","authentication","access denied","access","password","password","1045","privilege"]},{"code":"1062","title":"Duplicate entry for key","severity":"caution","meaning":"The inserted or updated row violates a UNIQUE index or the PRIMARY KEY; the same key value already exists.","cause":"A repeated INSERT with the same unique key, duplicate data entry, an AUTO_INCREMENT counter that has fallen behind, or existing duplicate rows when adding a unique index to an existing table.","fix":"The error message shows the conflicting key value and the index name. To update the existing row, consider INSERT ... ON DUPLICATE KEY UPDATE or INSERT IGNORE / REPLACE. Find duplicates: SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1. If you cannot add a unique index, clean up the duplicates first.","tags":["duplicate","duplicate entry","unique","unique key","1062","primary key","insert"]},{"code":"1213","title":"Deadlock found when trying to get lock; try restarting transaction","severity":"danger","meaning":"InnoDB detected a deadlock in which two or more transactions wait for each other's locks and broke the cycle by rolling back one of the transactions.","cause":"Row locks acquired in different orders, cross updates of the same rows, wide row ranges being locked (gap locks) due to missing indexes, or conflicts during foreign key checks.","fix":"View the latest deadlock details: SHOW ENGINE INNODB STATUS (LATEST DETECTED DEADLOCK section). Access rows/tables in the same order in all transactions and keep transactions short. Narrow the locked range by adding proper indexes. Catch the rolled-back transaction in the application and retry it (retry logic).","tags":["deadlock","deadlock detected","lock","row lock","innodb","1213","tx","transaction"]},{"code":"1205","title":"Lock wait timeout exceeded; try restarting transaction","severity":"danger","meaning":"A transaction waited for a required row lock for innodb_lock_wait_timeout seconds, could not acquire it and timed out.","cause":"Another transaction holding the lock for a long time without commit or rollback; idle open transactions, long-running bulk updates or contended hot rows.","fix":"Find the blocking transaction: join performance_schema.data_lock_waits with data_locks (information_schema.innodb_lock_waits/innodb_trx in older versions). Commit/roll back the blocking transaction or terminate it with KILL <trx_mysql_thread_id> if necessary. Keep transactions short and do not leave transactions idle (autocommit/application logic). If needed, tune innodb_lock_wait_timeout to the workload.","tags":["lock","lock wait","timeout","timed out","innodb","1205","wait","waiting"]},{"code":"1040","title":"Too many connections","severity":"danger","meaning":"The server reached the maximum number of concurrent connections allowed by max_connections; new connections are being rejected.","cause":"A wrongly sized connection pool, connection leaks, many short-lived connections, connections sleeping for a long time (Sleep) or a max_connections value too low for the workload.","fix":"Inspect connections: SHOW PROCESSLIST or SELECT * FROM performance_schema.processlist. Close unnecessary sleeping connections with KILL. Increase max_connections: SET GLOBAL max_connections = 500 (my.cnf to persist). The root fix is using a connection pool and closing idle connections with wait_timeout. One extra emergency admin connection is reserved for 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","severity":"safe","meaning":"The SQL statement has a syntax error; the server could not parse it. The message shows the text near the error.","cause":"A typo, a missing/extra comma or parenthesis, a reserved word used unquoted as a column/table name, syntax not supported by the MySQL version, or quoting/escaping errors.","fix":"The 'near ...' part of the message shows where the error starts; look immediately before it. Quote reserved words with backticks: `order`, `group`. Balance parentheses and commas. Verify that you are using syntax supported by your version. If you generate dynamic SQL, log and inspect the full resulting query.","tags":["syntax","syntax error","1064","parse","parsing","reserved word","reserved keyword"]},{"code":"1146","title":"Table doesn't exist","severity":"caution","meaning":"The table referenced in the query does not exist or cannot be found in the current database.","cause":"The table really does not exist, the wrong database is selected, a typo in the table name, table name case sensitivity on Linux (lower_case_table_names) or a missing/corrupted InnoDB table file (.ibd) (in 5.7 and earlier also the .frm definition file; in 8.0 definitions live in the data dictionary).","fix":"Verify the table: SHOW TABLES; and select the correct database: USE mydb. Watch out for case sensitivity (especially on Linux). Check the table name and schema with SELECT ... FROM information_schema.tables. If there is an InnoDB file problem, restore/repair may be required.","tags":["table","table missing","doesn't exist","not found","1146","schema","database"]},{"code":"1054","title":"Unknown column in field list","severity":"caution","meaning":"The column referenced in the query does not exist in the table or in the current scope.","cause":"A typo in the column name, the column has been dropped/renamed, a reference to the wrong table, ambiguous/wrong qualification in a JOIN, or a string value written without quotes and taken as a column (WHERE name = value instead of WHERE name = 'value').","fix":"List the columns: SHOW COLUMNS FROM my_table or DESCRIBE my_table. Fix the column name and qualify it as my_table.my_column in JOINs. Enclose string literals in single quotes; backticks are only for identifiers. You can check with 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","severity":"danger","meaning":"The client could not connect to the local MySQL server through the Unix socket. Usually the server is not running or the socket path is wrong.","cause":"The MySQL/MariaDB service is not running or has crashed, the socket file path (e.g. /var/run/mysqld/mysqld.sock) differs between client and server, permission problems, or the server has not started.","fix":"Check the service status and start it (systemctl status mysql / start mysql). Verify the correct socket path: SHOW VARIABLES LIKE 'socket' and the socket setting in my.cnf. If the path differs, specify it explicitly with --socket or use TCP: mysql -h 127.0.0.1 -P 3306. Check the server logs for startup/crash errors.","tags":["connection","connect","socket","unix socket","2002","server","local","localhost"]},{"code":"2003","title":"Can't connect to MySQL server (TCP/IP)","severity":"danger","meaning":"The client could not connect to the MySQL server over TCP/IP on the specified host and port.","cause":"The server is not running, wrong host/port, a firewall blocking port 3306, the server bound only to localhost (bind-address = 127.0.0.1) or skip-networking enabled.","fix":"Check that the server is up and listening on the correct port. For remote access, set bind-address = 0.0.0.0 (or the appropriate IP) in my.cnf and make sure skip-networking is off. Open port 3306 in the firewall. Verify host/port: mysql -h host -P 3306 -u app_user -p. Make sure the user has privileges from the remote host.","tags":["connection","connect","tcp","port","2003","network","firewall","bind-address"]},{"code":"2006","title":"MySQL server has gone away","severity":"caution","meaning":"The connection between client and server was lost during a command. The server may have closed the connection or rejected the packet.","cause":"An idle connection closed because wait_timeout/interactive_timeout expired, a query/data larger than max_allowed_packet, a server restart or crash, or a network outage.","fix":"For idle connection issues, use validation in the connection pool and review wait_timeout. If you send large data, increase max_allowed_packet (e.g. SET GLOBAL max_allowed_packet = 64*1024*1024, my.cnf to persist). Check the server logs for restarts/crashes and OOM traces. Add reconnect logic in the application for dropped connections.","tags":["connection","connection lost","gone away","disconnect","2006","timeout","max_allowed_packet"]},{"code":"1114","title":"The table is full","severity":"danger","meaning":"No more data can be written to the table because a storage limit or the end of available space was reached.","cause":"Disk full, the max_heap_table_size limit reached for MEMORY tables, tmp_table_size exceeded for temporary tables, a fixed-size tablespace defined by innodb_data_file_path filling up, or file system/table-specific size limits.","fix":"First check and free disk space. For MEMORY/temp tables, increase max_heap_table_size and tmp_table_size. For InnoDB, make sure innodb_data_file_path is autoextend or add space. Optimize queries that spill large temporary results to disk. For a permanent fix, increase storage capacity.","tags":["table full","table is full","disk","space","1114","space","memory","innodb"]},{"code":"1215","title":"Cannot add foreign key constraint","severity":"caution","meaning":"The foreign key constraint being defined cannot be created; MySQL cannot add the constraint.","cause":"Data type/length/signedness (signed-unsigned) or character set/collation mismatch between the child and referenced columns, no suitable index/PRIMARY KEY on the referenced column, tables using different storage engines (one is MyISAM) or the referenced table not existing yet.","fix":"Make sure the column types on both sides are EXACTLY the same (e.g. both INT UNSIGNED). Verify that the referenced column is backed by an index (usually PRIMARY KEY or UNIQUE). Make sure both tables are InnoDB. Use the same charset/collation for string keys. For details: 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","severity":"caution","meaning":"You tried to delete a parent row or change its key, but the foreign key constraint does not allow it because dependent child rows exist.","cause":"The referenced parent row still has child rows and the constraint uses RESTRICT/NO ACTION for ON DELETE/UPDATE.","fix":"First delete the dependent child rows or move them to another parent, then delete the parent row. If you want automatic behavior, redefine the constraint with ON DELETE CASCADE or ON DELETE SET NULL. Find dependent rows: 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","severity":"caution","meaning":"You are inserting or updating a child row, but the parent key value it points to does not exist in the referenced table; the foreign key constraint rejects it.","cause":"An INSERT/UPDATE referencing a non-existent parent key, data loaded in the wrong order (child first) or a deleted parent row.","fix":"Insert the parent row first, then the child row. Verify that the referenced value exists: SELECT * FROM parent_table WHERE key_column = value. Preserve parent-child order in bulk loads. Use SET FOREIGN_KEY_CHECKS=0 only in controlled maintenance operations and set it back to 1 when done.","tags":["foreign key","foreign key constraint","fk","1452","child","child row","reference","insert"]},{"code":"1366","title":"Incorrect string/integer value for column","severity":"caution","meaning":"A value incompatible with the column's type or character set was inserted into a column. Usually caused by a character encoding or type mismatch.","cause":"Writing a 4-byte emoji/character (utf8mb4) into a latin1 column, entering non-numeric text into a numeric column, a wrong connection charset, or sending a value of a different type than expected.","fix":"For character issues, convert the column/table to utf8mb4: ALTER TABLE my_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci and set the connection charset to utf8mb4. Validate types in the application for numeric fields. Remember that the server runs in strict SQL mode (STRICT); the root cause is the type/encoding of the data.","tags":["charset","character","incorrect value","1366","utf8mb4","encoding","type","collation"]},{"code":"1044","title":"Access denied for user to database","severity":"caution","meaning":"Although the user authenticated correctly, it does not have privileges to operate on the specified database.","cause":"No GRANT on the database for the user, a wrong database name, the privilege defined for a different host (user@host) or a non-existent database.","fix":"View the user's privileges: SHOW GRANTS FOR 'app_user'@'host'. Grant the required privilege: GRANT ALL PRIVILEGES ON mydb.* TO 'app_user'@'host'; (GRANT takes effect immediately, FLUSH PRIVILEGES is not needed). Verify the correct host match and database name. Following the principle of least privilege, grant only the rights that are needed.","tags":["access denied","access","database","database access","1044","grant","privilege","permission"]},{"code":"1264","title":"Out of range value for column","severity":"caution","meaning":"A value outside the range allowed by the type was inserted into a numeric or date column. This error is raised in strict SQL mode.","cause":"Exceeding the limit of types such as TINYINT/INT (e.g. 200 into TINYINT, or a negative value into an UNSIGNED column), date/number overflow, or sending a larger value than expected.","fix":"Widen the column type as needed (e.g. INT instead of TINYINT, BIGINT instead of INT) or fix the signedness (SIGNED/UNSIGNED). Validate value ranges in the application. Even if you want silent clipping behavior (not recommended), make the data compatible with the type instead of removing STRICT from 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","severity":"danger","meaning":"Two or more transactions each wait for locks held by the other (a deadlock). To break the cycle, SQL Server picks one transaction as the victim and rolls it back.","cause":"Transactions locking tables/rows in different orders, long-running transactions, wide scans caused by missing indexes, lock escalation and high concurrency are the typical causes.","fix":"Catch error 1205 in the application and retry the transaction automatically. Access objects in the same order in every transaction and keep transactions short. Examine the deadlock graph: use Extended Events (the system_health session) or trace flag 1222 for details. Add appropriate indexes to reduce scanning and locking. If needed, evaluate the READ COMMITTED SNAPSHOT (RCSI) isolation level.","tags":["deadlock","lock","locking","victim","rollback","1205","retry"]},{"code":"2627","title":"Violation of PRIMARY KEY / UNIQUE KEY constraint. Cannot insert duplicate key in object","severity":"caution","meaning":"You tried to insert or update a value that already exists in column(s) protected by a PRIMARY KEY or UNIQUE constraint. Duplicate keys are not accepted.","cause":"Inserting the same unique value a second time, an UPDATE colliding with an existing value, a missing uniqueness check, or two concurrent requests producing the same row.","fix":"Check whether the row exists before inserting, or use MERGE / IF NOT EXISTS. For idempotent inserts, apply the \"INSERT ... WHERE NOT EXISTS\" pattern. Catch the error and return a meaningful message. If you genuinely need duplicate data, review the constraint. Use IDENTITY or SEQUENCE to avoid collisions when generating keys.","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 ...","severity":"caution","meaning":"A UNIQUE INDEX rejected a row because of a duplicate key value. 2627 is raised for constraints, 2601 for unique indexes.","cause":"Writing an existing value again into column(s) protected by a UNIQUE INDEX, concurrent inserts, or a collision among rows covered by a filtered unique index.","fix":"Remove or update the duplicate value. If duplicates must be tolerated, consider the IGNORE_DUP_KEY = ON option (duplicates are silently skipped). Add a NOT EXISTS check before inserting. If there is a filtered index, verify its WHERE clause and the 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","severity":"caution","meaning":"The statement violated a FOREIGN KEY or CHECK constraint. Either it referenced a parent row that does not exist, wrote a value that breaks a CHECK rule, or tried to delete a parent row that is still referenced.","cause":"FK: the parent row referenced by the inserted child row does not exist. DELETE/UPDATE: child rows depend on the parent row being deleted/changed. CHECK: the column value does not satisfy the defined condition.","fix":"Insert the parent row first, then the child row. For deletes, remove the dependent rows first or define ON DELETE CASCADE where appropriate. For a CHECK violation, make the value comply with the rule. Identify the violated constraint from the constraint name in the error text.","tags":["foreign key","fk","check","constraint","constraint violation","referential","547"]},{"code":"18456","title":"Login failed for user","severity":"danger","meaning":"A login attempt was rejected during authentication. The user name/password may be wrong, the account may be disabled, or the user may have no access to the database.","cause":"Wrong password, nonexistent login, disabled/locked account, using a SQL login while only Windows authentication is enabled, or no access to the default database. The State code in the error text identifies the real cause (State 5: login does not exist, State 7: login disabled and password also wrong, State 8: wrong password (most common), State 18: password must be changed, State 38: the specified database cannot be opened, State 58: SQL login attempted while the server is in Windows-only authentication mode).","fix":"Find the root cause from the State value in the SQL Server error log (details are not sent to the client for security reasons). Verify the user name/password. Check that the server runs in 'SQL Server and Windows Authentication Mode' (mixed mode); if you changed it, restart the service. Verify the login's user mapping and access permission in the target database.","tags":["login","session","authentication","authentication failed","password","18456","state"]},{"code":"8152","title":"String or binary data would be truncated","severity":"caution","meaning":"You tried to write data longer than a column's defined length. SQL Server stops the statement to prevent data loss (2628 is the newer version of the same error that reports the column/value).","cause":"For example writing 15 characters into a VARCHAR(10) column, a target column narrower than the source, a data type/length mismatch, or insufficient field size in ETL/transfers.","fix":"Widen the column to fit the data (ALTER TABLE ... ALTER COLUMN) or shorten the data. On SQL Server 2019+, use error 2628, which shows the table/column/value that overflowed; on older versions, check columns one by one with LEN() to find the offending row. In transfers, align source and target schema lengths.","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","severity":"caution","meaning":"The enhanced version of 8152 (SQL Server 2019 and later). It explicitly reports which table, which column and which value would be truncated.","cause":"Writing a text/binary value longer than the target column's defined length. Usually a schema mismatch or input data of unexpected length.","fix":"Look at the table and column name in the error text; either widen the column or shorten the source value. In SQL Server 2019, 2628 is raised by default only at compatibility level 150+; at lower levels enable it with ALTER DATABASE SCOPED CONFIGURATION SET VERBOSE_TRUNCATION_WARNINGS = ON or trace flag 460. From SQL Server 2016 SP2 CU6 and 2017 CU12 onward, trace flag 460 gives the same detailed message. Align field lengths with the target in the transfer layer.","tags":["truncation","truncate","overflow","column","column name","length","2628","460"]},{"code":"1222","title":"Lock request time out period exceeded","severity":"caution","meaning":"A query's timeout expired while it waited to acquire a lock. The request was canceled because another transaction held the required resource locked for too long.","cause":"Exceeding the wait time set with SET LOCK_TIMEOUT, long-running blocking transactions, open uncommitted transactions, long scans and lock conflicts.","fix":"Find the blocker: examine sys.dm_exec_requests, sys.dm_tran_locks and the blocking_session_id column, or use sp_who2 / sp_whoisactive. Shorten long transactions and commit them. Adjust LOCK_TIMEOUT if needed. On read-heavy systems, reduce reader-writer conflicts with RCSI/snapshot isolation.","tags":["lock timeout","lock wait timeout","blocking","blocked session","1222","lock_timeout"]},{"code":"9002","title":"The transaction log for database is full due to ...","severity":"danger","meaning":"The database's transaction log file is full and new transactions cannot be logged. The database rejects writes and may behave as if it were read-only.","cause":"No log backups in the FULL recovery model, a long-open transaction, replication/AlwaysOn lag, or the log being unable to grow (autogrow disabled or disk full). log_reuse_wait_desc shows the reason.","fix":"Check log_reuse_wait_desc in sys.databases. If it is LOG_BACKUP in the FULL model, take a log backup (BACKUP LOG). If it is ACTIVE_TRANSACTION, find the open transaction and commit/roll it back (DBCC OPENTRAN). Free disk space or fix the log file's autogrow setting. If the log really has grown, shrink it with DBCC SHRINKFILE after the backup. If you do not need the log chain, review the recovery model.","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","severity":"danger","meaning":"The filegroup that holds the data file is full and space cannot be allocated for new data. Inserts/updates fail.","cause":"The data file cannot grow (autogrowth disabled or MAXSIZE reached), no free disk space, the PRIMARY filegroup is exhausted, or a file size limit.","fix":"Free disk space or add a new disk/file. Enable autogrowth on the data file or enlarge it (ALTER DATABASE ... MODIFY FILE). Add a new file to the filegroup. Archive and purge unneeded/old data. For tempdb, check the tempdb files and their disk.","tags":["filegroup full","file full","allocate space","disk","autogrow","1105"]},{"code":"208","title":"Invalid object name","severity":"safe","meaning":"The table, view or object referenced in the query was not found. SQL Server could not resolve the specified name.","cause":"A misspelled object name, wrong database/schema context (a schema other than dbo), the object really not existing or not created yet, or a missing two-part name (schema.table).","fix":"Spell the object name and schema correctly; use the qualified name (e.g. dbo.Customers). Make sure you are connected to the right database (USE). Check that the object exists: SELECT * FROM sys.objects WHERE name = 'YourObjectName'. Make sure temporary tables are created within the same 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","severity":"caution","meaning":"The user has no permission to perform the requested operation (SELECT, INSERT, EXECUTE etc.) on the object. Access was denied for security reasons.","cause":"The required permission was not granted to the user/role, wrong schema ownership (broken ownership chaining), an explicit DENY on the object, or the user not being a member of a sufficient role.","fix":"Grant the required permission: GRANT SELECT ON dbo.MyTable TO UserName (or GRANT EXECUTE ON dbo.MyProcedure TO ...). Add the user to an appropriate role (e.g. db_datareader). Remove any explicit DENY. Arrange permissions according to the principle of least privilege. Audit existing permissions with 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","severity":"danger","meaning":"Authentication may succeed, but the connection is rejected because the requested database cannot be opened. Usually seen together with 18456.","cause":"The database does not exist, is OFFLINE/SINGLE_USER/RESTORING/SUSPECT, the login has no user mapping in that database, or the default database in the connection string is inaccessible.","fix":"Verify that the database exists and is ONLINE: SELECT name, state_desc FROM sys.databases. Create a user for the login in the database and grant permissions. Check the Initial Catalog/Database value in the connection string. If the database is RESTORING/SUSPECT, follow the repair/restore steps.","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)","severity":"danger","meaning":"The client reached the server but the connection dropped during the handshake. Usually caused by Named Pipes/TCP configuration, protocol settings or a server-side rejection. Related to 10054 (connection forcibly closed).","cause":"Shared Memory/Named Pipes/TCP protocols disabled, remote connections not allowed, the account unable to log in (see 18456), a firewall/network outage, or the server forcibly closing the connection (10054).","fix":"Enable TCP/IP and, if needed, Named Pipes in SQL Server Configuration Manager and restart the service. Verify that remote connections are allowed. Open the SQL port (default 1433) in the firewall. For authentication problems, examine the 18456 State code in the error log. Check network stability and TLS/encryption settings.","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)","severity":"danger","meaning":"SQL Server received an operating-system-level I/O error while reading or writing a data/log file. This is a serious integrity warning pointing to a disk hardware or storage subsystem problem.","cause":"Bad disk sectors, a faulty controller/driver, storage/SAN problems, file system errors, or a page that cannot be read at all (hard I/O error). Usually hardware related.","fix":"Take a good backup immediately (if possible). Examine the Windows event log and storage logs, and test hardware/disk health. Run DBCC CHECKDB to check integrity. Check the suspect_pages table (msdb.dbo.suspect_pages). If there is corruption, restore from a good backup or perform a page-level restore. Replace/repair the hardware.","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)","severity":"danger","meaning":"SQL Server read a page but its content is logically inconsistent (bad checksum, torn page or wrong page ID). The data page is corrupt.","cause":"Storage subsystem errors, sudden power loss/torn pages, controller or driver problems, silent corruption caught by PAGE_VERIFY CHECKSUM. Like 823, mostly hardware/storage related.","fix":"Run DBCC CHECKDB to determine the extent of the corruption. Restore from the last good backup or repair the affected pages with a page-level restore. Examine msdb.dbo.suspect_pages. Make sure PAGE_VERIFY = CHECKSUM is enabled. Inspect the hardware/storage. REPAIR_ALLOW_DATA_LOSS must be the last resort and causes data loss.","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","severity":"danger","meaning":"SQL Server could not allocate the memory needed to run the query from the relevant resource pool. The operation failed due to memory pressure.","cause":"Insufficient server memory, a low max server memory setting, memory-hungry queries (large sorts/hashes), a memory leak, many concurrent requests, or Resource Governor pool limits.","fix":"Review the max server memory setting and provide enough physical RAM. Optimize memory-hungry queries (proper indexes, fewer sorts/hashes). Monitor memory: sys.dm_os_memory_clerks, DBCC MEMORYSTATUS, Perfmon memory counters. Adjust Resource Governor pool limits. Reduce concurrency if necessary.","tags":["memory","memory pressure","insufficient memory","resource pool","701","oom"]},{"code":"845","title":"Time-out occurred while waiting for buffer latch","severity":"danger","meaning":"A timeout occurred while waiting for a buffer latch on a data page. SQL Server could not acquire the short-lived internal lock (latch) needed to access a page in a reasonable time; usually a sign of heavy I/O or memory pressure.","cause":"A slow/overloaded disk subsystem, pages constantly read from disk due to memory pressure, hardware problems, excessive paging, or a storage bottleneck.","fix":"Measure disk I/O latency (sys.dm_io_virtual_file_stats, Perfmon disk latency). Check storage performance and hardware health. Review memory sufficiency and the max server memory setting. Examine wait types (sys.dm_os_wait_stats, PAGEIOLATCH waits). Remove the storage bottleneck; provide faster disks/more RAM if needed.","tags":["buffer latch","latch timeout","845","io","pageiolatch","memory","memory pressure"]},{"code":"3201","title":"Cannot open backup device. Operating system error (backup device error)","severity":"caution","meaning":"The target backup device/file could not be opened during backup or restore. SQL Server could not access the specified path.","cause":"A wrong/nonexistent folder path, the SQL Server service account lacking write permission on the target folder, no access to a network share (UNC), a full disk, or the file being locked by another process.","fix":"Verify that the backup path exists and is spelled correctly. Grant the SQL Server service account write permission on the target folder. For UNC shares, check the service account's access. Make sure there is enough free disk space. For a local path, verify that the drive letter is visible from the server (not a client drive).","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","severity":"caution","meaning":"An insert or update tried to write a value that already exists in the collection into field(s) protected by a unique index. MongoDB rejects the duplicate key.","cause":"An _id collision, writing the same value a second time to a field with a unique index, a race condition during upsert, or null values colliding for more than one document in a unique index (non-sparse index).","fix":"Check whether the document exists before writing, or use an upsert: db.collection.updateOne(filter, {$set:{...}}, {upsert:true}). Look at the index name and the dup key value in the error message. If uniqueness is not actually required, drop the index; if nulls are the problem, use a partial/sparse index. In the application, catch the error and retry or report it to the user.","tags":["duplicate key","duplicate key","unique index","unique","e11000","upsert","_id"]},{"code":"11000","title":"DuplicateKey (error code 11000)","severity":"caution","meaning":"The numeric code for the duplicate key error (returned together with the E11000 text). A write operation violated a unique index constraint.","cause":"Writing the same unique field again, concurrent upserts generating the same key, or duplicate documents in a bulk write.","fix":"Catch error code 11000 in application code and implement idempotent behavior (update if it exists, insert otherwise). In bulk operations, use ordered:false to allow documents other than the duplicates to be written. Review the uniqueness rule and the index definition.","tags":["duplicatekey","11000","duplicate","unique","unique","bulk write","write error"]},{"code":"13","title":"Unauthorized (error code 13)","severity":"caution","meaning":"The authenticated user has no privilege for the attempted operation or resource. The user is logged in, but their roles do not cover this action.","cause":"The required role (e.g. readWrite, dbAdmin) is not granted to the user, the operation runs against the wrong database, or privileges are insufficient for an administrative command. Incomplete RBAC configuration.","fix":"Check the user's roles: db.getUser(\"app_user\"). Grant the required role: db.grantRolesToUser(\"app_user\", [{role:\"readWrite\", db:\"mydb\"}]). Make sure the operation runs in the correct database. Assign an appropriate built-in or custom role following the principle of least privilege. Verify the authSource value in the connection string.","tags":["unauthorized","unauthorized","authorization","role","role","rbac","13","permission"]},{"code":"18","title":"AuthenticationFailed (error code 18)","severity":"caution","meaning":"Authentication failed. The supplied username, password or credentials are invalid; the user could not log in.","cause":"Wrong username/password, wrong authSource database, mismatched authentication mechanism (SCRAM-SHA-1/SCRAM-SHA-256), or the user is not defined in the specified database.","fix":"Verify the username and password. Specify the correct authSource in the connection string (the database where the user was created, usually admin): ...?authSource=admin. Check that the authentication mechanism is compatible with the server. Make sure the user was created in the correct database. URL-encode the password if it contains special characters.","tags":["authentication failed","authentication","password","password","authsource","18","scram"]},{"code":"11600","title":"InterruptedAtShutdown (error code 11600)","severity":"danger","meaning":"The operation was interrupted because the server began shutting down. The in-flight operation could not complete because the mongod process was stopping.","cause":"A restart/shutdown of mongod, shutdown during a replica set primary stepdown, planned maintenance, a crash, or the process being killed by the OOM killer.","fix":"Wait for the server to come back up and retry the operation. In the application, catch transient errors and retry automatically; enable retryable writes. For unexpected shutdowns, review the mongod logs and system logs (OOM, crash). Use a replica set for high availability.","tags":["interrupted","shutdown","shutdown","downtime","11600","retry","replica set"]},{"code":"50","title":"MaxTimeMSExpired (error code 50)","severity":"caution","meaning":"The server canceled the operation because it exceeded the time limit set with maxTimeMS. The query did not complete within the allotted time.","cause":"Long-running queries, collection scans (COLLSCAN) caused by missing indexes, large data volumes, heavy aggregation operations or high server load.","fix":"Optimize the query: inspect the plan with explain() and add an appropriate index. If more time is genuinely needed, increase maxTimeMS. Simplify heavy aggregations or filter early with $match. Split results with pagination. Check server load and resource capacity.","tags":["maxtimems","timeout","timeout","50","slow query","slow query","index"]},{"code":"251","title":"NoSuchTransaction (error code 251)","severity":"caution","meaning":"The referenced multi-document transaction was not found. The transaction has already ended, timed out, or an invalid transaction number was used.","cause":"The transaction exceeded transactionLifetimeLimitSeconds and was aborted automatically, the session ended, a wrong/stale txnNumber, or continuing a transaction that was already aborted after an error.","fix":"Restart the transaction from the beginning (with a new session). Keep transactions short; design them to finish within the default lifetime (60 s). For errors labeled TransientTransactionError, retry the whole transaction safely. If long transactions are required, review the transactionLifetimeLimitSeconds setting.","tags":["nosuchtransaction","transaction","transaction","251","session","abort","retry"]},{"code":"112","title":"WriteConflict (error code 112)","severity":"caution","meaning":"A write conflict that occurs when two concurrent operations try to modify the same document(s). Because of optimistic concurrency control, WiredTiger rolls back one of the operations.","cause":"Highly concurrent writes to the same documents, conflicting updates during long-running transactions, heavy contention on a hot document.","fix":"Retry the operation; WriteConflict is usually transient and resolved by a retry (it is labeled TransientTransactionError). Keep transactions short and reduce the number of documents touched. Review the data model to reduce contention on the same document. Implement retries with exponential backoff in the application.","tags":["writeconflict","write conflict","112","concurrency","concurrency","retry","wiredtiger"]},{"code":"133","title":"FailedToSatisfyReadPreference (error code 133)","severity":"danger","meaning":"No suitable replica set member was found to satisfy the specified read preference (e.g. primary, secondary). The driver could not select a node to route the request to.","cause":"Replica set members are unreachable/down, no primary exists while an election is in progress, a network partition, no member matches the tag set criteria, or a majority (quorum) cannot be reached.","fix":"Check the replica set status: rs.status(). Verify node health and network connectivity. Wait for the primary election to complete and retry. Review your read preference setting; if needed, use a more flexible preference (e.g. primaryPreferred). If you use tag sets, make sure a matching member exists.","tags":["read preference","readpreference","133","replica set","election","secondary","primary"]},{"code":"6","title":"HostUnreachable (error code 6)","severity":"danger","meaning":"The driver/client could not reach the target MongoDB server (host). No connection could be established at the network level.","cause":"The server is down, wrong host/port, firewall blocking, a DNS resolution problem, a network outage, or a bindIp configuration that excludes the client.","fix":"Check that the hostname and port are correct (default 27017). Verify that the mongod service is running. Test the firewall and network access (ping/telnet). Make sure the bindIp setting in mongod.conf allows client access. Verify DNS/SRV records (for mongodb+srv connections).","tags":["hostunreachable","host","connection","network","network","6","firewall","bindip"]},{"code":"89","title":"NetworkTimeout (error code 89)","severity":"danger","meaning":"A network operation during communication with the server did not complete within the allotted time and timed out. The connection was established, but the response did not arrive in time.","cause":"A slow or unstable network, an overloaded server, a long-running operation delaying the response, socket/connect timeout values set too low, or network congestion.","fix":"Measure network latency and stability. Check server load and resources. Set connectTimeoutMS and socketTimeoutMS in the driver to realistic values. Catch transient errors and retry. Optimize slow queries to shorten response times.","tags":["networktimeout","network timeout","89","timeout","socket","network","retry"]},{"code":"262","title":"ExceededTimeLimit (error code 262)","severity":"caution","meaning":"The operation exceeded the time limit allowed on the server side and was terminated. Exceeding maxTimeMS or internal time limits can cause this error.","cause":"A long-running query/command, slow execution due to a missing index, heavy aggregation, scanning a large data set, or heavy server load.","fix":"Inspect the query with explain() and add an appropriate index. Split the operation into smaller chunks. Set maxTimeMS according to the actual need. Reduce data early in the aggregation pipeline with $match/$project. Verify that server resources are sufficient.","tags":["exceededtimelimit","time limit","262","timeout","maxtimems","slow","index"]},{"code":"292","title":"QueryExceededMemoryLimitNoDiskUseAllowed (error code 292)","severity":"caution","meaning":"A query or aggregation stage exceeded the allowed memory limit (default 100 MB) and failed because writing to disk was not allowed. Usually seen in sort or group stages.","cause":"In-memory sorting over large unindexed data, large $group/$sort aggregation stages, very large intermediate result sets, the allowDiskUse:false setting.","fix":"Allow spilling to disk in the aggregation with the allowDiskUse:true option: db.collection.aggregate(pipeline, {allowDiskUse:true}). Reduce memory usage by indexing the sorted fields. Filter the pipeline early with $match to reduce the data processed. Paginate results if necessary. In MongoDB 6.0+, disk use is enabled by default in most cases.","tags":["memory limit","memory limit","292","allowdiskuse","sort","aggregation","group"]},{"code":"26","title":"NamespaceNotFound (error code 26)","severity":"safe","meaning":"The referenced namespace (database.collection) was not found. The specified collection or index does not exist.","cause":"A misspelled collection name, a collection not yet created, the wrong database context, or attempting a drop/index operation on a nonexistent collection.","fix":"Verify the collection name and database: show collections. Remember that names are case-sensitive. MongoDB creates a collection automatically on the first write; if needed, create it explicitly with db.createCollection(). For operations such as drop, check that the collection exists beforehand.","tags":["namespacenotfound","namespace","collection missing","26","collection","not found","not found"]},{"code":"43","title":"CursorNotFound (error code 43)","severity":"caution","meaning":"The server-side cursor no longer exists. When the client requests the next batch (getMore), the error is returned because the cursor cannot be found.","cause":"The cursor timed out (default 10 minutes of inactivity), too long a wait between batches, a server restart, or the cursor was closed explicitly.","fix":"Process cursor results faster; do not wait long between batches. For long-running operations, use the noCursorTimeout option carefully (mongosh: cursor.noCursorTimeout(); PyMongo: no_cursor_timeout=True) and always close the cursor; a cursor inside a session is closed at the session timeout (localLogicalSessionTimeoutMinutes, default 30 min) even with this option. For large result sets, iterate with a smaller batchSize or use range-based pagination. Restart the operation and query again.","tags":["cursornotfound","cursor","cursor","43","getmore","timeout","batch","pagination"]},{"code":"211","title":"KeyNotFound (error code 211)","severity":"caution","meaning":"An expected key/value was not found. Usually indicates an inconsistency related to internal key management, session keys or the oplog/keys collection.","cause":"Missing/deleted signing keys in the admin.system.keys collection, clock synchronization problems in the replica set/sharded cluster, keyfile/key rotation problems, or corrupted internal state.","fix":"Verify clock synchronization (NTP) between replica set members. Check the contents of admin.system.keys and key generation. Review cluster status (rs.status(), sh.status()). If it persists, analyze the mongod logs and review the keyfile/identity configuration. Make sure the primary is running healthily so the keys can be regenerated.","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","severity":"caution","meaning":"A command was run against a key that is not appropriate for that key's data type. For example, applying a list (LPUSH) or hash (HSET) command to a key holding a string.","cause":"The key is of a different type than expected, the same key name is used with different data types, type confusion in the application logic, or an old/leftover key being overwritten.","fix":"Check the key type: TYPE mykey. Choose the command according to the key's actual type. Distinguish types in key naming (e.g. a set for user:1:tags, a string for user:1:name). If needed, delete the old key (DEL) and recreate it with the correct type. Enforce type consistency in the application layer.","tags":["wrongtype","wrong type","type","data type","key","key"]},{"code":"OOM","title":"OOM command not allowed when used memory > 'maxmemory'","severity":"danger","meaning":"Memory used by Redis has reached the maxmemory limit and write commands are rejected because the eviction policy cannot free space for new data. Memory is full; writes are not possible.","cause":"The maxmemory limit is exceeded, the noeviction policy is selected (no key is ever removed), data without TTL keeps growing, a memory leak, or insufficient RAM for the limit.","fix":"Inspect memory usage: INFO memory and MEMORY USAGE. Choose an appropriate eviction policy (e.g. allkeys-lru or volatile-lru): CONFIG SET maxmemory-policy allkeys-lru. Set TTLs on keys so they are cleaned up automatically. Increase maxmemory/RAM. Find and shrink big keys (redis-cli --bigkeys). Purge unnecessary data.","tags":["oom","out of memory","memory","maxmemory","eviction","memory","danger"]},{"code":"NOAUTH","title":"NOAUTH Authentication required","severity":"caution","meaning":"Redis requires authentication, but the client tried to run a command before authenticating. It must log in with AUTH first.","cause":"A requirepass/ACL password is set on the server, the client does not send the AUTH command, the password is missing from the connection string, or AUTH is not repeated after the connection is re-established.","fix":"Authenticate before running commands: AUTH password (with ACLs, AUTH app_user password). Add the password to the client/connection string. If you use a connection pool, make sure AUTH is performed on every new connection. Store the password securely (environment variable/secrets manager).","tags":["noauth","authentication","authentication","auth","password","requirepass","acl"]},{"code":"WRONGPASS","title":"WRONGPASS invalid username-password pair or user is disabled","severity":"caution","meaning":"The username/password pair given in the AUTH command is invalid, or the ACL user is disabled. Authentication was rejected.","cause":"Wrong password, wrong ACL username, the user is disabled in the ACL, the requirepass value has changed, or an escaping/encoding problem in the password.","fix":"Enter the correct username and password. Inspect the ACL user: ACL LIST and ACL GETUSER app_user. If the user is disabled, enable it (ACL SETUSER app_user on ...). Verify the password set with requirepass (CONFIG GET requirepass). When resetting the password, make sure all clients are updated.","tags":["wrongpass","password","invalid password","auth","acl","user","invalid"]},{"code":"READONLY","title":"READONLY You can't write against a read only replica","severity":"caution","meaning":"A write command was sent to a replica (slave) node. Replicas are read-only by default; writes can only be performed on the master.","cause":"The client connects to a replica instead of the master, the old master became a replica after a failover, misrouting to the wrong node in Sentinel/Cluster, or a load balancer forwarding writes to a replica.","fix":"Route write requests to the master node. If you use Sentinel, query the current master from Sentinel (SENTINEL get-master-addr-by-name). In Cluster, write to the correct shard master. Configure read/write splitting in the client library. After a failover, refresh connections so they point to the new 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'","severity":"danger","meaning":"The link between the replica and the master is down, and because replica-serve-stale-data is set to 'no', the replica refuses to answer requests with stale data.","cause":"The master node crashed or is unreachable, a network outage/partition, the master-replica replication link dropped, or the master cannot respond under heavy load.","fix":"Check the master node's status and network connectivity. Inspect replication status: INFO replication. Bring the master back up, or let a failover happen via Sentinel/Cluster. If serving stale data is acceptable, consider replica-serve-stale-data yes (mind the consistency risk). Use Sentinel/Cluster for high availability.","tags":["masterdown","master down","replication","replication","stale","failover","sentinel","danger"]},{"code":"CLUSTERDOWN","title":"CLUSTERDOWN The cluster is down / Hash slot not served","severity":"danger","meaning":"Redis Cluster is not fully operational. Some hash slots are not served by any node, so the cluster rejects requests.","cause":"Not enough master nodes reachable, one or more slots unassigned/uncovered, loss of majority (quorum), node crashes, or some slots unreachable while cluster-require-full-coverage is yes.","fix":"Check cluster state: CLUSTER INFO and CLUSTER NODES. Bring down masters back up, or let replicas be promoted. Reassign unassigned slots (redis-cli --cluster fix or CLUSTER ADDSLOTS). Verify that all slots are covered. If partial operation is acceptable, consider 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","severity":"caution","meaning":"Redis is starting and loading the dataset (RDB/AOF) from disk into memory. Most commands are temporarily rejected until loading completes.","cause":"The server has just started, a large RDB/AOF file is being loaded, a replica is performing a full sync from the master, or a recovery process after a restart.","fix":"Wait for loading to finish; the duration depends on data size. Monitor status: INFO persistence (wait while loading:1; ready when 0). In the application, catch this transient error and retry at short intervals. To shorten load time, review the dataset/persistence strategy (RDB vs AOF). Provide sufficient RAM and fast disks.","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","severity":"caution","meaning":"A long-running Lua script or function is executing and has exceeded busy-reply-threshold (named lua-time-limit before Redis 7; default 5000 ms). Because Redis is single-threaded, it cannot process other commands during this time; only SCRIPT KILL (FUNCTION KILL for functions) or SHUTDOWN NOSAVE is accepted.","cause":"An infinite loop or a very long-running Lua script/function, heavy computation, a script looping over large data, or a poorly written EVAL call.","fix":"If the script has not modified data, stop it: SCRIPT KILL. If the script has already performed writes, SCRIPT KILL does not work; as a last resort to preserve data consistency, SHUTDOWN NOSAVE may be needed (unsaved data is lost; be careful). Write scripts to be short and deterministic. Split long operations into chunks. Review the busy-reply-threshold (formerly lua-time-limit) setting.","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","severity":"danger","meaning":"Redis is configured to take RDB snapshots but cannot write to disk. In this state, if stop-writes-on-bgsave-error is enabled, Redis rejects all write commands.","cause":"A full disk, file system permission problems, a non-writable directory, background save (BGSAVE) errors, fork failure due to insufficient memory, or a disk hardware problem.","fix":"Free up disk space and check write permission on the RDB directory (dir). Inspect the latest BGSAVE error: INFO persistence and the Redis logs. To temporarily allow writes until the problem is fixed, stop-writes-on-bgsave-error no can be set (mind the durability risk). Ensure sufficient memory for fork and the vm.overcommit_memory=1 setting. Verify the persistence configuration (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","severity":"caution","meaning":"In Redis Cluster, a multi-key command (e.g. MGET, MSET, SUNION) was used, but the keys fall into different hash slots. Multi-key operations require all keys to be in the same slot.","cause":"In cluster mode, commands accessing several keys at once are called with keys spread across different slots. Keys are distributed to different nodes because they do not use a hash tag.","fix":"Use a hash tag to put related keys in the same slot: pin part of the key name with curly braces (e.g. {user1}:profile and {user1}:settings go to the same slot). Use multi-key commands only with keys sharing the same hash tag. Alternatively, split operations into single-key commands.","tags":["crossslot","cross slot","cluster","cluster","hash tag","slot","mget","mset"]},{"code":"MOVED","title":"MOVED slot ip:port (cluster redirect)","severity":"safe","meaning":"In Redis Cluster, the hash slot holding the requested key belongs to another node. The server permanently redirects the client to the correct node. This is a redirect rather than an error.","cause":"A client that is not cluster-aware sends a request to the wrong node, slots have been redistributed between nodes (resharding), or the slot map cached by the client is stale.","fix":"Use a cluster-aware client library; these follow the MOVED reply automatically, redirect to the correct node and update the slot map. Use the -c flag when connecting with redis-cli. Make sure your client refreshes the slot topology periodically (CLUSTER SLOTS/SHARDS).","tags":["moved","cluster","cluster","redirect","redirect","slot","resharding"]},{"code":"ASK","title":"ASK slot ip:port (cluster redirect)","severity":"safe","meaning":"In Redis Cluster, a slot is being migrated to another node, and the requested key may temporarily be on the target node. The server redirects the client to the target node for this request only (temporary redirect).","cause":"Accessing a key that has already been transferred within a slot being migrated during resharding. Unlike MOVED it is not permanent; it applies only at that moment.","fix":"Cluster-aware clients handle ASK redirects automatically: they send the ASKING command to the target node and retry the request, but do not update the slot map permanently. Use a suitable client library, and the -c flag for redis-cli. When resharding completes, redirects become MOVED and permanent.","tags":["ask","asking","cluster","cluster","migration","migration","redirect","redirect"]},{"code":"NOSCRIPT","title":"NOSCRIPT No matching script. Please use EVAL","severity":"caution","meaning":"The SHA1 digest of the Lua script called with EVALSHA was not found in the server's script cache. Redis does not know a script corresponding to this hash.","cause":"The server was restarted or the script cache was cleared with SCRIPT FLUSH, the script was never loaded, or the cache is empty on the new node after a failover.","fix":"Run the script with its full body via EVAL (Redis caches it automatically), or load it first with SCRIPT LOAD and use the returned SHA in EVALSHA. In the application, catch the NOSCRIPT error and implement fallback logic from EVALSHA to EVAL. Many Redis client libraries do this automatically.","tags":["noscript","script","evalsha","eval","lua","sha","script load","cache"]},{"code":"EXECABORT","title":"EXECABORT Transaction discarded because of previous errors","severity":"caution","meaning":"A transaction in a MULTI/EXEC block was aborted because of errors that occurred during the queueing stage. EXEC rejects the transaction without running any command.","cause":"A command between MULTI and EXEC contains a syntax error, or errors caught during queueing such as a nonexistent command name or a wrong number of arguments. Such errors invalidate the whole transaction.","fix":"Fix the syntax and arguments of the commands in the MULTI block. Check the replies while queueing commands; if any command returns an error instead of QUEUED, the transaction is aborted at EXEC. Rebuild the transaction with valid commands. Note: Redis transactions do not roll back; only queueing errors lead to EXECABORT, and runtime errors do not stop the other commands.","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))","severity":"danger","meaning":"Because disk usage exceeded the flood stage watermark (default 95%; on large disks determined by max_headroom), Elasticsearch placed a read_only_allow_delete block on indices that have a shard on the affected node, to keep the disk from filling up completely. Because of this block, writes to the index (index/update/delete document) are not possible; only deleting the index itself is allowed. ES 8.x returns this error with HTTP 429.","cause":"Disk usage on at least one node exceeded the cluster.routing.allocation.disk.watermark.flood_stage threshold. Even if the block is removed manually, Elasticsearch reapplies it while the disk is still full. Large indices without log rotation, uncleaned snapshots or insufficient disk capacity are typical causes.","fix":"First free up disk space: delete old/unneeded indices (DELETE /old-index), take a snapshot and remove indices, or grow the disk. In 7.4+, the block is removed automatically when disk usage drops below the high watermark; to clear it immediately without waiting, remove it manually: PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}. For a permanent fix, delete/archive old data automatically with ILM (Index Lifecycle Management) and review the watermark settings (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))","severity":"danger","meaning":"The index is read-only because index.blocks.read_only_allow_delete is true; new documents cannot be written or updated, only the index can be deleted. Elasticsearch usually sets this automatically when the disk flood stage watermark is exceeded; ES 8.x returns HTTP 429 to write requests.","cause":"The block was set automatically because disk usage exceeded the flood stage threshold, or the setting was set to true manually. If the block is removed without freeing disk space, Elasticsearch reapplies it.","fix":"Free up sufficient disk space. In 7.4+, an automatically set block clears itself when disk usage drops below the high watermark; for a manually set block, or to clear it without waiting, remove the block: PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}. For a single index: PUT /index-name/_settings {\"index.blocks.read_only_allow_delete\": null}. If it keeps recurring, increase disk capacity, set up an ILM policy and monitor which node is filling up with _cat/allocation.","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","severity":"danger","meaning":"A node's disk usage exceeded the high watermark (default 90%) or flood stage (default 95%) threshold. When the high watermark is exceeded, Elasticsearch tries to move shards from that node to other nodes; when flood stage is exceeded, it makes indices read-only.","cause":"The disk is filling up: continuously growing log/metric indices, old indices never deleted, an uncleaned snapshot repository, an undersized disk, or a single node overfilling due to unbalanced shard distribution.","fix":"Inspect disk usage: GET _cat/allocation?v. Delete old indices or snapshot and remove them, and grow the disk. You can temporarily raise the thresholds (carefully; in 8.x set all three together because of max_headroom validation): 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%\"}}. Once resolved, revert to the defaults by setting the same settings to null (transient settings are deprecated; do not use them). Permanent fix: lifecycle management with ILM, regular index cleanup and capacity planning. If indices became read-only due to flood stage, remove the block separately.","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 [...]]","severity":"danger","meaning":"A request was rejected because it would exceed the memory (heap) circuit breaker limit. Elasticsearch stops the request up front to prevent the node from crashing with an OutOfMemoryError. The [parent] breaker guards total memory usage; the [fielddata] breaker guards fielddata memory on text fields.","cause":"Insufficient JVM heap, very large/complex aggregations, large result sets, large bulk requests, fielddata loaded on text fields, or many heavy concurrent queries filling the heap. It is usually a symptom of heap pressure.","fix":"Shrink the queries: reduce aggregation size, bulk batch size and returned fields. Aggregate on the keyword sub-field (.keyword) instead of fielddata on text fields. Increase the JVM heap (~50% of server RAM, at most ~31GB). Monitor heap pressure: GET _cat/nodes?v&h=name,heap.percent. Fix the root cause (memory/query) rather than temporarily raising breaker limits. Check which breaker tripped with 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 [...]","severity":"caution","meaning":"While indexing a document, the value sent for a field did not match the type defined for that field in the mapping and could not be parsed. For example, text sent to a field defined as integer, or an invalid date format sent to a field defined as date.","cause":"Field type mismatch (a string for a numeric field, a malformed format for a date field), a date that does not match the expected date format, malformed/incomplete JSON, dynamically mistyped fields, or a scalar value sent where an object is expected.","fix":"Check the field name and expected type in the error message: GET /index-name/_mapping. Make the value in the document match the field type, or redefine the mapping with the correct type. For date fields, match the format parameter to the document format. The type of an existing field cannot be changed; if needed, create a new index and move the data with _reindex. Validate data in the application layer.","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","severity":"caution","meaning":"Because the index or object mapping is set to \"dynamic\": \"strict\", a document containing a new field not defined in the mapping was rejected. Strict mode prevents fields outside the schema from being added silently.","cause":"A field not present in the mapping was sent in the document (typo, new field, unexpected field) while the mapping is dynamic:strict. It is usually a deliberate safeguard to keep schema control tight.","fix":"Fix or remove the extra/misspelled field in the document. If the field is really needed, add it to the mapping first: PUT /index-name/_mapping {\"properties\": {\"new_field\": {\"type\": \"...\"}}}. If you want new fields to be added automatically, set dynamic to \"true\", or to \"false\" if they should be stored but not indexed. Use strict mode deliberately to preserve schema discipline.","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","severity":"caution","meaning":"An aggregation, sort or script was attempted on an analyzed text field. These operations are disabled by default on text fields because they require memory-expensive fielddata.","cause":"An aggregation/sort was applied directly to a text field. In Elasticsearch, text is used for full-text search and keyword for exact-value aggregation/sorting; mixing the two produces this error.","fix":"Use the field's keyword sub-field for aggregation/sorting (e.g. \"my_field.keyword\"). If the field is text only, add a keyword sub-field to the mapping (multi-field) and run _reindex. Enabling fielddata on a text field (fielddata=true) is possible but not recommended due to high memory consumption and circuit breaker risk; use it only when unavoidable.","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]","severity":"danger","meaning":"A search request failed on one or all of the required shards. This is a generic search error wrapping the actual underlying cause; the \"caused by\" section states the real problem (parse error, breaker, unsuitable field type, shard unreachable, etc.).","cause":"Invalid query syntax, aggregation on the wrong field type, too_many_clauses, a circuit breaker trip, an unassigned/unreachable shard, a query timeout or a node crash. The root cause is in the inner exception of the response.","fix":"Find the actual exception in the \"caused by\" / \"failed_shards\" details of the response and fix accordingly (correct the query, use a keyword field, breaker/heap settings, shard allocation). Check cluster health: GET _cluster/health and GET _cat/shards?v. If partial results are allowed and some shards failed, review the allow_partial_search_results behavior.","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)","severity":"caution","meaning":"A query exceeded the maximum allowed number of Lucene clauses. In ES 8.x this limit is not fixed; it is calculated automatically from the node's heap size and search thread count (it can drop to 1024 on small-heap nodes), and the error message shows the current value. Very large bool queries and queries that expand to many terms trigger it.","cause":"Programmatically generated bool queries with thousands of should/must clauses; wildcard/prefix/fuzzy/query_string queries expanding to many terms; deeply nested bool structures. Note: the terms query (a value list on a single field) is not subject to this limit; its cap is index.max_terms_count (default 65,536).","fix":"Simplify the query: convert many term clauses on a single field into one terms query (or a terms lookup for large lists). Use more specific query types or index_prefixes/keyword fields instead of wildcard/prefix. In ES 8.x the indices.query.bool.max_clause_count setting is deprecated and ignored; the way to raise the limit is to increase the node heap. Review the query generation logic.","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)","severity":"caution","meaning":"A document update/delete request was rejected by optimistic concurrency control. The document version (or _seq_no/_primary_term) does not match the one in the request; that is, someone else modified the document after you read it. Returns HTTP 409.","cause":"Concurrent updates to the same document, a write attempted with a stale version/seq_no, an attempt to create (op_type=create) a document that already exists, or a race condition during _bulk.","fix":"Re-read the document and retry the request with the current _seq_no and _primary_term (or version) (read-modify-write loop). For automatic retries use the retry_on_conflict parameter on the _update request; in _update_by_query/_delete_by_query pass conflicts=proceed to skip conflicting documents and continue. If the intent is create-only, the conflict is normal; catch 409 in the application and handle it appropriately (retry or skip).","tags":["version-conflict","409","concurrency","optimistic","seq-no","caution"]},{"code":"document_missing_exception","title":"document_missing_exception: [_doc][id]: document missing","severity":"caution","meaning":"The document to be updated (_update) was not found in the index. An update requires an existing document; if the document does not exist, this exception occurs.","cause":"The document was deleted earlier, was never created, the wrong _id was used, the request went to the wrong index, or a document written with custom routing was accessed without routing. (GET and _update are real-time; refresh delay does not cause this error.)","fix":"Verify that the _id and target index are correct: GET /index-name/_doc/id. If you want update-if-exists/create-if-missing behavior, use upsert (or doc_as_upsert:true) in the _update request. Create the document directly with index (PUT). In the application, catch this exception and fall back to upsert logic.","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)","severity":"caution","meaning":"The requested index (or alias) does not exist. The operation was rejected with HTTP 404 because no index with the given name was found.","cause":"The index was never created, was deleted, the index/alias name has a typo, the client is connected to the wrong environment (cluster), or a write to a nonexistent index was attempted while action.auto_create_index is disabled.","fix":"List existing indices: GET _cat/indices?v. Verify the name and alias: GET _alias. Create the index: PUT /index-name (with the required mappings/settings), or configure automatic creation with an index template. If automatic creation is desired, check the action.auto_create_index setting. Verify that you are not connected to the wrong 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)","severity":"safe","meaning":"The index (or resource) to be created already exists. The create request was rejected because an index with the same name exists. In older versions it also appears as index_already_exists_exception.","cause":"Trying to create the same index twice, non-idempotent setup scripts, concurrent create requests, or an attempt to recreate the same name with different settings.","fix":"If the index already exists, there is no need to create it; continue. If it must be recreated, delete it first (DELETE /index-name) and then create it (caution: data loss). Make setup scripts idempotent: check for existence first with GET _cat/indices. Prevent conflicts by using index templates or ILM/rollover for schema management.","tags":["already-exists","resource-already-exists","index","create","idempotent","safe"]},{"code":"cluster_health_red","title":"cluster health status: RED / unassigned shards","severity":"danger","meaning":"Cluster health RED means at least one primary shard is unassigned and unavailable. This is a critical state in which some data cannot be read/written, with a risk of partial data loss. (YELLOW means only replicas are unassigned; primaries are intact.)","cause":"A node crashed/left and the primary shards on it were left without a copy, allocation is blocked by the disk watermark, a corrupted shard, an error during snapshot restore, or allocation settings preventing shard assignment.","fix":"See why shards are unassigned: GET _cluster/allocation/explain or GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason. Bring crashed nodes back; if it is a disk problem, free up space (watermark). If allocation was blocked temporarily, trigger reallocation: POST _cluster/reroute?retry_failed=true. If a primary is completely lost, restore from a snapshot. In the RED state, focus on recovering primaries first.","tags":["cluster-health","red","unassigned","shard","primary","danger"]},{"code":"no_shard_available_action_exception","title":"no_shard_available_action_exception: No shard available for [...]","severity":"danger","meaning":"No active shard copy capable of serving a request was found. The request cannot be fulfilled because the relevant shard is unassigned, recovering, or its hosting node is unreachable.","cause":"A node crash/network partition, unassigned shards, an ongoing shard recovery, an allocation block due to the disk watermark, or the cluster being in the RED/YELLOW state.","fix":"Inspect cluster and shard state: GET _cluster/health and GET _cat/shards?v. See why shards are unassigned: GET _cluster/allocation/explain. Bring crashed nodes back, fix the disk problem, and wait for recovery to finish: GET _cat/recovery?v&active_only=true. If needed, retry allocation with 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)","severity":"danger","meaning":"An operation was rejected because the queue capacity of the relevant thread pool (search, write/bulk, etc.) is full. Under overload, Elasticsearch protects itself by turning requests away (backpressure) and returns HTTP 429 (Too Many Requests).","cause":"Very high concurrent request volume, very large or very frequent bulk requests, heavy aggregation/search load, insufficient node/CPU resources, or a thread pool queue too small for the workload.","fix":"Retry on the client side with exponential backoff; 429 is normally transient. Reduce the bulk batch size and sending rate, and lower concurrency. Monitor rejected operations: GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected. For sustained load, add nodes/resources (horizontal scaling). Increasing the queue size is a last resort; the root cause is capacity.","tags":["rejected-execution","429","thread-pool","queue","bulk","backpressure","danger"]},{"code":"master_not_discovered_exception","title":"master_not_discovered_exception / MasterNotDiscoveredException","severity":"danger","meaning":"The cluster could not elect/find an active master node. Without a master, shard allocation, mapping changes and cluster state updates cannot happen; the cluster becomes unable to process operations.","cause":"A majority (quorum) of master-eligible nodes is unreachable, a network partition (split brain prevention), incorrect discovery.seed_hosts / cluster.initial_master_nodes configuration, no bootstrap on a new installation, or too few master-eligible nodes.","fix":"Verify that master-eligible nodes are running and can see each other: GET _cat/nodes?v and GET _cluster/state. Check the discovery.seed_hosts and cluster.initial_master_nodes settings (on first setup). Verify network/firewall and inter-node (transport 9300) connectivity. Keep enough master-eligible nodes (preferably 3) running. Inspect the logs for master election messages.","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)","severity":"danger","meaning":"Authentication (401 Unauthorized) or authorization (403 Forbidden) failed. 401 indicates missing/incorrect credentials; 403 indicates the user is authenticated but lacks the required role/privilege for this action/index.","cause":"Wrong username/password or API key, a missing Authorization header, an expired/invalid token, the user lacking the relevant index or cluster privilege, or a missing/incorrect role assignment.","fix":"For 401: send the correct credentials/API key and check the Authorization header. For 403: inspect the user's roles and privileges (GET _security/user/app_user, GET _security/role/app_role) and add the required index/cluster privileges. Follow the principle of least privilege. If you use an API key, verify its expiration and permissions.","tags":["security","401","403","authentication","authorization","security","danger"]},{"code":"validation_exception","title":"validation_exception / illegal_argument_exception: Validation Failed: ...","severity":"caution","meaning":"The parameters or settings of a request failed validation. Elasticsearch rejected the request containing invalid or inconsistent values without executing it (e.g. invalid shard count, incompatible setting combination, missing required field).","cause":"An invalid index setting (e.g. negative/zero number_of_shards), an attempt to update a non-modifiable setting on an existing index, an incompatible parameter combination, a missing required field, or an out-of-range value.","fix":"Read the validation detail in the error message; it states clearly which parameter is invalid and why. Bring the value into a valid range/format. Specify non-modifiable (static) settings when creating a new index, not on an existing index (_reindex if needed). Supply the required fields the API expects. Verify the documented valid values of the setting.","tags":["validation","validation","settings","illegal-argument","invalid","caution"]},{"code":"task_cancelled_exception","title":"task_cancelled_exception: Task was cancelled / TaskCancelledException","severity":"caution","meaning":"A running task (search, _reindex, _update_by_query, etc.) was cancelled. Cancellation can happen by a user request to _tasks/_cancel, by the client closing the HTTP connection (client timeout/abort; Elasticsearch cancels the associated search automatically), or by cancellation of a parent task or an expired async search.","cause":"The user cancelled the task with POST _tasks/<task_id>/_cancel (X-Opaque-Id only identifies and helps find the task; it does not cancel it), a client timeout dropped the connection, long-running expensive queries, or adaptive resource management stopped a heavy task.","fix":"If the task was cancelled unexpectedly, review client timeout and connection settings; optimize the heavy query (less data, more specific filters). For long operations, run _reindex/_update_by_query asynchronously (wait_for_completion=false) and monitor with GET _tasks. View active tasks: GET _tasks?detailed=true. If needed, split expensive queries or use 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","severity":"danger","meaning":"An error occurred during recursive SQL that Oracle runs internally in the background. This is a wrapper (outer) error that hides the actual error; the real cause is in the second error code listed directly below it.","cause":"Data dictionary access, a failing DDL trigger, a full SYSTEM tablespace, insufficient temporary space, a corrupted data dictionary object, or a failing LOGON trigger that runs at session login.","fix":"Read the second line that follows ORA-00604 in the error stack; that is the real cause, and fix it. For example, if you see ORA-01653, extend the tablespace. If a DDL/LOGON trigger is suspect, disable it temporarily: ALTER TRIGGER trigger_name DISABLE; Check SYSTEM and TEMP tablespace usage with DBA_FREE_SPACE.","tags":["recursive","recursion","dictionary","data dictionary","trigger","wrapper"]},{"code":"ORA-01438","title":"value larger than specified precision allowed for this column","severity":"caution","meaning":"A number exceeding the defined precision and scale limits was written to a NUMBER column. For example, a value with a 3-digit integer part does not fit in a NUMBER(4,2) column.","cause":"The column's NUMBER(p,s) definition is too narrow for the data; a calculation result is larger than expected; the decimal scale reduces the digits left for the integer part (NUMBER(5,3) allows only 2 integer digits).","fix":"Widen the column capacity to match the real data range: ALTER TABLE my_table MODIFY (my_column NUMBER(12,2)); Round the value in the application, or fit it to the scale with ROUND/TRUNC. To find the overflowing row, narrow down your query while testing, and add input validation.","tags":["number","precision","precision","scale","scale","overflow","numeric"]},{"code":"ORA-01400","title":"cannot insert NULL into (column)","severity":"caution","meaning":"A NULL value was inserted into, or set on, a column with a NOT NULL constraint. A mandatory field cannot be left empty.","cause":"No value supplied for a mandatory column in an INSERT, an UPDATE setting the column to NULL, no DEFAULT defined for the column, or an empty value coming from a subquery/application.","fix":"Supply a valid value for the column named in the error message. Define a sensible default for the column: ALTER TABLE my_table MODIFY (my_column DEFAULT 0); (DEFAULT applies only when the column is omitted from the INSERT entirely; for explicitly sent NULL values use 12c+ DEFAULT ON NULL: ALTER TABLE my_table MODIFY (my_column DEFAULT ON NULL 0);) If the rule is not truly mandatory, drop the constraint: ALTER TABLE my_table MODIFY (my_column NULL); Validate empty values in the application before sending them.","tags":["not null","null","empty","insert","constraint","constraint","mandatory"]},{"code":"ORA-02291","title":"integrity constraint violated - parent key not found","severity":"danger","meaning":"A foreign key constraint was violated. No parent row was found corresponding to the value inserted into the child table; that is, the referenced record does not exist.","cause":"An INSERT/UPDATE referencing a nonexistent parent record, the parent table row not inserted yet, the wrong load order (child before parent), or the match failing due to case or whitespace differences.","fix":"Insert the relevant record into the parent table first, then into the child table. Verify that the reference exists: SELECT * FROM parent_table WHERE key_col = value; In bulk loads, process tables in the correct order (parent first). To find the violating row, compare the value in the child table with the parent table using NOT IN / an anti-join.","tags":["fk","foreign key","foreign key","constraint","constraint","parent","reference"]},{"code":"ORA-02292","title":"integrity constraint violated - child record found","severity":"danger","meaning":"A parent row was deleted or its key changed, but the foreign key constraint did not allow it because dependent child records exist.","cause":"Trying to DELETE a parent row that has records in the child table; the FK is defined without ON DELETE CASCADE; dependent records were not cleaned up first.","fix":"First delete the dependent child records or move them to another parent record, then delete the parent row. If the business rule allows it, recreate the foreign key with 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; To find dependent rows, query the child table by the parent key.","tags":["fk","foreign key","foreign key","child","child record","delete","cascade"]},{"code":"ORA-00979","title":"not a GROUP BY expression","severity":"caution","meaning":"A non-aggregated column was used in the SELECT list together with an aggregate function (SUM, COUNT, etc.), but the column was not added to the GROUP BY clause. Oracle does not know how to group that column.","cause":"A column in the SELECT but not in the GROUP BY; expressions outside aggregate functions missing from the GROUP BY list; inconsistency in HAVING or select expressions.","fix":"Add all non-aggregated columns to the GROUP BY list: SELECT department, COUNT(*) FROM employee GROUP BY department; Alternatively, wrap that column in an aggregate function (MAX, MIN) too, or redesign the grouping with an analytic function (OVER()).","tags":["group by","grouping","aggregate","aggregation","sum","count","query"]},{"code":"ORA-00918","title":"column ambiguously defined","severity":"caution","meaning":"A column with the same name exists in more than one table in the query, and because it is used without a table/alias prefix, Oracle cannot determine which table it belongs to.","cause":"Both joined tables have a column with the same name (e.g. ID in both); the column is used unqualified (without a prefix) in SELECT, WHERE or ORDER BY.","fix":"Qualify every ambiguous column with the table name or alias: SELECT a.id, b.id FROM tableA a JOIN tableB b ON a.id = b.a_id; Give tables short aliases and prefix all columns to improve readability and clarity.","tags":["ambiguous","ambiguous column","column","column","join","alias","query"]},{"code":"ORA-06550","title":"line/column PLS-... PL/SQL compilation error","severity":"caution","meaning":"A syntax or semantic error was found while compiling a PL/SQL block, procedure or anonymous block. The code failed at the compilation stage before it could run.","cause":"Typos, a reference to an undeclared variable or object, a call with the wrong number/type of parameters, a missing semicolon, a call to a nonexistent procedure/package, or a missing privilege.","fix":"Go to the line and column number in the error message; read the PLS-XXXX sub-code. To see all compilation errors: SHOW ERRORS; or SELECT * FROM user_errors WHERE name = 'OBJECT_NAME'; Verify object names, parameter signatures and the required GRANTs, then recompile.","tags":["plsql","compilation","compile","pls","syntax","procedure","code"]},{"code":"ORA-04068","title":"existing state of packages has been discarded","severity":"caution","meaning":"While a running session was using a package's state, the package was recompiled, so that state was invalidated. The package instance held by the session is no longer valid.","cause":"A package body or specification was recompiled with ALTER/CREATE OR REPLACE while sessions depending on it were active; it usually occurs during application deployment on a live system. Seen together with ORA-04061/04065.","fix":"Re-running the call (retry) usually resolves it; on the second attempt the package loads in its new form. Catch ORA-04068 in the application and retry automatically. Apply package changes in maintenance windows when user activity is low. A stateless package design reduces this problem.","tags":["package","package","state","package state","recompile","compile","retry"]},{"code":"ORA-01427","title":"single-row subquery returns more than one row","severity":"caution","meaning":"A subquery expected to return a single row returned more than one row. For example, the subquery on the right side of an equality (=) comparison produced multiple values.","cause":"A subquery returning multiple rows where = or an assignment is expected; an insufficiently narrowed WHERE condition; a non-unique match in the correlation; a scalar subquery in the SELECT list returning multiple rows.","fix":"Narrow the subquery so it returns a single row (additional WHERE, unique key). If multiple values are really expected, use IN instead of =: WHERE id IN (SELECT id FROM ...). Or add an aggregate to the subquery (MAX, MIN), or reduce it to one row with ROWNUM = 1. First run the subquery on its own and check how many rows it returns.","tags":["subquery","subquery","single row","single row","in","scalar","query"]},{"code":"ORA-12899","title":"value too large for column","severity":"caution","meaning":"A value longer than the defined maximum length was written to a character/data column. For example, a 15-character text does not fit in a VARCHAR2(10) column.","cause":"The column width is insufficient for the data; in a multibyte character set (UTF8) the character count exceeds the byte limit (BYTE semantics); a different character set during data migration; a concatenation result growing longer.","fix":"Widen the column: ALTER TABLE my_table MODIFY (my_column VARCHAR2(100)); For multibyte content use CHAR semantics: my_column VARCHAR2(100 CHAR). If long values must be shortened, truncate them with SUBSTR. The error message gives the actual and allowed length; find the overflowing record with 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","severity":"danger","meaning":"In a MERGE (or updatable join) statement, the source side produced more than one matching row for the same target row. Oracle stopped the operation because it cannot decide which source value to update with.","cause":"The MERGE ... USING source contains duplicate matches for the ON condition; the source query is not unique on the target key; multiple source rows map to the same target row.","fix":"Make the source query unique on the target key. Find duplicate matches: SELECT key_col, COUNT(*) FROM (source_query) GROUP BY key_col HAVING COUNT(*) > 1; Deduplicate the source with DISTINCT or an aggregate/analytic function (ROW_NUMBER), then rerun the MERGE. Make sure the ON condition is selective enough.","tags":["merge","merge","duplicate","duplicate rows","source","source","stable"]},{"code":"ORA-08103","title":"object no longer exists","severity":"danger","meaning":"An object (table, index, LOB segment) accessed by a running query no longer exists, or its physical structure changed. Usually a DDL/TRUNCATE happened underneath the object while the query was in progress.","cause":"The same object was TRUNCATEd/DROPped/SHRUNK while the query was open; a move (MOVE) or partition maintenance; a corrupted index/segment; the underlying structure disappearing while a LOB segment is accessed; a concurrent DDL conflict.","fix":"Verify that the object still exists and is valid: SELECT status FROM user_objects WHERE object_name = 'NAME'; Rebuild the relevant index/object (REBUILD) or rerun the query. Avoid TRUNCATE/DROP on objects while long-running queries use them. If corruption is suspected, check with ANALYZE ... VALIDATE STRUCTURE.","tags":["object","object","truncate","drop","ddl","corruption","segment"]},{"code":"ORA-01843","title":"not a valid month","severity":"caution","meaning":"While converting text to a DATE, the month part could not be interpreted as a valid month. Usually the date format does not match the format of the data.","cause":"No format mask passed to TO_DATE and the session's NLS_DATE_FORMAT does not match the data; day and month swapped (DD/MM vs MM/DD confusion); a language/locale difference (in month names); corrupted or unexpected date text.","fix":"Always pass an explicit format in TO_DATE calls: TO_DATE('2026-07-04', 'YYYY-MM-DD'). For text containing month names, specify the language: TO_DATE(str, 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH'). Set the session format if needed: ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'. Validate invalid date text before loading.","tags":["date","date","to_date","nls","format","month","month"]},{"code":"ORA-14400","title":"inserted partition key does not map to any partition","severity":"danger","meaning":"The partition key value in the inserted row does not map to any partition defined on the table. No range or list partition exists that covers the value.","cause":"In RANGE partitioning, a date/value above the highest range was inserted and there is no MAXVALUE partition; in LIST partitioning, a new value that is not defined; the new partition was not created in advance at a month/year boundary.","fix":"Add the missing partition: ALTER TABLE my_table ADD PARTITION p_2026_08 VALUES LESS THAN (TO_DATE('2026-09-01','YYYY-MM-DD')); For RANGE, add a MAXVALUE partition as a safety net. Define a DEFAULT partition for LIST to catch unexpected values. Use automatic partitioning (INTERVAL partitioning) so new partitions are created automatically.","tags":["partition","partition","partition key","range","list","maxvalue","insert"]},{"code":"ORA-01536","title":"space quota exceeded for tablespace","severity":"danger","meaning":"The user's space quota on the specified tablespace is exhausted. Even if the tablespace has free space, new space cannot be allocated because the limit granted to the user has been exceeded.","cause":"The user has a limited quota (not UNLIMITED); the quota filled up with data growth; an excessive load into a table/index; writing to the wrong tablespace; segments growing faster than expected.","fix":"Increase the user's quota: ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; or give a specific value: QUOTA 500M ON users. See current usage: SELECT * FROM dba_ts_quotas WHERE username = 'APP_USER'. Archive/purge unnecessarily large segments or move objects to an appropriate tablespace.","tags":["quota","quota","tablespace","space","space","user","user"]},{"code":"direct path read","title":"Wait: session reading blocks directly into PGA bypassing buffer cache","severity":"caution","meaning":"The session spends time in this wait event while reading blocks directly into PGA memory rather than through the database buffer cache. It is usually associated with large full table scans and parallel queries.","cause":"Many blocks read from disk because of full scans of large tables, parallel queries, sort/hash operations spilling to the temporary tablespace, the serial direct path read decision for serial full scans, and queries that cannot use an index.","fix":"Identify SQL performing heavy full scans with AWR/ASH and add appropriate indexes. Reduce unnecessary scans of large tables through query tuning. Investigate storage latency (I/O) and move to faster disks if needed. Review the degree of parallelism. For operations spilling to TEMP, evaluate the PGA_AGGREGATE_TARGET size.","tags":["wait","wait","direct path read","full scan","full table scan","io","pga"]},{"code":"latch: cache buffers chains","title":"Wait: contention on cache buffers chains latch","severity":"danger","meaning":"There is contention on the cache buffers chains (CBC) latch, which protects blocks in the buffer cache. This wait rises when many sessions try to reach the same frequently accessed (hot) blocks.","cause":"Frequently read hot blocks; inefficient SQL generating high logical reads; repeated access to the same index root blocks in nested loop joins; unevenly distributed heavy concurrent access; small but heavily read tables.","fix":"Find the SQL generating the most logical reads (V$SQL, ASH) and reduce buffer access by improving indexes/the access plan. Identify hot blocks (X$BH / related views). Consider converting unnecessary nested loops to hash joins. For small, heavily read reference tables, spread the access pattern or cache them.","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":"The function (or operator) called in the query was not found with this name and these argument types. PostgreSQL resolves functions by both name and argument types; if the signature does not match, this error is raised.","cause":"A misspelled function name; argument types that do not match the defined signature (e.g. a number instead of text); a missing explicit cast; the function's schema not being in search_path; the required extension not being installed.","fix":"Verify that the function exists and check its signature: \\df function_name. Cast the arguments explicitly: SELECT fn(value::text). Add the schema prefix or set search_path. If an extension is required, install it: CREATE EXTENSION extension_name; For operator errors, make the types on both sides match.","tags":["function","function not found","undefined","operator","cast","type conversion","42883"]},{"code":"42804","title":"datatype_mismatch","severity":"caution","meaning":"The expected data type and the supplied data type do not match. For example, assigning a value of the wrong type to a column, CASE branches returning different types, or UNION branches whose types do not line up.","cause":"An assignment incompatible with the column type; CASE/COALESCE branches producing different types; corresponding columns in a UNION having different types; a RETURNed value not matching the function's return type; a non-boolean where a boolean is expected.","fix":"Cast the expression to the correct type: value::integer or CAST(value AS numeric). Make all CASE branches return the same type. In a UNION, align the types of corresponding columns on both sides. Make sure the WHERE condition yields a boolean.","tags":["type","data type","mismatch","type mismatch","cast","union","42804"]},{"code":"22P02","title":"invalid_text_representation","severity":"caution","meaning":"A text value was not a valid representation for the target data type during conversion. For example, trying to convert the text 'abc' to integer raises this error.","cause":"Non-numeric text sent to a numeric column/parameter; an empty string ('') converted to a number/date; invalid text for a UUID or enum; malformed JSON; input data not in the expected format.","fix":"Verify that the converted value is actually valid. Validate input in the application before type conversion. Convert empty strings to NULL: NULLIF(value, ''). For safe conversion on PostgreSQL 16+, test the value beforehand with pg_input_is_valid('abc', 'integer') and use pg_input_error_info() for error details; on older versions, consider a validation check (regex). Narrow down which row is bad.","tags":["cast","type conversion","invalid text","invalid input","integer","uuid","22P02"]},{"code":"22003","title":"numeric_value_out_of_range","severity":"caution","meaning":"A numeric value is outside the range allowed by the target type. For example, writing a value larger than 2 billion into an integer column, or a number too large for a numeric(5,2) column.","cause":"Exceeding smallint/integer/bigint limits; exceeding numeric(p,s) precision/scale; arithmetic overflow; a sum/product exceeding the type's capacity; a sequence reaching the upper bound of its type.","fix":"Change the column/parameter to a wider type (bigint instead of integer, increase numeric precision): ALTER TABLE my_table ALTER COLUMN my_column TYPE bigint; Round (ROUND) or validate the value according to the business rule. If an intermediate calculation overflows, compute the expression as numeric. For sequence overflow, widen the type.","tags":["numeric","number","out of range","overflow","numeric overflow","integer","22003"]},{"code":"23514","title":"check_violation","severity":"caution","meaning":"A CHECK constraint was violated. The inserted or updated row does not satisfy a logical condition defined on the table (e.g. price > 0).","cause":"Data violating a business rule (negative price, invalid status code, date range violation); no validation on the application side; a constraint too strict for the data; records breaking the rule during a bulk load.","fix":"Use the constraint name in the error message to find which condition was violated; view the definition: \\d table_name. Query the violating rows by inverting the condition. Fix the data, or if the rule has really changed, update the constraint: 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":"An arithmetic expression attempted division (or modulo) by zero. PostgreSQL does not allow division by zero and aborts the query.","cause":"The divisor column/expression evaluating to 0; a zero denominator in a ratio/percentage calculation; dividing by a COUNT or SUM that is zero; unexpected zero values in unfiltered data.","fix":"Use NULLIF to return NULL when the denominator is zero: numerator / NULLIF(denominator, 0). If desired, turn the result into a default with COALESCE: COALESCE(numerator / NULLIF(denominator,0), 0). Or use CASE WHEN denominator = 0 THEN NULL ELSE numerator/denominator END. Filter out zero cases in the data beforehand.","tags":["division","divide","zero","divide by zero","nullif","arithmetic","22012"]},{"code":"42601","title":"syntax_error","severity":"caution","meaning":"A syntax error was found in the SQL statement. PostgreSQL could not parse the query and reports the approximate location (position) of the error.","cause":"A missing/extra comma or parenthesis; a reserved keyword (such as user, order) used as an unquoted column name; a missing semicolon; a misspelled command; an unterminated string literal; use of version-specific syntax.","fix":"Use the 'position' value in the error message to locate the problem; inspect the part immediately before it. Quote reserved words with double quotes: \"order\". Check that parentheses and quotes balance. Test a complex query by splitting it into pieces. Consult the documentation for your version for the correct syntax.","tags":["syntax","syntax error","parse","parser","keyword","position","42601"]},{"code":"42P07","title":"duplicate_table","severity":"caution","meaning":"The table (or similar object) being created already exists. CREATE failed because an object with the same name is present.","cause":"Re-creating an existing table with CREATE TABLE; running the script twice; re-applying migrations; a name clash with a view/sequence/index of the same name.","fix":"Use IF NOT EXISTS to skip creation when the object already exists: CREATE TABLE IF NOT EXISTS my_table (...). If you really want to re-create it, drop it first: DROP TABLE IF EXISTS my_table; Make migrations safe to re-run (idempotent). Check the existing object with \\dt.","tags":["duplicate","duplicate object","table","relation","create","already exists","42P07"]},{"code":"42701","title":"duplicate_column","severity":"caution","meaning":"The same column name was used more than once in a table definition or ALTER operation, or an already existing column was added again.","cause":"Writing the same column name twice in CREATE TABLE; adding an already existing column with ALTER TABLE ADD COLUMN; a copy-paste mistake; a migration running again.","fix":"Remove or rename the duplicate column name in the table definition. Check first to avoid re-adding an existing column: \\d table_name. Where the version supports it, add safely: ALTER TABLE my_table ADD COLUMN IF NOT EXISTS my_column type; Make sure migrations are idempotent.","tags":["duplicate","duplicate column","column","column exists","alter","add column","42701"]},{"code":"3D000","title":"invalid_catalog_name","severity":"danger","meaning":"The database you are trying to connect to or access does not exist. No database with the given name was found.","cause":"A misspelled database name in the connection string; the database not yet created or dropped; case sensitivity; connecting to the wrong server; a wrong name in the environment (env) configuration.","fix":"List existing databases: psql -l or SELECT datname FROM pg_database. Correct the database name in the connection string. Create it if needed: CREATE DATABASE database_name; Verify that you are connecting to the correct server/port. Check the DB name in the application configuration.","tags":["database","database not found","catalog","does not exist","missing","connection","3D000"]},{"code":"3F000","title":"invalid_schema_name","severity":"caution","meaning":"The referenced schema does not exist or cannot be found in search_path. There is no schema with the given name.","cause":"A misspelled schema name; the schema never created; search_path not including the schema; a case difference; a wrong prefix when accessing an object with a schema prefix.","fix":"List existing schemas: \\dn or SELECT schema_name FROM information_schema.schemata. Correct the schema name. Create it if needed: CREATE SCHEMA schema_name; Set search_path: SET search_path TO schema_name, public; Access objects with the correct schema prefix.","tags":["schema","schema not found","search_path","does not exist","missing","invalid","3F000"]},{"code":"0A000","title":"feature_not_supported","severity":"caution","meaning":"The feature you are trying to use is not supported in this context or in this PostgreSQL version. Even though the requested operation may be valid, it cannot be applied in the current situation.","cause":"Syntax/feature not yet available in the version; an ALTER not supported for a particular column/table type; an operation not supported by a foreign data wrapper (FDW); restrictions on logical replication/constraint combinations; conversions not allowed for certain data types.","fix":"Read the full error message; it usually states clearly what is not supported. Use an alternative approach (e.g. new column + copy + rename instead of changing the column type directly). If the feature arrived in a newer version, consider upgrading. If you hit FDW/extension limits, check the documentation.","tags":["not supported","unsupported","feature","unsupported feature","version","upgrade","0A000"]},{"code":"57P03","title":"cannot_connect_now","severity":"danger","meaning":"The server cannot accept connections right now; usually the database is still starting up or performing recovery. Connections can be opened once the system is ready.","cause":"PostgreSQL was just started and is still in its startup/recovery phase; crash recovery; during a restore from backup; a standby not yet ready to accept queries; a shutdown in progress.","fix":"Wait briefly and reconnect; the server accepts connections once startup completes. Monitor recovery progress in the server log. If it takes long, there may be a large WAL replay or a recovery problem. Add retry logic to the application.","tags":["connection","connection refused","starting up","startup","recovery","crash recovery","57P03"]},{"code":"2BP01","title":"dependent_objects_still_exist","severity":"caution","meaning":"The drop was blocked because other objects depend on the object being dropped. DROP fails as long as dependent objects exist.","cause":"Trying to drop a table while views/foreign keys/functions depend on it; objects depending on a type or schema; DROP without CASCADE; a column/sequence referenced by other objects.","fix":"Remove the dependent objects first or drop with CASCADE: DROP TABLE my_table CASCADE; (remember that CASCADE also drops the dependent objects; be careful). To see the dependencies, read the error detail (DETAIL line) or inspect pg_depend/the relevant catalogs. Plan the drop order correctly.","tags":["dependency","dependent objects","cascade","drop","objects","object","2BP01"]},{"code":"55006","title":"object_in_use","severity":"danger","meaning":"The operation cannot be performed on the target object (usually a database) because it is being used by other sessions. For example, a database with active connections cannot be dropped.","cause":"Open connections to the database during DROP DATABASE; an operation requiring an exclusive lock on an object in use; another session holding the object.","fix":"Find and terminate open connections to the target database: SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'db_name' AND pid <> pg_backend_pid(); Block new connections (if needed). On PostgreSQL 13+, drop it while forcibly closing connections: 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":"A text value was not in a valid format when converted to a date/time type (date, time, timestamp). The value does not match the expected date-time format.","cause":"Date text in an unexpected format (day/month/year order confusion); invalid day/hour values; converting an empty string ('') to a date; missing or extra components; locale/format mismatch; a wrong format mask for to_timestamp/to_date.","fix":"Convert the date text with the correct format: to_date('2026-07-04', 'YYYY-MM-DD') or to_timestamp(str, 'YYYY-MM-DD HH24:MI:SS'). Prefer the ISO 8601 format (YYYY-MM-DD). Turn empty values into NULL with NULLIF(value,''). Validate input data before loading; narrow down the bad row.","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","severity":"caution","meaning":"A column with the same name exists in more than one table in the query, and because it is used without a table/alias prefix MySQL cannot tell which table it belongs to.","cause":"Both joined tables having a column with the same name (e.g. id in both); the column written unqualified (without a prefix) in SELECT, WHERE, ON, GROUP BY or ORDER BY.","fix":"Qualify every ambiguous column with the table name or alias: SELECT a.id, b.id FROM a JOIN b ON a.id = b.a_id; Give tables short aliases and prefix all conflicting columns. Reduce ambiguity in joins on common columns with USING(my_column).","tags":["ambiguous","ambiguous column","column","column name","join","alias","1052"]},{"code":"1136","title":"Column count doesn't match value count","severity":"caution","meaning":"The number of columns in the INSERT statement does not match the number of values in the VALUES list. Each column must get exactly one value.","cause":"The column list and the value list in an INSERT having different lengths; in an INSERT without a column list, the values not matching the table's column count; a missing/extra comma; the table structure changed but the script was not updated.","fix":"Make the column count and value count equal. Always use an explicit column list: INSERT INTO my_table (a, b, c) VALUES (1, 2, 3); Check the table structure: DESCRIBE my_table; Match the values one-to-one with the columns and fix extra/missing commas.","tags":["column count","column count mismatch","insert","values","mismatch","value count","1136"]},{"code":"1048","title":"Column 'x' cannot be null","severity":"caution","meaning":"An attempt was made to insert NULL into a column defined NOT NULL, or to set that column to NULL. A mandatory field cannot be left empty.","cause":"No value given for a mandatory column in the INSERT; an UPDATE setting the column to NULL; no DEFAULT for the column; an empty value coming from the application/a subquery; strict SQL mode not silently filling the empty value.","fix":"Provide a valid value for the mandatory column. Define a sensible default for the column: ALTER TABLE my_table MODIFY my_column INT NOT NULL DEFAULT 0; If the rule is unnecessary, allow NULL: ALTER TABLE my_table MODIFY my_column INT NULL; Validate empty values in the application before sending them.","tags":["not null","null","empty value","insert","cannot be null","mandatory","1048"]},{"code":"1292","title":"Incorrect datetime value / truncated incorrect value","severity":"caution","meaning":"A value was invalid or malformed when converted to the target column type (especially date/time or numeric). In strict mode MySQL rejects it as an error.","cause":"Invalid dates or values such as '0000-00-00'; a wrong date format; non-numeric text in a numeric column; strict SQL mode (STRICT_TRANS_TABLES) being active; conversion problems caused by a different character set/collation.","fix":"Bring the value into a valid format for the column type ('YYYY-MM-DD' for dates). Use NULL instead of empty/invalid dates. If needed, give an explicit format with STR_TO_DATE: STR_TO_DATE(str, '%Y-%m-%d'). If temporary tolerance is required, review sql_mode, but preferably fix the data. Narrow down to find the offending row.","tags":["datetime","date","truncated","incorrect value","strict mode","format","1292"]},{"code":"1406","title":"Data too long for column","severity":"caution","meaning":"A value longer than the column's defined maximum length was written. For example, a 20-character string does not fit into a VARCHAR(10) column.","cause":"Column width too small for the data; characters in a multibyte character set (utf8mb4) taking more space than expected; a concatenation result growing longer; strict SQL mode being active (otherwise the value is silently truncated).","fix":"Widen the column: ALTER TABLE my_table MODIFY my_column VARCHAR(255); For very long content, consider the TEXT type. If the value must be shortened, trim it in the application or with SUBSTRING. Find the overflowing row: SELECT * FROM my_table WHERE CHAR_LENGTH(my_column) > 10; Add input validation.","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","severity":"caution","meaning":"The index (or key) being created exceeded the maximum allowed length in bytes. With multibyte character sets in particular, VARCHAR indexes reach this limit quickly.","cause":"An index on a wide VARCHAR column with utf8mb4 (4 bytes per character); an old InnoDB row format (REDUNDANT/COMPACT; on 5.7 and earlier also a disabled innodb_large_prefix); the total length of a multi-column composite index exceeding the limit; a large primary key.","fix":"Shorten the indexed column length or use a prefix index: CREATE INDEX idx ON my_table (my_column(191)); Move to the 3072-byte limit with the DYNAMIC/COMPRESSED row format (DYNAMIC is the default in MySQL 8.0; innodb_large_prefix was removed in 8.0 and is only needed on 5.7 and earlier). If needed, review the column's character set/length.","tags":["index","index length","key too long","key","prefix","utf8mb4","1071"]},{"code":"1050","title":"Table already exists","severity":"caution","meaning":"The table being created already exists. CREATE TABLE failed because a table with the same name is present.","cause":"Re-creating an existing table; a script run twice; migrations applied again; another object (a view) using the same name.","fix":"Use IF NOT EXISTS to create the table only if it is absent: CREATE TABLE IF NOT EXISTS my_table (...); If you really want to re-create it, drop it first: DROP TABLE IF EXISTS my_table; List existing tables: SHOW TABLES; Make migrations safe to re-run (idempotent).","tags":["table exists","table already exists","duplicate","duplicate table","create table","1050"]},{"code":"1051","title":"Unknown table 'x'","severity":"caution","meaning":"The referenced table does not exist. No table with the given name was found during a drop, alter or query.","cause":"A misspelled table name; the table never created or already dropped; the wrong database (schema) selected; case sensitivity (table names are case-sensitive on Linux); DROP TABLE on a nonexistent table.","fix":"List existing tables: SHOW TABLES; and select the right database: USE mydb; Fix the table name and its case. To avoid the error on drop: DROP TABLE IF EXISTS my_table; If it is in another schema, qualify it as 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","severity":"caution","meaning":"The column, index or foreign key being dropped with ALTER TABLE does not exist. A nonexistent object cannot be dropped.","cause":"Giving a nonexistent object name in DROP COLUMN/INDEX/FOREIGN KEY; a misspelled name; the object already dropped; confusing the foreign key name with the index name; a migration run a second time.","fix":"Verify the object's real name: SHOW CREATE TABLE my_table; or SHOW INDEX FROM my_table; Drop it using the correct name. For a foreign key use its symbol name: ALTER TABLE my_table DROP FOREIGN KEY fk_name; MySQL has no DROP COLUMN IF EXISTS syntax (only MariaDB does); in idempotent migrations, first check that the column exists 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","severity":"caution","meaning":"In an UPDATE or DELETE statement, the table being updated/deleted cannot itself be used directly as a source in a subquery (in FROM). MySQL does not allow the same table to be both target and source this way.","cause":"A subquery reading the same target table directly, as in UPDATE my_table ... WHERE id IN (SELECT ... FROM my_table ...); a subquery referencing the same table in a DELETE.","fix":"Wrap the subquery in a derived table and give it a new alias so MySQL treats it as a separate copy: UPDATE my_table SET c = 1 WHERE id IN (SELECT id FROM (SELECT id FROM my_table WHERE condition) AS t); Alternatively, write it as a self-join: 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","severity":"caution","meaning":"The called stored procedure or function was not found in the specified database. No routine with this name is defined.","cause":"A misspelled procedure name; the procedure in the wrong database or never created; the wrong schema selected with USE; no EXECUTE privilege to call the procedure; case sensitivity.","fix":"Verify that the procedure exists and in which schema: SHOW PROCEDURE STATUS WHERE Db = 'mydb'; Select the right database: USE mydb; or call it fully qualified: CALL mydb.my_procedure(...); Grant the required privileges: 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","severity":"danger","meaning":"The user lacks the privilege required for the attempted operation. The operation requires a right beyond the permissions granted to the account.","cause":"A missing administrative privilege such as SUPER, PROCESS, RELOAD or CREATE USER; the permission needed to read a global view/table not granted; a specific operation (SET GLOBAL, binlog access) requiring elevated privileges.","fix":"View current privileges: SHOW GRANTS FOR CURRENT_USER(); Grant the required permission with a privileged account: GRANT PROCESS ON *.* TO 'app_user'@'host'; (GRANT takes effect immediately; FLUSH PRIVILEGES is not needed). Where possible, use dynamic privileges (e.g. SYSTEM_VARIABLES_ADMIN) instead of SUPER. Follow the principle of least privilege.","tags":["access denied","privilege","missing privilege","permission","grant","access","1227"]},{"code":"3730","title":"Cannot drop table referenced by a foreign key constraint","severity":"danger","meaning":"The table cannot be dropped because another table's foreign key references it. As long as the reference exists, DROP is blocked.","cause":"Trying to drop a parent table while child tables linked to it by an FK still exist; not respecting the dependency order; not removing the foreign keys first.","fix":"First remove the referencing foreign key: ALTER TABLE child_table DROP FOREIGN KEY fk_name; then drop the table. Find dependent tables/constraints: SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME = 'my_table'; In bulk operations, SET FOREIGN_KEY_CHECKS = 0; can be used temporarily, but be careful and set it back to 1 afterwards.","tags":["foreign key","fk","drop table","referenced","reference","constraint","3730"]},{"code":"2013","title":"Lost connection to MySQL server during query","severity":"danger","meaning":"The connection between the client and the MySQL server dropped while the query was running. It was cut before the server could respond or return results.","cause":"A long-running query exceeding net_read_timeout/net_write_timeout; the server terminating the query/connection due to out-of-memory (OOM); a network outage; exceeding max_allowed_packet; the server crashing or restarting; a very large result set.","fix":"Check network stability and the server error log; see whether there was an OOM or crash. Increase the timeouts: SET GLOBAL net_read_timeout = 120; Split large queries into batches. Increase max_allowed_packet. Add connection retry logic in the application. Optimize long queries.","tags":["lost connection","connection dropped","timeout","network","network outage","oom","2013"]},{"code":"1153","title":"Got a packet bigger than 'max_allowed_packet' bytes","severity":"danger","meaning":"The client or server tried to send a data packet larger than the maximum allowed packet size (max_allowed_packet). This usually happens with large BLOB/TEXT values or very large bulk INSERTs.","cause":"Inserting very large BLOB/TEXT data; a huge single INSERT or multi-row bulk INSERT; a query returning a large result; a small max_allowed_packet value; importing large rows with mysqldump.","fix":"Increase max_allowed_packet (on both server and client): SET GLOBAL max_allowed_packet = 67108864; (64MB) and reconnect. To make it permanent, add max_allowed_packet = 64M under [mysqld] in my.cnf. Split large bulk operations into smaller batches. Evaluate whether you really need to store very large values.","tags":["max_allowed_packet","packet","packet size","blob","large data","insert","1153"]},{"code":"1969","title":"MariaDB: Query execution was interrupted (max_statement_time exceeded)","severity":"caution","meaning":"MariaDB-specific (ER_STATEMENT_TIMEOUT): the query was interrupted because it exceeded the maximum execution time defined by max_statement_time. MySQL does not have this code; the MySQL equivalent is error 3024 (ER_QUERY_TIMEOUT, max_execution_time).","cause":"In MariaDB, exceeding the session/global max_statement_time limit or a limit set with SET STATEMENT max_statement_time=N FOR ...; an inefficient plan, a missing index, a large data scan or lock waits.","fix":"Analyze the query with EXPLAIN and speed it up by adding indexes and improving the plan. If the query genuinely needs to run long, raise the limit in MariaDB: SET SESSION max_statement_time = 60; (seconds) or SET STATEMENT max_statement_time = 60 FOR SELECT ...; If you use MySQL, see the 3024 entry (max_execution_time, milliseconds; MAX_EXECUTION_TIME hint). Split large operations.","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","severity":"caution","meaning":"A divide-by-zero error occurred because the divisor in an arithmetic expression is zero. SQL Server cannot complete the division and stops the query.","cause":"A column or expression used as the divisor evaluates to 0, a zero denominator in average/ratio calculations, division without NULLIF, or unexpected zero values in the data.","fix":"Guard the denominator with NULLIF (e.g. value / NULLIF(denominator, 0)); the result becomes NULL and a default can be supplied with ISNULL/COALESCE. Use CASE WHEN denominator = 0 THEN NULL ELSE value/denominator END. Filter out zero values with WHERE before dividing. If needed, evaluate SET ARITHABORT/ANSI_WARNINGS behavior.","tags":["math","arithmetic","divide by zero","division by zero","8134"]},{"code":"245","title":"Conversion failed when converting the value to data type","severity":"caution","meaning":"A value could not be converted to the target data type. For example, an implicit/explicit conversion failed because of unsuitable content while converting a string value to int or date.","cause":"Converting non-numeric text to int/decimal, an invalid date format, implicit type conversion in column comparisons, incompatible types in a WHERE or JOIN condition, or malformed input data.","fix":"Use the safe conversion variants: TRY_CAST or TRY_CONVERT (return NULL on failure). Identify bad rows with ISNUMERIC/TRY_CONVERT. Align column types and eliminate implicit conversion. Validate input and use an explicit format for dates (e.g. a CONVERT style).","tags":["conversion","type conversion","cast","convert","data type","245"]},{"code":"515","title":"Cannot insert the value NULL into column","severity":"caution","meaning":"An attempt was made to insert or update a NULL value into a column defined as NOT NULL. The operation was rejected because the column does not allow nulls.","cause":"A required column omitted from the INSERT statement, missing/empty fields in the source data, a NOT NULL column without a default value, or NULL sent by the application.","fix":"Supply a valid value for required columns. Add DEFAULT constraints to appropriate columns. Validate the source data for missing fields and assign defaults with ISNULL/COALESCE. If the column does not really need to be mandatory, review the schema to allow NULL.","tags":["null","not null","insert","constraint","constraint violation","515"]},{"code":"8115","title":"Arithmetic overflow error converting expression to data type","severity":"caution","meaning":"The value produced by an arithmetic operation or conversion exceeded the range the target data type can hold. An overflow occurred because the number is too large.","cause":"A sum/product (SUM) exceeding the int range, a large value assigned to a column with a small type, insufficient decimal precision/scale, or an IDENTITY value reaching the type limit.","fix":"Use a wider type for intermediate calculations (e.g. SUM with CAST(my_column AS bigint)). Increase decimal precision/scale. Enlarge the column type to fit the data. Convert expressions at risk of overflow before computing and constrain input ranges.","tags":["overflow","arithmetic overflow","arithmetic","math","int","bigint","8115"]},{"code":"512","title":"Subquery returned more than 1 value","severity":"caution","meaning":"A subquery used in a context expecting a single value (=, <, >, comparison or assignment) returned more than one row. SQL Server expected a single scalar value but encountered multiple values.","cause":"A subquery assumed to return a single value for the comparison returns multiple rows, a non-unique match condition, a missing filter, or an incorrectly constructed correlated query.","fix":"Reduce the subquery to a single row (e.g. TOP 1, MAX/MIN, or an appropriate GROUP BY). If multiple values are expected, use IN or EXISTS instead of =. Rewrite with a JOIN. Investigate why the subquery returns multiple rows and fix the filter/key conditions.","tags":["subquery","scalar subquery","more than one","512","in","exists"]},{"code":"207","title":"Invalid column name","severity":"caution","meaning":"The column name referenced in the query was not found in the table/view. No column exists with that name, or it is out of scope.","cause":"A spelling/case error, the wrong table alias, a column not yet added or renamed, the wrong schema/table context, or omitting square brackets instead of quotes.","fix":"Verify the column name and table (check sys.columns or Object Explorer). Use the correct alias and schema. For newly added columns, make sure the object is up to date; recompile the stored procedure if needed. Use square brackets for names containing spaces/special characters.","tags":["column","column name","invalid column","invalid column name","alias","207"]},{"code":"156","title":"Incorrect syntax near the keyword","severity":"caution","meaning":"There is a syntax error near a keyword. SQL Server could not parse the statement because a reserved word is used in an unexpected position.","cause":"Using a reserved word (e.g. ORDER, USER, TABLE) as a column/object name without brackets, a missing comma/parenthesis, clauses in the wrong order, or a misplaced clause (WHERE, GROUP BY).","fix":"Inspect the area around the keyword the error points to. If you use reserved words as object names, wrap them in square brackets (e.g. [User]). Fix missing commas, parentheses, and clause order. Test the syntax by breaking the query into parts.","tags":["syntax","syntax error","keyword","reserved word","156"]},{"code":"2812","title":"Could not find stored procedure","severity":"caution","meaning":"The stored procedure called with EXEC was not found in the current database/schema scope. No procedure exists with that name.","cause":"A typo in the procedure name, a missing or wrong schema prefix (e.g. dbo. omitted), the procedure being in a different database, not yet created, or not visible due to permissions.","fix":"Verify the procedure name and schema; use the fully qualified name in the call (e.g. EXEC dbo.ProcedureName). Make sure you are in the correct database (USE DatabaseName). Check that the procedure exists (sys.procedures / OBJECT_ID). Grant the required EXECUTE permission and verify that the deployment script ran.","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","severity":"danger","meaning":"The query processor exhausted its internal resources for an overly complex query and could not produce an execution plan. The query cannot be optimized because it is too large/complex.","cause":"Queries with very long IN lists or many OR/UNION clauses, an excessive number of JOINs, huge auto-generated queries, queries with too many parameters, or excessively deep expression trees.","fix":"Simplify the query: move long IN lists into a temp table/table-valued parameter, reduce OR conditions with UNION ALL or a rewrite, and split the complex query into parts. Reduce the number of JOINs. Batch auto-generated queries. Review indexes and query design when needed.","tags":["query processor","internal resources","plan","complex query","8623","danger"]},{"code":"4104","title":"The multi-part identifier could not be bound","severity":"caution","meaning":"A dot-separated multi-part identifier (e.g. table.column or alias.column) could not be resolved. SQL Server could not bind this name to a table/column in the query.","cause":"A wrong or undefined table alias, a reference to a table not in FROM/JOIN, a column attributed to the wrong table, using the real table name after defining an alias, or a typo.","fix":"Verify that the aliases used are defined in the FROM/JOIN clause. Qualify the column with the correct table/alias. If you aliased a table, use the alias everywhere (not the real name). Add the missing table to the JOIN and check the names.","tags":["identifier","multi-part","multi-part identifier","bind","alias","4104"]},{"code":"2714","title":"There is already an object named in the database","severity":"caution","meaning":"An object with the same name as the one being created (table, view, procedure, etc.) already exists in the database. The CREATE statement failed due to a name conflict.","cause":"Running the same CREATE script a second time, not dropping the existing object first, a different schema assumption, non-idempotent deployment/migration scripts, or a naming conflict.","fix":"Check whether the object exists first: create conditionally with IF OBJECT_ID('dbo.Name') IS NULL, or use CREATE OR ALTER (for procedures/views/functions). If necessary, DROP the existing object and recreate it. Write migration scripts to be idempotent. Verify that you are working in the correct schema/database.","tags":["already exists","object exists","create","object","duplicate object","2714"]},{"code":"601","title":"Could not continue scan with NOLOCK due to data movement","severity":"caution","meaning":"During a scan with NOLOCK (READ UNCOMMITTED), the scan could not continue because of concurrent data movement (page splits/moves). The lock-free read hit an inconsistent state.","cause":"Scanned pages being relocated by concurrent updates/deletes/page splits while reading under the NOLOCK hint or the READ UNCOMMITTED isolation level.","fix":"Rerun the query (it is usually transient). Avoid the NOLOCK hint where possible; use READ COMMITTED SNAPSHOT (RCSI) isolation for non-blocking yet consistent reads. On write-heavy tables, prefer snapshot isolation over dirty reads.","tags":["nolock","scan","data movement","601","isolation","read uncommitted"]},{"code":"50000","title":"Custom error raised with RAISERROR","severity":"caution","meaning":"This is the default number (50000) for custom (user-defined) errors raised with RAISERROR without specifying a message ID. Application/procedure logic deliberately raised an error.","cause":"A custom error raised with RAISERROR or THROW in a stored procedure or script on a business rule violation, a validation failure, deliberate flow control, or custom exception messages.","fix":"Read the RAISERROR/THROW message text to understand the actual business rule violation. Catch this error on the application side with TRY...CATCH and get details with ERROR_MESSAGE()/ERROR_NUMBER(). Define messages with sp_addmessage to give your custom errors meaningful numbers. Fix the triggering condition in the business logic.","tags":["raiserror","custom error","user-defined error","throw","50000","try catch"]},{"code":"3902","title":"The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION","severity":"caution","meaning":"A COMMIT TRANSACTION was executed but there is no corresponding open BEGIN TRANSACTION. No transaction to commit was found because the transaction count (@@TRANCOUNT) is zero.","cause":"Unbalanced BEGIN/COMMIT/ROLLBACK usage, the transaction already rolled back after an error, incorrect management of nested transaction counts, or calling COMMIT again after ROLLBACK in a CATCH block.","fix":"Balance the number of BEGIN and COMMIT statements. Check @@TRANCOUNT before committing (IF @@TRANCOUNT > 0 COMMIT). In TRY...CATCH, on error ROLLBACK based on XACT_STATE()/@@TRANCOUNT and prevent an unnecessary COMMIT. Review nested transaction logic.","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":"The document being inserted or updated was rejected because it does not satisfy the JSON Schema validation rules (validator) defined on the collection.","cause":"The document does not conform to the schema: a missing required field, a wrong BSON type, a value outside the allowed range/pattern, additional fields being forbidden, or a rule violation while validationAction is 'error'.","fix":"Inspect the error details (errInfo) to see which rule was violated. Make the document conform to the schema: add required fields and correct the types. Review the collection validator with collMod. validationLevel/validationAction can be relaxed temporarily, but keeping the rules is preferable for data quality.","tags":["validation","validation","schema","schema","121","document"]},{"code":"40","title":"ConflictingUpdateOperators","severity":"caution","meaning":"An update applied more than one conflicting update operator to the same field (path). MongoDB cannot modify the same path twice in a single operation.","cause":"Conflicting operators targeting the same field in one update document (e.g. both $set and $inc, or modifying a parent and a child path at the same time), a badly constructed update, or a nested path conflict.","fix":"Merge the conflicting operators on the same field into a single operator, or split the update into separate operations. Do not modify a parent and a child path (e.g. a and a.b) at the same time. Review the update document and make sure each field is modified only once.","tags":["update","update","operator","conflict","conflict","40"]},{"code":"66","title":"ImmutableField (cannot update _id)","severity":"caution","meaning":"An attempt was made to update an immutable field, typically the document's _id field. _id cannot be changed after the document is created.","cause":"An update attempting to change the _id field, updating a shard key field in a way that is not allowed, or setting a field treated as immutable to a new value.","fix":"Do not update the _id value; if a different _id is needed, insert the document with the new _id and delete the old one (insert + delete). Follow MongoDB rules for shard key updates. Remove immutable fields from the update document and set only modifiable fields.","tags":["immutable","_id","immutable","update","shard key","66"]},{"code":"2","title":"BadValue","severity":"caution","meaning":"An invalid or unexpected value was passed to a command or query. MongoDB could not interpret the argument as an acceptable value.","cause":"An invalid operator argument, an out-of-range number, an unsupported option value, a malformed sort specification, a malformed query expression, or an inappropriate combination of parameters.","fix":"Correct the argument based on the field/value hint in the error message. Validate command and query options against the documentation. Check the format of sort/projection/aggregate expressions. Bring value ranges and types into the valid range.","tags":["badvalue","invalid value","argument","2","query"]},{"code":"14","title":"TypeMismatch","severity":"caution","meaning":"The type of the supplied value does not match the expected BSON type. A different data type was expected for a field or argument.","cause":"Text supplied where a number is expected, a string used instead of a date, a single value sent where an array is expected, an argument of the wrong type passed to an aggregate/query operator, or type inconsistency in the schema.","fix":"Supply the correct BSON type for the field/argument. Convert types in the application layer before saving data (e.g. write numbers as actual Number values). Verify the types expected by query and aggregate operators. Enforce type consistency with a schema validator.","tags":["typemismatch","type mismatch","bson","type","14"]},{"code":"10334","title":"BSONObjectTooLarge","severity":"danger","meaning":"A BSON document exceeded the maximum allowed size (16 MB). MongoDB cannot store or return a single document above this limit.","cause":"Exceeding the 16 MB limit by continuously appending data/array elements to a document, very large embedded arrays, huge documents produced by an aggregation, or large binary data stored directly in the document.","fix":"Review the data model: split large arrays into separate collections and use a referencing model. Use GridFS for large binary files. Bound unbounded arrays (e.g. the capped/bucket pattern). Keep document size under control in aggregation stages.","tags":["bson","16mb","too large","large document","gridfs","10334","danger"]},{"code":"46","title":"LockBusy","severity":"caution","meaning":"The requested lock or resource could not be acquired immediately because it was held by another operation, and the operation was rejected instead of waiting. Lock waits that end in a timeout are a separate code (24 LockTimeout).","cause":"Concurrent administrative/DDL operations on the same collection or database (e.g. two index builds at once, collMod, drop or rename), a conflict with an in-progress maintenance operation, or an internal operation that cannot wait even briefly for a lock hitting a busy resource.","fix":"Find the conflicting operation: use db.currentOp() to inspect DDL or maintenance operations running on the same namespace. Retry the command after the conflicting operation finishes. Serialize administrative/DDL operations and do not run them in parallel on the same collection. If it persists, investigate the operation holding the resource in the mongod logs.","tags":["lock","lock","lockbusy","busy","ddl","46"]},{"code":"96","title":"OperationFailed","severity":"danger","meaning":"A generic operation failure; an unexpected error occurred while the command was running and the operation could not complete. It is the generic error used when no more specific code exists.","cause":"A server-side internal error, resource exhaustion, an inconsistent state, a subsystem failure (storage, replication), or various failure conditions specific to a particular command.","fix":"Determine the root cause by examining the full error message text and the server logs (mongod.log). Retry the operation; the condition may be transient. Check resources (disk, memory) and replica set health. If the problem recurs, review the command and its input data.","tags":["operationfailed","operation failed","generic error","96","danger"]},{"code":"72","title":"InvalidOptions","severity":"caution","meaning":"Invalid or mutually incompatible options were passed to a command. The specified option combination is not supported for this command.","cause":"An unsupported option name, mutually exclusive options used together, an option not valid for the running version, an invalid write concern/read concern combination, or an index/collation option conflict.","fix":"Verify the options accepted by the command in the documentation and remove incompatible ones. Make sure your version supports the option. Review write/read concern and collation settings. Use only valid option combinations.","tags":["invalidoptions","invalid option","options","72","command"]},{"code":"9","title":"FailedToParse","severity":"caution","meaning":"The server could not parse the submitted command, query or expression. The structure is syntactically invalid or not in the expected format.","cause":"A malformed JSON/BSON structure, missing or extra fields, incorrect operator usage, invalid aggregation pipeline syntax, or a command document that does not match the expected format.","fix":"Correct the command/query/pipeline structure according to the documentation. Validate the JSON structure (missing brackets/commas, wrong field names). Check the syntax of aggregation stages. Make sure operators are used correctly and arguments are supplied in the expected format.","tags":["failedtoparse","parse","parsing","syntax","9","query"]},{"code":"134","title":"ReadConcernMajorityNotAvailableYet","severity":"caution","meaning":"A read with majority read concern was requested, but a majority-committed snapshot is not yet available. The server cannot currently provide this consistency level.","cause":"The replica set has just started or is unstable, majority writes have not yet been committed, replication lag, not enough members reachable, or the cluster is still in its startup phase.","fix":"Wait briefly and retry; the read succeeds once the majority snapshot is available. Check replica set health and replication lag (rs.status()). Make sure enough members are up. If not required, consider a lower read concern (e.g. local).","tags":["readconcern","majority","majority","replica","134","consistency"]},{"code":"202","title":"NetworkInterfaceExceededTimeLimit","severity":"danger","meaning":"A network request (e.g. inter-member communication or a client connection) exceeded its time limit. The operation could not complete because of a network timeout.","cause":"Network latency/outage, a slow or unreachable target member, excessive load, misconfigured timeout values, DNS/connection problems, or geographically distant members.","fix":"Check network connectivity and latency; verify reachability between members. Inspect the health and load of the target member. Configure timeout settings (connect/socket timeout) for your environment. Add retry logic. If it persists, review the infrastructure/network configuration.","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","severity":"caution","meaning":"The authenticated ACL user is not authorized for the command it wants to run or the key it accesses. Redis rejects the operation according to the ACL rules.","cause":"The ACL user lacks the command/category permission, the key pattern is not in its allow list, a channel (pub/sub) restriction applies, or the user was given narrowly scoped privileges.","fix":"Inspect the user's permissions: ACL GETUSER app_user. Add the required commands/categories and key patterns (e.g. ACL SETUSER app_user +get +set ~app:*). Following least privilege, grant only the permissions needed. Verify that you are connected as the right user.","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","severity":"caution","meaning":"A command expecting an integer (e.g. INCR, EXPIRE, LPOP count) received a non-numeric or out-of-range value. Redis could not interpret the value as an integer.","cause":"The key targeted by INCR/DECR holding a text (non-integer) value, an argument given as text instead of a number, a number too large/small for the range, or a negative value where none is allowed.","fix":"Check the key's value: GET mykey and TYPE mykey. For numeric operations, make sure the key holds an integer value. Verify that arguments are valid integers within the correct range. If needed, reset the value (SET mykey 0) or re-create it with the correct 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","severity":"caution","meaning":"A command expecting a decimal (float) number (e.g. INCRBYFLOAT, a ZADD score) received a value that is not a valid floating-point number. Redis could not parse the value as a float.","cause":"The key targeted by INCRBYFLOAT not holding a valid float, a non-numeric argument, the wrong decimal separator (a dot is required, not a comma) or a malformed number.","fix":"Check the key's value with GET and make sure it contains a valid float. Use a dot as the decimal separator (e.g. 3.14). Verify that the argument is numeric and correctly formatted. If necessary, reset the value to a valid float.","tags":["err","float","decimal","incrbyfloat","zadd","value"]},{"code":"ERR increment or decrement would overflow","title":"ERR increment or decrement would overflow","severity":"caution","meaning":"An INCR/DECR (or INCRBY/DECRBY) was rejected because the result would overflow the 64-bit signed integer range. The value would exceed the upper or lower bound.","cause":"The key's value being very close to the int64 limit (about 9.2 quintillion) so that the increment/decrement overflows, a very large INCRBY argument, or a counter growing uncontrolled.","fix":"Design counter values to stay within int64 bounds. For large counts, apply scaling or periodic resets. Keep the increment (the INCRBY argument) within a reasonable range. If very large values are required, use a different counting strategy in the application layer.","tags":["err","overflow","integer overflow","incr","decr","int64"]},{"code":"ERR max number of clients reached","title":"ERR max number of clients reached","severity":"danger","meaning":"The number of connected clients reached the maxclients limit and new connections cannot be accepted. Redis refuses further client connections.","cause":"A connection leak (connections never closed), an insufficient maxclients value, a misconfigured connection pool, a sudden surge of clients, or a low operating system file descriptor limit.","fix":"Inspect current connections: CLIENT LIST and INFO clients. Fix connection leaks; size the client pool correctly and reuse connections. If needed, raise maxclients (CONFIG SET maxclients) and the OS ulimit (file descriptor) limit. Set a timeout for idle connections.","tags":["err","maxclients","connection","client","limit","danger"]},{"code":"BUSYKEY","title":"BUSYKEY Target key name already exists","severity":"caution","meaning":"RESTORE (or a similar command) failed because the target key already exists. By default Redis does not overwrite an existing key.","cause":"The key name being restored with RESTORE already exists in the database, previous data was not cleaned up, or multiple restore attempts to the same key.","fix":"If you want to overwrite the existing key, add the REPLACE option to RESTORE. Alternatively, delete the target key first (DEL mykey) or use a different key name. Check existence beforehand with EXISTS mykey.","tags":["busykey","restore","already exists","target key","replace","key"]},{"code":"ERR no such key","title":"ERR no such key","severity":"caution","meaning":"The key to operate on was not found in the database. Commands that require an existing key, such as RENAME, DUMP and MOVE, do not work on a nonexistent key.","cause":"The key was never created, it expired and was removed due to its TTL, a wrong key name/spelling, the wrong database (SELECT) index, or the key was already deleted with DEL.","fix":"Check that the key exists: EXISTS mykey. Make sure you use the correct key name and the correct database (SELECT db). RENAME requires the source key to exist. Review the logic for keys that expire via TTL. Re-create the key if needed.","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","severity":"caution","meaning":"The serialized data (DUMP output) passed to RESTORE is invalid; the version is incompatible or checksum verification failed. Redis cannot restore the data safely.","cause":"The DUMP data being corrupted/modified, moving between different, incompatible Redis versions, corruption during encoding/decoding (e.g. wrong byte handling), or a missing/truncated payload.","fix":"Make sure the DUMP data is transported intact (binary-safe); transfer it as raw bytes rather than through a text encoding. Verify that the source and target Redis versions are compatible. Take a fresh DUMP and transfer it again. For migration, consider the MIGRATE command instead of RESTORE.","tags":["err","dump","restore","checksum","checksum mismatch","version"]},{"code":"NOREPLICAS","title":"NOREPLICAS Not enough good replicas to write","severity":"danger","meaning":"Because of the min-replicas-to-write setting, writes are rejected since there are not enough healthy replicas. Redis blocks writes for data safety.","cause":"Not enough replicas connected/up to date, replica crashes, replication lag exceeding the min-replicas-max-lag limit, network problems, or missing replicas after a failover.","fix":"Check replica status: INFO replication. Bring down replicas back up and reduce replication lag. Fix network connectivity. Review the min-replicas-to-write / min-replicas-max-lag settings for your environment (the balance between durability and availability). Provide enough replicas.","tags":["noreplicas","replica","write","min-replicas","replication","danger"]},{"code":"ERR This instance has cluster support disabled","title":"ERR This instance has cluster support disabled","severity":"caution","meaning":"A cluster command (CLUSTER ...) was run but this Redis instance was not started in cluster mode. The command was rejected because cluster support is disabled.","cause":"cluster-enabled yes not set, the instance running in standalone mode, cluster commands sent to a standalone server, or connecting to the wrong server.","fix":"If you want to use cluster features, start the instance in cluster mode: set cluster-enabled yes in the configuration, restart, and form the cluster from the nodes. If you stay in standalone mode, do not use cluster commands. Verify that you are connected to the correct (cluster) server.","tags":["err","cluster","cluster mode","disabled","standalone","config"]},{"code":"ERR unknown subcommand","title":"ERR unknown subcommand or wrong number of arguments","severity":"caution","meaning":"A container command (e.g. CONFIG, CLIENT, ACL, XINFO) received an invalid subcommand, or the subcommand was sent the wrong number of arguments. Redis did not recognize the subcommand.","cause":"A typo in the subcommand name, a subcommand not present in the Redis version in use, a wrong/missing argument count, or incorrect subcommand syntax.","fix":"Use the correct subcommand name and arguments; consult the command's HELP subcommand (e.g. CONFIG HELP, CLIENT HELP). Verify that your Redis version supports that subcommand. Fix the argument count and syntax according to the 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)","severity":"caution","meaning":"The client tried to authenticate with AUTH but no password (requirepass) or ACL password is defined on the server. AUTH was rejected because there is no credential to verify. Redis 6+ returns \"ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?\" in this case; \"Client sent AUTH, but no password is set\" is the pre-Redis 6 message.","cause":"requirepass empty on the server, the client sending AUTH unnecessarily, a password defined in the connection string while the server has none, or a configuration mismatch.","fix":"If the server does not require a password, remove the AUTH command/password setting from the client. If you want password protection, set requirepass on the server (CONFIG SET requirepass password) or define a password for the ACL user. Make client and server configuration consistent.","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":"The request failed validation before execution. A required field is missing or fields are invalid, so Elasticsearch rejects the operation.","cause":"Missing required parameters (e.g. no index name specified), an empty body, an invalid combination of fields, or required information not supplied in a bulk/index request.","fix":"Review the list of missing/invalid fields in the error message and supply the required fields. Complete the request body and parameters according to the API documentation. In bulk requests, verify that each action line has correct metadata and a correct body.","tags":["validation","validation error","request","request error","action"]},{"code":"index_closed_exception","title":"index_closed_exception","severity":"caution","meaning":"The target index is closed. A closed index does not serve read/write requests, so the operation could not be performed.","cause":"The index was previously closed with the close API, closed during maintenance/ILM operations, or deactivated to save resources.","fix":"Reopen the index: POST /index_name/_open. After opening, wait for the shards to be allocated (green/yellow). Investigate why the index was closed (ILM policy, manual action). Review the strategy of keeping rarely accessed indices closed.","tags":["index closed","closed index","open","index","closed"]},{"code":"unavailable_shards_exception","title":"unavailable_shards_exception","severity":"danger","meaning":"One or more shards required for the operation are not available, so the request (usually a write) could not complete. Not enough active shard copies were found.","cause":"Unassigned shards due to a node crash/departure, a disk watermark breach, missing replicas, an unmet wait_for_active_shards condition, or red cluster health.","fix":"Check cluster and shard health: GET _cluster/health and GET _cat/shards?v. Investigate why shards are unassigned: GET _cluster/allocation/explain. Bring down nodes back up, free disk space (watermark), and review replica settings. If needed, set wait_for_active_shards appropriately.","tags":["shard","unavailable","inaccessible","cluster","allocation","danger"]},{"code":"query_shard_exception","title":"query_shard_exception","severity":"caution","meaning":"A query failed while executing on a particular shard. It usually occurs because the query is incompatible with the mapping or has an invalid structure.","cause":"Querying a nonexistent field or a field of the wrong type, attempting aggregation/sorting on a text field (fielddata disabled), invalid query syntax, wrong analysis, or an incompatible field type.","fix":"Review the field/reason in the error details. Check the mapping of the queried field: GET /index/_mapping. For sorting/aggregation, use the keyword sub-field (field.keyword) instead of text. Fix the query syntax and make sure field types are compatible with the query.","tags":["query","query error","shard","mapping","fielddata","query shard"]},{"code":"script_exception","title":"script_exception","severity":"caution","meaning":"An error occurred while compiling or executing a script (usually Painless). The script is written incorrectly or threw an exception at runtime.","cause":"Painless syntax error, null value access, type mismatch, use of a nonexistent field/variable, exceeding the script compilation limit, or a disallowed API call.","fix":"Review the compile/runtime details in the error message. Fix the script syntax and field access; add null checks (e.g. doc['field'].size() > 0). Make frequently used scripts parameterized (stored/params) so the compilation limit is not exceeded. Use correct field names and types.","tags":["script","painless","compilation","runtime","script exception"]},{"code":"number_format_exception","title":"number_format_exception","severity":"caution","meaning":"A value that should be numeric could not be converted to a number. An invalid/non-numeric string was supplied where a field or parameter expects a number.","cause":"Non-numeric text sent to a numeric (integer/long/double) field, an attempt to convert an empty string to a number, a wrong decimal format, or input data incompatible with the mapping.","fix":"Send valid numeric values to the field. Validate/transform source data before indexing. Use null for empty values or omit the field. Review the coerce setting and field type in the mapping. Use a period as the decimal separator.","tags":["number format","number","conversion","numeric","format"]},{"code":"connect_transport_exception","title":"connect_transport_exception","severity":"danger","meaning":"Could not connect to a node over the transport (9300) layer. Inter-node or client-cluster communication could not be established, so the operation could not be performed.","cause":"Target node down/unreachable, network/firewall issues, wrong transport address/port, TLS configuration mismatch, a DNS problem, or a cluster name/version mismatch.","fix":"Verify that the target node is running and that the transport port (default 9300) is reachable over the network. Check firewall/network rules and the discovery configuration. Make sure TLS/certificate settings are consistent across all nodes. Review node logs and verify cluster.name/version compatibility.","tags":["transport","connection","connect","network","node","danger"]},{"code":"repository_missing_exception","title":"repository_missing_exception","severity":"caution","meaning":"The snapshot repository referenced in a snapshot/restore operation was not found. No repository is registered under the given name.","cause":"The repository was never registered, a typo in the name, the repository was deleted, it is defined on a different cluster, or it was not re-registered after the cluster state was reset.","fix":"List registered repositories: GET _snapshot/_all. Re-register the repository with the correct name and settings: PUT _snapshot/repo_name (with type and settings). Make sure the repository name is spelled correctly. Verify that access to the shared repository (fs/s3, etc.) works on all nodes.","tags":["repository","snapshot repository","snapshot","missing","backup","restore"]},{"code":"concurrent_snapshot_execution_exception","title":"concurrent_snapshot_execution_exception","severity":"caution","meaning":"A new snapshot/restore request was rejected because a conflicting snapshot operation is running at the same time. Another snapshot operation is still in progress on the repository or index.","cause":"A previous snapshot still in progress, a concurrent snapshot/restore conflict affecting the same index, an SLM (snapshot lifecycle) policy colliding with a manual snapshot, or triggers at short intervals.","fix":"Check running snapshots: GET _snapshot/repo/_current and GET _snapshot/_status. Wait for the current operation to finish, or cancel it if necessary: DELETE _snapshot/repo/snapshot_name. Schedule snapshots so they do not overlap. Coordinate manual operations with SLM.","tags":["snapshot","concurrent","conflict","simultaneous","slm","backup"]},{"code":"invalid_index_name_exception","title":"invalid_index_name_exception","severity":"caution","meaning":"The given index name does not follow Elasticsearch naming rules. The name contains invalid characters or violates format rules.","cause":"Uppercase letters, disallowed characters (\\ / * ? \" < > | space , #), a name starting with _, - or +, a name that is too long (over 255 bytes), or reserved names such as . / ... (Names starting with a dot are not forbidden but are reserved for hidden/system indices and produce a warning.)","fix":"Make the index name compliant: use lowercase only; avoid forbidden characters; do not start the name with _, - or +, and do not use names starting with a dot except for system/hidden indices. Keep the name under 255 bytes. Enforce these rules in the application when generating index names.","tags":["index name","index naming","invalid","invalid name","naming"]},{"code":"resource_not_found_exception","title":"resource_not_found_exception","severity":"caution","meaning":"The requested resource (document, index, task, pipeline, model, etc.) was not found. No resource exists for the given ID/name.","cause":"A nonexistent document ID, an index/pipeline that was deleted or never created, a misspelled name, a task not yet completed or already cleaned up, or a request sent to the wrong cluster.","fix":"Verify the resource name/ID and that it exists (e.g. GET /index/_doc/id, GET _ingest/pipeline/name). Make sure you are sending the request to the correct index and cluster. Review the order of delete/create operations. For tasks, check the status with the _tasks API.","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":"A cluster state update (e.g. index creation, mapping change) did not complete within the allotted time and timed out. The master node could not process the update in time.","cause":"Master node under heavy load, a very large/complex cluster state, frequent and intensive mapping/template changes, a slow master, or master election problems.","fix":"Check master node load and health: GET _cluster/health, GET _cat/master, and GET _cluster/pending_tasks for pending tasks. Shrink the cluster state (reduce unnecessary indices/mappings/templates, prevent dynamic mapping explosion). Run heavy DDL-like operations in batches during low-traffic periods. Increase master resources if needed.","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","severity":"caution","meaning":"The name of the object you are trying to create (table, index, view, sequence, synonym, etc.) is already used by another object in the same schema. In Oracle, object names are unique within a schema, and even different types such as tables, views and sequences share the same namespace.","cause":"The name chosen in the CREATE statement conflicts with an existing object. A common trap: trying to create a sequence or view with the same name as a table; or an automatically created index name (e.g. the primary key index) colliding with the name you specified.","fix":"Choose a different name or remove the conflicting object. To find which object uses the name: SELECT object_type, object_name FROM user_objects WHERE object_name = 'YOUR_NAME'; If you really want to replace it, DROP the old object (back up its data first). CREATE OR REPLACE is available for views, synonyms, types and PL/SQL objects (procedure, function, package, trigger); it does not exist for tables, indexes and sequences.","tags":["ddl","create","name","conflict","duplicate","object"]},{"code":"ORA-02449","title":"unique/primary keys in table referenced by foreign keys","severity":"caution","meaning":"The primary or unique key of the table you want to drop is referenced by foreign keys in other tables. Oracle does not let you drop the table without removing these dependencies first.","cause":"You ran DROP TABLE without CASCADE CONSTRAINTS while other tables reference this table through FKs. Enforcing referential integrity, Oracle prevents orphaned foreign keys.","fix":"To drop it together with the dependent foreign keys: DROP TABLE table_name CASCADE CONSTRAINTS; This also removes the related FK constraints in the other tables (it does not delete the data in those tables). To see which constraints depend on it: 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'","severity":"danger","meaning":"The user has no quota on the tablespace it is trying to write to. In Oracle, a user must have a quota on the tablespace to allocate space for an object.","cause":"The user has not been granted a quota on the tablespace; typically a new user without the UNLIMITED TABLESPACE system privilege tries to create a table/index or insert data into its default tablespace. It appears during INSERT, CREATE TABLE or index growth.","fix":"As a DBA, grant the user a quota: ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; or a specific limit: ALTER USER app_user QUOTA 500M ON users; Alternatively (use with care) GRANT UNLIMITED TABLESPACE TO app_user; can be granted, but this gives unlimited rights on all tablespaces and is generally not recommended.","tags":["tablespace","quota","tablespace quota","privilege","privilege","insert"]},{"code":"ORA-01110","title":"data file N: 'path'","severity":"danger","meaning":"A supplementary (secondary) message indicating a problem with the datafile of the given number and path. It never appears alone; it is preceded by another ORA error reporting the actual problem, and this line only tells you which datafile is affected.","cause":"It usually accompanies a primary error such as the datafile being offline (ORA-00376), needing media recovery (ORA-01113), block corruption (ORA-01578) or the file being unreadable. Disk failure, accidental deletion, a moved file or an incomplete restore from backup can trigger it.","fix":"Read the actual ORA error preceding this line and resolve it. Check the affected file: SELECT file#, name, status FROM v$datafile; If recovery is needed: RECOVER DATAFILE 'path'; then ALTER DATABASE DATAFILE 'path' ONLINE; If the file is physically lost, restore and recover it with 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","severity":"danger","meaning":"Oracle cannot archive an online redo log file and therefore cannot reuse that log group. When archiving is stuck in ARCHIVELOG mode, the database may stop accepting new transactions and hang.","cause":"Usually the archive log destination or the Fast Recovery Area (FRA) is full; disk space has run out; the archive destination is unreachable; or there is a permission/mount problem. Often seen together with ORA-19809 (limit exceeded for recovery files) or ORA-00312.","fix":"Free space in the archive destination/FRA: back up old archived logs with RMAN to a destination outside the FRA and delete them (DELETE ARCHIVELOG ALL COMPLETED BEFORE ...; with Data Guard, do not delete archived logs not yet shipped/applied on the standby), or increase db_recovery_file_dest_size. Check destination status: ARCHIVE LOG LIST; and SELECT dest_name, status, error FROM v$archive_dest WHERE status != 'INACTIVE'; Once the problem is fixed, archiving resumes where it left off.","tags":["archivelog","archive","redo","fra","recovery area","archiver stuck"]},{"code":"ORA-19809","title":"limit exceeded for recovery files","severity":"danger","meaning":"The space allocated to the Fast Recovery Area (FRA) (db_recovery_file_dest_size) is full. With no room left for archived logs, flashback logs and backups, Oracle cannot create new recovery files and the database may hang.","cause":"Archived logs in the FRA accumulated because they were not regularly backed up and purged; flashback log usage grew; or the allocated size is too small for the actual workload. Seen together with archiving errors such as ORA-16038.","fix":"Review FRA usage: SELECT * FROM v$recovery_file_dest; and SELECT * FROM v$flash_recovery_area_usage; Delete unneeded archived logs with RMAN: DELETE ARCHIVELOG ALL BACKED UP 1 TIMES TO DISK; (with Data Guard, first verify they were applied on the standby; CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY is recommended) or, as a permanent fix, increase the size: ALTER SYSTEM SET db_recovery_file_dest_size = 50G SCOPE=BOTH; Set up an RMAN job that backs up archived logs regularly.","tags":["fra","recovery area","db_recovery_file_dest_size","archivelog","space","rman"]},{"code":"ORA-00376","title":"file N cannot be read at this time","severity":"danger","meaning":"The datafile with the given number cannot be read right now because it is offline or inaccessible. Queries and operations accessing data in the affected tablespace fail with this error.","cause":"The datafile was taken offline automatically after an error or manually; it is awaiting media recovery; the disk became inaccessible; or a file that cannot be recovered in NOARCHIVELOG mode was left offline. Usually accompanied by an ORA-01110 line identifying the file.","fix":"View file status: SELECT file#, name, status FROM v$datafile WHERE status != 'ONLINE'; Recover it and bring it online: RECOVER DATAFILE N; then ALTER DATABASE DATAFILE N ONLINE; If there is physical corruption/loss, restore and recover with RMAN: RESTORE DATAFILE N; RECOVER DATAFILE N; Also fix the underlying disk/hardware problem.","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","severity":"danger","meaning":"The listener could not find a suitable server process (handler) for the requested connection type (dedicated or shared). The client can reach the listener, but the connection cannot be handed off to a server process.","cause":"The instance is not registered with the listener or its registration is broken; the PROCESSES/SESSIONS limit is reached and no new dedicated process can be spawned; no/insufficient dispatchers in a shared server configuration; or the instance has just started and has not yet registered with the listener.","fix":"Check the services the listener sees: lsnrctl services (handler count and states). Re-register the instance with the listener: ALTER SYSTEM REGISTER; Check the PROCESSES limit: SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); If needed, increase it with ALTER SYSTEM SET processes=... SCOPE=SPFILE; and restart the instance.","tags":["listener","tns","connection","handler","processes","dispatcher"]},{"code":"ORA-28001","title":"the password has expired","severity":"caution","meaning":"The password of the database user you are connecting as has expired because the PASSWORD_LIFE_TIME of its profile elapsed. No session can be opened until the password is changed.","cause":"The password lifetime defined in the user's profile has ended and the grace period has also elapsed. In Oracle 11g and later, passwords in the DEFAULT profile expire every 180 days; this causes unexpected outages especially for long-lived application service accounts.","fix":"Change the password: ALTER USER app_user IDENTIFIED BY new_password; To avoid recurring issues with application accounts, use a profile without password expiry: CREATE PROFILE app_profile LIMIT PASSWORD_LIFE_TIME UNLIMITED; then ALTER USER app_user PROFILE app_profile; Monitoring that warns before expiry is also useful. Check the status with ACCOUNT_STATUS in DBA_USERS.","tags":["password","password expired","expired","profile","account","login"]},{"code":"ORA-38029","title":"object statistics are locked","severity":"caution","meaning":"Statistics gathering/deletion was rejected because the optimizer statistics on a table (or schema) are locked. Locked statistics are protected from being recomputed with DBMS_STATS.","cause":"DBMS_STATS.LOCK_TABLE_STATS or LOCK_SCHEMA_STATS was called earlier (e.g. to keep statistics fixed), or a Data Pump import (especially CONTENT=METADATA_ONLY) unintentionally left the statistics locked. Running GATHER_TABLE_STATS while they are locked raises this error.","fix":"Unlock the statistics: EXEC DBMS_STATS.UNLOCK_TABLE_STATS('APP_SCHEMA','MY_TABLE'); or at schema level DBMS_STATS.UNLOCK_SCHEMA_STATS('APP_SCHEMA'); If you do not want to remove the lock but still want to gather, use FORCE=>TRUE: EXEC DBMS_STATS.GATHER_TABLE_STATS('APP_SCHEMA','MY_TABLE', force=>TRUE); To view the lock status: 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":"The statement is more complex than PostgreSQL can process; usually very deeply nested expressions or many nested operators make the expression tree exceed the parser/planner stack limit. Belongs to Class 54 (program_limit_exceeded).","cause":"Excessively deep parentheses/nested subqueries or OR/AND chains; huge auto-generated WHERE ... IN (...) lists; or very large expression trees arising from recursive views. The max_stack_depth limit is reached.","fix":"Simplify the query: move long IN lists into a temporary table or a VALUES set and JOIN to it; break up nested expressions; review huge ORM-generated queries. If truly necessary and your OS stack limit allows it, raise max_stack_depth carefully (e.g. SET max_stack_depth = '4MB';) but keep it below the OS ulimit.","tags":["program_limit","complex","stack","nested","query","max_stack_depth"]},{"code":"58030","title":"io_error","severity":"danger","meaning":"PostgreSQL hit an operating-system-level input/output (I/O) error while reading or writing a file. This indicates a physical or file-system problem accessing database files. Belongs to Class 58 (system_error).","cause":"Disk failure or bad sectors; a full disk; file system errors; an outage on network-attached storage (NFS/SAN); permission problems; or exhausted OS file descriptor/inode limits.","fix":"Check disk health and free space (df -h, dmesg, SMART data). Check the PostgreSQL logs for which file is affected. Fix file system errors; replace hardware if necessary. If data corruption is suspected, plan a restore from the last good backup, and keep checksums enabled going forward (initdb --data-checksums).","tags":["system_error","io","disk","hardware","file","corruption"]},{"code":"22004","title":"null_value_not_allowed","severity":"caution","meaning":"A NULL was passed where NULL is not accepted. This differs from a column NOT NULL constraint (23502); it usually comes from passing NULL to a parameter or operation that does not accept NULL in a function or procedure context. Belongs to Class 22 (data_exception).","cause":"Assigning NULL to a PL/pgSQL variable declared NOT NULL; passing a NULL query string to EXECUTE; or passing NULL to built-in function arguments that do not accept it. Note: a function marked STRICT does not raise an error on NULL input — it returns NULL without executing.","fix":"Find the failing line (CONTEXT) and guard the value reaching it with a NULL check, or supply a default with COALESCE: COALESCE(value, default_value). In PL/pgSQL, validate values before assigning them to NOT NULL variables; in dynamic SQL make sure the string passed to EXECUTE is not NULL. Prevent mandatory fields from being sent as NULL at the application layer.","tags":["data_exception","null","function","strict","parameter","coalesce"]},{"code":"42P18","title":"indeterminate_datatype","severity":"caution","meaning":"PostgreSQL could not infer the data type of an expression or parameter from context. Most often seen when an untyped placeholder (such as $1) or a bare NULL is used with no type hint around it.","cause":"Untyped parameters in prepared statements; comparisons where both sides are indeterminate, such as \"WHERE $1 = $2\"; or context-free use of NULL/array literals. The planner cannot decide which type to use.","fix":"Add an explicit cast to the indeterminate expression: $1::text, $1::integer, NULL::date, etc. For example: SELECT $1::int + 1; Specify types for driver/ORM parameters. In column comparisons, make sure at least one side has a known type.","tags":["syntax","type","indeterminate","cast","parameter","prepared"]},{"code":"42P02","title":"undefined_parameter","severity":"caution","meaning":"The query references a parameter that does not exist. PostgreSQL reports that the referenced positional parameter (e.g. $2) is not defined. Belongs to Class 42 (syntax_error_or_access_rule_violation).","cause":"Referencing more parameters than supplied in a statement prepared with PREPARE (e.g. sending two parameters and using $3); skipped parameter numbering; or wrong $N indexes in dynamically concatenated SQL.","fix":"Make sure the parameter numbers match the number of values you supply; verify they start at $1 and continue without gaps. Define parameter types completely in the PREPARE statement: PREPARE p(int, text) AS SELECT ... WHERE a=$1 AND b=$2; If you use an ORM/driver, compare the number of bound parameters with the placeholders in the query.","tags":["syntax","parameter","prepared","undefined","placeholder","sql"]},{"code":"2F003","title":"prohibited_sql_statement_attempted","severity":"caution","meaning":"A statement that is prohibited in that context was attempted inside an SQL function/routine. Belongs to Class 2F (sql_routine_exception). For example, a function expected to be read-only attempting to modify data.","cause":"Core PostgreSQL rarely raises this code; it usually comes from extension or procedural language code. Note: a function marked IMMUTABLE/STABLE that runs INSERT/UPDATE/DELETE does not raise 2F003; it raises '... is not allowed in a non-volatile function' with code 0A000 (feature_not_supported).","fix":"Read which statement was prohibited in the error message and check the documentation of the routine/extension involved. If you get the non-volatile function error (0A000), define the data-modifying function as VOLATILE (this is the default): CREATE FUNCTION ... LANGUAGE plpgsql VOLATILE. Remove data-modifying calls from places that must be read-only (index expressions, constraint checks).","tags":["sql_routine","function","volatile","prohibited","immutable","side effect"]},{"code":"40003","title":"statement_completion_unknown","severity":"danger","meaning":"The outcome of the transaction is unknown; the connection to the server dropped before it was clear whether the transaction completed (especially during COMMIT). The client cannot know for certain whether the changes are durable. Belongs to Class 40 (transaction_rollback).","cause":"The network dropped, the server crashed or a failover occurred after COMMIT was sent but before the response arrived. The transaction may have completed on the server, or it may have been rolled back.","fix":"Do not blindly retry the transaction; first verify whether it was actually applied (e.g. by querying for the expected row). Design transactions to be idempotent; use unique business keys/idempotency keys. Implement robust reconnect and validation logic in the connection pool.","tags":["transaction","commit","unknown","connection","idempotent","failover"]},{"code":"08003","title":"connection_does_not_exist","severity":"danger","meaning":"An operation was attempted on a database connection that does not exist (already closed). The application tried to send a query using an invalid connection handle. Belongs to Class 08 (connection_exception).","cause":"Connection timeout, closure by the server, a network outage, or a connection taken from the pool that is actually dead; or reuse of a connection that was close()d in application code.","fix":"Enable liveness checks in the connection pool (validation query / keepalive); configure settings that evict dead connections from the pool (e.g. connectionTestQuery/maxLifetime in HikariCP). In the application, make sure the connection is open before using it and reconnect automatically when it drops. Review TCP keepalive and idle_in_transaction_session_timeout values.","tags":["connection","connection","closed","pool","pool","timeout"]},{"code":"25006","title":"read_only_sql_transaction","severity":"caution","meaning":"A data-modifying command (INSERT/UPDATE/DELETE/DDL) was attempted inside a read-only transaction. The transaction is configured for reads only. Belongs to Class 25 (invalid_transaction_state).","cause":"SET TRANSACTION READ ONLY or the default_transaction_read_only=on setting; or, most commonly, a write attempt against a read replica (hot standby / streaming replica). Standby servers are read-only by definition.","fix":"Route writes to the primary server; check your connection routing that splits reads and writes. If it was set by mistake: SET TRANSACTION READ WRITE; or server-wide default_transaction_read_only=off. Make sure your application sends read-write traffic to the correct node.","tags":["transaction","read only","read only","replica","standby","primary"]},{"code":"21000","title":"cardinality_violation","severity":"caution","meaning":"More than one row was returned where a single row was expected. A scalar subquery returned multiple rows, or MERGE/ON CONFLICT DO UPDATE tried to affect the same target row a second time. Belongs to Class 21.","cause":"A subquery used in the SELECT list or in the form WHERE = (subquery) returning multiple rows; the same conflict key being proposed more than once in a single INSERT ... ON CONFLICT DO UPDATE ('cannot affect row a second time'); or multiple source rows matching one target row in MERGE. UPDATE ... FROM does not raise an error on multiple matches; the target row is updated from only one of the source rows, unpredictably.","fix":"Make sure the subquery returns a single row: add LIMIT 1, use an aggregate (MAX/MIN), or supply a condition that makes the match unique. In MERGE and ON CONFLICT patterns, deduplicate the source side with DISTINCT ON/aggregate; do the same for UPDATE ... FROM to avoid silently wrong results. To find unexpected multiple matches, run the subquery on its own and check the row count.","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","severity":"danger","meaning":"MariaDB-specific: the running connection/thread was terminated by the server, the in-flight query was interrupted, and the client connection was dropped. This code does not exist in MySQL; there the same situation appears as 1317 (ER_QUERY_INTERRUPTED) when the query is interrupted, and as 2013 (Lost connection) on the client side when the connection drops.","cause":"An administrator or monitoring tool sending a KILL command; the server entering shutdown; idle connection limits in MariaDB such as idle_transaction_timeout/wait_timeout; or the server dropping the connection under resource pressure. Also seen during maintenance in cloud/proxy environments.","fix":"Investigate why the query was killed: SHOW PROCESSLIST and the slow query log for long-running queries; server maintenance/restart schedules; connection kill policies. Catch this error in the application and retry safely (if idempotent). Optimize and split long queries; use automatic reconnect in the connection pool.","tags":["connection","connection killed","kill","thread","shutdown","retry"]},{"code":"1290","title":"ER_OPTION_PREVENTS_STATEMENT: running with the --read-only option","severity":"caution","meaning":"The MySQL server rejected a data-modifying statement (INSERT/UPDATE/DELETE/DDL) because it is running with the --read-only or --super-read-only option. Under read_only, users with CONNECTION_ADMIN/SUPER can still write; with super_read_only enabled they cannot either, and the message reads '--super-read-only option'.","cause":"read_only=ON or super_read_only=ON; most often an accidental write attempt against a replica. Replicas and Group Replication secondaries are kept read-only for data consistency; failover automation puts the old primary into super_read_only. Writing to the wrong node during failover/switchover also triggers it.","fix":"Route writes to the primary (source) server. If this server really should become the new primary: SET GLOBAL read_only = OFF; (turning off read_only automatically turns off super_read_only as well; turning on super_read_only also turns on read_only). Do not change it manually on nodes managed by your HA tool (Orchestrator/Group Replication/InnoDB Cluster). To check the state: 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","severity":"caution","meaning":"InnoDB is running in read-only mode (innodb_read_only=ON) or the data directory/media is read-only; writes and DDL on InnoDB tables are rejected. Note: a write attempt while read_only/super_read_only is enabled returns 1290 (ER_OPTION_PREVENTS_STATEMENT), not this code.","cause":"The server was started with innodb_read_only=ON (e.g. read-only media, a shared data directory, a copy for analysis); in this mode changes such as INSERT/UPDATE/DELETE and CREATE/DROP/ALTER TABLE cannot be made to InnoDB tables.","fix":"If writes are needed, start the server without innodb_read_only: remove the innodb_read_only=ON line from my.cnf and restart (this variable is not dynamic and requires a restart). To check the state: SELECT @@innodb_read_only; Verify that the data directory and file system are writable. If you are writing to a replica/HA secondary (1290), route writes to the primary.","tags":["innodb_read_only","read only","read-only mode","innodb","1836"]},{"code":"3819","title":"ER_CHECK_CONSTRAINT_VIOLATED: Check constraint is violated","severity":"caution","meaning":"The row you are inserting or updating violates a CHECK constraint defined on the table. In MySQL 8.0.16 and later, CHECK constraints are actually enforced (earlier versions ignored them).","cause":"The column value does not satisfy the condition in the CHECK expression (e.g. entering a negative price when CHECK (price >= 0) exists). After upgrading from older MySQL versions to 8.0.16+, it can appear unexpectedly because previously ignored constraints are now enforced.","fix":"Make sure the value satisfies the constraint condition; add validation on the application side. To see the constraint definition: SHOW CREATE TABLE my_table; If the constraint is truly unnecessary: ALTER TABLE my_table DROP CHECK constraint_name; or temporarily ALTER TABLE my_table ALTER CHECK constraint_name NOT ENFORCED; Also fix the source of the bad data.","tags":["check","constraint","check constraint","validation","8.0","violation"]},{"code":"1118","title":"ER_TOO_BIG_ROWSIZE: Row size too large","severity":"caution","meaning":"The row size of the table you are defining exceeds the maximum row size allowed by the storage engine. The maximum row size for non-BLOB/TEXT columns is 65535 bytes; InnoDB has additional limits that depend on the page size.","cause":"Defining many wide columns (especially long VARCHAR/CHAR); each character taking up to 4 bytes in multibyte character sets such as utf8mb4; or large columns stored inline in InnoDB's COMPACT/REDUNDANT row format.","fix":"Convert some large columns to TEXT/BLOB (these can be stored off-page); shrink unnecessary or very wide columns, or split the table in two. In InnoDB, using ROW_FORMAT=DYNAMIC or COMPRESSED helps by moving large columns off-page: ALTER TABLE my_table ROW_FORMAT=DYNAMIC; Also take innodb_page_size into account.","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","severity":"caution","meaning":"You used a BLOB or TEXT column in an index/key definition without specifying a prefix length. These types cannot be indexed in full; only the first N characters/bytes can be indexed.","cause":"Defining an INDEX/UNIQUE/PRIMARY KEY on a TEXT/BLOB column without a length in CREATE TABLE or CREATE INDEX. MySQL requires a prefix length because it cannot index these potentially very large values in full.","fix":"Specify a prefix length in the index, such as INDEX (description(255)). Example: ALTER TABLE my_table ADD INDEX idx_description (description(191)); (191 is commonly used with utf8mb4 because of the InnoDB key prefix limit). If full-text search is needed, consider a FULLTEXT index. For fields that should be short and unique, converting the column to VARCHAR may be more appropriate.","tags":["index","key","blob","text","prefix","prefix length"]},{"code":"1592","title":"ER_BINLOG_UNSAFE_STATEMENT: Unsafe statement written to the binary log","severity":"caution","meaning":"While using the STATEMENT-based binlog format, a statement with a nondeterministic result (one that could produce a different result on the replica) was executed. MySQL issues a warning when writing it to the binlog because there is a risk of data inconsistency between source and replica.","cause":"With binlog_format=STATEMENT: nondeterministic functions such as UUID(), SYSDATE() or USER() (NOW() replicates safely with STATEMENT), UPDATE/DELETE with LIMIT but no ORDER BY, INSERTs mixed with AUTO_INCREMENT, or certain user-defined/system functions. These statements may not produce the same result on every server.","fix":"Switch the binlog format to row-based (recommended; ROW has been the default since MySQL 5.7.7): binlog_format=ROW in my.cnf or SET PERSIST binlog_format = ROW; (the binlog_format variable is deprecated since 8.0.34; the change takes effect for new sessions). ROW format replicates nondeterministic statements safely. If you must stay on STATEMENT, replace nondeterministic functions/expressions with constant values and add a deterministic ORDER BY to DML with LIMIT.","tags":["binlog","replication","unsafe","statement","row","nondeterministic"]},{"code":"3140","title":"ER_INVALID_JSON_TEXT: Invalid JSON text","severity":"caution","meaning":"Invalid (syntactically broken) JSON text was passed to a JSON column or JSON function. MySQL rejects the operation because it cannot parse the value as JSON.","cause":"An unclosed quote/bracket, a trailing comma, single quotes, escape character errors, or plain text sent to a JSON column. Appears when malformed text is passed to functions such as JSON_SET, JSON_INSERT, or to a JSON-typed column.","fix":"Fix the JSON text to valid syntax: use double quotes, remove trailing commas, verify escapes. Test whether the text is valid: SELECT JSON_VALID('{...}'); (returns 1 if valid). Use a reliable library to serialize JSON in the application layer and validate it before sending it to the server.","tags":["json","invalid","syntax","json_valid","parse","data"]},{"code":"1526","title":"ER_NO_PARTITION_FOR_GIVEN_VALUE: Table has no partition for value","severity":"caution","meaning":"The partitioning key value of the row you are inserting does not fit any defined partition. In RANGE or LIST partitioning, the insert fails because no partition covers the incoming value.","cause":"In RANGE partitioning, inserting a value beyond the highest bound with no MAXVALUE partition defined (e.g. a future date); in LIST partitioning, an unlisted value arriving. Common in time-partitioned tables when the partition for the new period is not added in time.","fix":"Add a partition that covers the value. A new upper partition for RANGE: ALTER TABLE my_table ADD PARTITION (PARTITION p2026 VALUES LESS THAN (2027)); For a catch-all partition, define MAXVALUE for RANGE and the relevant values for LIST. For time-based tables, set up a maintenance job (event/scheduler) that adds partitions automatically.","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","severity":"danger","meaning":"SQL Server tried to allocate a new page for an object, but the filegroup has insufficient disk space. The data files can neither grow nor find a free page, so the write fails.","cause":"The disk holding the data file is full; autogrow is disabled on the file or MAXSIZE has been reached; or every file in the filegroup has hit its size ceiling. Common during large INSERTs/index builds.","fix":"Free space on the disk or add space to the filegroup. Grow the file or enable autogrow: ALTER DATABASE db MODIFY FILE (NAME = logical_name, SIZE = 20GB, MAXSIZE = UNLIMITED, FILEGROWTH = 512MB); Alternatively, add a new file to the filegroup (on another disk). To see file usage, check sys.database_files and free space with DBCC SQLPERF.","tags":["disk","filegroup","file group","autogrow","space","allocation"]},{"code":"3729","title":"Cannot DROP because it is being referenced by object","severity":"caution","meaning":"The object you want to drop (table, type, function etc.) is referenced by another object, and that dependency prevents the drop. Schema-bound objects in particular are protected this way.","cause":"Trying to drop a table/object that a view or function created WITH SCHEMABINDING depends on; dropping a table referenced by a foreign key; or removing a user-defined type while a table/parameter still uses it.","fix":"Drop the referencing objects or remove the dependency first. To find dependencies: SELECT referencing_schema_name, referencing_entity_name FROM sys.dm_sql_referencing_entities('app_schema.object', 'OBJECT'); For schema-bound views, first remove SCHEMABINDING with ALTER VIEW or drop the view, then drop the table.","tags":["drop","reference","schemabinding","view","dependencies","dependency"]},{"code":"3961","title":"Snapshot isolation transaction failed because the object was modified by a DDL statement","severity":"caution","meaning":"A transaction running at snapshot isolation failed because an object it accessed was changed by a DDL statement (schema change) in another concurrent transaction. Metadata is not versioned, so the snapshot transaction cannot continue.","cause":"While a transaction started at SNAPSHOT isolation (SET TRANSACTION ISOLATION LEVEL SNAPSHOT) is running, another session runs DDL such as ALTER TABLE/CREATE INDEX on a table it accesses. Snapshot transactions can see row versions but not schema versions. This error does not occur under READ COMMITTED SNAPSHOT (RCSI); RCSI takes a new snapshot for each statement.","fix":"Retry the transaction; it usually succeeds once the DDL has completed. As a permanent fix, perform schema changes (DDL) in maintenance windows without heavy snapshot workload. Catch this error in the application and implement automatic retry logic. Shorten long-running snapshot transactions.","tags":["snapshot","isolation level","ddl","concurrency","retry","isolation"]},{"code":"1785","title":"Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths","severity":"caution","meaning":"The foreign key you are adding could create a cycle or multiple cascading delete/update paths to the same table in cascade (ON DELETE/UPDATE CASCADE) operations. SQL Server rejects it to avoid ambiguous cascade behavior.","cause":"An FK with a cascade action creating a circular reference between tables (A->B->A) or multiple cascade paths to one table. SQL Server disallows this to prevent ambiguous cascade ordering and infinite loops.","fix":"Define the foreign key as ON DELETE NO ACTION / ON UPDATE NO ACTION (remove the cascade): ALTER TABLE child ADD CONSTRAINT fk ... FOREIGN KEY (...) REFERENCES parent(...) ON DELETE NO ACTION; Implement the required cascading delete with an INSTEAD OF DELETE trigger: the trigger deletes the child rows first, then the parent row (e.g. 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;). An AFTER DELETE trigger does not work; the FK check runs before the trigger, so the delete is rejected with error 547. Review circular dependencies in the data model.","tags":["foreign key","fk","cascade","circular reference","cycle","constraint"]},{"code":"9421","title":"XML parsing: illegal xml character","severity":"caution","meaning":"SQL Server encountered a disallowed (invalid) character while parsing text as XML. The conversion to the XML type or the assignment to an XML column/variable failed because of this character.","cause":"Control characters forbidden in XML 1.0 (e.g. some characters in the 0x00-0x1F range), wrong encoding (encoding mismatch), or corrupt/unsanitized data from another source. Usually appears during CAST(... AS XML) or an INSERT into an XML column.","fix":"Strip invalid characters from the text before converting it to XML. Make the source encoding (UTF-8/UTF-16) compatible with the target; declare the correct encoding in the XML declaration. Remove control characters with REPLACE/NCHAR, or generate and validate the XML with a standard library in the application layer. If you must store the raw text, consider 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","severity":"caution","meaning":"The object or security principal (user, role, login) you referenced either does not exist or you lack the permission to access/act on it. For security reasons SQL Server combines both cases into one message.","cause":"Using a nonexistent user, role, schema or object name in a command such as GRANT/ALTER/DROP; running in the wrong database context (the object is in another DB); or lacking the privilege for the operation. Typos, case and schema prefixes are frequent causes.","fix":"Verify that the object/principal name is spelled correctly and that you are in the right database. For users: SELECT name, type_desc FROM sys.database_principals; for objects, query sys.objects. Make sure the executing account has the required permissions (e.g. ALTER ANY USER, CONTROL). Use the correct schema prefix (such as dbo.object).","tags":["permission","principal","privilege","not found","grant","security"]},{"code":"4834","title":"You do not have permission to use the bulk load statement","severity":"caution","meaning":"You lack the permission required to run a bulk load with BULK INSERT (or OPENROWSET BULK). Bulk loading accesses the file system and server resources directly, so it requires a separate privilege.","cause":"The login lacks the server-level ADMINISTER BULK OPERATIONS permission or is not a member of the bulkadmin server role (Azure SQL Database requires the database-level ADMINISTER DATABASE BULK OPERATIONS instead). INSERT permission on the target table and read access to the source file for the SQL Server service account are also required.","fix":"Grant the required permission: GRANT ADMINISTER BULK OPERATIONS TO login_name; (server level) or add the user to the appropriate role. Azure SQL DB uses ADMINISTER DATABASE BULK OPERATIONS. Also make sure the SQL Server service account has read permission on the target file/share. Following least privilege, grant it only to the accounts that need it.","tags":["bulk insert","privilege","permission","bulk load","bulkadmin","file"]},{"code":"10928","title":"Resource limit for the database has been reached (Azure SQL)","severity":"danger","meaning":"In Azure SQL Database the database reached a resource limit (such as concurrent requests/workers or sessions) and the new request was rejected. It indicates that the ceiling of the selected service tier (DTU or vCore) has been hit.","cause":"Exceeding the maximum concurrent requests (workers) or sessions allowed by the selected tier; a sudden load spike; inefficient/long-running queries consuming worker threads; or an oversized connection pool.","fix":"Reduce concurrency: optimize queries to shorten their duration, close unnecessary open transactions, keep the connection pool size reasonable. For sustained load, scale up the service tier (higher DTU/vCore). Monitor resource usage with sys.dm_db_resource_stats. For retryable errors, implement retry with exponential backoff in the application.","tags":["azure","limit","resource","dtu","vcore","concurrency"]},{"code":"40544","title":"The database has reached its size quota (Azure SQL)","severity":"danger","meaning":"Azure SQL Database reached the maximum data size quota set for the service tier. The database cannot grow any further, so new writes fail.","cause":"Data growing up to the tier limit; large indexes/log consuming space; or a small MAXSIZE allowed by the selected tier (e.g. Basic/Standard S-series). Unarchived historical data also fills the quota quickly.","fix":"Free space: delete/archive unneeded data, drop unused indexes, compress tables (data compression). As a permanent fix, increase MAXSIZE or move to a higher service tier: ALTER DATABASE db MODIFY (MAXSIZE = 250 GB); Monitor size usage with sys.database_files and related DMVs. Plan scaling ahead based on the growth trend.","tags":["azure","size","quota","maxsize","size quota","scaling"]},{"code":"40613","title":"Database is not currently available (Azure SQL)","severity":"danger","meaning":"In Azure SQL the target database is not reachable at the moment. Usually transient; it appears briefly while the platform moves, reconfigures or recovers the database.","cause":"Automatic scaling/failover, patching, load balancing or resource relocation on the Azure side; rarely a longer platform event. Connections are rejected until the database comes back online.","fix":"Wait briefly and retry; this is a transient error. Implement automatic retry with exponential backoff in the application; Azure SDK/retry libraries recognize this error code as transient. If the problem persists, check Azure Service Health and regional incidents. For high availability, consider geo-replication/a 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":"The referenced role is not defined in MongoDB. A nonexistent role name was used while granting a role to a user, updating a role or querying a role's privileges.","cause":"A misspelled or never-created role name in grantRolesToUser/createUser; looking up the role in the wrong database (roles are bound to a specific DB); or a custom (non-built-in) role that has not yet been defined with createRole.","fix":"Verify the role name and the database it belongs to. List existing roles: db.getRoles({showBuiltinRoles: false}) or the corresponding admin query across all DBs. If the custom role does not exist, create it first: db.createRole({role:'reader', privileges:[...], roles:[]}). Specify the correct db when granting: 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","severity":"caution","meaning":"An operation not supported in that context was attempted inside a multi-document transaction. Some commands (notably certain DDL and metadata operations) cannot run inside a transaction.","cause":"Unsupported operations inside the transaction such as creating collections/indexes (depending on version); certain count/list-type commands; or administrative commands not valid in a transaction session. Older MongoDB versions do not support DDL inside transactions (part of it arrived with 4.4+).","fix":"Move the unsupported operation out of the transaction (e.g. create the required collections/indexes before the transaction starts). Check your MongoDB version; 4.4+ supports some DDL inside transactions. Run the operation in a separate session in the driver. Limit the transaction to supported CRUD operations.","tags":["transaction","multi-document transaction","ddl","unsupported","session","crud"]},{"code":"20 IllegalOperation","title":"Illegal operation","severity":"caution","meaning":"The requested operation is invalid/forbidden for the server's current state or the target object's type. MongoDB returns this generic error when it rejects a logically disallowed action.","cause":"Attempting a collection-only operation on a view; an invalid rename between two databases; a forbidden change on system collections; or an administrative action not allowed by the replica set/sharding configuration. Varies by context.","fix":"Read the details of the error message; it usually states explicitly which operation was forbidden and why. Adapt the operation to the target object's type (e.g. work on the underlying collection instead of the view). For rename/move operations, review MongoDB's restrictions (such as renameCollection within the same DB). Check the cluster state (replica set/sharding).","tags":["illegal","invalid","operation","view","rename","system"]},{"code":"48 NamespaceExists","title":"Namespace (collection) already exists","severity":"safe","meaning":"The collection (namespace) you are trying to create already exists. MongoDB reports that it is present, so there is no need to create a new collection with the same name and options.","cause":"Trying to re-create an existing collection with createCollection (possibly with different options); schema/setup scripts run more than once; or attempting to redefine an existing name as a capped collection or with special options.","fix":"Usually safe to ignore; it means the collection already exists. Make your setup scripts idempotent: check existence before creating (db.getCollectionNames().includes('name')). To change the options you must drop and re-create the collection (back up the data first): db.my_collection.drop() then createCollection.","tags":["namespace","collection","collection exists","createcollection","already exists","idempotent"]},{"code":"11601 Interrupted","title":"Operation was interrupted","severity":"caution","meaning":"The running operation was interrupted. MongoDB terminated it before completion because of external intervention or a change in server state.","cause":"An administrator/tool killing the operation with db.killOp(); the server entering shutdown; a primary change in the replica set (stepdown/failover). (Exceeding maxTimeMS returns 50 MaxTimeMSExpired, not this error.) Long-running queries can be stopped this way.","fix":"Investigate why the operation was interrupted: currentOp/killOp history, failover events, server maintenance. If the operation is idempotent, retry it safely. Optimize and shorten long queries; add appropriate indexes. Use transient-error retry logic in the driver; ensure automatic reconnection on replica set failovers.","tags":["interrupted","interruption","killop","failover","stepdown","retry"]},{"code":"85 IndexOptionsConflict","title":"Index already exists with different options / different name","severity":"caution","meaning":"The index you are creating conflicts with an existing one: an index with the same name has different options, or an equivalent index with the same key and options already exists under another name. MongoDB does not let you re-create the same index with a conflicting definition.","cause":"Creating an index with the same name and key but different options (e.g. expireAfterSeconds, partialFilterExpression, collation); or redefining the equivalent of an existing index under a different name ('Index already exists with a different name'). Index definitions drifting over time in application/migration scripts is the typical cause. Same name with a different key gives 86 IndexKeySpecsConflict instead.","fix":"View the existing index: db.my_collection.getIndexes(). If you really want to change the options, drop the old index and create the new one: db.my_collection.dropIndex('index_name') then createIndex(...). Otherwise make the definition match the existing index exactly, or give the new index a different name. Make migration scripts idempotent.","tags":["index","indexes","options","conflict","unique","createindex"]},{"code":"67 CannotCreateIndex","title":"Cannot create index","severity":"caution","meaning":"The index build failed because of a constraint or limit. MongoDB could not build the requested index due to existing data or configuration.","cause":"Exceeding the maximum number of indexes allowed per collection (64); a key that is too large; or incompatible option combinations (e.g. an unsuitable field type for a TTL index). Note: building a unique index while the existing data contains duplicates raises E11000 DuplicateKey, not this code.","fix":"If you hit the index count limit, drop unused indexes (analyze usage with $indexStats). Check key size/type compatibility and the option combination. On large collections, build the index during low load (since 4.2 index builds do not block writes for long anyway; the background option is ignored).","tags":["index","indexes","unique","duplicate","limit","ttl"]},{"code":"27 IndexNotFound","title":"Index not found","severity":"safe","meaning":"The referenced index was not found on the collection. Usually appears when trying to drop or rename a nonexistent index.","cause":"Giving a wrong index name or wrong key specification in a command such as dropIndex/hideIndex; the index having been dropped earlier; or the auto-generated index name differing from what was expected (MongoDB generates names like field_1_field2_-1).","fix":"List existing indexes and their exact names: db.my_collection.getIndexes(). Repeat the operation with the correct name or key document: db.my_collection.dropIndex({field: 1}) or db.my_collection.dropIndex('field_1'). Write drop scripts so they do not fail when the index is absent (with an existence check).","tags":["index","indexes","dropindex","not found","getindexes","name"]},{"code":"79 UnknownReplWriteConcern","title":"Unknown replica set write concern","severity":"caution","meaning":"The specified write concern is not recognized. The w value used in the operation (a custom/tagged write concern name or a numeric value) is not defined in the replica set configuration.","cause":"Using a custom write concern name not defined in getLastErrorModes (e.g. a typo instead of 'majority', or a custom tag name). The configuration and the request do not match. Note: requesting a numeric w above the number of voting members is a different error (100 UnsatisfiableWriteConcern).","fix":"Make sure the write concern name you use is defined in the replica set settings. View the current configuration: rs.conf() (settings.getLastErrorModes). If you use a custom mode, define it first, or switch to standard values ('majority' or a valid numeric w). Review the writeConcern setting in the driver/application connection string.","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":"You tried to run the DEBUG command, but the server does not allow it. DEBUG is a low-level, dangerous diagnostic command; for security reasons it is disabled or restricted by default in newer Redis versions.","cause":"In Redis 7+, DEBUG subcommands do not run without the enable-debug-command setting; managed cloud Redis services usually disable the command completely; or the command is denied by an ACL. Test/diagnostic scripts (e.g. DEBUG SLEEP, DEBUG OBJECT) often hit this error.","fix":"Avoid DEBUG in production. If it is genuinely needed in a local/test environment, enable it in the configuration: enable-debug-command yes (or 'local') in redis.conf. On cloud services the command usually cannot be enabled at all; use safe commands such as MEMORY USAGE, OBJECT ENCODING and INFO for equivalent information. If an ACL restriction applies, review the permissions.","tags":["debug","command","prohibited","security","acl","config"]},{"code":"ERR Can't save in background: fork failed","title":"Background save failed: fork failed","severity":"danger","meaning":"Redis failed to create a child process (fork) for a background RDB snapshot or AOF rewrite. Because persistence cannot run, data cannot be written to disk.","cause":"The operating system cannot allocate enough memory for the fork; usually overcommit_memory is disabled and the system refuses when Redis tries to reserve roughly a copy of the memory it uses. Insufficient physical/virtual memory or process count limits can also cause it.","fix":"Enable memory overcommit on Linux: sysctl vm.overcommit_memory=1 (add it to /etc/sysctl.conf to make it permanent). Add memory to the server or lower Redis memory usage (maxmemory). Provide sufficient swap space. Check process/file descriptor limits (ulimit). If persistence is critical, do not go to production without these settings.","tags":["fork","rdb","aof","persistence","overcommit","memory"]},{"code":"NOPROTO unsupported protocol version","title":"Unsupported RESP protocol version","severity":"caution","meaning":"The client requested a RESP protocol version the server does not support. Usually returned when RESP3 (version 3) is requested with the HELLO command and the server supports only RESP2.","cause":"HELLO 3 was sent but the server runs an old Redis version (pre-6.0) or does not support RESP3; the client library requests RESP3 by default but the server is old. A version mismatch.","fix":"Configure your client to use RESP2, or request a valid version with HELLO (HELLO 2). Upgrade the server to a version that supports RESP3 (Redis 6+). Review the protocol version setting of your client library; most libraries can fall back to RESP2 automatically. Verify the server version: 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)","severity":"caution","meaning":"The configuration parameter you tried to change with CONFIG SET is not recognized in this Redis version, or the number of arguments is wrong. In Redis 7 the full message is \"ERR Unknown option or number of arguments for CONFIG SET - '<name>'\"; older versions return \"ERR Unsupported CONFIG parameter\". CONFIG GET does not raise an error for an unknown parameter; it returns an empty list.","cause":"A misspelled parameter name; using a parameter from a different Redis version (parameters can be added and removed between versions); or the managed cloud service not allowing certain parameters to be changed. Running old scripts on newer/older versions is the typical cause.","fix":"Verify the valid parameter name. List current settings with a pattern, such as CONFIG GET 'maxmemory*'. Check the parameter list in the documentation for your Redis version; see the version with INFO server. If restricted on a cloud service, use the parameter set the provider allows. Update your scripts for the target version.","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 was sent again while a MULTI (transaction) block was already open. Redis does not support nested transaction blocks; only one MULTI can be active at a time.","cause":"Application logic starting a new MULTI without closing the previous one with EXEC/DISCARD; reusing the same connection from a connection pool without properly resetting the transaction state; or a library wrapper unknowingly calling MULTI twice.","fix":"Close every MULTI block with a single EXEC (or DISCARD to cancel) and open a new block only after finishing the previous one. Clearly separate transaction start/end in the code flow. Make sure no transaction is left open before returning a connection to the pool. If nested logic is required, consider doing the atomic operation with a Lua script (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","severity":"caution","meaning":"A Lua script run with EVAL/EVALSHA tried to access an undefined global variable. To protect the scripting environment, Redis restricts creating/reading global variables and returns such access as an error.","cause":"Accidentally using a global instead of a local variable in the script (forgetting the local keyword); accessing an undefined name because of a typo; or trying to keep state in a global variable. The sandboxed Redis Lua environment does not allow these.","fix":"Declare all variables in the script with local: local x = ... Fix typos; make sure every variable is defined before use. Scripts should be stateless; for persistent state use Redis keys (KEYS/redis.call), not global Lua variables. Keep the script short and deterministic.","tags":["lua","eval","script","global","variable","local"]},{"code":"ERR unknown command","title":"Unknown command","severity":"caution","meaning":"Redis does not recognize the command you sent. The command name may be wrong, not available in this version, or disabled/renamed.","cause":"A misspelled command name; the command was added in a newer Redis version (not present on the old server); calling a command that requires a module (e.g. RedisJSON, RediSearch) without the module loaded; or the command was disabled with rename-command/ACL. Sometimes wrong argument splitting by the client also makes the command name look corrupted.","fix":"Verify the command name and syntax (COMMAND DOCS command or the documentation). Check the server version: INFO server. For a module command, verify the module is loaded: MODULE LIST. If the command was hidden with rename-command, review the configuration. Make sure your client library encodes the command correctly.","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)","severity":"caution","meaning":"In Redis Cluster, while a hash slot was being moved between nodes (rehashing/resharding), a request containing multiple keys belonging to the same slot was made, and some of the keys had already been moved while others had not. The server asks you to retry the operation shortly.","cause":"Running a multi-key command (MGET, MSET, multi-key Lua, transaction) while a slot migration is in progress. In the migrating slot some keys are on the source node and some on the target node, so the command cannot be executed atomically.","fix":"Catch this error in the client and retry after a short wait (TRYAGAIN is transient; it clears when resharding finishes). For multi-key operations, guarantee that all keys are in the same slot with a hash tag, such as {app_user123}.profile, {app_user123}.settings. Perform cluster rebalancing (resharding) during low-traffic periods.","tags":["cluster","slot","rehash","resharding","tryagain","hash tag"]},{"code":"ERR DISCARD without MULTI","title":"DISCARD without MULTI","severity":"caution","meaning":"DISCARD was sent while no MULTI block was open. DISCARD is used only to cancel an in-progress transaction; it returns this error when there is nothing to cancel.","cause":"Calling DISCARD without MULTI, or again after the transaction was already closed with EXEC/DISCARD; an unconditional DISCARD call in error handling code; or the connection state differing from what is expected (MULTI is not open on the connection taken from the pool).","fix":"Call DISCARD only when a MULTI is actually open; track transaction state in code. In error handlers, check whether a transaction is open before calling DISCARD. Use your library's transaction wrapper so it manages the state automatically. Guarantee a clean state in the connection pool.","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)","severity":"caution","meaning":"The search context used by a scroll query no longer exists on the server. It usually appears when continuing with a stored scroll_id after the scroll context has timed out or been cleared.","cause":"The keep-alive specified for the scroll expired (too long a pause between requests); the context was cleared manually (clear scroll); or the context was lost when a node restarted. Scroll contexts are held in node memory, so their lifetime is limited.","fix":"Pass a sufficient keep-alive on every scroll request (e.g. scroll=5m) and shorten the delay between pages. In modern Elasticsearch, use search_after + Point in Time (PIT) instead of scroll for deep pagination; it is more scalable and the recommended method. Release contexts when finished (clear scroll) so memory is not wasted.","tags":["scroll","search_after","pit","context","timeout","pagination"]},{"code":"aliases_not_found_exception","title":"Aliases missing","severity":"safe","meaning":"The alias you are trying to operate on does not exist. It is usually returned when removing, or acting on, an alias that does not exist.","cause":"The alias name is misspelled; the alias was deleted earlier; or a remove action in the _aliases API names an alias that was never attached. It also appears when no alias matches a wildcard pattern.","fix":"List existing aliases: GET _alias, or for a specific index GET my-index/_alias. Retry the operation with the correct name. Write deletion scripts so they do not fail when the alias is absent; handle actions conditionally in _aliases bulk operations. To repoint aliases atomically, use add and remove in a single _aliases request.","tags":["alias","alias name","not found","aliases","index","routing"]},{"code":"snapshot_missing_exception","title":"Snapshot is missing","severity":"caution","meaning":"The referenced snapshot was not found in the specified repository. It indicates that a nonexistent snapshot name was used during a restore, delete or status request.","cause":"The snapshot name is misspelled; the snapshot was deleted earlier; the wrong repository was queried; or the backup never completed. A similar result occurs when the repository definition has changed or is unreachable.","fix":"List the snapshots in the repository: GET _snapshot/repo_name/_all (or for a specific name GET _snapshot/repo_name/snapshot_name). Retry with the correct repository and snapshot name. Check the repository definition: GET _snapshot/repo_name. Verify that your backup policy (SLM) runs successfully and produces snapshots.","tags":["snapshot","backup","repository","repo","restore","not found"]},{"code":"index_template_missing_exception","title":"Index template is missing","severity":"safe","meaning":"The referenced index template was not found. A nonexistent template name was used while getting, deleting or performing an operation based on a template.","cause":"The template name is misspelled; the template was deleted earlier; the legacy template API was confused with the composable index template API; or the template was never created. It also appears when a setup script did not complete.","fix":"List existing templates. For composable templates: GET _index_template or GET _index_template/name. For legacy templates: GET _template. Retry with the correct API and name. If the template is missing, create it: PUT _index_template/name {...}. Make delete/update scripts idempotent so they do not fail when the template is absent.","tags":["index template","template","composable","legacy","not found","mapping"]},{"code":"settings_exception","title":"Invalid settings","severity":"caution","meaning":"The operation was rejected because the given setting is invalid. While applying index or cluster settings, there is a problem with the setting name, its value, or whether it can be changed in that context.","cause":"An unknown/misspelled setting name; a value of the wrong type or outside the allowed range; trying to change on a live index a static setting that can only be set at index creation (e.g. number_of_shards); or using a removed/renamed setting.","fix":"Validate the setting name and value against the documentation. Update dynamic settings on the live index: PUT my-index/_settings {\"index.number_of_replicas\": 2}. Static settings (e.g. number_of_shards) are set only at creation; changing them requires a reindex. View current settings: GET my-index/_settings.","tags":["settings","setting","invalid","static","dynamic","reindex"]},{"code":"shard_not_found_exception","title":"Shard not found","severity":"danger","meaning":"The target shard was not found. Elasticsearch could not complete the request because it could not reach the relevant shard of the specified index.","cause":"The shard is not yet assigned (unassigned) or is in recovery; the shard is temporarily missing because a node left the cluster; or a low-level request routed directly to a shard carries routing information that is no longer valid. It appears when cluster health is degraded.","fix":"Check cluster and shard health: GET _cluster/health and GET _cat/shards?v. See why shards are unassigned: GET _cluster/allocation/explain. If a node was lost, bring it back; review disk/allocation settings (watermark). If recovery is in progress, wait for it to finish. Keep an adequate replica count so data stays available when a single node is lost.","tags":["shard","shard","unassigned","unassigned shard","recovery","cluster health"]},{"code":"not_x_content_exception","title":"Compressor detection error / body is not valid content","severity":"caution","meaning":"Elasticsearch could not recognize the request body as valid structured content (XContent such as JSON). The data sent is not in the expected format or is corrupted.","cause":"The request body is invalid JSON; a wrong or missing Content-Type header (application/json not sent); the body was accidentally sent compressed/binary; or plain JSON was sent to endpoints such as _bulk that expect newline-delimited JSON (NDJSON). Escaping/encoding errors also trigger it.","fix":"Verify that the request body is valid JSON and send the Content-Type: application/json header. For the _bulk endpoint use NDJSON, where each line is a separate JSON object and the final line ends with a newline (Content-Type: application/x-ndjson). Make sure your client library serializes the body correctly. If you use compression, send the appropriate Content-Encoding header.","tags":["json","xcontent","content-type","bulk","ndjson","request body"]},{"code":"document_source_missing_exception","title":"Document source is missing","severity":"caution","meaning":"The operation needs the document's _source field, but it is not stored. An operation that requires _source (e.g. partial update, fetching source) was attempted on an index with source disabled.","cause":"The index mapping sets _source: {enabled: false}; since the document's original JSON is not stored, partial updates, reindex and queries that return _source cannot work. Configurations that disable source to save space cause this problem.","fix":"Operations that need _source require source to be enabled in the mapping. When creating a new index, leave _source enabled (it is enabled by default). If _source is disabled on an existing index, reindex the full document instead of updating it (overwrite with the index API). To save space, consider _source filtering (includes/excludes) or synthetic _source instead of disabling source entirely.","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","severity":"danger","meaning":"The instance cannot start because the automatic memory management requested via MEMORY_TARGET / MEMORY_MAX_TARGET is not supported by the operating system's shared memory configuration. On Linux this is usually because /dev/shm is smaller than the requested memory target.","cause":"MEMORY_TARGET is set larger than /dev/shm (mounted as tmpfs); or MEMORY_TARGET (AMM) is used while HugePages is enabled; AMM is not compatible with HugePages, whereas ASMM (SGA_TARGET) is. Oracle Automatic Memory Management works by creating files on /dev/shm, and if this space is insufficient the instance does not start.","fix":"Make /dev/shm larger than MEMORY_TARGET: remount tmpfs (mount -o remount,size=8G /dev/shm) and make it persistent in /etc/fstab. Alternatively, use separate settings instead of AMM: set MEMORY_TARGET and MEMORY_MAX_TARGET to 0 and define SGA_TARGET and PGA_AGGREGATE_TARGET. If you use HugePages, disable AMM and prefer ASMM (SGA_TARGET).","tags":["memory_target","memory","amm","dev/shm","hugepages","startup"]},{"code":"ORA-27102","title":"out of memory","severity":"danger","meaning":"During startup, the Oracle instance could not allocate the shared memory (SGA) segment it requested from the operating system. Usually seen together with ORA-27102 and a lower-level OSD/Linux error code (e.g. Linux-x86_64 Error: 28: No space left on device).","cause":"The memory requested for the SGA exceeds the kernel's shared memory limits (shmmax, shmall); the server lacks sufficient physical RAM/swap; or the HugePages configuration is missing/insufficient so a large SGA cannot be allocated. Common after SGA_MAX_SIZE is increased.","fix":"Increase the kernel shared memory parameters: set kernel.shmmax and kernel.shmall in /etc/sysctl.conf to cover the SGA and run sysctl -p. If you use HugePages, calculate and set vm.nr_hugepages based on the SGA. Check total RAM and swap on the server; if necessary, lower 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","severity":"danger","meaning":"A server process could not allocate space in its PGA (Program Global Area), i.e. its private process memory. Unlike shared memory in the SGA, this is the exhaustion of a single session's/process's own memory.","cause":"Very large sort/hash operations, oversized PL/SQL collections, variables accumulating in uncontrolled loops, or operating system process memory limits (ulimit -v/-d, vm.max_map_count, address space) have exhausted process memory. On 32-bit instances the address space limit can also trigger it.","fix":"Find what is consuming memory by examining the allocation category on the second line of the error (e.g. 'pga heap', 'koh-kghu sessi'). Review PGA_AGGREGATE_LIMIT / PGA_AGGREGATE_TARGET. Check operating system limits (ulimit -a; memlock, as, stack for the oracle user). Optimize the query/PL/SQL and reduce bulk processing batch sizes. If needed, contact Oracle Support with the trace file.","tags":["pga","process memory","memory","ulimit","sort","plsql"]},{"code":"ORA-12537","title":"TNS:connection closed","severity":"danger","meaning":"The TNS connection between client and server ended because the communication channel was closed unexpectedly. The session drops while the connection is being established or immediately afterwards.","cause":"The listener handed the client off to a server process, but the process could not be created or terminated immediately: instance crash, PROCESSES limit reached, operating system resource shortage, a firewall/middle tier cutting the connection, or a TCP.VALIDNODE_CHECKING/TCP.INVITED_NODES rule in sqlnet.ora rejecting the client. Sometimes an idle firewall between client and server drops the connection.","fix":"Examine listener.log and sqlnet.log. Check the PROCESSES/SESSIONS limits: SELECT * FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); Verify that the TCP.INVITED_NODES/TCP.EXCLUDED_NODES lists in the server-side sqlnet.ora do not reject the client IP (a logon version mismatch raises ORA-28040, not this error). Increase the idle timeout on the firewall or send keepalives with SQLNET.EXPIRE_TIME. Check the alert.log for instance crash records.","tags":["tns","connection","listener","processes","firewall","sqlnet"]},{"code":"ORA-03135","title":"connection lost contact","severity":"danger","meaning":"A previously established, working session lost contact with the server. A long-running query or an idle connection was cut off because the two sides could no longer reach each other.","cause":"The server process terminated unexpectedly (ORA-00600/07445, kill, resource limit); a RESOURCE_LIMIT profile terminated the session; an idle connection was dropped by a firewall/load balancer; or there was a network outage/packet loss. Typically occurs with queries that do not respond for a long time or with idle connections.","fix":"Search the alert.log and related trace files for why the process died (ORA-00600/ORA-07445?). On the network side, increase the firewall idle timeout and enable dead connection detection with SQLNET.EXPIRE_TIME in sqlnet.ora. Check CONNECT_TIME/IDLE_TIME limits in the user profile. Examine server resource pressure (memory, CPU, PROCESSES).","tags":["connection","sqlnet","idle","firewall","dead connection","network"]},{"code":"ORA-01033","title":"ORACLE initialization or shutdown in progress","severity":"danger","meaning":"The database instance you are connecting to is in the middle of startup (mount/open) or shutdown. Normal user connections are not accepted in this intermediate state.","cause":"The instance is stuck in NOMOUNT/MOUNT (e.g. awaiting media recovery), a SHUTDOWN is in progress, or the database is hung before reaching OPEN due to an error. Connecting to a freshly started instance before startup completes also returns this error.","fix":"Connect as SYSDBA and check the state: sqlplus / as sysdba; then SELECT status FROM v$instance; and SELECT open_mode FROM v$database; Try opening the instance: ALTER DATABASE OPEN; If recovery is required, run RECOVER DATABASE; then open it. Find the underlying error preventing open in the alert.log (missing/corrupt datafile, redo). If shutdown is hung, try SHUTDOWN ABORT followed by STARTUP.","tags":["startup","shutdown","mount","open","recovery","instance"]},{"code":"ORA-00600","title":"internal error code, arguments: [...]","severity":"danger","meaning":"An internal consistency check in the Oracle kernel failed. This is a generic internal error indicating the kernel code encountered an unexpected condition; the arguments in square brackets identify which code path was triggered.","cause":"Can be triggered by block/data corruption, an unfixed Oracle software bug, inconsistent patch levels, memory/hardware problems or unexpected edge cases. The first argument (e.g. [ktbdchk1]) indicates which layer the problem relates to and is key to support analysis.","fix":"This error should almost always be handled with Oracle Support. Preserve the generated trace file and the alert.log entry; search the first argument in My Oracle Support with the ORA-600 Troubleshooter and the 'ORA-600 Lookup Tool'. Apply the appropriate patch/PSU/RU. If corruption is suspected, verify block integrity with DBVERIFY/RMAN VALIDATE. In critical environments do not experiment in production without opening an SR.","tags":["internal","ora-600","kernel","bug","trace","support"]},{"code":"ORA-07445","title":"exception encountered: core dump","severity":"danger","meaning":"An Oracle server process crashed unexpectedly after receiving an operating system-level exception (e.g. SIGSEGV segmentation fault, access violation) and produced a core dump. Unlike ORA-00600, the error is triggered by a signal from the operating system.","cause":"Usually an Oracle software bug, memory corruption, corrupt data/index blocks, incompatible libraries or a hardware/memory failure. The function name and signal type (e.g. SIGSEGV) in the message identify the code path that crashed.","fix":"Preserve the generated trace and incident files and the alert.log, and search My Oracle Support with the function name + signal; a patch is usually required. Run hardware/memory tests and align operating system and Oracle patch levels. If corruption is suspected, run RMAN VALIDATE / DBVERIFY. If it recurs, open an SR and send the core dump to Support.","tags":["ora-7445","core dump","sigsegv","kernel","bug","trace"]},{"code":"ORA-01102","title":"cannot mount database in EXCLUSIVE mode","severity":"danger","meaning":"The database cannot be mounted in EXCLUSIVE mode because another instance (or leftover files from an old instance) still appears to hold a lock on it. The instance stops at the MOUNT stage.","cause":"The previous instance did not shut down cleanly and lock files (lk<SID> under $ORACLE_HOME/dbs, or leftover shared memory/semaphores) remain; another instance is already attached to the same database; or a second instance was started in a non-RAC installation. Leftover processes/shared memory from a previous instance or a stale lk<SID> file are the most common causes.","fix":"Verify that no other instance is actually running: ps -ef | grep pmon and ipcs -m (shared memory segments). If remnants exist, ABORT the instance and clean up; if necessary remove leftover segments/semaphores with ipcrm. Delete the lk<SID> lock file under $ORACLE_HOME/dbs (only if the instance is definitely down). Then try STARTUP MOUNT. On RAC, check CLUSTER_DATABASE and node status.","tags":["mount","exclusive","lock","startup","pmon","instance"]},{"code":"ORA-29913","title":"error in executing ODCIEXTTABLEOPEN callout","severity":"caution","meaning":"An error occurred while reading/parsing the source file when accessing an external table. Oracle failed in the access driver (ORACLE_LOADER / ORACLE_DATAPUMP) routine it calls to fetch external table data.","cause":"Usually the file is not present in the path the DIRECTORY object points to, the Oracle OS user lacks read permission on the file/directory, there is no write permission for log/bad files, or the access parameters (field delimiter, record format) are wrong. Often accompanied by sub-errors such as ORA-29400 and KUP-04xxx.","fix":"Read the sub-errors (KUP-xxxx) to determine the root cause. Verify the DIRECTORY path and that the file exists: SELECT directory_name, directory_path FROM all_directories; Grant the Oracle OS user read permission on the file and directory, and write permission on the log/bad directory. Match the ACCESS PARAMETERS section of the external table definition to the source file format. If necessary, check the READ/WRITE grants on the 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":"The client could not establish a connection to the PostgreSQL server. The connection failed at the network/address level, before it became a session.","cause":"Wrong host/port; an address the server is not listening on (connecting remotely while listen_addresses = 'localhost'); the server is down/unreachable; a firewall blocks the port (default 5432); or a DNS resolution failure. A misconfigured connection pool can also trigger it.","fix":"Verify that the server is up and listening on the correct address: check listen_addresses = '*' (or the relevant IP) and the port setting in postgresql.conf, then restart the server (listen_addresses and port require a restart; a reload is not enough). Test access from the client: psql -h host -p 5432 -U app_user. Open 5432/tcp in the firewall. Review the host/port/DNS values and the connection string.","tags":["connection","connection","network","listen_addresses","port","firewall"]},{"code":"08004","title":"sqlserver_rejected_establishment_of_sqlconnection","severity":"danger","meaning":"The server was reached, but it refused to establish the connection. Network access exists, but the server did not accept this connection by policy.","cause":"The PostgreSQL server rarely produces this code; it is mostly reported by the client driver or the connection pool. The server's actual codes for similar rejections are different: a pg_hba.conf rejection ('no pg_hba.conf entry for host', the reject method) returns 28000 (invalid_authorization_specification), max_connections exhaustion returns 53300 (too_many_connections), and a connection refused during startup/shutdown returns 57P03.","fix":"Read the real SQLSTATE and reason for the rejection in the server log. For a pg_hba rejection (28000), add a matching line for the client IP, user and database (e.g. host all all 10.0.0.0/24 scram-sha-256) and reload with SELECT pg_reload_conf();. For connection saturation (53300), compare client connections with max_connections using 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":"Your active connection was terminated because of an administrator command. The client receives this error when the server is shutting down or the session is terminated manually ('terminating connection due to administrator command').","cause":"A DBA killed the session with pg_terminate_backend(); the server is being shut down in fast/immediate mode (smart shutdown does not kill sessions, it waits for them to finish); or existing connections were dropped for a configuration change/planned maintenance. In some failover scenarios the old primary also cuts connections this way.","fix":"This is usually not a permanent error; the application must reconnect (retry). If unexpected, investigate the shutdown/terminate reason in the server log. For planned maintenance, implement automatic reconnection and connection pool health checks in the application. The server log does not show who terminated a session; to trace pg_terminate_backend calls, keep an audit trail with log_statement (or pgaudit).","tags":["shutdown","terminate","connection","maintenance","failover","retry"]},{"code":"53400","title":"configuration_limit_exceeded","severity":"danger","meaning":"The operation failed because a configuration limit was exceeded. The server rejected the request because of a defined limit (e.g. temp_file_limit, the number of replication slots or WAL senders).","cause":"Typical examples: a query's temporary file usage exceeding temp_file_limit ('temporary file size exceeds temp_file_limit'); all replication slots being in use ('all replication slots are in use', max_replication_slots); exceeding the max_wal_senders limit. Note: max_prepared_transactions exhaustion returns 53200, and prepared transactions being disabled (value 0) returns 55000.","fix":"The error message states which limit was exceeded. For temp_file_limit, optimize the query (use EXPLAIN ANALYZE to find sort/hash steps spilling to disk), increase work_mem at session level if needed, or raise the limit deliberately. If slots are exhausted, clean up unused slots after verifying their owners: SELECT slot_name, slot_type, active FROM pg_replication_slots; or increase max_replication_slots / max_wal_senders and restart the server.","tags":["limit","temp_file_limit","replication slot","max_wal_senders","configuration","resource"]},{"code":"XX001","title":"data_corrupted","severity":"danger","meaning":"PostgreSQL detected data corruption in a table/heap page. The query cannot complete safely because the content of the block read is inconsistent ('invalid page in block').","cause":"Disk/storage failure, file system corruption, fsync guarantees not being honored during a sudden power loss, faulty hardware (RAM/controller) or, rarely, a software bug. With checksums enabled, corruption is caught earlier.","fix":"First verify the hardware/storage and, if possible, plan to fall back to a physical backup (PITR) immediately. If a good backup exists, restore it. For recovery, AT THE RISK OF DATA and only as a last resort, you can zero out damaged pages with zero_damaged_pages = on and export the readable data (this causes data loss). Enable data_checksums (on an existing cluster with PG12+, pg_checksums --enable while the server is stopped), run RAM tests and take regular PITR backups.","tags":["corruption","corruption","checksum","page","hardware","pitr"]},{"code":"XX002","title":"index_corrupted","severity":"danger","meaning":"Corruption was detected in an index structure. Because the data the index points to is inconsistent with the actual rows in the heap, queries using the index can return wrong results or fail.","cause":"Hardware/storage failure, B-tree ordering broken by a collation (sort rule) change after an operating system glibc/ICU upgrade, or a sudden outage. Operating system collation updates in particular cause silent corruption in text indexes.","fix":"Rebuild the corrupted index: REINDEX INDEX idx_name; or REINDEX TABLE my_table; On large tables use REINDEX INDEX CONCURRENTLY to reduce locking. If collation is the cause, rebuild all affected text indexes and verify them with the amcheck extension (bt_index_check). If the root cause is hardware, replace the storage.","tags":["index","index","corruption","reindex","collation","amcheck"]},{"code":"25001","title":"active_sql_transaction","severity":"caution","meaning":"A command that can only run outside a transaction, or at its start, was called while a transaction was active. Some commands do not run inside a transaction block.","cause":"Settings such as SET TRANSACTION ISOLATION LEVEL must be issued before the first query in the block ('SET TRANSACTION ISOLATION LEVEL must be called before any query'). Similarly, some commands are rejected inside an open BEGIN/transaction. It usually comes from the application disabling autocommit and silently starting a transaction.","fix":"Issue transaction-level settings at the very start of the transaction, before any query runs: BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ... Check whether the client library opens an implicit transaction (autocommit setting). For commands that cannot run inside a transaction block, first close the current transaction with COMMIT/ROLLBACK.","tags":["transaction","transaction","isolation","set transaction","autocommit","begin"]},{"code":"42P16","title":"invalid_table_definition","severity":"caution","meaning":"The DDL command was rejected because the table definition is invalid. The requested table/column structure violates PostgreSQL rules.","cause":"Situations such as defining more than one primary key ('multiple primary keys for table are not allowed'), definitions conflicting with inheritance, generated/identity column restrictions, or a change to a structure that ALTER TABLE cannot produce. Common in auto-generated migration scripts.","fix":"Read the specific reason in the error message. Define only one PRIMARY KEY; if a multi-column key is needed, use a composite key of the form PRIMARY KEY (a, b). Review the identity/generated column rules. Check the current table definition with \\d my_table and fix the migration script.","tags":["ddl","table","primary key","definition","alter table","migration"]},{"code":"P0001","title":"raise_exception","severity":"caution","meaning":"A PL/pgSQL function or trigger deliberately raised an error with RAISE EXCEPTION. This error is produced not by the database engine but by business-rule code written by you (or a library).","cause":"When the business logic fails a validation, the developer called RAISE EXCEPTION 'message' USING ERRCODE = ...;. For example, a trigger raises a custom error to block a disallowed update or to enforce a consistency rule.","fix":"The error message and any HINT/DETAIL fields explain what the function rejected; satisfy the business rule accordingly. To find the source, inspect the relevant function/trigger code (the RAISE statements). If a specific SQLSTATE is needed, assign a meaningful code with RAISE ... USING ERRCODE instead of the generic P0001, so the application can tell it apart.","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":"The database (schema) you are trying to connect to or use does not exist on the server. The USE statement or the default database name in the connection string points to a non-existent schema.","cause":"The database was never created, its name is misspelled (case sensitivity can vary by operating system), it was dropped, or the application is connecting to the wrong environment (dev/prod). Schemas not being migrated when moving to a new server is another common cause.","fix":"List the existing databases: SHOW DATABASES; Make sure the name is spelled correctly and the case matches (the lower_case_table_names setting matters). If it does not exist, create it: CREATE DATABASE database_name CHARACTER SET utf8mb4; or restore it from backup. Verify the target environment in the application's connection configuration.","tags":["database","database","schema","use","name","connection"]},{"code":"1364","title":"Field 'name' doesn't have a default value","severity":"caution","meaning":"A row was inserted without a value for a column defined as NOT NULL with no DEFAULT. In strict SQL mode MySQL raises an error because it cannot fill in the missing required column automatically.","cause":"A required (NOT NULL, no DEFAULT) column was omitted from the INSERT, and STRICT_TRANS_TABLES/STRICT_ALL_TABLES is enabled in sql_mode. An application written for the old, permissive mode hits this error when moved to a server with strict mode enabled.","fix":"Add a value for the missing column to the INSERT, or define a sensible DEFAULT on the column: ALTER TABLE my_table MODIFY my_column ... NOT NULL DEFAULT 'value'; If the column really is optional, allow NULL. sql_mode can be relaxed as a temporary/compatibility workaround, but keeping strict mode is recommended for data quality.","tags":["insert","not null","default","strict mode","sql_mode","column"]},{"code":"1206","title":"The total number of locks exceeds the lock table size","severity":"danger","meaning":"InnoDB has exhausted the memory allocated for the locks it holds (the lock structures inside the buffer pool). It occurs when a single transaction accumulates too many row/gap locks, and the transaction is rolled back.","cause":"Very large single transactions: UPDATE/DELETE of millions of rows at once, a WHERE clause without an index that locks the whole table, or an innodb_buffer_pool_size that is too small for the workload. Large batch jobs are the classic trigger.","fix":"Split large DML into small batches (e.g. delete/update 10,000 rows at a time with LIMIT in a loop and COMMIT after each batch). Index the WHERE columns to reduce the number of locked rows. As a permanent fix, increase innodb_buffer_pool_size (lock structures are allocated from it). Avoid unnecessarily long transactions.","tags":["innodb","lock","lock","buffer pool","batch","memory"]},{"code":"1236","title":"Got fatal error 1236 from master when reading data from binary log","severity":"danger","meaning":"A replica (slave) server received a fatal error while reading data from the source's (master's) binary log, and replication stopped. The replica cannot find the position it expects in the source's binlog.","cause":"The required binary log files were deleted on the source (binlog_expire_logs_seconds — expire_logs_days before 8.0, removed in 8.4 — or PURGE BINARY LOGS); a GTID gap/mismatch; a corrupted binlog position after a source crash; or the replica fell so far behind that it requests a log that no longer exists. 'Could not find first log file name in binary log index file' is the typical sub-message.","fix":"Inspect replication status: SHOW REPLICA STATUS\\G (8.0.22+; SHOW SLAVE STATUS in older versions, the old syntax was removed in 8.4). List the binlogs available on the source: SHOW BINARY LOGS; If the missing log was purged, you must rebuild the replica from a new backup/GTID position (mysqldump --source-data — --master-data before 8.0.26, a deprecated alias in 8.0.26+ — or the CLONE plugin). Increase binlog retention on the source to fit the workload; if you use GTIDs, verify GTID gaps.","tags":["replication","replication","binlog","master","gtid","slave"]},{"code":"1129","title":"Host 'host' is blocked because of many connection errors","severity":"danger","meaning":"MySQL blocked a client host after consecutive connection errors from it exceeded the threshold (max_connect_errors). The host was blacklisted because of the handshakes that had failed up to that point.","cause":"A load balancer/monitoring tool sends incomplete or malformed handshakes to the TCP port; network problems cut connections midway; or a misconfigured client keeps attempting failed connections. When the error counter reaches max_connect_errors, the host is blocked.","fix":"Remove the block immediately: in SQL, TRUNCATE TABLE performance_schema.host_cache; (8.0.23+; FLUSH HOSTS was deprecated in 8.0.23 and removed in 8.4) or from the command line, mysqladmin flush-hosts. To see blocked hosts: SELECT host, ip, sum_connect_errors FROM performance_schema.host_cache; Fix the root cause: configure the health check/monitoring tool to perform a full MySQL handshake and check network stability. In healthy environments you can raise max_connect_errors to increase tolerance, but resolving the actual connection errors is what matters.","tags":["connection","host blocked","max_connect_errors","flush hosts","handshake","network"]},{"code":"1194","title":"Table 'name' is marked as crashed and should be repaired","severity":"danger","meaning":"A table, usually stored with the MyISAM engine (also Aria in MariaDB), is marked as corrupted/crashed and cannot be used until it is repaired. Queries accessing the table fail with this error.","cause":"The MyISAM table was not closed cleanly during a sudden server crash/power loss, the disk filled up, a hardware failure, or the mysqld process was killed. Because MyISAM offers no transactions or crash safety, this happens far more often than with InnoDB.","fix":"Repair the table: REPAIR TABLE table_name; or offline with myisamchk -r. Always back up the data files before repairing. As a permanent fix, move the tables to InnoDB: ALTER TABLE my_table ENGINE=InnoDB; which makes them crash-safe. Check disk health and the myisam_recover_options setting.","tags":["myisam","crashed","repair","corruption","corruption","innodb"]},{"code":"1615","title":"Prepared statement needs to be re-prepared","severity":"caution","meaning":"Execution of a server-side prepared statement was rejected because it became invalid after the metadata it depends on (table/view definition) changed. The statement must be prepared again.","cause":"DDL (ALTER TABLE) on the underlying table after the statement was prepared, the table being recreated, the metadata cache filling up and evicting the entry, or table_definition_cache/table_open_cache limits that are too small. Common on systems with heavy DDL and in some ORM/pool scenarios.","fix":"Most client libraries catch this and re-prepare the statement automatically; in the application, catch this error and retry prepare + execute. If it recurs frequently, increase table_definition_cache and table_open_cache. Avoid unnecessary DDL during application traffic.","tags":["prepared statement","re-prepare","ddl","metadata","cache","retry"]},{"code":"1553","title":"Cannot drop index needed in a foreign key constraint","severity":"caution","meaning":"You are trying to drop an index that is required by a foreign key constraint. InnoDB does not allow you to remove the index an FK relies on.","cause":"In InnoDB every foreign key requires an index on its referencing column(s). Dropping that index with DROP INDEX is rejected because it would leave the FK constraint without support. It usually comes up while reorganizing a table (index cleanup).","fix":"First drop the foreign key constraint, then drop the index: ALTER TABLE my_table DROP FOREIGN KEY fk_name; then DROP INDEX idx_name ON my_table; If needed, recreate the FK so that it relies on another suitable index. To list existing FKs: 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","severity":"caution","meaning":"You tried to load data from a client-side file with LOAD DATA LOCAL INFILE, but the feature is disabled on the client and/or server side. For security reasons LOCAL is disabled by default in MySQL 8.","cause":"The local_infile system variable is OFF on the server and/or the client driver has not enabled LOCAL INFILE. The feature is restricted by default because it can allow a malicious server to read client files.","fix":"If it is really needed, enable it on both sides: on the server SET GLOBAL local_infile = 1; on the client enable the driver's LOCAL INFILE flag (e.g. the allowLoadLocalInfile connection option / --local-infile=1). From a security standpoint, the preferred approach is to place the file on the server and use LOAD DATA INFILE without LOCAL, or use bulk INSERT/import tools. Disable local_infile again when you are done.","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","severity":"caution","meaning":"The table definition is invalid because the AUTO_INCREMENT rules were violated: a table can have only one auto-increment column, and that column must be part of a key (PRIMARY KEY or UNIQUE/index).","cause":"Defining more than one AUTO_INCREMENT column, or leaving the AUTO_INCREMENT column out of every key (index). It occurs during CREATE TABLE or ALTER TABLE.","fix":"Keep a single AUTO_INCREMENT column and make it part of a key: CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, ...); To fix an existing table, first add the column to a key: ALTER TABLE t ADD PRIMARY KEY (id); then define AUTO_INCREMENT. If you need more than one auto column, revisit the design; MySQL does not support it.","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":"The Transact-SQL parser found invalid syntax near the specified token and could not compile the statement. The query is rejected before it runs.","cause":"A typo, a missing comma/parenthesis, a reserved keyword (e.g. USER, ORDER) used without square brackets, an unterminated string literal, or incompatible syntax copied from another SQL dialect. Concatenation mistakes in dynamic SQL are also a frequent trigger.","fix":"Inspect the area around the reported token carefully; the error is often on the preceding line. Wrap reserved words in square brackets ([Order]) and check that parentheses and commas balance. If you build dynamic SQL, PRINT @sql to see the generated text and run it. Verify that the syntax is supported by the version/compatibility level.","tags":["syntax","syntax error","parse","tsql","dynamic sql","keyword"]},{"code":"8114","title":"Error converting data type X to Y","severity":"caution","meaning":"A value could not be converted from one data type to another because the types are incompatible. Unlike error 245, this is usually a failed type conversion at the parameter/expression level (e.g. varchar to numeric).","cause":"Sending non-numeric or malformed text to a numeric column/parameter; scale/precision overflow; trying to convert an empty string ('') to numeric; or passing an argument of the wrong type in a parameterized query. Common with implicit conversions generated by ORMs.","fix":"Verify that the converted values fit the target type. Use TRY_CONVERT / TRY_CAST for safe conversion; they return NULL on failure, so you can filter those rows with WHERE. Match parameter types to column types. Scan the data with ISNUMERIC/TRY_CAST to find offending rows, and clean up empty strings.","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","severity":"danger","meaning":"SQL Server could not allocate the memory the lock manager needs and cannot create more locks. The operation fails because the lock memory pool is exhausted.","cause":"Too many fine-grained (row/page) locks held at the same time: large, unindexed or bulk DML; lock escalation being prevented; general memory pressure on the server; or long-running open transactions. Occasionally external memory pressure is the trigger.","fix":"Split large operations into smaller batches to reduce the number of concurrent locks. Use proper indexes to reduce the number of rows scanned/locked. Review configurations that disable lock escalation: trace flags 1211/1224 and ALTER TABLE ... SET (LOCK_ESCALATION = DISABLE); check with SELECT name, lock_escalation_desc FROM sys.tables; and DBCC TRACESTATUS(-1); Check server memory and the max server memory setting, and relieve memory pressure.","tags":["lock","lock memory","memory","lock escalation","batch","resource"]},{"code":"3906","title":"Failed to update database because the database is read-only","severity":"caution","meaning":"You tried to modify data (INSERT/UPDATE/DELETE/DDL) in a database marked read-only. SQL Server rejected the write.","cause":"The database was set to READ_ONLY; the connection went to a secondary replica in an Availability Group (intended for reads); the database stayed read-only after a snapshot or restore; or the file system is read-only. An application accidentally routed to a secondary replica is the typical cause.","fix":"If it really must be writable: ALTER DATABASE mydb SET READ_WRITE; In an Availability Group, route the application to the primary replica (check ReadWrite connection routing / ApplicationIntent). A read-only snapshot cannot be written to; target the source database instead. Also verify file permissions and the file system.","tags":["read only","read-only database","availability group","replica","read_write","write"]},{"code":"15023","title":"User already exists in the current database","severity":"caution","meaning":"While adding a new user to the database, a user with the same name was found to already exist in that database. It also appears when mapping a login to an existing user (orphaned users).","cause":"Typically, when a database is restored from backup to a different server, login SIDs do not match the database users (orphaned users). Trying to add the same name again with CREATE USER, or trying to set up the login-user mapping manually, raises this error.","fix":"Remap the orphaned user to the existing login: ALTER USER app_user WITH LOGIN = login_name; (recommended over the legacy sp_change_users_login). If the user is genuinely redundant: DROP USER app_user; To find orphans: 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","severity":"danger","meaning":"A Windows authentication login attempt was rejected because it came from an untrusted domain (or Windows authentication was used where SQL authentication is required). The server does not trust this identity.","cause":"The client is connecting with a Windows identity from a domain/workgroup SQL Server does not trust; the machines are in different/untrusted domains; Kerberos/SPN configuration is wrong; or the connection string uses Integrated Security when SQL authentication is expected. A SQL login attempt while mixed mode is disabled shows a similar symptom.","fix":"If SQL authentication is required, switch the server to Mixed Mode and use a user name/password in the connection string (Integrated Security=false). If Windows authentication is required, verify the domain trust and the DNS/Kerberos SPN configuration. Make sure the client and server are in the same or a trusted domain. Narrow down the cause with the 'state' value in the error log.","tags":["login","authentication","windows","domain","kerberos","mixed mode"]},{"code":"605","title":"Attempt to fetch logical page belongs to a different allocation unit","severity":"danger","meaning":"While reading a page, SQL Server found a page belonging to a different object rather than the expected object/allocation unit. This is a strong indicator of serious physical/logical database corruption.","cause":"Broken page linkage caused by storage/disk failure, a controller or driver bug, write cache corruption during a sudden power loss, or file system damage. A 'transient 605' can occasionally stem from temporary I/O issues, but persistent ones are real corruption.","fix":"Immediately run DBCC CHECKDB ('mydb') WITH NO_INFOMSGS, ALL_ERRORMSGS to determine the extent of the damage. If possible, restore from a known-good backup (PITR that minimizes data loss is preferred). Test the storage/hardware and verify the PAGE_VERIFY CHECKSUM setting. REPAIR options can cause data loss, so use them only as a last resort and after taking a backup; in critical environments, engage Microsoft support.","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","severity":"caution","meaning":"You tried to change a column's type/property, but objects depending on that column (index, constraint, computed column, statistics, schema-bound view, etc.) prevented it. The column cannot be altered until the dependencies are removed.","cause":"The column is used in an index, a PRIMARY KEY/UNIQUE/CHECK/DEFAULT constraint, a computed column expression, a view/function defined WITH SCHEMABINDING, or a statistics object. A type/size change would invalidate those objects, so the ALTER is rejected.","fix":"First find the dependent objects and temporarily drop or disable them, alter the column, then recreate them. Query sys.sql_expression_dependencies and sys.indexes/sys.default_constraints to see the dependencies. Typical order: DROP CONSTRAINT/INDEX -> ALTER COLUMN -> CREATE again. For schema-bound views, temporarily remove SCHEMABINDING and add it back.","tags":["alter column","dependency","index","constraint","schemabinding","ddl"]},{"code":"7202","title":"Could not find server 'name' in sys.servers","severity":"caution","meaning":"The server name referenced in the query via a linked server or a four-part name (server.database.schema.object) was not found among the configured linked servers. SQL Server could not resolve the name.","cause":"The linked server was never defined; the name is misspelled; a linked server that exists in another environment (dev/prod) is missing in production; or OPENQUERY/the four-part name points to the wrong server name. Common when scripts are moved between servers.","fix":"List the defined linked servers: SELECT name FROM sys.servers; or EXEC sp_linkedservers. Add it if needed: EXEC sp_addlinkedserver @server='SERVER', ...; and set up login mapping with sp_addlinkedsrvlogin. Verify that the four-part name in the query uses the correct linked server name; if you only access a local object, remove the server prefix.","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","severity":"danger","meaning":"In Azure SQL Database, the service temporarily rejected the request due to resource pressure (engine throttling). The system is busy and the client is expected to retry shortly.","cause":"A resource such as CPU, connections, transaction log write rate or worker count hitting the limit of the selected service tier (DTU/vCore); a sudden load spike; or long-running/inefficient queries consuming the resource. The 'reason code' accompanying the error shows which resource is being throttled.","fix":"Implement retry logic with exponential backoff in the application; this is the expected behavior for transient errors. Review resource usage: sys.dm_db_resource_stats and Azure portal metrics. Optimize queries/indexes and reduce unnecessary concurrency. If saturation is persistent, scale up the service tier (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":"A write (or another operation that requires the primary) was sent to a replica set member that is not currently the primary. Only the primary accepts writes, so the operation was rejected.","cause":"The application is writing to a secondary; during a failover (election) the old primary became a secondary and the client is still connected to it; or the driver's read/write preference and topology information is stale. A missing replicaSet parameter in the connection string is the typical cause.","fix":"Configure the driver with all replica set members and the replica set name (replicaSet=... and all hosts in the connection string) so the driver discovers the primary automatically and re-routes on failover. Use the default read/write preference, which routes writes to the primary. Since this is a transient error, enable retryable writes in the application. Verify which member is PRIMARY with rs.status().","tags":["replica set","primary","failover","write","election","retryable"]},{"code":"91 ShutdownInProgress","title":"The server is in the process of shutting down","severity":"danger","meaning":"The operation could not complete because the server (mongod/mongos) is shutting down. A node that is shutting down rejects new requests and terminates in-progress operations.","cause":"A planned restart/maintenance, a shutdown preceding a primary stepdown in a replica set, a termination signal sent by the container/orchestrator, or the process being stopped due to resource exhaustion. It is expected during rolling upgrades.","fix":"This is usually transient; use retry logic in the application and a replica set connection so that requests are routed to another healthy member. If unexpected, check the mongod logs for the shutdown reason (signal, OOM killer, resources). During rolling maintenance, step down the primary cleanly before shutting it down (rs.stepDown()).","tags":["shutdown","shutting down","maintenance","stepdown","replica set","retry"]},{"code":"189 PrimarySteppedDown","title":"Primary stepped down while operation was in progress","severity":"danger","meaning":"The primary stepped down while the operation was running and no longer accepts writes. The in-progress command was therefore aborted.","cause":"An election was triggered in the replica set: the primary was stepped down manually for maintenance, lost contact with the majority, a priority change occurred, or there was a network partition. In-flight operations at failover time receive this error.","fix":"This is typically a retryable error; with retryable writes/reads enabled in the driver, the operation can be routed to the new primary automatically. Catch this error in the application and retry. If it happens frequently, investigate replica set health: check member states, network latency and oplog lag with rs.status(), and reduce unnecessary elections (heartbeat/timeout settings).","tags":["stepdown","election","failover","primary","replica set","retry"]},{"code":"24 LockTimeout","title":"Operation exceeded time limit waiting for a lock","severity":"caution","meaning":"An operation exceeded its time limit while waiting for a required lock (e.g. at database/collection level) and was aborted. The lock could not be acquired in time.","cause":"A long-running operation or DDL command (e.g. conflicting administrative operations on a collection) holds the required lock; heavy concurrent writes; or an operation bounded by maxTimeMS expires while waiting for a lock. Seen with conflicting index/collection operations.","fix":"Find the operation holding the lock: inspect active and waiting operations with db.currentOp(); terminate it with db.killOp() if necessary. Move conflicting administrative commands (index builds, collMod, compact) to off-peak hours. Keep operations short and avoid unnecessarily long transactions. If needed, tune maxTimeMS to the workload.","tags":["lock","lock wait","timeout","currentop","ddl","concurrency"]},{"code":"7 HostNotFound","title":"Could not find host","severity":"danger","meaning":"MongoDB could not resolve/find a host name it tried to connect to. The driver or a replica set member cannot reach the specified host, so the connection cannot be established.","cause":"DNS resolution failure, a wrong/stale host name in the connection string, replica set member host names (rs.conf) that the client cannot resolve, or a network/access problem. In container environments, internal host names that cannot be resolved from outside are the typical cause.","fix":"Verify that the host name is correct and resolvable (test DNS with ping/nslookup). Update the hosts in the connection string. Make sure replica set member names are resolvable by both clients and members; if needed, update the host names in rs.conf() to reachable FQDNs/IPs. If you use SRV (mongodb+srv), check the DNS SRV records.","tags":["host","dns","connection","replica set","network","srv"]},{"code":"175 QueryPlanKilled","title":"Query plan killed during execution","severity":"caution","meaning":"The plan of a running query became invalid because the underlying data/collection changed, and it was killed. The query was aborted because it could not continue safely.","cause":"While the query was running (especially with an open cursor), the collection was dropped, an index it was using was dropped, or a rename/reconfiguration occurred. Underlying changes during long-running getMore calls are the typical trigger.","fix":"Restart the query/cursor (retry). Make sure no destructive DDL (drop, dropIndex, rename) is run on the collection or index while queries are running; schedule such maintenance outside query traffic. Shorten long-running reads and make sure required indexes are permanent.","tags":["query","cursor","getmore","drop","index","plan"]},{"code":"11602 InterruptedDueToReplStateChange","title":"Operation interrupted due to a replication state change","severity":"danger","meaning":"The operation was interrupted because the replica set state changed (e.g. the primary changed). The in-progress command was terminated because it could not complete safely in the new topology.","cause":"In-flight operations are interrupted during an election, a primary stepdown, member role changes or a configuration change. It has the same root as 189 PrimarySteppedDown and usually appears at failover time.","fix":"This is a retryable error; with retryable writes/reads enabled in the driver, the operation can be retried against the new primary. Catch this error in the application and retry. If it recurs frequently, investigate the cause of the elections: network instability, heartbeat timeouts, overload or oplog lag. Monitor replica set health with rs.status().","tags":["replica set","election","failover","interrupted","retry","state change"]},{"code":"136 CappedPositionLost","title":"Capped collection position lost","severity":"caution","meaning":"A cursor reading a capped (fixed-size) collection (typically a tailable cursor) lost its position. The document it was positioned on no longer exists because the collection overwrote it cyclically.","cause":"When a capped collection is full, the oldest documents are removed and overwritten automatically; a tailable cursor following a collection that is written faster than it can consume falls behind and its position is overwritten. Seen in consumers that tail capped structures such as the oplog.","fix":"Reposition the cursor (open a new tailable cursor) and restart tailing; catch this error in the application and refresh the read. If the root cause is a consumer that cannot keep up with the write rate: increase the capped collection/oplog size or speed up the consumer. To track changes reliably, use change streams instead of tailable cursors where 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","severity":"caution","meaning":"An update tried to traverse a dotted field path (e.g. a.b.c), but part of the path hit a type that cannot contain subfields (a scalar value or an element of the wrong type). The path therefore could not be traversed.","cause":"Writing to a nested path such as a.b.c while field 'a' in the document is a scalar (e.g. number/string) rather than an object/array; or type mismatches such as finding an object where an array index is expected. Common in documents with an inconsistent schema (when updating deep paths with $set).","fix":"Before updating, make sure the path is of the expected type (object/array). Fix the incompatible documents or make the update conditional on the document structure. If needed, first convert the field to the correct type (e.g. replace the scalar value with an object), then write the nested field. Consider schema validation for schema consistency.","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":"You tried to create a consumer group on a stream with XGROUP CREATE, but a group with the same name already exists on that stream. Redis does not overwrite the existing group.","cause":"The application unconditionally tries to create the group at startup and the group is left over from a previous run; or multiple instances try to create the same group in parallel. Typical of non-idempotent startup logic.","fix":"Make group creation idempotent: catch and ignore the error (an existing group is fine). Use XGROUP CREATE stream group $ MKSTREAM to create the stream automatically if it does not exist yet. Run XINFO GROUPS stream to see existing groups. If you really want to reset it, first delete it with 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":"You ran a stream consumption command (XREADGROUP, XACK, XCLAIM, etc.), but the specified key (stream) or consumer group does not exist. The group/stream to operate on was not found.","cause":"The group was never created (XGROUP CREATE skipped); the stream key was deleted or never written; the group name/key is misspelled; or the group was removed with XGROUP DESTROY. Common when consumers start before the group is created.","fix":"First make sure the stream and group exist: XINFO STREAM stream and XINFO GROUPS stream. If the group is missing, create it: XGROUP CREATE stream group $ MKSTREAM (also creates the stream if needed). Add a step at application startup that creates the group (ignoring BUSYGROUP). Verify the key and group names are spelled correctly.","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":"You sent SCRIPT KILL or FUNCTION KILL, but no Lua script/function is currently running (and killable). The command was rejected because there is nothing to terminate.","cause":"The script had already finished by the time you tried to stop it; SCRIPT KILL was sent to the wrong node (while the script runs on another instance); or the BUSY condition resolved on its own before SCRIPT KILL. Usually a timing issue.","fix":"If no script is actually stuck, there is nothing to do; this is a safe/informational reply. If you get a BUSY error (a script exceeded busy-reply-threshold, named lua-time-limit before Redis 7), try SCRIPT KILL (FUNCTION KILL for functions) on the correct node where the script is running. If the script has modified data, SCRIPT KILL does not work; as a last resort SHUTDOWN NOSAVE may be needed. Break long scripts into pieces and respect the time limit.","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 is running in protected mode and rejected a password-less connection from outside (a non-loopback address). For security, unauthenticated remote access is blocked.","cause":"Redis, bound to all interfaces (or with a loose bind setting), received a connection from an external client without a password (requirepass) configured. protected-mode is on by default and in this situation blocks non-loopback requests. It indicates an unsecured Redis accidentally exposed to the internet.","fix":"The correct fix is to secure it: set a strong password with requirepass (or ACL users) and listen only on trusted interfaces with bind. If remote access is required, protect it with a password/TLS and restrict port 6379 with a firewall. protected-mode no may be set temporarily only on a closed/secure network, but it is NEVER recommended for internet-facing installations.","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":"After the connection entered Pub/Sub subscribe mode, you tried to run a regular command (e.g., GET/SET) that is not allowed in this mode. A connection in subscribe mode accepts only a limited set of commands. In older versions the message was \"ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context\".","cause":"Sending data commands over a connection that has switched to subscribe mode with SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE. In the RESP2 protocol a subscribed connection allows only (P|S)SUBSCRIBE, (P|S)UNSUBSCRIBE, PING, QUIT and RESET. Sharing a single connection for both pub/sub and regular work is the typical mistake.","fix":"Use a dedicated connection for Pub/Sub and open another one for regular commands (most client libraries do this automatically). Send UNSUBSCRIBE/PUNSUBSCRIBE to leave subscribe mode; regular commands then work. In the RESP3 protocol (HELLO 3) other commands are also allowed on a subscribed connection; switch to RESP3 if your driver supports it.","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":"In Redis Cluster mode you tried to switch to a different logical database (DB index) with SELECT. Cluster mode supports only database 0; multiple DBs cannot be used.","cause":"An application that switches between DBs 0-15 with SELECT on standalone Redis keeps sending the same command after moving to Redis Cluster. Because the cluster architecture distributes data by slot, the multi-DB concept is not supported.","fix":"Remove SELECT calls in cluster mode; only DB 0 is used. If you need logical separation, use key prefixes (namespaces) (e.g., app1:key). If certain keys must be processed together, use a hash tag ({user123}) so they land in the same slot. Make the application configuration cluster-aware.","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":"A Redis command was sent with fewer or more arguments than expected. The command was rejected without being processed because its syntax was not satisfied.","cause":"Missing/extra parameters (e.g., just SET key instead of SET key value); variables passed as empty/undefined; the command called with the wrong argument order; or faulty array/argument expansion (spread) in the client library. Common with dynamically built commands.","fix":"Check the command's correct signature (e.g., SET key value [options]); consult the official command reference. In client code, verify that arguments (especially those coming from arrays) are not empty/missing. If you build commands from user input, validate the argument count beforehand. Try the command manually in redis-cli to confirm the expected number of arguments.","tags":["syntax","arguments","argument","command","syntax","cli"]},{"code":"ERR Background save already in progress","title":"Background save already in progress","severity":"caution","meaning":"You tried to start a new background save (BGSAVE) or AOF rewrite, but a background save is already in progress. Redis does not allow a second save process at the same time.","cause":"A new BGSAVE/BGREWRITEAOF triggered before the previous one completed; monitoring scripts or the application repeatedly calling BGSAVE while fork/save takes a long time on a large dataset. A slow disk or a large RDB file prolongs the process.","fix":"Wait for the running save to finish; check its status with the rdb_bgsave_in_progress and aof_rewrite_in_progress fields in INFO persistence and the LASTSAVE timestamp. Make application/automation logic conditional so it does not trigger a new BGSAVE before the previous save finishes. If saves take too long, investigate disk speed and RDB size.","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":"The bit offset given to a bit operation such as SETBIT/GETBIT/BITCOUNT is invalid: it is not an integer or is outside the allowed range. Redis cannot perform a bit operation at that offset.","cause":"Passing a non-numeric value as the offset; using a negative offset; or giving a very large offset that exceeds the allowed upper bound (a bit string can be up to 512 MB, i.e., about 2^32 bits). Arises from calculation errors in bitmap-based counter/flag structures.","fix":"Make sure the offset is a non-negative integer within the allowed range (0 to ~2^32-1; strings are at most 512 MB). Validate offset calculations (e.g., those derived from user IDs) for overflow/negative values. If very large offsets are needed, design the data to be split across multiple keys (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":"A node is currently not connected to another node it needs to communicate with. The request could not be forwarded to the target node because the intra-cluster (transport layer) connection was lost.","cause":"Network outage or packet loss, the target node crashing/restarting, a firewall/port block (default transport port 9300), the node becoming temporarily unresponsive due to GC pauses, or a DNS/host resolution problem. It can appear transiently during rolling restarts.","fix":"Inspect cluster and node health: use GET _cluster/health and GET _cat/nodes?v to see which node is missing. Verify inter-node network access and firewall rules for the transport port (9300). Check the affected node's logs for crashes, GC pauses or OOM. Review network stability and the discovery/seed host configuration.","tags":["transport","node","connection","network","9300","cluster"]},{"code":"receive_timeout_transport_exception","title":"Request timed out waiting for a response from another node","severity":"danger","meaning":"A node sent a request to another node, but the response did not arrive within the configured time and the request timed out. The inter-node (transport) call could not complete.","cause":"The target node under heavy load, long GC pauses, slow disk/CPU pressure, network latency/packet loss, or the target node being too busy to respond. Common during heavy queries, large bulk operations and shard rebalancing.","fix":"Inspect resource usage on the affected nodes: check JVM heap, GC and thread pool saturation with GET _nodes/stats. Check rejected/queued tasks with GET _cat/thread_pool?v. Measure network latency. Reduce the load (smaller query/bulk sizes) and review heap and hardware resources. If persistent, add nodes to distribute the load.","tags":["transport","timeout","gc","load","network","thread pool"]},{"code":"routing_missing_exception","title":"Routing value is required for this document","severity":"caution","meaning":"A routing value is required to access/index the document but was not provided. Without routing, Elasticsearch could not determine which shard the document belongs to.","cause":"The index mapping defines _routing required=true; a parent-join (parent/child) relationship is used and child documents require routing; or the routing parameter was omitted from an index/get/update/delete request. Reading documents written with custom routing without supplying the routing is the typical cause.","fix":"Specify the routing value explicitly in the request (e.g. ?routing=value on index/get/update/delete calls). With parent-join, pass the parent's id as routing for child documents. Use the same routing for reads/updates/deletes that was used when the document was written. Review the _routing requirement (required) in the mapping.","tags":["routing","shard","parent-child","join","mapping","index"]},{"code":"too_many_buckets_exception","title":"Trying to create too many buckets","severity":"caution","meaning":"An aggregation query tried to produce more buckets than the allowed upper limit (search.max_buckets, default 65,536). The query was rejected to protect memory.","cause":"Aggregations such as terms/date_histogram run on high-cardinality fields with a very large size or a very fine interval; nested aggregations multiplying the bucket count. The limit is enforced because excessive bucket creation can exhaust node memory.","fix":"Reduce the number of buckets produced: lower the size of terms aggregations, widen the date_histogram interval, remove unnecessary nested aggregations. If many results are needed, page through them with a composite aggregation. Raising the limit should be a last resort and carries memory risk: search.max_buckets can be changed with PUT _cluster/settings, but do so carefully.","tags":["aggregation","bucket","max_buckets","terms","composite","memory"]},{"code":"shard_lock_obtain_failed_exception","title":"Obtaining shard lock timed out","severity":"danger","meaning":"Elasticsearch could not obtain the local directory lock (shard lock) required to operate on a shard within the timeout. The shard could not be started or reassigned.","cause":"A previous copy of the same shard (after close/relocation) has not yet released the lock; slow/hung I/O; disk problems; or stale shard state not cleaned up after a fast node restart. Usually transient, but if persistent the shard stays UNASSIGNED.","fix":"Check why the shard is unassigned: GET _cluster/allocation/explain. In most cases the lock is released shortly and the cluster retries on its own; if needed, trigger reallocation with POST _cluster/reroute?retry_failed=true. If persistent, examine the affected node's logs for I/O/disk errors and verify disk health. A clean restart of the problematic node can clear the lock.","tags":["shard","lock","unassigned","allocation","io","reroute"]},{"code":"parsing_exception","title":"Failed to parse the search request / query DSL","severity":"caution","meaning":"The submitted query body (query DSL) could not be parsed. The query could not run because the request JSON structure or query syntax does not match the format Elasticsearch expects.","cause":"Invalid JSON, misplaced/unknown query keys, a query type (e.g. bool, range) used with the wrong structure, a field name removed/changed between versions, or an extra/missing bracket. Common with hand-written or dynamically built queries.","fix":"Check the query body using the line/position hint in the error message; verify the JSON is valid. Compare the query DSL structure with the official reference (bool/must/filter placement, etc.). Update syntax removed in version upgrades. Simplify the query incrementally to isolate the faulty part; use the client library's query builder where 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","severity":"caution","meaning":"In a document being indexed, a term (especially a keyword/non-analyzed field value) exceeded the maximum allowed byte length (32766 bytes). Lucene cannot index a term that long.","cause":"A very long text value written to a keyword field (as a whole); e.g. an entire document body, base64 data or a long URL/identifier placed in a keyword field. Because keyword fields are not analyzed, the value is indexed as a single term and exceeds the limit.","fix":"Use a text (analyzed) field for long text; keyword is only for short values that need exact matching. To cap the value, use ignore_above in the mapping (values longer than this are not indexed but are still stored). If not needed, set very long fields to index:false. Re-evaluate the field design (text vs keyword) based on the data type.","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":"The total number of fields in an index mapping exceeded the allowed upper limit (index.mapping.total_fields.limit, default 1000). Indexing fails because new fields cannot be added.","cause":"Usually a mapping explosion: with dynamic mapping enabled, unstructured/variable-key JSON documents (e.g. objects whose keys come from user data) add every new field to the mapping. Over time the field count grows rapidly and hits the limit.","fix":"To fix the root cause, model variable-key data with a flattened field or as an array of key-value pairs instead of objects; restrict dynamic mapping (dynamic: false or strict). If you genuinely need more fields, raise the limit carefully: PUT idx/_settings {\"index.mapping.total_fields.limit\": 2000}. Monitor for mapping explosion by checking the field count with 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)","severity":"caution","meaning":"The ES|QL (_query), SQL (_sql) or EQL query was parsed syntactically but failed semantic verification: the referenced index, field (column) or function was not found, or the types are incompatible. The error text gives the problematic line/column position (e.g. \"line 1:18: Unknown column [field]\"). A snapshot repository verification failure is not this code; it is repository_verification_exception.","cause":"A misspelled field name or one not in the mapping; an index pattern that does not exist or matches no index; an unsupported operation on a text field (e.g. grouping on text instead of its keyword subfield); a comparison between incompatible types or an unknown function.","fix":"Fix the query using the line:column information in the error message. Verify field names and types: GET idx/_mapping (for SQL, DESCRIBE idx / SHOW COLUMNS). Check the index pattern with GET _cat/indices. Use the keyword subfield (field.keyword) for grouping/sorting; for type mismatches apply an explicit conversion (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","severity":"danger","meaning":"Oracle tried to make room in the Fast Recovery Area (FRA) for a new file (archived log, backup, flashback log) but could not reclaim the required space because the logical limit defined by DB_RECOVERY_FILE_DEST_SIZE is reached.","cause":"The FRA size (DB_RECOVERY_FILE_DEST_SIZE) is an upper bound independent of the physical disk, and archived logs, RMAN backups or flashback logs have filled it. The root cause is usually archived logs not being backed up and deleted regularly, or obsolete backups not being purged.","fix":"Review FRA usage: SELECT * FROM v$recovery_area_usage; and SELECT * FROM v$recovery_file_dest; Back up archived logs to a destination outside the FRA and delete them (RMAN: BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT;) or purge backups that are no longer needed (RMAN: DELETE OBSOLETE;). With Data Guard, do not delete archived logs not yet applied on the standby. For a permanent fix, increase DB_RECOVERY_FILE_DEST_SIZE if there is disk space. If Flashback Database is not needed, consider disabling it.","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'","severity":"danger","meaning":"A critical resource enqueue (e.g. CF - control file enqueue) was held by a process for much longer than allowed, and Oracle killed that process to prevent the system from hanging. It appears in alert.log together with a 'killing enqueue blocker' entry.","cause":"A background or user process held an important enqueue beyond the allowed time because of an I/O bottleneck, heavy CPU pressure or an OS stall (swapping, slow disks); for the control file (CF) enqueue this time is set by _controlfile_enqueue_timeout (default 900 s). Hidden (underscore) parameters should only be changed under Oracle Support guidance. Usually triggered by storage latency or slow processes such as LGWR/CKPT.","fix":"Review alert.log and the trace file of the killed process to determine which enqueue (usually the control file) and which background process is responsible. The real cause is usually storage/I/O latency: check disk response times, multipath and controller health. Reduce CPU/memory pressure and swap usage. If it recurs, share the trace analysis with Oracle Support.","tags":["enqueue","control file","i-o","storage","hang","background process"]},{"code":"ORA-04021","title":"timeout occurred while waiting to lock object","severity":"caution","meaning":"A timeout occurred while waiting to acquire a library cache lock/pin on an object (package, procedure, type, etc.). Usually occurs when trying to compile or alter a PL/SQL object while it is being executed or locked by another session.","cause":"The object to be altered/compiled is actively executing in another session or is locked by another DDL, so the exclusive library cache lock cannot be acquired. A long-running procedure call or a hung session may be holding the lock.","fix":"Find the session locking the object: SELECT * FROM dba_kgllock WHERE kgllkhdl IN (SELECT p1raw FROM v$session WHERE event LIKE 'library cache%'); or identify the session using the object with v$access and v$session. Retry the DDL/compile in a maintenance window with low workload. If needed, terminate the session safely. Briefly stopping the application makes acquiring the lock easier.","tags":["library cache","lock","plsql","ddl","compile","timeout"]},{"code":"ORA-04061","title":"existing state of package/procedure has been invalidated","severity":"caution","meaning":"The existing state of a called PL/SQL object (package, procedure, function) was invalidated because an object it depends on changed. It is a variant of the state-loss condition accompanied by ORA-04068; the package state held by the session is no longer valid.","cause":"While a session was using a package, DDL was applied to that package or its dependencies (table structure change, recompilation, grant change). As a result, the session's existing package state was invalidated. Seen in environments with frequent deployments when schema changes are made while the application is running.","fix":"The same call usually succeeds on the second attempt (since the state has been reset); catch this error in the application and retry safely. Make schema changes during low application traffic/in a maintenance window. Compile invalid objects in bulk: EXEC DBMS_UTILITY.COMPILE_SCHEMA('APP_SCHEMA'); Designs that minimize package state (global variables) reduce this effect.","tags":["plsql","package","invalidate","ddl","state","recompile"]},{"code":"ORA-01466","title":"unable to read data - table definition has changed","severity":"caution","meaning":"A query reading data from a past point in time (Flashback Query, AS OF or consistent read) failed because the table definition (DDL) changed between the queried past time and now. Oracle cannot safely read the data at that point in time with the current definition.","cause":"A DDL (adding/dropping a column, type change) was applied to the table after the timestamp targeted by the Flashback/AS OF query. Also, clock differences between client and server or queries of the very recent past (a few seconds) can raise this error due to SCN/time resolution.","fix":"Run the flashback query against a time/SCN before the table's DDL change. Where possible, use an exact SCN instead of a timestamp (AS OF SCN). For very recent past queries, go back a few seconds. If permanent historical access is needed, consider a versioning or archive table design instead of DDL.","tags":["flashback","as of","ddl","consistent read","scn","query"]},{"code":"ORA-08176","title":"consistent read failure; rollback data not available","severity":"danger","meaning":"While building a consistent read image, a query encountered blocks modified by an operation that generates no undo; since no undo/rollback data exists for these blocks, the consistent image could not be built. Unlike ORA-01555, the undo was never generated rather than overwritten. (The serializable isolation error is a separate code: ORA-08177.)","cause":"An operation that generates no undo runs on the same object while the query is in progress: direct-path loads (INSERT /*+ APPEND */, SQL*Loader DIRECT=TRUE), CREATE INDEX / index rebuild or discrete transactions.","fix":"Rerun the query after the direct-path load or index operation has finished (retry). Do not schedule long reports and direct-path load / index maintenance jobs on the same object in the same time window. If it recurs, identify the conflicting job with the alert log and ASH and separate the schedules.","tags":["undo","consistent read","direct path","append","create index","retry"]},{"code":"ORA-00020","title":"maximum number of processes (N) exceeded","severity":"danger","meaning":"The number of processes of the instance reached the upper limit defined by the PROCESSES initialization parameter, and no new connection/process can be created. New sessions cannot connect; even an administrator may be unable to connect.","cause":"An oversized application connection pool, connections that are never closed (leaks), more concurrent users than expected or a PROCESSES parameter set too low leads to the limit being exceeded. Sometimes hung background jobs or parallel query processes also consume processes.","fix":"You can connect with sqlplus -prelim / as sysdba, but this connection cannot run SQL (v$session cannot be queried); it is only usable for oradebug (e.g. hanganalyze) and SHUTDOWN ABORT. To free slots, carefully kill idle application server processes at the OS level (ps -ef | grep 'LOCAL=NO'); then, with a normal SYSDBA connection, find leaked sessions via v$session/v$process and close them with ALTER SYSTEM KILL SESSION. For a permanent fix, increase PROCESSES and restart the instance (if SESSIONS is set explicitly in the spfile it is not re-derived; update it too). On the application side, fix the connection pool maximum and idle connection cleanup.","tags":["processes","process","connection","limit","pool","init parameter"]},{"code":"ORA-01113","title":"file N needs media recovery","severity":"danger","meaning":"A datafile is being brought online or the database is being opened, but the file is inconsistent; media recovery must be applied first before proceeding. The file is at an older/inconsistent SCN than the control file expects.","cause":"The datafile was restored from an old backup, redo was not applied while it was offline, or the file is not current after an instance/storage crash. Typically caused by skipping the recover step after a restore, or by a file staying offline for a long time.","fix":"Recover the file or the database: RECOVER DATAFILE N; or RECOVER DATABASE; then ALTER DATABASE DATAFILE N ONLINE; or ALTER DATABASE OPEN; Make sure the required archived logs are accessible. With RMAN: after a RESTORE if needed, run RECOVER DATAFILE N;. Follow alert.log to see which logs are requested.","tags":["media recovery","datafile","redo","restore","recover","scn"]},{"code":"ORA-02393","title":"exceeded call limit on CPU usage","severity":"caution","meaning":"The CPU time allowed for a single call (CPU_PER_CALL), defined in the resource profile assigned to the session, was exceeded and Oracle terminated that call. It is a safeguard for resource limiting.","cause":"A CPU_PER_CALL limit is defined in the user's profile and RESOURCE_LIMIT=TRUE; a single SQL/PLSQL call exceeded this CPU threshold. Usually triggered by an inefficient/uncontrolled query consuming far more CPU than expected.","fix":"Optimize the query (proper index, correct plan, removing unnecessary full table scans) so that CPU consumption falls below the limit. If the business requirement is legitimate, increase CPU_PER_CALL in the profile for the appropriate user or set it to UNLIMITED: ALTER PROFILE ... LIMIT CPU_PER_CALL ...; Consider managing resources more flexibly with Database Resource Manager instead of profiles.","tags":["profile","cpu_per_call","resource_limit","resource","query","optimization"]},{"code":"ORA-12801","title":"error signaled in parallel query server Pnnn","severity":"danger","meaning":"In a query running in parallel, one of the parallel execution servers (parallel query slaves) hit an error, and this error was passed to the query coordinator. ORA-12801 is a wrapper; the actual error is on the second line following it.","cause":"A slave process hit a real error during a parallel operation: insufficient TEMP tablespace (ORA-01652), PGA memory exhaustion (ORA-04030), data corruption, parallel process limits or another underlying SQL error. ORA-12801 only reports that it occurred in a parallel context.","fix":"Focus on the actual error code immediately following ORA-12801 and resolve it (e.g. enlarging TEMP, PGA tuning, repairing a corrupt block). If needed, isolate the actual error by running the query serially, removing PARALLEL hints or lowering the degree of parallelism. Review PARALLEL_MAX_SERVERS and resource usage.","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":"An EXCLUDE (exclusion) constraint was violated. This constraint type prevents two rows from conflicting according to specific operators (e.g. range overlap &&); the inserted/updated row was rejected because it conflicts with an existing row.","cause":"Typically a GiST-based EXCLUDE constraint is defined (e.g. forbidding time overlaps with tstzrange && for appointment/room bookings) and the new row has a range/value that overlaps an existing row. Can also occur when two concurrent transactions insert overlapping ranges.","fix":"Check for the overlap with a query before inserting, or catch the error in the application and show the user a meaningful message. Query the relevant range/condition to find the conflicting existing row. If the business rule allows overlaps, relax the constraint; otherwise correct the input. Under concurrency, retry the transaction safely to serialize it.","tags":["exclude","constraint violation","gist","range","overlap","constraint"]},{"code":"23001","title":"restrict_violation","severity":"danger","meaning":"The SQL-standard restrict_violation code. Important: PostgreSQL reports a violation of a foreign key defined ON DELETE/ON UPDATE RESTRICT or NO ACTION not with this code but with 23503 (foreign_key_violation); in practice you will not see this code from core PostgreSQL.","cause":"When a parent row with dependent child rows is deleted or its key changed, PostgreSQL returns 23503. 23001 usually shows up in application code ported from another database or as a custom error from an extension. Unlike NO ACTION, RESTRICT cannot defer the check to the end of the transaction and fails immediately.","fix":"First delete/update the dependent child rows or move them to another parent; then process the parent row. If the business rule allows it, consider redefining the foreign key with ON DELETE CASCADE or SET NULL. In bulk deletes, get the deletion order right (child first, then parent).","tags":["foreign key","restrict","fk","delete","reference","constraint"]},{"code":"42P17","title":"invalid_object_definition","severity":"danger","meaning":"An object's definition (function, collation, trigger, rule etc.) is invalid. PostgreSQL indicates that the given definition is internally inconsistent or unacceptable; the object cannot be created/altered as written.","cause":"Common causes: contradictory attribute combinations in a function (e.g. marked IMMUTABLE but behaving as volatile), an invalid collation/character set definition, or an unsuitable trigger/rule definition. Inconsistent options in row-level security (RLS) and some DDL can also trigger it.","fix":"Read the error details (DETAIL/HINT) to determine which part of the definition was rejected. Align function volatility labels with the actual behavior (VOLATILE/STABLE/IMMUTABLE). Verify that the collation name exists in pg_collation. Fix the definition according to the documented syntax/rules and run it again.","tags":["function","collation","definition","volatility","ddl","object"]},{"code":"42710","title":"duplicate_object","severity":"caution","meaning":"The object being created (role, type, constraint, extension, schema, index name etc.) already exists. PostgreSQL does not allow a second object with the same name/definition.","cause":"A command such as CREATE ROLE, CREATE TYPE, CREATE EXTENSION or ADD CONSTRAINT ran while an object with the same name already existed. Usually caused by a script run twice, non-idempotent migrations, or two concurrent sessions trying to create the same object.","fix":"Check whether the object exists (pg_roles for roles, pg_type for types, pg_extension for extensions). Write migrations idempotently: use CREATE ... IF NOT EXISTS where available, or run DROP ... IF EXISTS first. For a constraint conflict, rename the existing constraint or drop and re-create it.","tags":["duplicate","role","type","extension","constraint","migration"]},{"code":"22008","title":"datetime_field_overflow","severity":"caution","meaning":"While computing or parsing a date/time value, one of the fields fell outside its valid range. For example, an impossible component such as month 13 or day 32, or a result beyond the supported time range, was produced.","cause":"An invalid date input (e.g. '2026-02-30'), an out-of-range component, or an INTERVAL addition exceeding the supported date range. Parsing with the wrong format/locale or inserting unvalidated user input is the typical cause.","fix":"Validate date inputs in the application before inserting and send them in the correct format (preferably ISO 8601: YYYY-MM-DD). When parsing with TO_DATE/TO_TIMESTAMP, match the format mask to the input. Keep arithmetic results within the supported date range; run suspicious values through bounds checks.","tags":["date","time","overflow","interval","validation","format"]},{"code":"22026","title":"string_data_length_mismatch","severity":"caution","meaning":"The length of a bit string does not match the fixed length required by the operation. In PostgreSQL it most often appears when a bit string of a different length is written to a fixed-length bit(n) type, or when bitwise AND/OR/XOR is applied to bit strings of different lengths.","cause":"Writing a bit string shorter/longer than n to a bit(n) column (e.g. B'101' into a bit(4) column: 'bit string length 3 does not match type bit(4)'), or applying &, |, # to bit strings of different lengths ('cannot AND bit strings of different sizes'). It is not caused by text or character encoding; an overlong value for char(n)/varchar(n) raises 22001 instead.","fix":"Bring the value to the target length: use an explicit cast (B'101'::bit(4) pads with zeros on the right and truncates any excess), or define the column as bit varying(n) if the length can vary. Cast the operands to the same length before bitwise operations; check the length with length().","tags":["bit","length","bit string","varbit","conversion","validation"]},{"code":"2201B","title":"invalid_regular_expression","severity":"caution","meaning":"The pattern given to a regular expression operation (~, ~*, SIMILAR TO, regexp_matches, substring etc.) is invalid. PostgreSQL could not compile the pattern because its syntax is wrong.","cause":"A regex pattern with unbalanced parentheses, invalid character classes, bad escape sequences or an unsupported construct. A common cause is using user input directly as a pattern without escaping its special characters.","fix":"Fix the regex pattern according to POSIX/ARE syntax; check parentheses, brackets and escape sequences. If you are searching for user input as literal text rather than as a pattern: PostgreSQL has no built-in regexp_quote function, so escape the special characters by prefixing them with a backslash via regexp_replace, or avoid regex entirely and use strpos()/position() or LIKE ... ESCAPE. Validate the pattern by testing it in a separate tool.","tags":["regex","regular expression","pattern","similar to","escape","syntax"]},{"code":"44000","title":"with_check_option_violation","severity":"danger","meaning":"An INSERT/UPDATE through a view defined WITH CHECK OPTION was rejected because the resulting row no longer satisfies the view's WHERE condition. The view prevents writing rows that would fall outside its own filter.","cause":"An updatable view is defined WITH CHECK OPTION and the write moves the row outside the view's defining condition (e.g. setting a row to 'inactive' while the view filters status='active'). A similar check can fire in row-level security (RLS) policies.","fix":"Make sure the written values satisfy the view's WHERE condition, or perform the operation directly on the base table instead of the view (if privileges allow). If the business rule allows the row to leave the filter, redefine the view without WITH CHECK OPTION. For RLS, review the policy's WITH CHECK expression.","tags":["view","check option","updatable view","rls","insert","update"]},{"code":"40002","title":"transaction_integrity_constraint_violation","severity":"danger","meaning":"The SQL-standard code for an integrity constraint checked at end of transaction being violated at COMMIT. Important: PostgreSQL does not produce this code in practice; it returns deferred (DEFERRED) constraint violations at COMMIT with the constraint's own code: 23503 for foreign keys, 23505 for uniqueness.","cause":"A foreign key or unique constraint defined DEFERRABLE INITIALLY DEFERRED was temporarily broken in intermediate steps of the transaction and not fixed by COMMIT time; in PostgreSQL this is reported as 23503/23505. For example, inserting mutually referencing rows in the wrong order without fixing them causes this.","fix":"Fix the transaction logic so that all deferred constraints are satisfied before COMMIT (add missing references, resolve conflicts). If needed, check the constraint early within the transaction with SET CONSTRAINTS ... IMMEDIATE to catch the problematic step sooner. Reorder inserts/updates for mutual references.","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)","severity":"caution","meaning":"A column that is neither in GROUP BY nor wrapped in an aggregate function was used in the SELECT list or in HAVING/ORDER BY. Because the ONLY_FULL_GROUP_BY SQL mode is enabled, MySQL rejects this nondeterministic expression.","cause":"The query selects a column other than the grouped columns directly, but that column may not have a single value per group. ONLY_FULL_GROUP_BY is enabled by default in MySQL 5.7+; queries relying on the old, lenient GROUP BY behavior raise this error.","fix":"Either add the column to the GROUP BY list or wrap it in an aggregate function (MAX(), MIN(), ANY_VALUE()). If you know the value is truly unique within the group, ANY_VALUE() deliberately suppresses the ambiguity. Disabling the mode (removing ONLY_FULL_GROUP_BY from sql_mode) instead of fixing the query brings back the risk of wrong results; avoid it.","tags":["group by","only_full_group_by","sql_mode","any_value","aggregate","query"]},{"code":"1267","title":"Illegal mix of collations","severity":"caution","meaning":"In a comparison or combining operation (JOIN, WHERE, UNION, CONCAT), two strings with different collations were combined and MySQL could not determine which collation rules to apply.","cause":"Usually comparing columns with different character sets/collations (e.g. utf8mb4_general_ci vs. utf8mb4_unicode_ci, or latin1 vs. utf8mb4), or a mismatch between a column and the connection collation. Common when tables/databases were created with different defaults.","fix":"Bring both sides of the comparison to the same collation: specify an explicit collation with COLLATE (e.g. col1 = col2 COLLATE utf8mb4_unicode_ci) or use CONVERT(... USING ...). For a permanent fix, convert the affected columns/tables to a single consistent character set and collation. Align the connection collation (collation_connection) as well.","tags":["collation","collation mismatch","character set","join","utf8mb4","convert"]},{"code":"1030","title":"Got error N from storage engine","severity":"danger","meaning":"The storage engine (InnoDB/MyISAM) could not complete an operation and returned a low-level error code. This is a generic report of a disk, file system or engine-internal problem below the SQL layer. The sub-code in the parentheses/message shows the actual cause.","cause":"Common causes: disk/temp space full (e.g. errno 28 - No space left on device), file permission problems, corrupted table files, InnoDB tablespace problems or OS-level I/O errors. Sort/group queries that produce large temporary tables can also trigger it.","fix":"Decode the sub-error code (on Linux, perror N shows its meaning, e.g. perror 28). Check disk usage and tmpdir space; free up space. Verify file ownership/permissions. For InnoDB, review the details in the error log; if corruption is suspected, run CHECK TABLE and recover if necessary. Optimize queries that produce huge temporary results.","tags":["storage engine","innodb","disk","errno","tmpdir","i-o"]},{"code":"1197","title":"Multi-statement transaction required more than 'max_binlog_cache_size'","severity":"danger","meaning":"The binlog data generated by a single transaction exceeded the memory/cache limit defined by max_binlog_cache_size. The transaction did not fit into the binary log, so it failed and was rolled back.","cause":"A very large transaction (a single INSERT/UPDATE/DELETE of millions of rows or a huge bulk load) filled the binlog cache. With binary logging enabled (replication/point-in-time recovery), large transactions can easily exceed this limit.","fix":"Split the large transaction into smaller batches; COMMIT after each batch. If truly large transactions are unavoidable, increase max_binlog_cache_size (and binlog_cache_size if needed). In ETL/bulk loads, a chunked load with intermediate COMMITs prevents both this error and long-held locks.","tags":["binlog","max_binlog_cache_size","transaction","batch","replication","bulk"]},{"code":"1785","title":"Statement violates GTID consistency","severity":"danger","meaning":"With GTID (Global Transaction Identifier) based replication enabled, the executed statement violates GTID consistency rules. GTID mode does not allow certain statement types that cannot be safely assigned to a single GTID.","cause":"1785 (ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE) occurs specifically when non-transactional (e.g. MyISAM) tables are updated in the same transaction as transactional (InnoDB) tables, or in a multi-statement transaction without autocommit. In older versions, CREATE TABLE ... SELECT (before 8.0.21; allowed in 8.0.21+ for engines supporting atomic DDL) and CREATE/DROP TEMPORARY TABLE inside a transaction (before 8.0.13; allowed in 8.0.13+ when binlog_format is ROW/MIXED) also violate GTID consistency (these are reported with separate error codes). They are rejected when enforce_gtid_consistency=ON.","fix":"Do not mix non-transactional engine tables with transactional tables in the same transaction; update MyISAM tables with single autocommit statements or move all tables to InnoDB: ALTER TABLE my_table ENGINE=InnoDB; In older versions (before 8.0.21), split CREATE TABLE ... SELECT into two steps (CREATE TABLE, then INSERT ... SELECT); before 8.0.13, create and drop temporary tables outside an explicit transaction.","tags":["gtid","replication","consistency","temporary table","myisam","innodb"]},{"code":"3024","title":"Query execution was interrupted, maximum statement execution time exceeded","severity":"caution","meaning":"A SELECT query was interrupted by MySQL because it exceeded the maximum execution time defined for it (max_execution_time / MAX_EXECUTION_TIME hint). This is a timeout safeguard that prevents long-running queries from tying up resources.","cause":"The query ran longer than the session/global max_execution_time value or the MAX_EXECUTION_TIME(N) optimizer hint on the SELECT. Usually caused by an inefficient plan, a missing index, a large scan or slowdowns under heavy concurrency. (Applies only to read-only SELECTs.)","fix":"Analyze the query with EXPLAIN and optimize it (proper indexes, more selective predicates, fewer full table scans). If the query legitimately needs to run long, increase the MAX_EXECUTION_TIME hint for that SELECT or the session max_execution_time. Run heavy analytical queries offline/on a replica.","tags":["timeout","max_execution_time","query","explain","index","select"]},{"code":"1298","title":"Unknown or incorrect time zone","severity":"caution","meaning":"The specified time zone name was not recognized by MySQL. A CONVERT_TZ() call, the time_zone setting or an operation referring to a named time zone (e.g. 'Europe/Istanbul') failed because that time zone is not defined on the server.","cause":"For MySQL to use named time zones, the time zone tables in the mysql schema (time_zone, time_zone_name, etc.) must be loaded. If these tables are empty, only numeric offsets ('+03:00') work; named zones are unknown and this error is raised.","fix":"Load the time zone tables. On Linux, import the system zoneinfo data with mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql; then restart the server or run FLUSH TABLES. If loading named zones is not possible, use a numeric offset ('+03:00'). Verify the spelling of the name (case, '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)","severity":"caution","meaning":"A stored function was being created, but binary logging is enabled and the function is not declared with any of the determinism/data access characteristics. MySQL rejects such a function because it is ambiguous from a replication safety standpoint.","cause":"The function was defined without the DETERMINISTIC, NO SQL or READS SQL DATA characteristics while log_bin is ON and log_bin_trust_function_creators=OFF. Without these characteristics, MySQL cannot be sure the function produces the same result on the replica.","fix":"Declare the function's behavior with the correct characteristics: add DETERMINISTIC if it is truly deterministic, NO SQL if it does not access data, READS SQL DATA if it only reads. If you are sure the functions are safe, you can set log_bin_trust_function_creators=1 as a central policy; however, declaring the characteristic correctly is the safest approach. Make sure the characteristic matches the function's actual behavior.","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":"The value being assigned to a system/session variable (with SET) is invalid. The value was rejected because it is outside the allowed set, of the wrong type or in an unsuitable format.","cause":"Common examples: assigning an invalid/misspelled mode name to sql_mode, giving an undefined option to an enum variable, or assigning NULL to a variable that does not accept NULL. Numeric values in the wrong unit/range can also trigger it.","fix":"Verify the valid values and type the variable accepts; for ENUM/SET-type variables (e.g. sql_mode, which is a SET), spell value/mode names exactly and separate multiple values with commas. Check the valid value range in the documentation. For lists such as sql_mode, read the current value (SELECT @@sql_mode) and add/remove items safely on top of it.","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","severity":"danger","meaning":"A query timed out while waiting on the resource semaphore for the memory grant it needs to execute. Because memory pressure prevented a grant from being allocated, the operation was canceled.","cause":"Concurrent memory-intensive queries (large sorts, hash joins, hash aggregates) consumed the entire query memory pool; new queries queued on the semaphore and timed out. Oversized memory grant estimates (stale statistics, bad plans) or insufficient server memory are the root cause.","fix":"Identify queries waiting for or holding memory with sys.dm_exec_query_memory_grants and the resource semaphore DMVs. Update statistics and fix plans that cause inflated memory grants (appropriate indexes, better estimates). Reduce the concurrency of memory-intensive queries; if needed, cap memory grants with Resource Governor. Review the server memory configuration (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","severity":"danger","meaning":"The memory grant required by the query was not available and the operation could not be performed. Unlike 8645, this is not a wait but an outright failure to provide the large memory block requested; it usually indicates severe memory pressure.","cause":"The available query memory on the server cannot satisfy the grant requested by a single operator (e.g., a very large sort/hash). Excessively high memory estimates, a low max server memory, external memory pressure or a low Resource Governor limit, REQUEST_MAX_MEMORY_GRANT_PERCENT (workload group) or MAX_MEMORY_PERCENT (resource pool), can be the cause.","fix":"Optimize queries that inflate memory grants (update statistics, proper indexing, splitting the query). Monitor total and available memory; check the max server memory setting and memory pressure from external applications. If Resource Governor is in use, review the workload group's REQUEST_MAX_MEMORY_GRANT_PERCENT and the pool's MAX_MEMORY_PERCENT settings. Move memory-intensive batch jobs to off-peak hours.","tags":["memory grant","memory","resource governor","statistics","hash","pressure"]},{"code":"5061","title":"ALTER DATABASE failed because a lock could not be placed on database","severity":"danger","meaning":"An ALTER DATABASE operation (e.g., switching to single-user mode, changing a file, setting an option) could not acquire the required lock on the database because other active connections/sessions are using it.","cause":"The operation was attempted while the database was in use; the exclusive lock required for SET SINGLE_USER, RESTRICTED_USER or similar option changes could not be acquired because of active user connections. Common during frequent deploys/maintenance while application connections are open.","fix":"Identify the other sessions connected to the database (sys.dm_exec_sessions, sp_who2) and close them safely. For maintenance, change the mode while rolling back open transactions with the ROLLBACK option: ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; Perform the operation when application traffic is low or in a maintenance window. Remember to switch back to MULTI_USER when done.","tags":["alter database","lock","single_user","connection","maintenance","rollback"]},{"code":"3702","title":"Cannot drop database because it is currently in use","severity":"caution","meaning":"An attempt was made to drop or rename a database, but it was rejected because the database is still in use by active connections. SQL Server cannot drop a database that is in use.","cause":"Other sessions (an application pool, an open SSMS tab, background jobs) are connected to the database being dropped. Even your own session having that database as its context (USE) can trigger it. Open connections from monitoring/backup tools are also a common cause.","fix":"Switch to another database before dropping (USE master;). Find and close the connected sessions, or first put the database in single-user mode: ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; then run DROP DATABASE [db]; Stop application connection pools and make sure monitoring tools are not opening connections.","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","severity":"caution","meaning":"An attempt was made to drop a database user/role (database principal), but it could not be dropped because the principal owns one or more schemas in the database. It cannot be dropped until the schemas it owns are transferred to another principal.","cause":"The user being removed is the OWNER of at least one schema (e.g., a schema created with its own name by default, or one assigned via AUTHORIZATION). SQL Server blocks the drop to avoid leaving orphaned schemas.","fix":"Find the schemas owned by the user: SELECT name FROM sys.schemas WHERE principal_id = USER_ID('app_user'); Transfer ownership of these schemas to another principal (e.g., dbo): ALTER AUTHORIZATION ON SCHEMA::[app_schema] TO dbo; If the schema is no longer needed (and is empty), DROP SCHEMA. After ownership has been transferred, run DROP USER again.","tags":["schema","principal","user","owner","alter authorization","drop user"]},{"code":"1934","title":"INSERT/UPDATE failed because the following SET options have incorrect settings","severity":"danger","meaning":"A DML operation (INSERT/UPDATE/DELETE) failed because required SET options were set incorrectly. It typically occurs when the mandatory SET settings for indexed views, indexes on computed columns, filtered indexes or spatial/XML indexes are not met.","cause":"The affected table/view has an object that requires specific SET options (notably QUOTED_IDENTIFIER and ANSI_NULLS ON, NUMERIC_ROUNDABORT OFF, etc.), but the session's current SET options do not meet that requirement. Old client drivers or scripts that explicitly set wrong SET options trigger it.","fix":"Set the required options on the connection/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; Make sure the application driver sends these options correctly when connecting. The error message names the incorrect options; fix those.","tags":["set options","indexed view","computed column","quoted_identifier","ansi_nulls","dml"]},{"code":"468","title":"Cannot resolve the collation conflict","severity":"caution","meaning":"In an operation (JOIN, UNION, WHERE comparison or equality), two string expressions with different collations met and SQL Server could not resolve which collation to apply. The operation was canceled due to the ambiguity.","cause":"The compared columns are defined with different collations; most often seen when the database collation differs from the server/tempdb collation, or when tables in databases with different collations are joined. Joining temporary tables (tempdb collation) with permanent tables is a classic trigger.","fix":"Specify an explicit collation in the comparison: ... WHERE a.col = b.col COLLATE DATABASE_DEFAULT (or a concrete collation name). Use COLLATE DATABASE_DEFAULT when creating temporary table columns. For a permanent fix, align the affected columns/databases to a single consistent collation. If mismatches with tempdb are frequent, DATABASE_DEFAULT is the most practical fix.","tags":["collation","collation","join","tempdb","database_default","conflict"]},{"code":"4864","title":"Bulk load data conversion error (type mismatch or invalid character)","severity":"caution","meaning":"During a bulk load (BULK INSERT, bcp or OPENROWSET(BULK...)), a field in the source file could not be converted to the target column's data type. The data in the reported row/column does not match the expected type.","cause":"A value in the source data file does not fit the target column type: text in a numeric column, wrong date format, unexpected decimal/thousands separator, wrong character encoding (UTF-8/UTF-16), or columns shifting due to wrong field/row terminators. Not skipping the header row is also a common cause.","fix":"Go to the row and column reported by the error and inspect the source data. Match the field (FIELDTERMINATOR) and row (ROWTERMINATOR) terminators to the file; use FIRSTROW=2 to skip the header row. Set CODEPAGE or DATAFILETYPE for the correct code page/encoding. Make date/number formats compatible with the target type. Using a format file makes the mapping reliable.","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","severity":"caution","meaning":"A login attempted to access a specific database but has no access to it under the current security context. The login can connect to the server but has no corresponding user/permission in the target database.","cause":"The login has no mapped user in the database (orphaned or never created), database access is restricted, or the guest account is disabled. A common case is SSMS Object Explorer trying to read metadata of a database the login has no access to.","fix":"Create a user for the login in the target database and grant the required permissions: USE [db]; CREATE USER [login] FOR LOGIN [login]; and assign the appropriate roles/permissions. If an orphaned user exists, map it with ALTER USER ... WITH LOGIN = ... If access is genuinely needed, add it to the appropriate database role; if not, the error is expected behavior (no access to that database).","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":"A primary election is currently in progress in the replica set. Until the election completes the primary is undetermined, so writes (and some reads) are temporarily not accepted.","cause":"The cluster is electing a new primary after the current primary went down, a network partition, a step-down for maintenance, or a node restart. An election usually takes a few seconds; operations arriving within this window hit this condition.","fix":"This is transient; enable your driver's retry feature (retryable writes/reads) so the operation is retried automatically once the election finishes. If elections are frequent or long, investigate the root cause: network stability, node health, vote configuration, and election timeout settings (settings.electionTimeoutMillis). Inspect cluster state with rs.status().","tags":["replica set","election","primary","retry","step-down","high availability"]},{"code":"225 TransactionTooOld","title":"TransactionTooOld","severity":"caution","meaning":"A command was run inside a multi-document transaction, but its transaction number is older than the latest transaction number the server has seen for that session. In other words, a newer transaction has been started, so this old one is no longer valid.","cause":"A new transaction was started in the same session before the old one completed, or transaction numbers advanced out of order due to concurrency. It usually happens when a session object is shared incorrectly or multiple threads use the same session.","fix":"Manage the session lifecycle correctly for each logical transaction; do not share a session concurrently across multiple threads. Run transactions with the driver's recommended withTransaction/callback API (it handles retries and number management). Close the previous transaction with commit/abort before starting a new one.","tags":["transaction","session","session misuse","concurrency","withtransaction","multi-document transaction"]},{"code":"246 SnapshotUnavailable","title":"SnapshotUnavailable","severity":"caution","meaning":"A read with the requested snapshot read concern (readConcern: snapshot) could not be performed because the server cannot provide a consistent snapshot for that point in time. It is usually related to a read timestamp that has not advanced far enough or has fallen behind.","cause":"readConcern 'snapshot' is used (mostly in multi-document transactions) but a consistent view for the requested atLeast/at timestamp is not yet available; e.g. a newly created collection/index, a catalog change, or replication lag. The server rejects the read in this case.","fix":"This is usually transient; retry the operation after a short delay (the driver's transient transaction error retry mechanism covers this). If you recently ran DDL (collection/index creation), start the transaction after it. Check replication lag and node health. If a snapshot read is not truly needed, choose a more suitable readConcern.","tags":["snapshot","readconcern","transaction","timestamp","retry","replication"]},{"code":"257 TransactionTooLarge","title":"TransactionTooLarge","severity":"danger","meaning":"A multi-document transaction exceeded the allowed size/operation-count limits. The transaction was rejected because the changes accumulated in it (oplog entry or memory usage) went over the upper limit.","cause":"Modifying/inserting a large number of documents in a single transaction pushed the total data produced by the transaction too high (e.g. the volume of changes that must be held in the WiredTiger cache, or server limits such as transactionLimitBytes). Since 4.2 a transaction can be split across multiple oplog entries, so the 16 MB oplog entry limit alone is not decisive. Using transactions for bulk data migration/ETL easily hits this limit.","fix":"Split the transaction into smaller pieces; commit each batch in a separate transaction. Multi-document transactions are designed to be short and small; prefer doing large bulk updates without transactions (idempotent, chunk by chunk) or splitting them with bulk write. Avoid modifying thousands of documents in a single transaction.","tags":["transaction","size","oplog","batch","bulk","bulk write"]},{"code":"276 IndexBuildAborted","title":"IndexBuildAborted","severity":"danger","meaning":"An in-progress index build was aborted/stopped before completion. The target index was not created successfully.","cause":"The index build may have been aborted due to insufficient disk/memory on the node, an explicit abort via dropIndexes, a primary step-down/replica state change, a uniqueness violation (conflicting documents while building a unique index), or a node restart.","fix":"Find the abort reason in the mongod logs (disk space, memory, uniqueness conflict, etc.) and resolve it. If you are building a unique index, clean up conflicting documents first. Provide sufficient disk/memory and make sure the replica state is stable. After fixing the issue, rebuild the index with createIndex; on large collections choose a low-load period.","tags":["index","index build","aborted","unique","disk","replica set"]},{"code":"86 IndexKeySpecsConflict","title":"IndexKeySpecsConflict","severity":"caution","meaning":"The index being created has the same key specification (same fields/order) as an existing index but different options or a different name. MongoDB does not allow two conflicting index definitions with the same key.","cause":"An attempt was made to create a second index on the same field(s) with a different name or different options (e.g. different collation, partialFilterExpression, unique setting). It usually happens when an index definition is changed in a migration/code and re-run.","fix":"Inspect the existing indexes: db.collection.getIndexes() Find the conflicting index with the same key. To switch to the options you want, first drop the old index (dropIndex), then recreate it with the new definition. If only the name differs, keep using the existing index; do not create an unnecessary second definition.","tags":["index","getindexes","collation","unique","conflict","createindex"]},{"code":"166 CommandNotSupportedOnView","title":"CommandNotSupportedOnView","severity":"caution","meaning":"The command is not supported on a view. MongoDB views are read-only and accept only certain read operations; writes or unsupported commands are rejected.","cause":"Write operations such as insert/update/delete, or certain commands not supported on views (e.g. some text/geo operations, direct index creation), were attempted on a view. Seen when the application tries to write to a view thinking it is a collection.","fix":"Perform writes on the underlying (source) collection the view is based on; views are for reads only. Verify whether the command is supported on views and use supported read/aggregation operations. If you need a writable structure, design a materialized collection ($merge/$out) instead of a view.","tags":["view","read-only view","read only","aggregation","write","collection"]},{"code":"168 InvalidPipelineOperator","title":"InvalidPipelineOperator","severity":"caution","meaning":"An unrecognized or invalid operator/stage was used in an aggregation pipeline. MongoDB could not resolve the specified operator name and rejected the pipeline.","cause":"A misspelled operator name (e.g. '$groups' instead of '$group'), a new operator not available in the MongoDB version in use, or an expression used in the wrong context. A missing $ prefix or an operator used in an invalid position also triggers it.","fix":"Verify the spelling of operator/stage names and the $ prefix. Make sure the operator is supported by the server's MongoDB version (new operators do not exist in older versions). Compare the pipeline with the documented syntax; use the expression inside the correct stage (e.g. $expr, $project). Isolate the stage and test it with a small sample.","tags":["aggregation","pipeline","operator","syntax","version","expr"]},{"code":"118 NamespaceNotSharded","title":"NamespaceNotSharded","severity":"caution","meaning":"An operation that only makes sense on sharded collections was run against a collection/namespace that is not yet sharded. The command could not be applied because the target collection is not sharded.","cause":"moveChunk, split, or certain shard-key-based administrative commands were called on a collection without sharding enabled. It usually happens because the collection was not sharded with shardCollection or the wrong namespace was targeted.","fix":"Check the collection's sharding status: sh.status() or config.collections. If the operation really requires sharding, first enable sharding on the database (sh.enableSharding) and shard the collection (sh.shardCollection with a suitable shard key). Otherwise these administrative commands are not needed on unsharded collections; verify that you are targeting the correct 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","severity":"danger","meaning":"The Redis server could not parse the byte stream from the client according to the RESP (Redis Serialization Protocol). Because the element count declared by a multibulk command was invalid, the connection was closed with a protocol error.","cause":"Usually triggered by a non-Redis client (e.g., an HTTP request, an application connected to the wrong port) sending raw data to the Redis port, a broken/incomplete client library, faulty framing in a pipeline, or huge requests exceeding protocol limits (proto-max-bulk-len).","fix":"Verify that only a real Redis client connects to the Redis port (no stray HTTP/other protocol traffic). Update the client library and avoid custom/manual command generation that breaks RESP framing. If you send very large values, review the proto-max-bulk-len limit and request size. Make sure no intermediate proxy on the network corrupts the data.","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","severity":"caution","meaning":"When running a Lua script (EVAL/EVALSHA/FCALL), the specified number of keys (numkeys) is greater than the number of arguments actually passed to the command. Redis rejects the command because it cannot split the KEYS and ARGV arrays.","cause":"The EVAL syntax is 'EVAL script numkeys key [key ...] arg [arg ...]'. The numkeys value was miscalculated (set too high) or the expected keys were never added to the command. A common programming mistake.","fix":"Make numkeys exactly equal to the number of keys actually passed to the command. Use your client library's EVAL wrapper (which usually takes KEYS and ARGV as separate lists) so numkeys is computed automatically. When calling the script, split keys into KEYS and other parameters into ARGV correctly.","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 was sent while inside an open MULTI block. For optimistic locking, WATCH must put keys under watch BEFORE the transaction starts; if called after MULTI it is rejected. The error does not abort the transaction; only WATCH fails and nothing is watched.","cause":"WATCH and MULTI written in reverse order in code (MULTI first, then WATCH); calling WATCH inside the client library's pipeline/transaction wrapper; or using a pooled connection left in MULTI state. In this case EXEC runs even if the keys expected to be watched have changed; the expected conflict protection does not kick in.","fix":"Correct order: WATCH key -> (read values) -> MULTI -> commands -> EXEC. If EXEC returns nil, a watched key changed; retry the operation from the start. Use the client library's WATCH/transaction API (e.g., watch + pipeline helpers). Note: pub/sub commands such as SUBSCRIBE are not rejected inside MULTI; they are queued and the connection switches to subscribe mode after EXEC; this is also not recommended, use a dedicated connection for 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 was run on a replica (replica/slave) instance. WAIT, which waits for a write to reach a given number of replicas, is meaningful only on the primary (master); it cannot be used on a replica.","cause":"The application mistakenly sent WAIT over a replica connection. This usually happens because the connection is routed to a read replica, or because the client is still connected to a replica after a role change (failover).","fix":"Send WAIT only to the primary node. Make sure the client/proxy configuration routes writes and WAIT calls to the master. Detect role changes after failover and redirect connections to the new primary (you can verify the role with the ROLE command).","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":"An attempt was made to start an AOF (Append Only File) rewrite (BGREWRITEAOF), but an AOF rewrite is already in progress. Two AOF rewrites cannot run at the same time.","cause":"A new rewrite request arrived while a previous BGREWRITEAOF (manual, or automatic via the auto-aof-rewrite-percentage trigger) was still running. On large datasets an AOF rewrite can take a long time and lead to this conflict.","fix":"Wait for the running rewrite to finish; watch the aof_rewrite_in_progress field in INFO persistence output. If the operation is legitimately running, do not send another request. If conflicts are frequent, review the automatic rewrite thresholds (auto-aof-rewrite-percentage/min-size) and disk I/O performance.","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","severity":"danger","meaning":"A MIGRATE command (usually while moving slots/keys in Redis Cluster) hit an I/O error or timeout while connecting to the target instance or reading its reply. The key migration could not be completed safely.","cause":"Network latency/outage between the source and target nodes, the target node being overloaded or unresponsive, a MIGRATE timeout that is too short, or a large key that cannot be transferred within the given time. Common during cluster rebalancing (resharding).","fix":"Check network connectivity and latency between the source and target nodes. Specify a longer timeout for MIGRATE (especially for large keys). Verify the target node's health/load and memory status. When moving large keys during resharding, choose low-load hours; retry the migration if needed.","tags":["migrate","cluster","resharding","timeout","network","i-o"]},{"code":"ERR Error compiling script (new function)","title":"Error compiling Lua script","severity":"caution","meaning":"The Lua script sent with EVAL could not be compiled by Redis's embedded Lua interpreter. It was rejected before execution started because the script contains a syntax error.","cause":"Syntax errors in the Lua script: unclosed parentheses/quotes, misspelled keywords, a missing 'end', invalid operators or an unsupported Lua construct. Incorrect concatenation/escaping of the script on the client side can also trigger it.","fix":"Fix the Lua syntax using the line/character information in the error message (check matching parentheses, quotes and 'end'). Validate the script by compiling it in a separate Lua environment. Make sure the client builds the script correctly (no escaping/concatenation errors). Preload validated scripts with SCRIPT LOAD and call them with EVALSHA.","tags":["lua","eval","compilation","syntax","script load","script"]},{"code":"ERR FAILOVER requires connected replicas.","title":"FAILOVER requires connected replicas","severity":"caution","meaning":"FAILOVER was run to start a coordinated (graceful) failover, but it was rejected because no suitable replica connected to the primary is available to take over. Failover cannot happen without a target to hand the role to.","cause":"The primary currently has no connected replicas (replicas are down, disconnected from the network or not in sync), or the specified target replica is not eligible/connected. When manual FAILOVER (Redis 6.2+) is called, the replication topology does not meet the required condition. Note: FAILOVER works only with standalone (master/replica) replication; in Redis Cluster, CLUSTER FAILOVER is used on the replica.","fix":"Make sure at least one healthy, connected and up-to-date replica exists (check connected_slaves and sync status with INFO replication). Fix network/access issues on the replicas so they connect to the primary. To fail over to a specific target, use FAILOVER TO host port and verify that replica is eligible.","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":"An invalid expiration time was given to SET (with the EX/PX/EXAT/PXAT options). Redis rejected the command because it does not accept a meaningless time to live such as zero or a negative value.","cause":"Sending 0 or a negative value with EX/PX, or the application miscalculating the expiration and producing an invalid number. Commands such as SETEX/GETEX return this error similarly for non-positive durations. Some client bugs come from mixing up seconds and milliseconds.","fix":"Always send the expiration as a strictly positive integer; EX expects seconds, PX milliseconds. Validate the duration calculation in the application (it must not produce negative/zero results) and do not mix up units (seconds vs. milliseconds). To delete a key immediately, use DEL/UNLINK instead of setting an expiration. For absolute time, pass a valid future Unix timestamp to EXAT/PXAT.","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":"While starting, an Elasticsearch node could not bind a socket to listen on the transport (inter-node communication) or HTTP port. It usually comes with the 'Address already in use' sub-error, and the node cannot start.","cause":"The requested port (default transport 9300, HTTP 9200) is already used by another process; most often a previous instance of the same node did not shut down cleanly, another ES instance/service on the same host conflicts, or network.host/port is misconfigured. Permission/access restrictions can also prevent binding.","fix":"Find who holds the port (Linux: ss -ltnp | grep 9300 or lsof -i:9300) and terminate the conflicting process, or assign the node a different port (transport.port / http.port). Verify that the previous ES process has actually exited. Make sure network.host and the port range settings are correct and bindable. Then restart the node.","tags":["transport","port","9300","bind","network.host","startup"]},{"code":"translog_corrupted_exception","title":"translog_corrupted_exception","severity":"danger","meaning":"A shard's transaction log (translog) could not be read because it is corrupted. During recovery, Elasticsearch detected that the translog failed its integrity check, and the shard could not start.","cause":"Translog files are usually corrupted by a sudden power loss, a full disk, a file system error, hardware/disk failure, or external operations on the data directory (copying, manual intervention). Partially completed writes during a forced shutdown (kill -9) can also cause it.","fix":"First try to recover the data from a replica or a snapshot; if a healthy copy exists, rebuilding the corrupted shard from it is the safest path. If no other copy exists and you accept data loss, stop the node and clean the corrupted translog/data with bin/elasticsearch-shard remove-corrupted-data --index <index> --shard-id <n> (this permanently loses data not yet committed; the old elasticsearch-translog tool was removed in 7.0). The tool prints the POST _cluster/reroute command (allocate_stale_primary, accept_data_loss: true) needed to allocate the shard after the node restarts. Back up the data directory before the operation. Check disk health and free space.","tags":["translog","shard","recovery","corruption","snapshot","disk"]},{"code":"recovery_engine_exception","title":"recovery_engine_exception","severity":"danger","meaning":"An engine-level error occurred during a shard's recovery, and the shard could not be opened successfully. Recovery is the step performed during a node restart, replica allocation or snapshot restore.","cause":"An underlying problem during recovery: corrupted Lucene segments or translog, insufficient disk space, file permissions, a network interruption between source and target nodes, or version/compatibility problems. It usually wraps a more specific sub-error (corruption, I/O).","fix":"Inspect the underlying (caused_by) error to identify the real problem (corruption, disk, network, etc.). Verify disk space and file permissions. If replica allocation fails, see why with _cluster/allocation/explain and trigger reallocation if needed. If there is corruption, recover from a healthy replica/snapshot. If it is network-related, check inter-node connectivity and recovery settings.","tags":["recovery","shard","engine","allocation","corruption","disk"]},{"code":"snapshot_in_progress_exception","title":"snapshot_in_progress_exception","severity":"caution","meaning":"The requested operation was rejected because it conflicts with a snapshot currently in progress. For example, an attempt to delete/close an index or modify the repository while a snapshot is being taken is blocked.","cause":"While a snapshot was still running, an attempt was made to delete/close indices included in that snapshot, remove/modify the repository, or start another conflicting snapshot/restore operation. Elasticsearch does not allow these conflicting operations, to preserve consistency.","fix":"Wait for the running snapshot to finish: monitor progress with GET _snapshot/_status. If the operation really must be canceled, stop it by deleting the snapshot: DELETE _snapshot/repo/snapshot_name (this aborts the running snapshot). After the snapshot completes or is canceled, rerun your original operation (index deletion, repository change).","tags":["snapshot","repository","restore","index deletion","conflict","backup"]},{"code":"repository_verification_exception","title":"repository_verification_exception","severity":"danger","meaning":"While a snapshot repository was being registered or verified, the check that all nodes can access the repository failed. Because repository verification did not pass, snapshot/restore operations cannot be performed reliably.","cause":"A shared file system (fs) repository is not mounted/accessible at the same path on all nodes, path.repo is missing from the configuration, file permissions are insufficient, or cloud/object stores (S3, GCS, Azure) have incorrect credentials/network access. Some nodes being unable to write to the repository is the typical cause.","fix":"For a shared fs repository, make sure path.repo is defined in elasticsearch.yml on all nodes and that the same path is mounted readable/writable on every node. Fix file permissions (elasticsearch user). For cloud repositories, verify credentials (keystore), the bucket name and network/access permissions. Re-verify the repository with 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":"An error occurred in the reduce phase of a search. The coordinating node hit a problem while merging (reducing) partial results/aggregations from different shards and could not produce the final result.","cause":"Usually a memory/resource limit is exceeded while merging results (e.g. very large aggregations, a circuit breaker tripping), an incompatible/invalid aggregation structure, or a partial result returned by a shard cannot be merged. Common with very high-cardinality aggregations.","fix":"Read the caused_by detail to identify the root cause. Lighten heavy aggregations: lower size on high-cardinality terms aggregations, paginate with a composite aggregation, and reduce unnecessary sub-aggregations. Monitor memory pressure and split the query into smaller parts if needed. If a circuit breaker trips, reduce the query cost.","tags":["search","reduce","aggregation","coordinating node","memory","cardinality"]},{"code":"aggregation_execution_exception","title":"aggregation_execution_exception","severity":"caution","meaning":"An error occurred during execution of an aggregation. Although the aggregation definition was parsed, a problem arose at runtime while processing the target fields/values.","cause":"The aggregation is incompatible with the field type (e.g. aggregating a text field without fielddata), invalid values during sorting/calculation, a pipeline aggregation referencing a nonexistent path (bucket path), or unsupported field type combinations. Runtime errors in script-based aggregations also trigger it.","fix":"Inspect the field/aggregation name in the error detail. Make sure the aggregated fields are of a suitable type (keyword/numeric instead of text; use fields with doc_values enabled if needed). In pipeline aggregations, verify that buckets_path matches actual bucket names. Add null/type checks in script aggregations. Fix the mapping with appropriate field types.","tags":["aggregation","execution","fielddata","keyword","pipeline","mapping"]},{"code":"failed_node_exception","title":"failed_node_exception","severity":"danger","meaning":"During a cluster-wide operation (e.g. search, stats, management command), a specific node in the cluster could not process or respond to the request. The coordinating node wraps the failure from that node in this exception.","cause":"The node crashed, dropped off the network, could not respond due to overload/GC pauses, disk/resource problems, or a shard error on that node. It can also appear transiently during cluster reconfiguration (nodes leaving/joining).","fix":"Use caused_by to determine on which node and why the failure occurred. Check node health: GET _cat/nodes, GET _nodes/stats (CPU, memory, GC, disk). If the node is under resource pressure, review the load/heap settings; verify network connectivity. If the node left the cluster, wait for it to rejoin and for shard allocation to recover (GET _cluster/health).","tags":["node","cluster","network","gc","shard","resources"]},{"code":"elasticsearch_parse_exception","title":"elasticsearch_parse_exception","severity":"caution","meaning":"Elasticsearch hit a generic parsing error while parsing the request body (JSON query, settings or configuration). The request does not structurally match the expected format.","cause":"Malformed/invalid JSON (missing comma, unclosed bracket), misplaced fields, unexpected value types (e.g. text where a number is expected), or an invalid time/range expression. Incorrect nesting of a DSL query also produces this exception.","fix":"Run the request body through a JSON validator; check brackets, quotes and commas. Check against the Elasticsearch DSL documentation that fields are in the right place and of the right type. Use the position/field hint in the error message to fix the problematic part. If you use a client library, make sure serialization produces valid JSON.","tags":["parse","json","dsl","query","parsing","request"]}]},"health/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"checks":[{"id":"version-uptime","group":"General","title":"Version and 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":"Instance status should be OPEN. If startup_time is unexpectedly recent (fresh startup), an unplanned restart may have occurred; investigate. The version_full column is 18c+; on 12c and earlier use the version column.","severity":"safe"},{"id":"alertlog-errors","group":"General","title":"Recent critical errors in the 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":"No ORA- errors should be returned for the last 24 hours. Entries such as ORA-00600, ORA-07445 and ORA-01578 (block corruption) require immediate action.","severity":"danger"},{"id":"invalid-objects","group":"General","title":"INVALID objects","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":"Ideally there should be no INVALID objects. If any exist, recompile with EXEC DBMS_UTILITY.COMPILE_SCHEMA or utlrp.sql; invalid objects under SYS/SYSTEM take priority.","severity":"caution"},{"id":"tablespace-usage","group":"Capacity","title":"Tablespace usage","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":"pct_used above 85% is the warning threshold, above 90% critical. Tablespaces with autoextend off are at higher risk of filling up; add a datafile or enable autoextend.","severity":"danger"},{"id":"largest-segments","group":"Capacity","title":"Largest segments (tables/indexes)","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":"Shows the segments consuming the most space. Unexpected growth may indicate a need for archiving/purging or partitioning.","severity":"safe"},{"id":"undo-temp-usage","group":"Capacity","title":"UNDO and TEMP tablespace usage","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":"If TEMP usage constantly hits the ceiling, there are large sort/hash operations. If the active UNDO space cannot satisfy retention, there is a risk of ORA-01555 (snapshot too old).","severity":"caution"},{"id":"sessions-vs-limit","group":"Capacity","title":"Session count vs limit","command":"SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('sessions','processes','transactions');","expect":"If current_utilization approaches limit_value (especially above 85%), new connections may be refused. max_utilization shows the historical peak; review the processes parameter.","severity":"caution"},{"id":"fra-usage","group":"Capacity","title":"Flash Recovery Area (FRA) usage","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":"When the FRA reaches 85% a warning is written to the alert log; at 100% archive log writing stops and the database hangs (archiver hung, ORA-00257). Clean up RMAN backups/archives or increase db_recovery_file_dest_size.","severity":"danger"},{"id":"buffer-cache-hit","group":"Performance","title":"Buffer cache hit ratio","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":"On OLTP systems typically above 90% is expected. Persistently low values may indicate an undersized SGA/buffer cache or poor indexing; interpret it together with wait events, not on its own.","severity":"safe"},{"id":"top-sql","group":"Performance","title":"Most expensive SQL statements (elapsed time)","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":"Queries with the highest total elapsed time. Those with high sec_per_exec or very high executions are tuning candidates; examine them with AWR/SQL Tuning Advisor.","severity":"safe"},{"id":"long-active-sessions","group":"Performance","title":"Long-running active sessions","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":"Active sessions with a very high last_call_et (seconds) may be stuck or running a heavy query. The event column shows the resource being waited on.","severity":"caution"},{"id":"blocking-sessions","group":"Performance","title":"Blocking and waiting sessions (locks)","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":"No rows should be returned. Blocking sessions cause application latency/deadlocks; if blocker_sid holds a lock for a long time (large seconds_in_wait), assess with the application team and, if necessary, run ALTER SYSTEM KILL SESSION.","severity":"danger"},{"id":"rman-last-backup","group":"Backup & Recovery","title":"Last successful RMAN backup status","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":"The latest backup should have status = COMPLETED and a start_time fresh enough for your RPO policy (e.g. within the last 24 hours). FAILED or 'COMPLETED WITH WARNINGS' entries must be investigated.","severity":"danger"},{"id":"archivelog-mode","group":"Backup & Recovery","title":"Archivelog mode and archiver status","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":"A production database should have log_mode = ARCHIVELOG (otherwise point-in-time recovery is not possible). If no archives were produced in the last 24 hours, the archiver may be stuck.","severity":"danger"},{"id":"locked-expired-accounts","group":"Security","title":"Locked or expired accounts","command":"SELECT username, account_status, lock_date, expiry_date, profile FROM dba_users WHERE account_status != 'OPEN' ORDER BY username;","expect":"LOCKED/EXPIRED critical application accounts cause unexpected outages. Default accounts (SCOTT, HR, etc.) being locked/disabled is good. EXPIRED(GRACE) indicates an upcoming password expiry.","severity":"caution"},{"id":"privileged-users","group":"Security","title":"Highly privileged users (DBA role)","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":"The DBA role should be held only by necessary, known accounts. Unexpected grantees or those with admin_option = YES are a privilege escalation risk; review them according to the principle of least privilege. For accounts with SYSDBA/SYSOPER privileges, also check: SELECT username, sysdba, sysoper FROM v$pwfile_users;","severity":"caution"},{"id":"default-password-users","group":"Security","title":"Open accounts with unchanged default passwords","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":"Accounts with has_default_password = YES must have their password changed immediately or be locked (ALTER USER ... ACCOUNT LOCK). For the password lifetime policy, also check PASSWORD_LIFE_TIME in DBA_PROFILES.","severity":"caution"},{"id":"dataguard-lag","group":"Replication","title":"Data Guard standby lag","command":"SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN ('apply lag','transport lag');","expect":"apply lag and transport lag should be as close to 0 as possible. Lag in the order of minutes/hours indicates a network or MRP (Managed Recovery Process) problem; the failover target is at risk.","severity":"danger"},{"id":"dataguard-mrp","group":"Replication","title":"Standby apply process (MRP) status","command":"SELECT name, role, action, thread#, sequence#, block# FROM v$dataguard_process WHERE name LIKE 'MRP%' OR role LIKE 'RFS%' ORDER BY name;","expect":"On a physical standby the MRP0 process should be visible with action APPLYING_LOG or WAIT_FOR_LOG. If there is no MRP, apply has stopped; if there is no RFS, logs are not arriving from the primary. v$dataguard_process is 12.2+; on 12.1 and earlier use the (now deprecated) v$managed_standby (process, status columns).","severity":"danger"}]},"health/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"checks":[{"id":"version-uptime","group":"General","title":"Version and 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":"Shows the version and start time. If uptime is unexpectedly short (recent start), an unplanned restart or crash may have occurred; check the logs.","severity":"safe"},{"id":"recent-errors","group":"General","title":"Checkpoint statistics","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":"If the number of requested checkpoints (num_requested / checkpoints_req) is high relative to timed ones (num_timed / checkpoints_timed), WAL is filling too fast; increase max_wal_size. In PG17 the counters moved from pg_stat_bgwriter to pg_stat_checkpointer.","severity":"caution"},{"id":"table-bloat","group":"General","title":"Bloated tables and dead tuple ratio","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":"Tables with a high dead_pct (e.g. above 20%) are bloated; this affects VACUUM performance and disk usage. If last_autovacuum is very old, review autovacuum settings or run a manual VACUUM (ANALYZE).","severity":"caution"},{"id":"database-size","group":"Capacity","title":"Database sizes","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":"Shows database sizes. Evaluate unexpected growth together with disk capacity (df -h); PostgreSQL stops write operations when the disk is full.","severity":"danger"},{"id":"largest-tables","group":"Capacity","title":"Largest tables (including indexes)","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":"Shows the tables that take up the most space. If index_toast_size is much larger than the table size, there may be excessive/unused indexes; consider archiving or partitioning.","severity":"safe"},{"id":"temp-files","group":"Capacity","title":"Temporary file usage","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":"High temp_files/temp_bytes indicate that work_mem is insufficient and sort/hash operations are spilling to disk. Consider increasing work_mem or optimizing the queries.","severity":"caution"},{"id":"connections-vs-limit","group":"Capacity","title":"Connection count 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":"Only client connections are counted (the backend_type = 'client backend' filter is needed because pg_stat_activity also includes background processes). Above 80% pct_used, new connections risk being rejected (especially once superuser_reserved_connections is reached). A connection pooler (PgBouncer) is recommended.","severity":"caution"},{"id":"wal-directory-size","group":"Capacity","title":"WAL directory size and replication slot retention","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":"Inactive (active = false) replication slots cause WAL to accumulate and the pg_wal directory to fill up. If retained_wal is large, verify the slot's owner (slot_type, database, plugin): active = false can also indicate a temporarily disconnected standby or a logical subscriber/CDC (e.g. Debezium) slot. If it is truly unused, drop it with pg_drop_replication_slot; a standby that depended on the dropped slot must be rebuilt, and the subscriber/CDC side must be resynchronized. For permanent protection, set an upper bound with max_slot_wal_keep_size (PG13+).","severity":"danger"},{"id":"cache-hit-ratio","group":"Performance","title":"Buffer cache hit ratio","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":"For OLTP workloads the target is typically above 99%. A consistently low value may indicate that shared_buffers is too small or the working set does not fit in memory.","severity":"safe"},{"id":"top-queries","group":"Performance","title":"Most expensive queries (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 must be in shared_preload_libraries (requires a restart) and installed in the database (CREATE EXTENSION pg_stat_statements). The total_exec_time/mean_exec_time names are for PG13+; PG12 uses total_time/mean_time. Queries that consume the most total time are tuning candidates; run EXPLAIN ANALYZE for those with a high mean_ms.","severity":"safe"},{"id":"long-running-queries","group":"Performance","title":"Long-running active queries","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":"Active queries running longer than 1 minute may be stuck or heavy. wait_event shows what is being waited on. This query lists only active queries; check 'idle in transaction' sessions (which prevent VACUUM from removing dead tuples) separately with state = 'idle in transaction' and xact_start.","severity":"caution"},{"id":"blocking-locks","group":"Performance","title":"Blocking sessions and waiting locks","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":"No rows should be returned. Blocking sessions (blocking_pid) cause application delays and timeouts; investigate a long-blocking transaction and, if necessary, terminate it with pg_terminate_backend.","severity":"danger"},{"id":"archiver-status","group":"Backup & Recovery","title":"WAL archiving health (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 should be 0 and last_failed_time should be older than last_archived_time. If since_last_archive is very large, archiving has stopped (archive_command failure); the PITR backup chain is broken.","severity":"danger"},{"id":"last-basebackup","group":"Backup & Recovery","title":"Last backup and WAL archive freshness","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":"Verify the time of the last base backup from the backup tool (pgBackRest / Barman / pg_basebackup job logs); it must be fresh enough for your RPO policy. For PITR the WAL archive must also be continuous: if since_last_archive is large or failed_count is increasing, the archive chain may be broken.","severity":"danger"},{"id":"locked-expired-roles","group":"Security","title":"Expired or expiring login roles","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":"Application roles whose rolvaliduntil has passed are blocked from logging in, causing downtime. Plan renewal in advance for passwords that expire within the next 7 days.","severity":"caution"},{"id":"superuser-roles","group":"Security","title":"Highly privileged roles (SUPERUSER / create privileges)","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 should exist only on required, known accounts. Roles with rolbypassrls = true bypass row-level security (RLS); unexpected high privileges should be revoked according to the principle of least privilege.","severity":"caution"},{"id":"hba-trust-auth","group":"Security","title":"Weak authentication (trust) rules","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 allows passwordless access and is dangerous in production; 'password' sends the password in clear text. Replace them with scram-sha-256. (Requires PG 10+.)","severity":"danger"},{"id":"replication-lag","group":"Replication","title":"Streaming replication lag (primary side)","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":"Run on the primary. state should be streaming and replay_lag (bytes/time) should be small. If the standby does not appear at all, the connection has dropped; if sync_state = async, synchronous replication is not working as expected.","severity":"danger"},{"id":"replica-lag-standby","group":"Replication","title":"Replication lag (standby side)","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":"Run on the standby server. in_recovery should be true. If replication_delay is in the range of minutes/hours there may be apply lag; however, this value also grows when there are no writes on the primary (false alarm), so interpret it together with the LSN difference; if the gap between receive_lsn and replay_lsn is large, WAL replay cannot keep up.","severity":"danger"}]},"health/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"checks":[{"id":"version-uptime","group":"General","title":"Version and 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":"Shows the version and uptime in seconds. If uptime_seconds is unexpectedly small, an unplanned restart or crash may have occurred; check the error log.","severity":"safe"},{"id":"error-log-status","group":"General","title":"Recent error log entries (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":"Shows recent errors and warnings (requires MySQL 8.0+). Investigate prio = 'Error' entries (especially InnoDB, lock and disk errors). On older versions, check the server's log_error file.","severity":"danger"},{"id":"crashed-tables","group":"General","title":"Corrupted tables / tables to check","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":"Lists each table's engine and last update time. MyISAM tables can crash; when in doubt, verify integrity with CHECK TABLE ... or mysqlcheck. InnoDB is recommended for critical data.","severity":"caution"},{"id":"schema-sizes","group":"Capacity","title":"Schema (database) sizes","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":"Shows schema sizes and reclaimable data_free space. Evaluate together with disk capacity; if free_mb is high, space can be reclaimed with OPTIMIZE TABLE.","severity":"danger"},{"id":"largest-tables","group":"Capacity","title":"Largest tables (including data_free / fragmentation)","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":"Shows the largest tables. Tables with a high data_free_mb are fragmented; space can be reclaimed with OPTIMIZE TABLE. Consider partitioning for very large tables.","severity":"safe"},{"id":"innodb-tablespace","group":"Capacity","title":"InnoDB tablespace and temporary space usage","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":"Shows how full the InnoDB datafiles are. If innodb_file_per_table is off, ibdata1 keeps growing and never shrinks. For temp tablespace bloat, also check the SHOW ENGINE INNODB STATUS output.","severity":"caution"},{"id":"connections-vs-limit","group":"Capacity","title":"Connection count and max_connections limit","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":"If max_used approaches max_connections (above 85%), there is a risk of 'Too many connections' errors. A high aborted_connects points to authentication/network problems; use a connection pool.","severity":"caution"},{"id":"binlog-size","group":"Capacity","title":"Binary log files and total size","command":"SHOW BINARY LOGS;","expect":"Lists binlog files and their sizes. If the total size is straining disk space, review binlog_expire_logs_seconds (expire_logs_days before 8.0; that variable was deprecated in 8.0 and removed in 8.4); if replication is in use, do not purge before the replicas have read them.","severity":"caution"},{"id":"buffer-pool-hit","group":"Performance","title":"InnoDB buffer pool hit ratio","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":"hit_ratio_pct is typically targeted above 99%. If it stays low, innodb_buffer_pool_size is insufficient; consider increasing it up to ~70-80% of RAM on a dedicated server (less on a shared server).","severity":"safe"},{"id":"top-queries","group":"Performance","title":"Most expensive queries (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":"The sys schema and performance_schema must be enabled. The queries consuming the most total_latency are tuning candidates. If rows_examined_avg is much larger than rows_sent_avg, an index is missing; examine it with EXPLAIN.","severity":"safe"},{"id":"long-running-queries","group":"Performance","title":"Long-running active queries","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":"Queries running longer than 60 seconds may be stuck or heavy. The state column shows what is being waited on. Long sessions in the 'Sleep' state also exhaust the connection pool; use KILL <id> if necessary.","severity":"caution"},{"id":"blocking-locks","group":"Performance","title":"Blocking sessions and waiting locks","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":"Should return no rows. A blocking transaction (blocking_thread) causes application latency and lock wait timeout errors. Investigate a long-blocking transaction and terminate it with KILL if necessary. (MySQL 8.0 syntax.)","severity":"danger"},{"id":"binlog-enabled","group":"Backup & Recovery","title":"Binary log and recovery settings","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 must be ON for point-in-time recovery. For durability, sync_binlog = 1 and innodb_flush_log_at_trx_commit = 1 are recommended (otherwise transactions can be lost in a crash). binlog_format = ROW is the safest for replication.","severity":"danger"},{"id":"last-backup-note","group":"Backup & Recovery","title":"Last backup time (binlog / tool indicator)","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 does not track backup status by itself; verify the last successful backup time from the mysqldump / MySQL Shell dump / Percona XtraBackup tool logs (mysqlpump was deprecated in 8.0.34 and removed in 8.4). The backup must be as fresh as your RPO policy requires (e.g. within the last 24 hours).","severity":"danger"},{"id":"locked-expired-accounts","group":"Security","title":"Locked accounts or accounts with expired passwords","command":"SELECT user, host, account_locked, password_expired, password_lifetime FROM mysql.user ORDER BY user, host;","expect":"Critical application accounts with account_locked = 'Y' or password_expired = 'Y' cause outages. If password_lifetime is NULL and global expiration is off, the password never expires; this may be a policy gap.","severity":"caution"},{"id":"anonymous-nopassword-users","group":"Security","title":"Anonymous users and users without a password","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":"Should return no rows. Anonymous users (user = '') and accounts without a password are a serious security hole; remove them with mysql_secure_installation or assign strong passwords.","severity":"danger"},{"id":"super-privilege-users","group":"Security","title":"Highly privileged (SUPER / GRANT) users","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":"Privileges such as SUPER, FILE and GRANT OPTION should exist only on necessary, known accounts. Unexpected grantees are a privilege escalation risk; pay particular attention to highly privileged accounts defined with % (any host).","severity":"caution"},{"id":"replica-status","group":"Replication","title":"Replica (standby) status and lag","command":"SHOW REPLICA STATUS;","expect":"Run on the replica (MySQL 8.0.22+). Replica_IO_Running and Replica_SQL_Running must both be 'Yes'. Seconds_Behind_Source should be small; if it is NULL, replication has stopped. Check the Last_Error field. (SHOW SLAVE STATUS on older versions.)","severity":"danger"},{"id":"replication-threads","group":"Replication","title":"Replication connection and applier status (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 and sql_state must be 'ON'. If io_error/sql_error is populated, replication has failed and stopped; in multi-source setups each channel_name must be healthy individually.","severity":"danger"}]},"health/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"checks":[{"id":"version-uptime","group":"General","title":"Version and 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":"Verify the version and patch level. If uptime is very short (less than a few hours), an unexpected restart may have occurred; review the SQL Server error log.","severity":"safe"},{"id":"database-states","group":"General","title":"Databases in a problem state (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":"All production databases must be ONLINE. SUSPECT, RECOVERY_PENDING or EMERGENCY requires immediate action; start recovery and review the error log.","severity":"danger"},{"id":"recent-errors","group":"General","title":"Recent significant errors (memory/error ring buffer)","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":"Review recent exception/error records. Frequently recurring 823/824/825 (I/O consistency) errors point to disk corruption. For details, also read the error log on the server with xp_readerrorlog.","severity":"caution"},{"id":"data-log-file-usage","group":"Capacity","title":"Data and log file usage","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":"Run in each database. If used space is very close to allocated space (85%+) and autogrowth is capped, plan growth or add disk. If the log file is constantly full, review the backup/recovery model settings.","severity":"caution"},{"id":"largest-tables","group":"Capacity","title":"Largest tables (size and row count)","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":"Identify the tables consuming the most space. Unexpected growth may indicate a need for archiving, index maintenance or partitioning.","severity":"safe"},{"id":"tempdb-usage","group":"Capacity","title":"tempdb space usage","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":"If tempdb is about to run out of free space (low unallocated), queries may fail. If the version store keeps growing, there are long-running open transactions; investigate with sys.dm_tran_active_snapshot_database_transactions.","severity":"caution"},{"id":"connection-count","group":"Capacity","title":"Connection count and distribution","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":"Track connection counts per application/database. An unusually high number of connections from a single application may indicate a connection pool leak. Do not approach the max worker threads limit.","severity":"safe"},{"id":"vlf-count","group":"Capacity","title":"Log file VLF count (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":"A high VLF count (more than a few hundred, e.g., 500+) means the log file grew in very small increments; this slows down recovery and backup performance. Shrink the log and regrow it in one large step.","severity":"caution"},{"id":"buffer-cache-ple","group":"Performance","title":"Buffer Cache Hit Ratio and 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":"Buffer cache hit ratio should ideally be 99%+ (hit_ratio_raw / hit_ratio_base). If Page Life Expectancy is consistently low (rule of thumb: below 300 s per 4 GB per NUMA node), there is memory pressure; more RAM or query optimization is needed.","severity":"caution"},{"id":"expensive-queries","group":"Performance","title":"Most expensive queries (CPU and reads)","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":"Identify the most expensive queries by CPU and logical reads. Queries with high reads are a sign of missing indexes; review their execution plans and add indexes or rewrite the queries.","severity":"safe"},{"id":"blocking-sessions","group":"Performance","title":"Blocking sessions","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":"A blocking session (blocking_session_id <> 0) means queries are making each other wait. Long-lasting blocking causes application timeouts; find the head blocker and consider terminating the long transaction.","severity":"danger"},{"id":"wait-stats","group":"Performance","title":"Top wait statistics (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":"Interpret the dominant wait types: PAGEIOLATCH = disk I/O bottleneck, CXPACKET/CXCONSUMER = parallelism, LCK_M = locking, WRITELOG = log disk, RESOURCE_SEMAPHORE = memory. A high signal wait percentage indicates CPU pressure.","severity":"safe"},{"id":"last-backup","group":"Backup & Recovery","title":"Last backup times (full / differential / 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":"Every production database must have a recent full backup. If last_full_backup is NULL or very old (beyond the RPO), the backup job is failing. In the FULL recovery model, without log backups the log file keeps growing.","severity":"danger"},{"id":"recovery-model","group":"Backup & Recovery","title":"Recovery model consistency","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":"Critical databases must use FULL for point-in-time recovery. log_reuse_wait_desc = LOG_BACKUP means a log backup is pending; ACTIVE_TRANSACTION points to a long-running transaction.","severity":"caution"},{"id":"sysadmin-members","group":"Security","title":"sysadmin role members","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":"sysadmin membership must be minimized. Unexpected logins (especially application accounts or individual users) are a privilege escalation risk; apply the principle of least privilege.","severity":"danger"},{"id":"sa-and-password-policy","group":"Security","title":"sa account and logins with password policy disabled","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":"The sa account should be disabled or renamed. SQL logins with is_policy_checked = 0 carry a weak/stale password risk; enable the password policy (CHECK_POLICY).","severity":"caution"},{"id":"alwayson-sync","group":"Replication","title":"Always On synchronization state and queues","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 should be SYNCHRONIZED (synchronous) or SYNCHRONIZING (asynchronous), and synchronization_health should be HEALTHY. If log_send_queue or redo_queue is growing, the replica is falling behind; the data loss/RTO risk on failover increases.","severity":"caution"}]},"health/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"checks":[{"id":"version-uptime","group":"General","title":"Version and uptime","command":"db.serverStatus().version; db.serverStatus().uptime","expect":"Verify that the version is supported and patched. uptime is in seconds; if it is very small (a few thousand seconds), an unexpected restart has occurred; review the mongod logs.","severity":"safe"},{"id":"replset-status","group":"General","title":"Replica set member states (health)","command":"rs.status().members.forEach(function(m) { print(m.name + \" | \" + m.stateStr + \" | health=\" + m.health); })","expect":"Every member should have health=1 and there must be exactly one PRIMARY. health=0 or (not reachable/healthy), DOWN and RECOVERING states indicate a failure. If there is more than one PRIMARY or none, there is an election problem.","severity":"danger"},{"id":"dbstats-sizes","group":"Capacity","title":"Database sizes (data / storage / index)","command":"db.stats(1024*1024)","expect":"Monitor dataSize, storageSize and indexSize (MB). If storageSize is much larger than dataSize, there may be fragmentation. Keeping indexSize within RAM (WiredTiger cache) is critical for performance.","severity":"safe"},{"id":"disk-space","group":"Capacity","title":"Disk space usage (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":"If the ratio of fsUsedSize to fsTotalSize exceeds 85%, the disk is about to fill up; MongoDB stops writes when the disk is full. Plan to add disk or archive data.","severity":"caution"},{"id":"largest-collections","group":"Capacity","title":"Largest 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":"Identify the collections using the most space. Unexpected growth may indicate a need for archiving, a TTL index or sharding. Review collections whose index size exceeds their data size.","severity":"safe"},{"id":"connections","group":"Capacity","title":"Connection count (current / available)","command":"db.serverStatus().connections","expect":"If current approaches available, the connection limit is about to be reached and new connections will be rejected. Check the application connection pool settings and possible leaks.","severity":"caution"},{"id":"wiredtiger-cache","group":"Performance","title":"WiredTiger cache usage and eviction","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":"Eviction is triggered when used memory exceeds 80% of the maximum cache; above 95%, application threads are forced into eviction and performance drops. A high dirty page ratio indicates write pressure.","severity":"caution"},{"id":"slow-queries-profiler","group":"Performance","title":"Slow queries (profiler) and 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":"This check is read-only: it first shows the profiler status (was: 0 off, 1 slow operations only, 2 all), then lists the recorded slow operations. If the profiler is off (was: 0), system.profile stays empty; for diagnosis, enable it as a separate step with db.setProfilingLevel(1, {slowms: 100}) and disable it with db.setProfilingLevel(0) when done. Queries showing COLLSCAN in planSummary perform a full collection scan and are missing an index. Optimize queries with high millis values by indexing. The profiler adds overhead, so use it carefully in production.","severity":"caution"},{"id":"long-running-ops","group":"Performance","title":"Long-running operations (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":"Active operations with high secs_running values can cause blocking. Terminate them with db.killOp(opid) when necessary. Operations with waitingForLock=true are waiting for a lock; investigate the root operation.","severity":"danger"},{"id":"oplog-window","group":"Backup & Recovery","title":"Oplog window (replication history)","command":"rs.printReplicationInfo()","expect":"The oplog window (configured oplog size and log length in hours) should be long enough. If the window is short (a few hours), a slow secondary may fall into a full resync (initial sync). Backup and point-in-time recovery strategies depend on the oplog.","severity":"caution"},{"id":"backup-note","group":"Backup & Recovery","title":"Backup status and last backup note","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 has no built-in scheduled backup; mongodump, file system snapshots, Ops Manager or PBM are used. Verify the last successful backup time and restore test in the external tool. On a replica set, the oplog window (oplogWindowHours) bounds how far back oplog-based PITR can go; it must not be shorter than the backup interval. Check that RPO/RTO targets are met.","severity":"danger"},{"id":"users-roles","group":"Security","title":"User and role list","command":"db.getSiblingDB('admin').system.users.find({}, {user: 1, db: 1, roles: 1, _id: 0}).forEach(printjson)","expect":"Review the defined users and their roles. Unexpected accounts or overly broad roles (root, __system, dbOwner) are a privilege escalation risk. Apply the principle of least privilege.","severity":"caution"},{"id":"auth-enabled","group":"Security","title":"Is authentication (authorization) enabled","command":"db.getSiblingDB('admin').runCommand({getCmdLineOpts: 1}).parsed.security","expect":"security.authorization must be 'enabled'. If it is disabled (or there is no security block), the database is open to access without authentication; this is a serious security hole, enable it immediately.","severity":"danger"},{"id":"secondary-lag","group":"Replication","title":"Secondary replication lag","command":"rs.printSecondaryReplicationInfo()","expect":"The 'replication lag' value for each secondary should be low (a few seconds). If lag is large (minutes), the secondary cannot keep up with the load or there is a network/disk problem; the risk of data loss and read inconsistency on failover increases.","severity":"caution"},{"id":"member-health","group":"Replication","title":"Member health and election state","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":"All members should be healthy (health=1) and in the expected state (PRIMARY/SECONDARY). An error message in lastHeartbeatMessage indicates a network partition or an unreachable member. Loss of majority stops writes.","severity":"caution"}]},"health/redis.json":{"engineId":"redis","engine":"Redis","order":6,"checks":[{"id":"server-version-uptime","group":"General","title":"Version and uptime","command":"redis-cli INFO server | grep -E \"redis_version|uptime_in_days|redis_mode|os\"","expect":"redis_version should be a supported, patched release. If uptime_in_days is very small, an unexpected restart has occurred; redis_mode (standalone/sentinel/cluster) confirms the configuration.","severity":"safe"},{"id":"persistence-status","group":"General","title":"Persistence status (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 and aof_last_write_status must be 'ok'. An 'err' value indicates a disk/permission problem or a full disk and carries a risk of data loss. loading:1 means the server is still loading data.","severity":"danger"},{"id":"memory-usage","group":"Capacity","title":"Memory usage (used_memory / maxmemory)","command":"redis-cli INFO memory | grep -E \"used_memory_human|used_memory:|maxmemory_human|maxmemory:|maxmemory_policy\"","expect":"If used_memory approaches maxmemory (85%+), there is a risk of eviction or OOM. If maxmemory=0 there is no limit, and Redis can consume all RAM and bring the system down; always set a limit and an appropriate maxmemory_policy.","severity":"caution"},{"id":"memory-fragmentation","group":"Capacity","title":"Memory fragmentation ratio","command":"redis-cli INFO memory | grep -E \"mem_fragmentation_ratio|mem_allocator\"","expect":"mem_fragmentation_ratio is ideally between 1.0 and 1.5. Above 1.5 indicates high fragmentation (wasted memory). Below 1.0 indicates that Redis is being swapped to disk (a serious performance problem); check system RAM.","severity":"caution"},{"id":"evicted-keys","group":"Capacity","title":"Evicted keys (evicted_keys)","command":"redis-cli INFO stats | grep -E \"evicted_keys|expired_keys|keyspace_misses\"","expect":"If evicted_keys keeps increasing, Redis has reached its memory limit and is discarding data; this can cause unwanted data loss. Consider adding memory, using TTLs, or reviewing maxmemory_policy.","severity":"caution"},{"id":"dbsize-keyspace","group":"Capacity","title":"Key count and keyspace distribution","command":"redis-cli DBSIZE; redis-cli INFO keyspace","expect":"Monitor the total key count (DBSIZE) and the per-database distribution (INFO keyspace: keys, expires, avg_ttl). A growing keyspace without TTLs (low expires) causes memory bloat. Sudden jumps in key count can be a sign of abnormal behavior.","severity":"safe"},{"id":"hit-ratio","group":"Performance","title":"Cache hit ratio (keyspace hits/misses)","command":"redis-cli INFO stats | grep -E \"keyspace_hits|keyspace_misses\"","expect":"Hit ratio = hits / (hits + misses). For cache use the ratio should be high (80%+). A low ratio indicates inefficient caching, wrong TTLs or a cache-miss pattern; make sure frequently requested data is cached.","severity":"safe"},{"id":"slowlog","group":"Performance","title":"Slow command log (SLOWLOG)","command":"redis-cli SLOWLOG GET 10","expect":"Examine slow commands (default threshold 10ms). O(N) commands (KEYS, SMEMBERS, large HGETALL, SORT) block single-threaded Redis and delay every request. Replace them with alternatives such as SCAN.","severity":"caution"},{"id":"clients","group":"Performance","title":"Connected and blocked clients (clients)","command":"redis-cli INFO clients | grep -E \"connected_clients|blocked_clients|maxclients|client_recent_max_input_buffer\"","expect":"If connected_clients approaches maxclients, new connections are rejected. A high blocked_clients count (waiting on BLPOP/BRPOP) may be by design, but an unexpected rise is a sign of congestion. Check for connection leaks.","severity":"safe"},{"id":"ops-per-sec","group":"Performance","title":"Instantaneous throughput (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 shows the current workload. Sudden drops/spikes are a sign of anomalies. If rejected_connections > 0, the maxclients limit has been reached. Because it is single-threaded, very high ops can create a CPU bottleneck.","severity":"safe"},{"id":"rdb-last-save","group":"Backup & Recovery","title":"Last RDB save and pending changes","command":"redis-cli INFO persistence | grep -E \"rdb_last_save_time|rdb_changes_since_last_save|rdb_last_bgsave_status\"; redis-cli LASTSAVE","expect":"If rdb_changes_since_last_save is very high, a lot of data has accumulated since the last snapshot; these changes are lost in a crash. If rdb_last_save_time (LASTSAVE, Unix time) is very old, review the save interval or backup frequency.","severity":"caution"},{"id":"aof-rewrite","group":"Backup & Recovery","title":"AOF rewrite status","command":"redis-cli INFO persistence | grep -E \"aof_enabled|aof_last_bgrewrite_status|aof_rewrite_in_progress|aof_current_size|aof_base_size\"","expect":"In scenarios that require durability, aof_enabled=1 must be set. aof_last_bgrewrite_status must be 'ok'; 'err' indicates a disk/persistence problem. If aof_current_size is much larger than base_size, a rewrite is needed.","severity":"caution"},{"id":"acl-auth","group":"Security","title":"ACL / authentication (requirepass) setting","command":"redis-cli ACL LIST; redis-cli ACL GETUSER default","expect":"There must be at least one password-protected user. If the flags in the ACL GETUSER default output show 'on' and 'nopass' (requirepass is also this user's password), Redis is open to access without authentication; this is a serious security hole. Passwords are shown only as hashes; do not use CONFIG GET requirepass for this check because it dumps the password in plain text. Define least-privilege users with ACLs.","severity":"danger"},{"id":"dangerous-commands","group":"Security","title":"Dangerous commands and 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 should be 'yes' and bind should be limited to safe interfaces. Dangerous commands such as FLUSHALL, FLUSHDB, CONFIG, KEYS and DEBUG should be disabled in production with rename-command or restricted with ACLs.","severity":"caution"},{"id":"replication-status","group":"Replication","title":"Replication status (role, master link)","command":"redis-cli INFO replication","expect":"Verify role (master/slave), connected_slaves and the status of each replica. On a replica, master_link_status must be 'up'; 'down' means the link is broken. If master_last_io_seconds_ago is high (lag), the risk of data loss and inconsistency increases.","severity":"danger"}]},"health/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"checks":[{"id":"cluster-health","group":"General","title":"Cluster health (green / yellow / red)","command":"GET _cluster/health","expect":"status should be 'green': all primary and replica shards are allocated. 'yellow' means primaries are intact but some replicas are unassigned (redundancy risk); 'red' means at least one primary shard is lost/unassigned (part of the data is unavailable, immediate action required). unassigned_shards should be 0.","severity":"danger"},{"id":"cluster-version","group":"General","title":"Version and cluster information","command":"GET /","expect":"version.number should be a supported version with security patches applied. Old/EOL (end-of-life) versions carry security and stability risks. Also verify that all nodes run the same version with GET _cat/nodes?v&h=name,version (a temporary mix during a rolling upgrade is normal).","severity":"safe"},{"id":"pending-tasks","group":"General","title":"Pending cluster tasks","command":"GET _cluster/pending_tasks","expect":"The list should be empty or very short. Tasks that keep piling up and wait for a long time (high time_in_queue) indicate that the master node is overloaded or that cluster state updates are stalled; this degrades response time for the whole cluster.","severity":"caution"},{"id":"disk-allocation-watermark","group":"Capacity","title":"Disk allocation and watermark status","command":"GET _cat/allocation?v","expect":"disk.percent for each node should be below the high watermark (default 90%). Above 85% is a warning, 90% (high) triggers shard relocation, and 95% (flood_stage) makes indices read-only. Verify that the shards column is balanced across nodes; an uneven distribution causes a single node to fill up early.","severity":"danger"},{"id":"largest-indices","group":"Capacity","title":"Largest indices (store size)","command":"GET _cat/indices?v&s=store.size:desc","expect":"Track the indices consuming the most space and their doc.count values. Uncontrolled growth of log/metric indices is the main cause of full disks; define rollover and deletion policies for them with ILM (Index Lifecycle Management). Pay extra attention to indices with status 'close' or with yellow/red health.","severity":"caution"},{"id":"jvm-heap-usage","group":"Capacity","title":"JVM heap and RAM usage (per node)","command":"GET _cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m","expect":"heap.percent should stay consistently below 75%; sustained values of 85%+ mean memory pressure, frequent GC pauses, and circuit breaker risk. The JVM heap should be ~50% of server RAM and at most ~31GB (compressed oops limit). For high heap that does not come down, optimize queries or add nodes.","severity":"caution"},{"id":"shard-count-limits","group":"Capacity","title":"Shard count and per-node shard limit","command":"GET _cluster/health?level=shards","expect":"active_shards and the number of shards per node should be reasonable. The default upper limit is cluster.max_shards_per_node = 1000 (per data node); approaching this limit and having thousands of small shards in total (oversharding) creates master pressure and wastes memory. The old \"~20 shards per GB of heap\" rule does not apply to 8.3+; current guidance is 10-50GB per shard and heap planning based on the number of fields (mappings) per node. initializing_shards / relocating_shards should not stay high for long. If there are too many shards, use larger/fewer shards or rollover.","severity":"caution"},{"id":"http-connections","group":"Capacity","title":"HTTP connection count","command":"GET _nodes/stats/http","expect":"http.current_open should be within reasonable limits. If http.total_opened grows very fast, clients are not using a connection pool and open a new connection per request (inefficient). An abnormally high current_open points to a connection leak or possible resource exhaustion.","severity":"safe"},{"id":"thread-pool-rejections","group":"Performance","title":"Thread pool rejections and queue saturation","command":"GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected","expect":"The rejected column should be 0. An increasing rejected value in the write/bulk and search pools shows that the cluster is overloaded and returning HTTP 429 (es_rejected_execution_exception). A constantly full queue is a sign of congestion. Apply backoff on the client, reduce the batch size, or add capacity.","severity":"danger"},{"id":"query-cache-hit","group":"Performance","title":"Query cache hit ratio","command":"GET _nodes/stats/indices/query_cache","expect":"The ratio hit_count / (hit_count + miss_count) should be high for repeated filter queries. A low hit ratio with high evictions indicates that the cache size is insufficient or that queries are too variable to be cached. Using stable filters (filter context) increases the hit ratio.","severity":"safe"},{"id":"slowlog-config","group":"Performance","title":"Slow query log (slowlog) settings","command":"GET /_all/_settings?filter_path=**.search.slowlog,**.indexing.slowlog","expect":"Search and indexing slowlog thresholds should be defined on important indices (e.g. index.search.slowlog.threshold.query.warn: 10s). With the slowlog disabled, diagnosing slow queries becomes harder. Enable thresholds and monitor the logs to identify expensive queries (wide aggregations, wildcards, deep pagination).","severity":"safe"},{"id":"hot-threads","group":"Performance","title":"Hot threads","command":"GET _nodes/hot_threads","expect":"Shows the threads consuming the most CPU. Used during high CPU/slowness to determine which operation (heavy aggregation, merge, GC, script) is the bottleneck. If the same expensive query/merge keeps appearing, optimize that query or the indexing strategy.","severity":"safe"},{"id":"fielddata-memory","group":"Performance","title":"Fielddata memory usage","command":"GET _cat/fielddata?v","expect":"Fielddata memory usage should be kept low. Aggregating/sorting on text fields loads fielddata into memory and raises the risk of circuit breaker (Data too large) errors and OOM. For fields with high fielddata, switch to a keyword sub-field (doc_values).","severity":"caution"},{"id":"snapshot-status","group":"Backup & Recovery","title":"Snapshot repositories and latest snapshot status","command":"GET _snapshot/_all","expect":"At least one snapshot repository should be defined. After confirming the repositories exist, check the status of the latest snapshots (together with the verify check below). If there is no repository, the cluster has no backup and data can be permanently lost in a RED state. Set up SLM (Snapshot Lifecycle Management) for regular automatic snapshots.","severity":"danger"},{"id":"snapshot-latest-state","group":"Backup & Recovery","title":"Success status of the latest snapshots","command":"GET _snapshot/<repo>/_all?verbose=false","expect":"The state of the latest snapshots should be 'SUCCESS'. 'PARTIAL' means some shards could not be backed up, 'FAILED' means the backup failed; both mean data loss on restore. The date of the most recent snapshot (end_time) should not be too old (take backups at a frequency that meets your RPO target).","severity":"danger"},{"id":"snapshot-repo-verify","group":"Backup & Recovery","title":"Snapshot repository verification","command":"POST _snapshot/<repo>/_verify","expect":"Verification confirms that all nodes can access the repository (shared file system, S3, HDFS, etc.) and lists the nodes that can access it in the response. If it returns an error, some nodes cannot write to the backup repository; in that case snapshots will be incomplete/failed. Fix repository access and credentials.","severity":"caution"},{"id":"security-enabled","group":"Security","title":"Is security (X-Pack security) enabled","command":"GET _xpack/usage","expect":"security.enabled and security.available should be true. With security disabled, the cluster is accessible without authentication; such a cluster exposed to the internet or an untrusted network is a serious vulnerability (data leak/deletion). Authentication, role-based access, and TLS must be enabled.","severity":"danger"},{"id":"users-roles","group":"Security","title":"Users and roles","command":"GET _security/user","expect":"Review the defined users and role assignments. Make sure built-in accounts (elastic, kibana_system, etc.) are protected with strong passwords. Following the principle of least privilege, define separate users with limited roles for each application; avoid unnecessarily widespread use of the superuser role. Use GET _security/role for roles.","severity":"caution"},{"id":"tls-transport-http","group":"Security","title":"TLS / HTTPS status (transport and HTTP)","command":"GET _nodes/settings?filter_path=**.xpack.security.transport.ssl.enabled,**.xpack.security.http.ssl.enabled","expect":"xpack.security.transport.ssl.enabled should be true (inter-node encryption, mandatory). Ideally http.ssl.enabled should also be true (client-to-cluster HTTPS). With TLS disabled, credentials and data travel over the network in clear text. Also check certificate expiry and the verification mode (verification_mode: certificate/full).","severity":"danger"},{"id":"unassigned-shards","group":"Replication","title":"Unassigned shards and their reasons","command":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason","expect":"All shards should have state 'STARTED'. Shards in the UNASSIGNED state and their unassigned.reason (NODE_LEFT, ALLOCATION_FAILED, DISK_THRESHOLD, etc.) should be investigated. An unassigned primary means RED health, an unassigned replica means YELLOW. Use GET _cluster/allocation/explain for the root cause.","severity":"danger"},{"id":"recovery-active","group":"Replication","title":"Ongoing shard recoveries","command":"GET _cat/recovery?v&active_only=true","expect":"Normally there should be no active recovery (empty list). Temporary recovery after adding/removing a node or a failover is normal, but recoveries that stay stuck for a long time without progress (%progress not increasing) point to a network/disk bottleneck. If rebalancing (relocation) affects the workload, review recovery throttling settings (indices.recovery.max_bytes_per_sec).","severity":"caution"},{"id":"replica-count","group":"Replication","title":"Replica count per index","command":"GET _cat/indices?v&h=index,health,pri,rep,docs.count","expect":"In production indices rep (number_of_replicas) should be at least 1; on an index with 0 replicas, losing a node means permanent data loss. However, in a multi-node cluster the replica count should not exceed (number of data nodes - 1); otherwise the extra replicas cannot be allocated and health stays YELLOW. Investigate indices whose health column is not green.","severity":"danger"}]},"recipes/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"recipes":[{"id":"redo-switch","task":"Redo log switch (switch the current log file)","category":"Redo & Archive","command":"ALTER SYSTEM SWITCH LOGFILE;","what":"Closes the current redo log group and moves to the next one; in archivelog mode it triggers archiving.","when":"To produce an archive manually, force a log switch, or for testing.","caution":"Frequent switches degrade performance; if the log files are too small, review their sizing.","tags":["redo","switch","logfile","archive","archive","alter system"]},{"id":"redo-add-group","task":"Add a new redo log group","category":"Redo & Archive","command":"ALTER DATABASE ADD LOGFILE GROUP 4\n  ('/u01/oradata/ORCL/redo04a.log',\n   '/u02/oradata/ORCL/redo04b.log') SIZE 512M;","what":"Creates a new redo log group with the specified size and members (multiple members = mirror).","when":"When redo throughput is insufficient or you want to increase the number of groups.","caution":"Make sure the group number does not collide with existing groups; check with V$LOG.","tags":["redo","logfile","group","add","add","alter database"]},{"id":"redo-resize","task":"Resize a redo log group (drop + add)","category":"Redo & Archive","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":"A redo log cannot be resized directly; it is done by dropping the group and adding it back with the new size.","when":"When redo files are too small/large, to tune checkpoint frequency.","caution":"Only an INACTIVE or UNUSED group can be dropped; do not drop the CURRENT/ACTIVE group. At least two groups must always remain. REUSE reuses the existing file.","tags":["redo","resize","size","drop","add","logfile","reuse"]},{"id":"enable-archivelog","task":"Put the database in ARCHIVELOG mode","category":"Redo & Archive","command":"SHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;\n-- Verify:\nARCHIVE LOG LIST;","what":"Puts the database in archive log mode; filled redo logs are archived, making hot backups and point-in-time recovery possible.","when":"For the backup and recovery strategy of a production database (mandatory before RMAN, Data Guard).","caution":"Requires an instance restart; plan the downtime. Make sure the archive destination (FRA or LOG_ARCHIVE_DEST) has enough space; if it fills up, the DB halts. On RAC: stop all instances with srvctl stop database -d DB, open a single instance with STARTUP MOUNT, switch to ARCHIVELOG, shut it down and start with srvctl start database -d DB (11gR2+ does not require cluster_database=false).","tags":["archivelog","archive","mode","mount","recovery","recovery"]},{"id":"fra-full-fix","task":"Fix a full FRA / archive destination","category":"Redo & Archive","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":"When the Fast Recovery Area or archive destination fills up, the database hangs (ORA-00257). It is fixed by enlarging the space or cleaning up archives.","when":"ORA-00257 archiver error, when the DB stops accepting new transactions.","caution":"Deleting archives that have not been backed up destroys recoverability. Take an RMAN backup first, then delete. Do not delete with rm from the OS; that requires an RMAN CROSSCHECK. While the FRA is full, back up archives to a destination outside the FRA (FORMAT '/backup/arc_%U'). With Data Guard, do not delete archives not yet shipped/applied to the standby; otherwise a gap forms on the standby. Use CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY.","tags":["fra","recovery area","ora-00257","archiver","archive","full","cleanup"]},{"id":"redo-status","task":"View redo log status","category":"Redo & Archive","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":"Shows the status of redo log groups (CURRENT/ACTIVE/INACTIVE), their sizes, member files and archive status.","when":"When reviewing the redo configuration, or to see which group is active before a resize.","caution":"Query only; no risk.","tags":["redo","status","status","v$log","v$logfile","logfile"]},{"id":"asm-add-disk","task":"Add disks to an ASM diskgroup","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":"Adds new disks to the DATA diskgroup and starts an automatic rebalance; capacity grows.","when":"When an ASM diskgroup starts filling up, or when expanding storage.","caution":"Requires the SYSASM privilege. Disk sizes should be similar. Rebalance creates I/O load; use a low POWER during busy hours.","tags":["asm","diskgroup","add disk","add disk","rebalance","alter diskgroup"]},{"id":"asm-drop-disk","task":"Remove a disk from an ASM diskgroup","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":"Removes the disk from the diskgroup; its extents are moved to the remaining disks (rebalance). When complete, the disk's HDR_STATUS becomes FORMER.","when":"When replacing a failed disk, reducing capacity or migrating storage.","caution":"Do NOT physically pull the disk BEFORE the rebalance finishes, otherwise data is lost. Make sure the remaining disks have enough free space.","tags":["asm","drop disk","remove disk","rebalance","v$asm_operation","alter diskgroup"]},{"id":"asm-rebalance-power","task":"Set the ASM rebalance POWER","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":"Sets the parallelism/speed of an ongoing rebalance. A higher POWER means faster but more I/O.","when":"To speed up a rebalance that is too slow, or to slow down/stop one that is affecting production.","caution":"POWER 0 suspends the rebalance (a disk drop will not complete). A high value affects production I/O.","tags":["asm","rebalance","power","power","diskgroup","tuning"]},{"id":"asm-diskgroup-space","task":"View ASM diskgroup free space","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":"Shows the total, free and usable (after redundancy) space of each diskgroup.","when":"For capacity planning, when space runs low, before deciding to add disks.","caution":"usable_file_mb accounts for redundancy; with NORMAL/HIGH redundancy this column is the real usable space. Query only; no risk.","tags":["asm","diskgroup","space","space","free","v$asm_diskgroup","capacity"]},{"id":"asm-monitor-rebalance","task":"Monitor an ASM rebalance","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":"Shows the progress, power and estimated remaining time of ongoing rebalance/resync operations.","when":"When tracking whether the rebalance has finished after adding/removing disks.","caution":"Empty result = no active operation (completed). Query only; no risk.","tags":["asm","rebalance","monitor","monitor","v$asm_operation","progress"]},{"id":"add-tablespace","task":"Add a new tablespace","category":"Storage","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":"Creates a new permanent tablespace in which table/index segments can be placed.","when":"When allocating separate storage for a new application/schema.","caution":"Specify MAXSIZE, otherwise the disk can fill up. In ASM the file name is generated automatically.","tags":["tablespace","create","create","datafile","storage","storage","autoextend"]},{"id":"add-datafile","task":"Add a datafile to an existing tablespace","category":"Storage","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":"Grows an existing tablespace's capacity by adding a new datafile.","when":"When the tablespace is full and a single file approaches the smallfile limit (32G/64G).","caution":"A smallfile tablespace has a per-file block limit; add a new file if needed. Specify MAXSIZE.","tags":["datafile","add","add","tablespace","alter tablespace","storage"]},{"id":"resize-datafile","task":"Resize a datafile / set autoextend","category":"Storage","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":"Sets a datafile to a fixed size or configures its automatic growth behavior.","when":"To reclaim space (shrink), preallocate space, or stop uncontrolled growth.","caution":"Shrinking with RESIZE cannot go below the used blocks at the end of the file (ORA-03297). Move the segments first.","tags":["datafile","resize","size","autoextend","maxsize","alter database"]},{"id":"move-datafile-online","task":"Move a datafile online","category":"Storage","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":"Moves a datafile to a new location while the database is open (12c+); changes storage without downtime.","when":"When changing the disk/mount point, migrating to ASM, or moving to hot storage.","caution":"Requires 12c or later and Enterprise Edition. The move generates extra I/O. If you do not use KEEP, the old file is deleted.","since":"Oracle 12c","note":"Online datafile move (ALTER DATABASE MOVE DATAFILE) arrived with Oracle 12c (12.1); before that, moving a datafile meant taking the tablespace/datafile OFFLINE, copying it at the OS level and remapping it with ALTER DATABASE RENAME FILE.","tags":["datafile","move","move","online","alter database move datafile","asm"]},{"id":"temp-tablespace","task":"Add a temp tablespace / change the default","category":"Storage","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":"Provides temporary space for sort/hash/global temporary table operations; you can move the default to the new tablespace.","when":"For ORA-01652 (unable to extend temp segment), a temp bottleneck, or resizing temp.","caution":"After changing the default, do not DROP the old temp immediately if it is in use; drop it once sessions have drained.","tags":["temp","tablespace","tempfile","ora-01652","default temporary","storage"]},{"id":"tablespace-space","task":"Tablespace free space query","category":"Storage","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":"Shows the total and free space and the usage percentage of each tablespace (based on currently allocated space, excluding autoextend potential).","when":"Space monitoring, finding which tablespace needs to be extended.","caution":"This query is based on the current datafile size; it can grow further with autoextend. For temp use DBA_TEMP_FREE_SPACE. Query only; no risk.","tags":["tablespace","space","space","free","dba_free_space","usage","storage"]},{"id":"alter-parameter","task":"Change a parameter (with SCOPE)","category":"Memory & Parameters","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":"Changes an init parameter in the running instance and/or the spfile. SCOPE determines the behavior.","when":"For tuning, capacity adjustments, and error fixes that require a parameter change.","caution":"Static parameters (processes, sga_max_size...) only accept SCOPE=SPFILE and require a restart. A wrong value can make the instance unable to start; take a pfile backup first for critical parameters.","tags":["parameter","parameter","alter system","scope","spfile","tuning"]},{"id":"flush-pool","task":"Shared pool / buffer cache flush","category":"Memory & Parameters","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":"Flushing the shared pool evicts all cached SQL/PL-SQL and dictionary information from memory; flushing the buffer cache evicts all data blocks.","when":"To test plan problems, after a one-off large operation, or for a consistent benchmark start.","caution":"Be careful IN PRODUCTION! After a flush all SQL is re-parsed (hard parse storm) and blocks are read from disk; a serious temporary performance drop follows.","tags":["flush","shared pool","buffer cache","memory","alter system","cache"]},{"id":"spfile-pfile","task":"Create a pfile from the spfile and vice versa","category":"Memory & Parameters","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":"Converts between the binary spfile and the text pfile; the pfile can be edited by hand.","when":"Backup before a parameter change, repairing a corrupt spfile, fixing a static parameter by hand.","caution":"After CREATE SPFILE FROM PFILE, changes take effect only after a restart. Verify the path before overwriting an existing spfile.","tags":["spfile","pfile","create pfile","create spfile","parameter","backup"]},{"id":"sga-pga-target","task":"Change SGA / PGA targets","category":"Memory & Parameters","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":"Sets the SGA and PGA memory targets; Oracle manages the components automatically (ASMM/AMM).","when":"When RAM is added to the server, during a memory bottleneck or memory pressure.","caution":"sga_target cannot exceed sga_max_size. sga_max_size is static and requires a restart. Leave RAM for the operating system and other instances; over-allocation causes swapping/OOM.","tags":["sga","pga","memory","memory","target","sga_target","pga_aggregate_target"]},{"id":"kill-session","task":"Kill a session (kill session)","category":"Sessions & Processes","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":"Terminates the session with the given SID,SERIAL#; its open transaction is rolled back.","when":"To stop a session holding locks, running a runaway query, or leaking connections.","caution":"If there is an open transaction, the rollback can take a long time (it finishes quickly if little work was done). Verify with a query first so you do not kill the wrong SID. Rollback of large DML creates I/O load.","tags":["kill session","kill session","v$session","alter system","sid","serial"]},{"id":"long-running-queries","task":"View long-running queries / sessions","category":"Sessions & Processes","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":"Lists active sessions running longer than 60 seconds and the progress of long-running batch operations.","when":"On slowness complaints, to find which query is keeping the system busy.","caution":"last_call_et is the time since the last call; it can also be large for an idle session, so the status='ACTIVE' filter matters. Query only; no risk.","tags":["long running","long query","v$session","v$sql","v$session_longops","active"]},{"id":"find-blocking","task":"Find the blocking session","category":"Sessions & Processes","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":"Lists blocker and waiter session pairs with the wait time and event.","when":"To find the culprit in lock problems such as \"application hung / enq: TX wait\".","caution":"Confirm with the business owner before killing the blocker; it may be a critical operation. Query only; no risk.","tags":["blocking","lock","lock","blocker","waiter","v$session","blocking_session"]},{"id":"gather-stats","task":"Gather statistics (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":"Gathers table/index statistics for the optimizer; ensures correct execution plans are produced.","when":"After large DML/loads, when plans regress, after a new index/table.","caution":"Statistics changes can change execution plans; on critical systems test/compare with AWR first. DEGREE parallelism uses CPU. GATHER_DATABASE_STATS is heavy during busy hours.","tags":["dbms_stats","statistics","statistics","gather","optimizer","maintenance"]},{"id":"rebuild-index-online","task":"Rebuild an index online","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":"Rebuilds the index without blocking DML (ONLINE); removes fragmentation and can change the tablespace.","when":"Index bloat/fragmentation, moving an index to another tablespace, repairing an UNUSABLE index.","caution":"ONLINE uses extra temporary space and I/O. PARALLEL persists; reset it with NOPARALLEL after the rebuild, otherwise queries run in parallel unintentionally. ONLINE is not supported for bitmap indexes in every version. ONLINE rebuild is available only in Enterprise Edition.","tags":["index","rebuild","online","rebuild","alter index","fragmentation","maintenance"]},{"id":"rman-full-backup","task":"Quick full backup with 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":"Takes a compressed full backup of the whole database and archive logs in parallel over two channels, backs up the controlfile and purges obsolete backups.","when":"Daily/weekly full backup, to create a recovery point.","caution":"COMPRESSED uses CPU. DELETE OBSOLETE removes old backups according to the retention policy; set the policy (CONFIGURE RETENTION POLICY) correctly. ARCHIVELOG requires the DB to be in ARCHIVELOG mode. Make sure the backup destination has enough space.","tags":["rman","backup","backup","full","compressed","archivelog","maintenance"]},{"id":"controlfile-trace","task":"Take a controlfile trace backup","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":"Produces the SQL script (trace) that recreates the controlfile and/or a binary copy of it.","when":"After structural changes (adding datafiles/redo), when preparing for disaster recovery, or to recreate the controlfile if all copies are lost.","caution":"Refresh the trace file whenever the structure changes; an old script creates the wrong structure. REUSE overwrites an existing file. No risk to the system, but store the generated file securely.","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":"Check RAC database, instance and service status with srvctl","category":"RAC & 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":"Shows the runtime state of the database, each instance and its services as seen by Clusterware (CRS), plus the registered configuration; -verbose adds the instance open mode.","when":"As the first check before and after any RAC intervention; during an outage, to see which node/instance is affected.","caution":"Read-only. Run it from the database Oracle home (oracle user); a srvctl from a Grid home of a different version can return misleading results or errors.","since":"12c","note":"The long options -db/-instance/-service are 12c+; the 11g short options (-d, -i, -s) still work in 19c but are deprecated.","tags":["rac","srvctl","status","instance","service","cluster","config"],"untested":true},{"id":"rac-srvctl-start-stop","task":"Stop / start a RAC database or a single instance with srvctl (rolling)","category":"RAC & 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":"Stops/starts the database or the selected instance in a controlled way through Clusterware; with -failover the services on the stopped instance move to available instances, and -drain_timeout waits for sessions to drain.","when":"For rolling (node by node) restarts during patching, OS maintenance or parameter changes; for a planned full shutdown.","caution":"In RAC use srvctl instead of SQL*Plus SHUTDOWN; otherwise Clusterware does not know the resource TARGET and may restart the instance. stop database shuts down all instances = full outage. If services have no available instances defined, -failover cannot move the connections.","since":"12c","note":"-drain_timeout and -failover are 12.2+. Use -stopoption abort only in emergencies; the next startup requires instance recovery.","tags":["rac","srvctl","stop","start","rolling","drain","shutdown"],"untested":true},{"id":"rac-service-add","task":"Add a RAC service with preferred/available instances (including Application Continuity)","category":"RAC & 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":"Defines a service for applications that normally runs on the preferred instances and moves to an available instance when they fail; with -role PRIMARY it starts only in the primary role under Data Guard.","when":"To connect applications to a service rather than an instance, to partition workloads across nodes, and to enable seamless failover (drain/failover) during planned maintenance.","caution":"Applications must connect with this service name (through SCAN), not with a SID or instance name. -failovertype TRANSACTION (Application Continuity) requires application/driver support; if unsure, start with -failovertype SELECT (TAF) or NONE. Do not use SYS$ services or the default service named after db_unique_name for applications.","since":"12c","note":"-pdb is 12c+, -drain_timeout and -stopoption are 12.2+. Change it with srvctl modify service; a permanent preferred change requires -modifyconfig.","tags":["rac","service","srvctl","preferred","available","taf","application continuity"],"untested":true},{"id":"rac-service-relocate","task":"Relocate a RAC service to another instance","category":"RAC & 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":"Moves a running service from one instance/node to another; new connections go to the target instance and existing sessions are left to close on their own during drain_timeout.","when":"To empty a node before maintenance, to move a service back to its preferred instance after a failover, or to rebalance load.","caution":"Relocation is temporary: it does not change the service configuration (preferred/available); when the instance restarts the service returns to its preferred instance. -force kills existing sessions; if the application cannot reconnect, users see an outage.","since":"12c","note":"-drain_timeout and -wait are 12.2+. The SQL query also runs on a single instance; the srvctl part requires RAC.","tags":["rac","service","relocate","move","srvctl","drain","gv$session"],"untested":true},{"id":"rac-crsctl-stat-res","task":"Check cluster resource status (crsctl stat res -t)","category":"RAC & 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":"Lists every resource managed by Clusterware (database, instance, listener, SCAN, VIP, ASM, disk group, network) per node with TARGET/STATE in tabular form.","when":"After node startup, after patching and in every outage investigation; to find OFFLINE, INTERMEDIATE or UNKNOWN resources.","caution":"Read-only. Do not manage ora.* resources directly with crsctl start/stop/modify; use srvctl for database resources (the supported method).","note":"The resource name contains the lowercase db_unique_name (ora.orcl.db). The -init output is fully visible only as root/grid.","tags":["rac","grid","crsctl","resource","resource","clusterware","status"],"untested":true},{"id":"rac-crsctl-check-cluster","task":"Check cluster stack health, the node list and the Clusterware version","category":"RAC & 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":"Shows that the Cluster Ready Services, Cluster Synchronization Services and Event Manager daemons are running on every node, the cluster members, and the active/software version of Clusterware.","when":"In the morning health check, after a node eviction or reboot, and after a rolling patch/upgrade to confirm all nodes are at the same level.","caution":"Read-only. If activeversion -f shows \"ROLLING PATCH\" or a different patch level, a rolling operation is incomplete; finish it (crsctl stop rollingpatch / rootcrs -postpatch) before making new changes.","since":"12c","note":"activeversion -f and softwareversion -all are 12c+.","tags":["rac","grid","crsctl","olsnodes","health","cluster","version"],"untested":true},{"id":"rac-ocr-backup","task":"Check OCR integrity and take an OCR backup (ocrcheck / ocrconfig)","category":"RAC & 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":"Shows the integrity, locations and backups of the Oracle Cluster Registry (the cluster configuration repository); -manualbackup takes an immediate physical OCR backup.","when":"Right before cluster configuration changes such as adding/removing nodes, Grid patching/upgrade, voting disk or network (oifcfg) changes.","caution":"ocrconfig -restore must run with CRS stopped on all nodes and in exclusive mode; restoring the wrong backup can leave the cluster unable to start, so do it with Oracle Support. Automatic backups may stay on the local disk of a single node by default; move the location to a shared disk group.","note":"Run as the grid user, ocrcheck skips the logical corruption check (\"Logical corruption check bypassed\"). Backups to a disk group are 12c+.","tags":["rac","ocr","ocrcheck","ocrconfig","backup","backup","grid"],"untested":true},{"id":"rac-votedisk-query","task":"Check voting disks and CSS timeouts","category":"RAC & 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":"Shows the state of the voting disks (voting files) used for cluster membership and the network/disk heartbeat timeouts; replace votedisk moves all voting files to the target disk group.","when":"During storage changes, disk group redesign or a node eviction investigation; to confirm a majority (quorum) of voting disks is accessible.","caution":"A node that cannot access more than half of the voting disks reboots itself. The target disk group needs enough failure groups (normal redundancy: 3, high: 5; use a quorum failgroup if needed). Do not change misscount/disktimeout without Oracle Support approval.","note":"Defaults: misscount 30 s, disktimeout 200 s (Linux).","tags":["rac","voting disk","votedisk","css","misscount","quorum","eviction"],"untested":true},{"id":"rac-cluvfy-checks","task":"Run pre/post checks with the Cluster Verification Utility (cluvfy)","category":"RAC & 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":"Validates network connectivity, clock synchronization, OCR, ASM, user equivalence and OS prerequisites on all nodes; reports problems with suggested fixes.","when":"Before and after Grid installation, node addition and upgrades; as a baseline validation for unexplained cluster problems.","caution":"Read-only, but it connects to all nodes over ssh; the grid user needs ssh equivalence. Do not run the -fixup scripts (root) without reviewing them; they change OS parameters.","note":"For a broader best-practice scan use orachk/exachk from AHF. Downloading the latest cluvfy from My Oracle Support gives more checks.","tags":["rac","cluvfy","check","verification","precheck","grid","orachk"],"untested":true},{"id":"rac-node-eviction-diag","task":"Diagnose the cause of a node eviction / unexpected node reboot","category":"RAC & 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":"Follows the node eviction chain in order: CRS-16xx messages in the CRS alert log, heartbeat losses in ocssd.trc, OS metrics from CHM, and the logs of all nodes collected with TFA.","when":"When a node reboots unexpectedly or is evicted from the cluster, for root cause analysis (RCA) and an Oracle Support SR.","caution":"Collect logs from every node and covering the event time; trace files rotate and evidence can be lost. Verify the time zones are the same across nodes (CTSS/NTP). Do not apply \"fixes\" such as raising misscount before evidence is collected.","note":"Typical causes: private interconnect outage/packet loss, voting disk I/O latency, CPU/memory exhaustion (OOM, swap), clock jumps. The path is the 12c+ ADR layout; in 11g it is under $GRID_HOME/log/<host>/.","tags":["rac","eviction","reboot","ocssd","tfactl","oclumon","rca"],"untested":true},{"id":"rac-gv-cluster-waits","task":"Analyze cluster-wide wait events (gc waits) per instance with GV$","category":"RAC & 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":"Uses GV$ views to show Cluster-class waits (gc cr/current block 2-way/3-way, gc buffer busy, gc cr request) and their average latency across all instances in one query.","when":"When users report slowness on RAC, to tell whether the problem comes from the interconnect/global cache and which instance it concentrates on.","caution":"gv$active_session_history requires the Diagnostics Pack license. If avg_ms is above a few ms, check the interconnect first (MTU, packet loss, netstat -s) and LMS CPU; gc buffer busy usually points to an application hot block (sequence, right-hand index).","note":"Also runs on a single instance (returns inst_id = 1).","tags":["rac","gv$","gc","wait","wait event","interconnect","global cache"]},{"id":"asm-asmcmd-lsdg-du","task":"Show disk group, disk and directory usage with 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":"Shows from the command line the redundancy, total/free space and Usable_file_MB of disk groups, the state of the disks and the space a database occupies in ASM.","when":"For capacity checks, to identify candidate disks before adding disks, and to find which database consumes space when a \"disk group full\" alert fires.","caution":"Read-only. If Usable_file_MB in lsdg is negative, the disk group cannot restore redundancy after losing a disk/failgroup; free space or add disks immediately. du returns two values: Used_MB and Mirror_used_MB (mirrored usage).","note":"For the SQL equivalent see the asm-diskgroup-space and asm-disk-health recipes.","tags":["asm","asmcmd","lsdg","lsdsk","du","space","disk group"],"untested":true},{"id":"asm-disk-health","task":"Check ASM disk state, error counters and candidate disks with 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":"Lists each disk's header_status (MEMBER, CANDIDATE, PROVISIONED, FORMER), mount/mode state, failgroup and read/write error counters; the number of offline disks per disk group and the ASM clients.","when":"Before adding disks, to check that a new LUN shows as CANDIDATE/PROVISIONED; on a disk failure, to see which disk is OFFLINE and any I/O errors.","caution":"Read-only. v$asm_disk runs disk discovery on every call and is slow on systems with many disks; for frequent monitoring use v$asm_disk_stat (no discovery, does not show new disks). Never add a disk whose header_status is MEMBER to another disk group with FORCE; it destroys the existing data.","note":"Returns no rows without ASM; meaningful output requires an ASM instance.","tags":["asm","v$asm_disk","disk","header_status","error","candidate","failgroup"]},{"id":"asm-diskgroup-attributes","task":"View and change ASM disk group attributes (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":"Shows and changes the disk group compatibility levels (compatible.asm/rdbms), the AU size, and how long a temporarily failed disk is kept before being dropped (disk_repair_time).","when":"After a Grid upgrade, to enable new ASM features (flex disk groups, larger file limits), or to extend disk_repair_time during planned storage maintenance.","caution":"compatible.asm and compatible.rdbms CANNOT BE LOWERED ONCE RAISED. If compatible.rdbms is higher than the COMPATIBLE setting of the oldest database using this disk group, that database cannot mount it. Raise compatible.asm first, then compatible.rdbms. au_size can only be set when the disk group is created.","note":"Without ASM the query returns no rows. The default disk_repair_time is 12h.","tags":["asm","attribute","compatible","disk_repair_time","disk group","au_size","v$asm_attribute"],"untested":true},{"id":"dg-show-configuration","task":"Show the Data Guard broker configuration and database status (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":"Shows the overall broker configuration status (SUCCESS/WARNING/ERROR), the protection mode, each database's role, transport/apply lag and inconsistencies between the broker and the spfile.","when":"Before and after every Data Guard operation (switchover, failover, parameter change); in the daily health check.","caution":"Read-only. While the broker is enabled, do not change Data Guard parameters such as log_archive_dest_n or log_archive_config with SQL; the broker overwrites them with its own values. Make changes with EDIT DATABASE ... SET PROPERTY, and in 19c+ use EDIT DATABASE ... SET PARAMETER for initialization parameters.","note":"StatusReport lists ORA- errors per database; it is the fastest way to find the cause of a WARNING status. InconsistentProperties is deprecated in 19c and always returns no value; use InconsistentLogXptProps for redo transport inconsistencies.","tags":["data guard","dgmgrl","broker","configuration","standby","status","lag"],"untested":true},{"id":"dg-readiness-sql","task":"Check primary/standby role and switchover readiness with 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":"Shows the database role, open mode, protection mode, switchover_status (TO STANDBY / TO PRIMARY / SESSIONS ACTIVE / NOT ALLOWED), FORCE LOGGING and flashback state, plus errors and gaps for the redo destinations.","when":"As a precheck before switchover/failover, after building a new standby, and for basic monitoring where the broker is not used.","caution":"Read-only. If FORCE_LOGGING=NO, NOLOGGING operations leave corrupt blocks on the standby; ALTER DATABASE FORCE LOGGING must be enabled on a Data Guard primary. If FLASHBACK_ON=NO, the old primary cannot be reinstated after a failover and must be rebuilt.","note":"Also runs on the primary; v$archive_gap is meaningful only on a physical standby.","tags":["data guard","v$database","switchover_status","force logging","gap","role","standby"]},{"id":"dg-lag-apply-rate","task":"Measure transport/apply lag, apply rate and Data Guard processes on the 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":"Shows how far the standby is behind the primary (transport lag = redo not yet received, apply lag = redo not yet applied), when the backlog will be applied, the apply rate and which process is working on which sequence.","when":"For RPO/RTO monitoring, and on a lag alert to tell whether the problem is in the network (transport) or in standby I/O/CPU (apply).","caution":"Read-only. If datum_time is old (far behind time_computed), the standby is not receiving data from the primary and the lag value is unreliable. Check whether DelayMins is set; an intentional delay looks like apply lag.","since":"12c","note":"v$dataguard_process is 12.2+. Without a standby, v$dataguard_stats returns no rows.","tags":["data guard","lag","lag","apply","transport","v$dataguard_stats","rpo"]},{"id":"dg-validate-database","task":"Validate the standby with the broker before 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":"Checks whether the standby is ready for a role change (\"Ready for Switchover / Failover: Yes\"), the number/size of standby redo logs, flashback and apply state, spfile differences, and the static services the broker needs to restart instances.","when":"As a mandatory step before every planned switchover; after building a new standby and after network/listener changes.","caution":"Read-only. If you see \"Ready for Switchover: No\" or a standby redo log warning, do not proceed with the switchover. A static connect identifier error means the broker cannot start the instance during a switchover, which forces manual intervention.","since":"12c","note":"VALIDATE DATABASE is 12.1+, the SPFILE option is 12.2+, NETWORK CONFIGURATION and STATIC CONNECT IDENTIFIER are 18c+.","tags":["data guard","dgmgrl","validate","validate","switchover","standby redo log","broker"],"untested":true},{"id":"dg-switchover","task":"Perform a planned switchover (DGMGRL, role change without data loss)","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":"Swaps the roles of the primary and the standby in a controlled way: all primary redo is shipped and applied on the standby, the old primary restarts as a standby and redo apply starts automatically.","when":"For planned maintenance at the primary site (OS, hardware, rolling upgrade), DR drills or data center moves.","caution":"The primary has a short outage; application connections must be routed to the new primary through services defined with -role PRIMARY and FAN/TAF/AC. Long-running transactions and high apply lag lengthen the switchover; bring the lag close to zero first. In RAC the broker manages the instances itself; do not intervene with srvctl.","since":"12c","note":"In 12.1+ the broker performs every step with one command; without the broker 11g used ALTER DATABASE COMMIT TO SWITCHOVER in SQL (12c+: ALTER DATABASE SWITCHOVER TO <db> VERIFY).","tags":["data guard","switchover","dgmgrl","role change","planned","standby","primary"],"untested":true},{"id":"dg-failover","task":"Perform a failover after losing the primary (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":"Promotes the standby to the primary role when the primary is unavailable; a complete failover first applies all received redo, IMMEDIATE skips apply.","when":"When the primary site, storage or database is lost beyond recovery and the RTO does not allow waiting for a switchover.","caution":"This role change is irreversible. First make sure the primary is REALLY down; if the old primary stays open and the application keeps writing to it, you get split-brain and divergent data (shut down/isolate the old primary). In MaxPerformance mode you lose as much data as the transport lag. Afterwards review all standbys and the backup strategy for the new primary.","note":"To recover the old primary see the dg-reinstate-database recipe (requires flashback).","tags":["data guard","failover","dgmgrl","disaster","disaster recovery","data loss","split-brain"],"untested":true},{"id":"dg-reinstate-database","task":"Recover the old primary as a standby after a 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":"Flashes the old primary back to before the failover SCN with flashback database, converts it to a physical standby and starts receiving redo from the new primary; no need to rebuild the standby from scratch.","when":"After a failover, once the old primary server/storage is reachable again; after an FSFO automatic failover (the observer usually reinstates it by itself).","caution":"Flashback must have been enabled on the old primary BEFORE the failover and the flashback logs must cover the failover SCN (db_flashback_retention_target, FRA size); otherwise the reinstate fails and the standby must be rebuilt with RMAN duplicate. Data written to the old primary after the failover is permanently lost.","note":"Prerequisite check: the flashback_on column in the dg-readiness-sql recipe.","tags":["data guard","reinstate","flashback","failover","old primary","standby","dgmgrl"],"untested":true},{"id":"dg-apply-transport-state","task":"Stop / start redo apply or redo transport with the broker; configure delayed apply","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":"Persistently stops/starts redo apply (MRP) on the standby or redo shipping on the primary through the broker; DelayMins holds redo for the given time before applying it.","when":"For maintenance or backups on the standby, planned network outages, controlling transport during large bulk loads on the primary, or building a delayed standby against accidental deletes.","caution":"While the broker is enabled, do not use ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL in SQL; the broker restores the state. During APPLY-OFF the apply lag and the failover time (RTO) grow; during TRANSPORT-OFF data generated on the primary is unprotected (RPO). If FSFO is enabled these operations can prevent a failover.","note":"With DelayMins > 0, redo is applied from archived logs after the delay rather than through real-time apply; the standby keeps receiving redo during the delay.","tags":["data guard","apply","transport","mrp","delaymins","stop","dgmgrl"],"untested":true},{"id":"dg-fsfo-observer","task":"Enable Fast-Start Failover (FSFO) and start the 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":"Lets the observer fail over to the standby automatically after the threshold when the primary is unreachable, and reinstate the old primary automatically when it comes back.","when":"On critical systems whose RTO target is minutes, without waiting for human intervention.","caution":"The observer must not run on the same server/data center as the primary or the standby; otherwise FSFO fails on a site loss or can make the wrong decision during a network partition. Flashback must be enabled on both databases. A threshold that is too low turns short network glitches into unnecessary failovers. For planned maintenance, DISABLE FAST_START FAILOVER or account for the observer before shutting down the primary.","since":"12c","note":"FASTSYNC is 12.2+, IN BACKGROUND and multiple observers (SHOW OBSERVER) are 12.2+. IN BACKGROUND requires credentials in an Oracle wallet (mkstore).","tags":["data guard","fsfo","fast-start failover","observer","automatic failover","maxavailability","dgmgrl"],"untested":true},{"id":"dg-snapshot-standby","task":"Convert a physical standby to a snapshot standby and back (test environment)","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":"Creates an automatic guaranteed restore point on the standby and opens it read-write; when converting back it flashes back to that point and applies the redo accumulated from the primary.","when":"To rehearse an upgrade, test an application release or run Real Application Testing (DB Replay) on production data without creating a separate copy.","caution":"While it is a snapshot standby no redo is applied: the RPO is preserved but the failover time (RTO) grows with the accumulated redo, and it cannot be an FSFO target. Flashback logs can fill the FRA; monitor FRA space. Changes made in the snapshot are permanently discarded when converting back.","note":"SQL equivalent: ALTER DATABASE CONVERT TO SNAPSHOT STANDBY / CONVERT TO PHYSICAL STANDBY (without the broker).","tags":["data guard","snapshot standby","test","restore point","flashback","convert","dgmgrl"],"untested":true},{"id":"rman-catalog-create","task":"Create an RMAN recovery catalog and register the database","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":"Builds a recovery catalog that stores backup metadata in a central schema, independent of the controlfile retention (control_file_record_keep_time), and registers the target database.","when":"To keep the backup history of many databases in one place, to use primary/standby backups interchangeably in Data Guard, and for long-term (KEEP) backups and stored scripts.","caution":"The catalog database must be on a different server from the databases it protects and must itself be backed up. The catalog schema version cannot be lower than the newest RMAN client in use; a newer client requires UPGRADE CATALOG. In Data Guard register only the primary; standbys are defined by DB_UNIQUE_NAME with CONFIGURE DB_UNIQUE_NAME ... CONNECT IDENTIFIER.","note":"Keep the catalog schema in a separate database; if it lives with the target, a disaster takes the catalog with it.","tags":["rman","catalog","catalog","recovery catalog","register","backup","backup"],"untested":true},{"id":"rman-duplicate-standby","task":"Create a physical standby from the active database with 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":"Copies the primary datafiles over the network to the standby server (without intermediate backup files), creates the spfile adapted for the standby, and applies the available archivelogs with DORECOVER.","when":"When building a new Data Guard standby or rebuilding a standby that cannot be reinstated.","caution":"Generates network and I/O load on the primary; do not run it at peak hours, or limit the number of channels. NOFILENAMECHECK is safe only when the standby is on a SEPARATE server; on the same server it can overwrite the primary files. Add standby redo logs (same size as the primary online redo, one extra group per thread) before or after, then configure the broker.","since":"12c","note":"Active duplicate with USING [COMPRESSED] BACKUPSET and SECTION SIZE is 12c+; 11g uses image copies. If you use a file system instead of ASM, set 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":"Validate and clean up RMAN backup records (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 checks whether the backups in the RMAN records still exist on disk/tape; DELETE EXPIRED removes the records of missing files, and DELETE OBSOLETE deletes backups no longer needed by the retention policy from both the records and the disk.","when":"When backup files were deleted/moved at the OS level, when the FRA fills up, and as part of the regular backup maintenance job.","caution":"DELETE OBSOLETE physically deletes files; verify the list with REPORT OBSOLETE first and make sure the retention policy (RECOVERY WINDOW / REDUNDANCY) is what you expect. If you run CROSSCHECK while the tape/media manager is temporarily unreachable, valid backups show as EXPIRED; do not run DELETE EXPIRED at that moment.","note":"In Data Guard, use CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY for archivelog deletion.","tags":["rman","crosscheck","expired","obsolete","cleanup","retention","backup"],"untested":true},{"id":"rman-validate","task":"Validate the database and backups against 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":"Reads datafiles and backup sets to detect physical and (with CHECK LOGICAL) logical block corruption, recording findings in v$database_block_corruption. The SQL query shows which table/index the corrupt block belongs to.","when":"After corruption errors such as ORA-01578/ORA-08103, after storage problems, and as a regular (weekly) integrity check; to prove backups are really restorable.","caution":"It performs a full read; on large databases it generates heavy I/O, so run it outside peak hours. The dba_extents query can be slow on large databases. If the corrupt block is in temporary (TEMP) or free space, no object match is returned.","since":"11g","note":"The queries return no rows when there is no corruption. VALIDATE with SECTION SIZE is 11g+.","tags":["rman","validate","corruption","corruption","check logical","block","ora-01578"],"untested":true},{"id":"rman-restore-preview","task":"See which backups a restore would use and validate them (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 reports from the catalog/controlfile which backup sets/copies and which archivelog range a restore would use; VALIDATE actually reads those backups and verifies there is no corruption or missing file.","when":"For planning before a real restore, to prove the backup strategy meets the RPO target, and as a periodic restore test.","caution":"PREVIEW looks only at the records; it is misleading if files were deleted, so run CROSSCHECK first or use RESTORE ... VALIDATE. VALIDATE reads the whole backup; it can take long over tape/network. Make the date depend on the TO_DATE mask, not on the session NLS settings.","note":"PREVIEW RECALL can also request backups that must be recalled from tape (requires media manager support).","tags":["rman","restore","preview","validate","restore test","backup","rpo"],"untested":true},{"id":"rman-block-media-recovery","task":"Repair corrupt blocks online (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":"Restores only the corrupt blocks from a backup (or from flashback logs / an Active Data Guard standby) and rolls them forward with redo; the datafile and tablespace stay online, and the application gets errors only when touching those blocks.","when":"When isolated block corruption appears with ORA-01578 / ORA-01110; when restoring the whole datafile would cause unnecessary downtime.","caution":"Requires a clean backup containing the block and all archivelogs from then to now; corruption caused by a NOLOGGING operation (ORA-26040) cannot be repaired this way, the object must be rebuilt. This is an Enterprise Edition feature. If you do not find the source of corruption (storage, HBA, firmware), it will recur.","note":"With Active Data Guard, 11.2+ automatic block media recovery repairs corrupt blocks from the standby automatically.","tags":["rman","block recovery","corrupt block","corruption","ora-01578","recover","repair"],"untested":true},{"id":"rman-tspitr","task":"Return a tablespace to a point in the past (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 recovers SYSTEM/UNDO/SYSAUX and the target tablespace up to the requested time in a temporary auxiliary instance, then plugs it back into the target database with transportable tablespace; the rest of the database stays current.","when":"When a faulty batch job or a wrong DROP/TRUNCATE damaged data in only one tablespace (or a self-consistent set of tablespaces) and flashback options are not sufficient.","caution":"ALL changes made in that tablespace and all objects created after the target time are lost. The tablespace set must be self-contained (no index/table, partition, LOB or FK in another tablespace). It cannot be used for tablespaces containing SYSTEM, UNDO or SYS objects. After TSPITR, earlier backups of that tablespace are unusable; take a backup immediately. AUXILIARY DESTINATION needs free space for SYSTEM+SYSAUX+UNDO+the target tablespace.","note":"For a single table, rman-recover-table is less destructive.","tags":["rman","tspitr","tablespace","pitr","point-in-time","auxiliary","rollback"],"untested":true},{"id":"rman-recover-table","task":"Recover a single table to a past point in time from an RMAN backup (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 recovers an automatic auxiliary instance to the requested time, exports the table (or partition) with Data Pump and imports it into the target database under a different name with REMAP; the auxiliary instance is then removed.","when":"To recover the data of a single table that was deleted/updated by mistake or dropped with PURGE, beyond what Flashback Table/Query undo retention allows.","caution":"AUXILIARY DESTINATION needs free space for SYSTEM, SYSAUX, UNDO and the table's tablespace; the operation can take hours. Without REMAP TABLE the operation fails if a table with the same name exists; always use REMAP or NOTABLEIMPORT to avoid overwriting the original. Tables in the SYS/SYSTEM schemas and in the SYSTEM/SYSAUX tablespaces cannot be recovered this way. Requires Enterprise Edition.","since":"12c","note":"Required: a full backup plus archivelogs covering the target time. For a 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":"Create a new PDB from 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":"Creates a new, empty pluggable database from the seed template; defines the local admin user, the default tablespace and the total size limit in the same command.","when":"When opening an isolated database for a new application/schema; when preparing test and development environments.","caution":"The new PDB comes up MOUNTED; it needs OPEN and SAVE STATE (to stay open after a CDB restart). Wrong FILE_NAME_CONVERT paths write the files to the wrong directory; without OMF a full file path is mandatory for DATAFILE (ORA-02236). Licensing: in 19c EE, 3 user PDBs per CDB can be used without the Multitenant option.","since":"12c","note":"In RAC, add an application service to the PDB with srvctl add service -pdb.","tags":["multitenant","pdb","create","create pluggable database","seed","cdb","pdb$seed"]},{"id":"pdb-clone-local","task":"Clone a PDB within the same CDB (local / hot clone)","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":"Creates a new PDB in the same CDB by copying the datafiles of the source PDB; with local undo and ARCHIVELOG it can run while the source is open read-write (hot clone).","when":"To create a test/development copy of a production PDB, rehearse an upgrade or patch, or take a quick copy before a risky change.","caution":"The copy generates extra I/O on the source and needs as much space as the source on the target. In shared undo mode or in NOARCHIVELOG the source must be READ ONLY (19c documentation). The clone contains production data: check masking, DB links, jobs (DBMS_SCHEDULER) and connections to external systems before opening it.","since":"12c","note":"A hot clone with the source read-write requires local undo + ARCHIVELOG in 19c. For a fast thin copy use SNAPSHOT COPY (requires suitable storage).","tags":["multitenant","pdb","clone","clone","hot clone","copy","local undo"]},{"id":"pdb-clone-remote","task":"Clone a PDB from another CDB over a 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":"Copies a PDB from the source CDB to the target CDB over the network through a DB link; no intermediate files or unplug needed.","when":"To move a PDB to another server/CDB, create a test copy of production on a separate server, or move to a CDB with a newer release.","caution":"The source and target CDBs must be compatible in endianness, character set (or target AL32UTF8) and options; a PDB cloned to a newer release opens RESTRICTED at first and needs datapatch/upgrade. Drop the DB link and the common user when the clone is finished. Hot clone requires local undo + ARCHIVELOG on the source.","since":"12c","note":"To move it (the source is removed) use the 12.2+ RELOCATE option: CREATE PLUGGABLE DATABASE <pdb> FROM <pdb>@clone_link RELOCATE.","tags":["multitenant","pdb","remote clone","db link","clone","migration","relocate"]},{"id":"pdb-unplug-plug","task":"Unplug a PDB and plug it into another (or the same) CDB","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":"Detaches the PDB from the CDB and writes its metadata to an XML manifest (or a single .pdb archive together with the datafiles); after a compatibility check it plugs it back into the target CDB with the same files.","when":"To move a PDB to a newer-release CDB on the same server (unplug/plug upgrade), for CDB consolidation, or for moves where cloning over the network is not possible.","caution":"After unplugging, the PDB is unavailable in the source CDB (downtime). Files plugged in with NOCOPY now belong to the target CDB; take a file-level copy or an RMAN backup first so you can fall back on failure. Until ERROR-type violations are resolved the PDB opens RESTRICTED. When plugging back into the same CDB, omitting AS CLONE can cause a GUID conflict.","since":"12c","note":"A .pdb archive (12.2+) includes the datafiles, so it moves as a single file; with the XML method the datafiles are copied separately.","tags":["multitenant","pdb","unplug","plug","migration","xml manifest","check_plug_compatibility"]},{"id":"pdb-open-save-state","task":"Open PDBs and keep them open across CDB restarts (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":"Opens PDBs READ WRITE and saves their current open state so that the PDB opens automatically in the same mode when the CDB restarts.","when":"After creating, plugging in or cloning a PDB; when you notice PDBs stay MOUNTED after a CDB restart.","caution":"SAVE STATE records the state at the time of the command: if you run it while the PDB is closed, the \"closed\" state is recorded (or the record is removed). In RAC the state is per instance; with service-based management (PDB services via srvctl), Clusterware startup is preferred over SAVE STATE. On a Data Guard standby the open mode depends on the role.","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":"Set per-PDB CPU, memory, I/O and storage limits","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":"Limits how much CPU, SGA/PGA, IOPS/MBPS and total storage a PDB can use from the CDB through PDB-level parameters; prevents \"noisy neighbor\" PDBs from affecting the others.","when":"In consolidated CDBs, when running applications with different SLAs in the same CDB, or when a PDB is seen to exhaust the whole server.","caution":"The PDB sga_target must be smaller than the CDB sga_target; values that are too low cause ORA-04031 and performance problems. The sum of sga_min_size must not reserve the whole CDB SGA. For cpu_count in a PDB to take effect, a resource manager plan must be active in the CDB. When MAXSIZE is reached the PDB gets ORA-65114.","since":"12c","note":"23ai requires sga_min_size to be below 20% of sga_target (ORA-56746), the limit in 19c is 50%. MAX_IOPS/MAX_MBPS are 12.2+, PDB memory parameters are 12.2+.","tags":["multitenant","pdb","resource","resource","cpu_count","sga_target","maxsize"]},{"id":"pdb-container-context","task":"Confirm which container you are in and query all PDBs from the root (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":"Shows the container the session is connected to; from the root, CDB_ views and the CONTAINERS() clause collect dictionary information from all open PDBs in a single query.","when":"To make sure you are in the right container before a DDL/parameter change, and to build a space, user and object inventory across all PDBs.","caution":"Read-only. Do not accidentally create application objects or local users in the root (CDB$ROOT); ALTER SYSTEM in the root affects the whole CDB. CDB_ views and CONTAINERS() return only open PDBs; MOUNTED PDBs do not appear in the result.","since":"12c","note":"To switch containers: ALTER SESSION SET CONTAINER = <pdb>; (see rosetta \"Change the active database\").","tags":["multitenant","container","con_name","cdb_","containers","pdb","root"]},{"id":"pdb-refreshable-clone","task":"Create a refreshable PDB, refresh it and convert it to an independent PDB","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":"Creates a copy that is kept current by applying the source PDB's redo periodically or on demand; it opens READ ONLY for reporting and becomes an independent read-write PDB with REFRESH MODE NONE.","when":"For a regularly refreshed reporting/test copy of production, a low-downtime PDB migration (final refresh + REFRESH MODE NONE), or a \"cheap PDB-level standby\".","caution":"The PDB must be closed during a refresh; while open READ ONLY the automatic refresh waits. The archivelogs needed for the refresh must be accessible on the source. REFRESH MODE NONE is irreversible; the copy can no longer be refreshed. Requires local undo + ARCHIVELOG on the source. It does not replace Data Guard for real DR.","since":"12c","note":"12.2+. After REFRESH MODE NONE the clone becomes a regular PDB and can be opened READ WRITE; this cannot be undone.","tags":["multitenant","pdb","refreshable","refresh","refresh","reporting","clone"]}]},"recipes/oracle-ops.json":{"engineId":"oracle","engine":"Oracle","order":2,"recipes":[{"id":"awr-snapshot-report","task":"Take an AWR snapshot and generate an AWR report between two snapshots","category":"Performance","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":"Produces a report between two AWR snapshots covering the problem window: load profile, top wait events, top SQL and instance statistics. The table function lets you pull the report from a script or a scheduler job.","when":"When analyzing a slowdown in a specific time window, for before/after comparison around a load test or batch, and for capacity reporting.","caution":"Requires the Diagnostics Pack license (CONTROL_MANAGEMENT_PACK_ACCESS='DIAGNOSTIC' or 'DIAGNOSTIC+TUNING'); without it, set the parameter to 'NONE' and use Statspack. A report cannot span an instance restart. Ranges longer than an hour average out and hide short spikes; keep the window tight.","note":"For PDB-level AWR (12.2+) check AWR_PDB_AUTOFLUSH_ENABLED=TRUE and AWR_SNAPSHOT_TIME_OFFSET/AWR_PDB_MAX_PARALLEL_SLAVES; a report taken inside a PDB shows only that PDB's data. Interval/retention: 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":"AWR diff (compare period) report between two periods","category":"Performance","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":"Puts two snapshot ranges side by side and shows percentage differences in DB time, wait events, load profile and per-SQL statistics. Directly answers \"it was fine yesterday, slow today\".","when":"Regression analysis after an upgrade/patch, parameter change, deployment or statistics gathering; comparing the same time slot on two days.","caution":"Requires the Diagnostics Pack license. Compared periods should have the same length and similar workload, otherwise the deltas are misleading. To compare different databases (e.g. prod vs test), move AWR data with awrextr.sql/awrload.sql and pass the matching dbid2.","tags":["awr diff","comparison","compare period","awrddrpt","regression","performance","diagnostics pack"]},{"id":"ash-top-activity","task":"Top wait events and top SQL from ASH for the last minutes","category":"Performance","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":"Uses Active Session History samples to show, as percentages, which wait events and which SQL statements consumed DB time in the last minutes; diagnoses the incident without waiting for an AWR snapshot.","when":"During a live slowdown, for short spikes that disappear in AWR averages, and to see what a specific session or SQL was waiting on.","caution":"Querying V$ACTIVE_SESSION_HISTORY and DBA_HIST_ACTIVE_SESS_HISTORY requires the Diagnostics Pack license. ASH is sampled: sample counts estimate time, they are not exact measurements, and very short SQL may not appear. The in-memory ASH buffer holds only a few hours on busy systems.","tags":["ash","active session history","top wait","top sql","wait","ashrpt","real-time analysis","diagnostics pack"]},{"id":"addm-task-report","task":"Run an ADDM analysis and get its report","category":"Performance","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 analyzes DB time between two AWR snapshots and reports bottlenecks with their impact (findings) and concrete recommendations.","when":"To get a quick \"where should I look\" summary before reading the AWR report, or to analyze a custom range beyond the automatic ADDM run after each snapshot.","caution":"Requires the Diagnostics Pack license. Do not apply ADDM recommendations (parameter increases, running SQL Tuning Advisor, etc.) blindly; validate memory recommendations against the AWR advisory sections. The range cannot span a restart.","tags":["addm","dbms_addm","advisor","finding","finding","recommendation","diagnostics pack","performance"]},{"id":"sql-monitor-report","task":"Get a SQL Monitor report (running or completed long SQL)","category":"Performance","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 shows, for every step of a long-running or parallel SQL's execution plan, the actual rows, elapsed time, memory/temp usage and ASH activity; you can watch progress while the SQL is still running.","when":"When asking whether a batch/report query is stuck and how much is left, for parallel query distribution (PX skew) analysis, and to find gaps between estimated (E-Rows) and actual rows.","caution":"Requires the Tuning Pack license (Diagnostics Pack is a prerequisite). By default only SQL consuming 5 s of CPU/I-O or running in parallel is monitored; use the /*+ MONITOR */ hint for others. Very large plans (_sqlmon_max_planlines, default 300) are not monitored. Entries stay in memory for a limited time; in 12c+ older reports are also available from 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":"Enable/disable 10046 trace for a session and summarize with TKPROF","category":"Performance","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":"Extended SQL trace (10046) writes parse/execute/fetch times, wait events and bind values for every SQL the session runs into a trace file; TKPROF turns it into a readable report aggregated per SQL.","when":"When AWR/ASH are not enough and you must prove end to end where a single user operation spends its time; for application-to-database round-trip (SQL*Net) problems.","caution":"No license required. Trace files grow quickly and add overhead to the target session; keep it short and always disable it (check DBA_ENABLED_TRACES for leftovers). binds=TRUE writes sensitive data (national IDs, card numbers, etc.) into the trace file. ALTER SESSION SET EVENTS requires the ALTER SESSION privilege (otherwise ORA-01031). TKPROF does not show bind values by default; keep the raw .trc file.","note":"Connection pool or service-level tracing produces multiple files; merge them with trcsess (trcsess output=combined.trc clientid=<id> *.trc) and then run TKPROF.","tags":["10046","trace","tkprof","dbms_monitor","sql trace","wait","bind","session tracing"]},{"id":"xplan-allstats-last","task":"Show the actual execution plan with row source statistics (ALLSTATS LAST)","category":"Performance","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 is an estimate; this shows the plan that actually ran, with Starts, E-Rows (estimate), A-Rows (actual), A-Time, Buffers and memory usage per step. It is the most direct way to find the step where the cardinality misestimate starts.","when":"When tuning a single slow SQL, to prove the need for statistics/histograms, and to measure the effect of a hint or index change.","caution":"No license required. With SET SERVEROUTPUT ON, sql_id=>NULL shows the DBMS_OUTPUT call as the last cursor; set it OFF first. Do not enable statistics_level=ALL system-wide; it adds noticeable CPU overhead. The hint changes the SQL text, so it gets a new sql_id. The user needs read access to 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":"Create and run a SQL Tuning Advisor task and read its recommendations","category":"Performance","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":"Runs the optimizer in an extended \"tuning mode\" for the SQL and produces recommendations for missing statistics, new indexes, a SQL profile, alternative plans and SQL restructuring; validates the profile recommendation with test executions.","when":"When a single problematic SQL needs a quick second opinion, or when looking for a SQL profile fix for a plan regression without changing code.","caution":"Requires the Tuning Pack license. COMPREHENSIVE scope may execute the SQL for testing (only the query part for DML); set time_limit in production. Index recommendations ignore the rest of the workload; evaluate DML cost separately. The user needs the ADVISOR privilege.","tags":["sql tuning advisor","dbms_sqltune","tuning task","recommendation","sql profile","index recommendation","tuning pack"]},{"id":"sql-profile-accept","task":"Accept, disable and drop a SQL profile","category":"Performance","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":"A SQL profile gives the optimizer extra cardinality/selectivity corrections (OPT_ESTIMATE and similar) so it produces a better plan without changing the SQL text; with FORCE_MATCH it also applies to statements that differ only in literals.","when":"When the SQL cannot be changed (packaged application), when STA recommends a validated improvement, or as a fast fix for a plan regression.","caution":"Requires the Tuning Pack license. A profile does not \"lock\" the plan; it corrects estimates, so the plan can still change when data distribution changes. If plan stability is mandatory, use a SQL plan baseline. When both exist, the optimizer chooses among the baseline's accepted plans. Always read the validation results (the report's 'Validation results' section) before accepting.","tags":["sql profile","accept_sql_profile","dbms_sqltune","force_match","plan fix","dba_sql_profiles","tuning pack"]},{"id":"spm-load-from-cursor-cache","task":"Load a good plan from the cursor cache as a SQL plan baseline","category":"Performance","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":"Uses SQL Plan Management to make the chosen plan an ACCEPTED baseline; the optimizer then uses only accepted baseline plans and parks newly found plans until they are evolved.","when":"To return to a known good plan during a plan regression, and to protect critical SQL plans before an upgrade or statistics change.","caution":"No extra license on Enterprise Edition (on SE2 it is limited to one baseline plan per SQL). Requires ADMINISTER SQL MANAGEMENT OBJECT. A baseline applies to the exact same SQL text (signature); it does not help SQL that uses literals. If an index used by the plan is dropped, the plan cannot be reproduced (REPRODUCED=NO) and the optimizer falls back to cost-based choice. OPTIMIZER_USE_SQL_PLAN_BASELINES must be TRUE (default).","note":"If the good plan is no longer in the cursor cache, load it from AWR: 12.2+ DBMS_SPM.LOAD_PLANS_FROM_AWR(begin_snap, end_snap, basic_filter => q'[sql_id='<sql_id>']'); on earlier releases go through a SQL Tuning Set with 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":"Evolve unaccepted SQL plan baseline plans","category":"Performance","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":"When a baseline exists, new plans found by the optimizer are stored with ACCEPTED=NO. The evolve task test-executes these candidates against the accepted plan and accepts only those that are clearly better.","when":"For baselined SQL when a new index or new statistics may have produced a better plan; to decide manually without waiting for the automatic SYS_AUTO_SPM_EVOLVE_TASK (12c+).","caution":"Requires Enterprise Edition. Evolve actually executes the SQL (only the query part for DML); set time_limit for heavy SQL and avoid peak hours. Requires ADMINISTER SQL MANAGEMENT OBJECT and ADVISOR. SQL parsed as SYS cannot be evolved (ORA-16963); run it as the application user.","since":"Oracle 12c","note":"The pre-12c DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE function is deprecated; 12c+ uses the task-based API (CREATE/EXECUTE/IMPLEMENT_EVOLVE_TASK). Automatic task behavior: 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":"Fix, disable or drop a SQL plan baseline plan","category":"Performance","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":"Manages baseline plan attributes: a FIXED plan is preferred over other accepted plans and stops automatic evolution of new plans; ENABLED=NO disables a plan without deleting it; DROP removes it permanently.","when":"To pin a plan firmly for a critical SQL, to disable a bad plan that was accepted by mistake, or to clean up baselines that are no longer needed.","caution":"Requires Enterprise Edition. FIXED plans can degrade as data grows and prevent the optimizer from using a better plan; review them periodically. A SQL whose plans are all ENABLED=NO is optimized freely. DROP is irreversible; back up to a staging table with DBMS_SPM.PACK_STGTAB_BASELINE first.","tags":["spm","baseline","fixed","alter_sql_plan_baseline","drop_sql_plan_baseline","disable plan","display_sql_plan_baseline"]},{"id":"bind-sensitivity-acs-check","task":"Check bind peeking and adaptive cursor sharing status","category":"Performance","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":"The optimizer picks a plan by peeking at the bind value on the first hard parse. Adaptive cursor sharing can create different child cursors/plans per bind value on columns with histograms; these queries show whether the cursor is bind-sensitive/aware, which value was peeked, and why children were created.","when":"For \"the same SQL is sometimes fast, sometimes slow\" complaints, with skewed data distribution, and when the child cursor count explodes (high version count).","caution":"No license required; read-only. V$SQL_BIND_CAPTURE is sampled (about every 15 minutes by default) and only holds WHERE/HAVING binds; it is not the value of every execution. ACS makes a cursor bind-aware only after a few bad executions. For critical SQL, loading several plans into a SQL plan baseline is more deterministic than relying on ACS.","since":"Oracle 11g","note":"Adaptive cursor sharing was introduced in 11g. For high version count analysis, the REASON column (XML) of V$SQL_SHARED_CURSOR gives the detailed cause.","tags":["bind peeking","adaptive cursor sharing","acs","bind variable","v$sql_bind_capture","child cursor","version count"]},{"id":"top-sql-awr-history","task":"Find top SQL by elapsed time/CPU in a past period (AWR + V$SQLAREA)","category":"Performance","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":"Sums the *_DELTA columns in DBA_HIST_SQLSTAT to list the most expensive SQL in a snapshot range, broken down by plan_hash_value, which also reveals plan changes for the same SQL. The V$SQLAREA query gives cumulative values for what is still in the cache.","when":"For \"what loaded the system last night between 02:00 and 04:00?\", to detect plan changes (same sql_id, different plan_hash_value), and to build a list of tuning candidates.","caution":"Querying DBA_HIST_* requires the Diagnostics Pack license; V$SQLAREA does not. AWR keeps only top SQL (top-N by default), so small but very frequent statements may be missing. V$SQLAREA values are lost when the cursor ages out. Deltas remain correct across a restart, but the cache-based query starts from zero.","tags":["top sql","dba_hist_sqlstat","v$sqlarea","elapsed","cpu","most expensive query","plan change","diagnostics pack"]},{"id":"stats-stale-prefs","task":"Find stale statistics and set per-table statistics preferences (prefs)","category":"Statistics","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":"Statistics become stale when the change ratio exceeds STALE_PERCENT (default 10%). Preferences persist per-table sampling, histogram, parallelism and staleness thresholds so the nightly automatic statistics task applies the same rules.","when":"When the 10% threshold triggers too late on large tables, when specific columns must always get a histogram, and to define the gathering strategy in the dictionary instead of in scripts.","caution":"No license required. Preferences are overridden by explicit parameters in a manual GATHER call. Changing METHOD_OPT adds or removes histograms and can change plans. DBA_TAB_MODIFICATIONS is current only after a flush (automatic flush roughly every 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":"Lock and unlock table/schema statistics","category":"Statistics","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":"Locks statistics against change; the automatic task skips locked objects and a table-level GATHER call raises ORA-20005. force=>TRUE gathers without removing the lock.","when":"To keep representative statistics on staging/volatile tables that are emptied and refilled during the day, to protect a critical table's plans from statistics changes, or to preserve values set with DBMS_STATS.SET_TABLE_STATS.","caution":"No license required. Locked statistics drift from reality over time and cause cardinality errors; report locked objects regularly. A schema lock does not cover tables added to the schema later. On a locked table, CREATE/REBUILD INDEX does not gather index statistics automatically either.","tags":["lock stats","lock statistics","unlock_table_stats","lock_schema_stats","ora-20005","stattype_locked","dbms_stats"]},{"id":"stats-restore-history","task":"Restore statistics to a point in the past","category":"Statistics","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 saves the previous statistics in SYSAUX before every gather (31 days by default). The RESTORE_* procedures return table/schema statistics to their state at the given time; with no_invalidate=>FALSE cursors are re-parsed immediately.","when":"As the fastest rollback when a plan regression follows the nightly statistics gathering, and to test whether a change was caused by statistics.","caution":"No license required. Locked statistics cannot be restored (unlock first). The next automatic gather brings new statistics back after the restore; for a permanent fix set table preferences, lock the statistics, or use a SQL plan baseline. A long retention grows SYSAUX (WRI$_OPTSTAT_* tables).","tags":["restore stats","restore statistics","restore_table_stats","dba_tab_stats_history","stats history","plan regression","dbms_stats"]},{"id":"stats-pending-publish","task":"Gather statistics as pending, test them and publish","category":"Statistics","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":"With the PUBLISH=FALSE preference, gathered statistics are written to a pending area instead of the dictionary, so other sessions are unaffected. Plans are validated in a test session with optimizer_use_pending_statistics, then published or discarded with one command.","when":"To test the plan impact of refreshed statistics on critical OLTP tables in production without risk, and for a controlled cutover after a large data load.","caution":"No license required. While PUBLISH=FALSE remains, statistics gathered by the automatic task also pile up as pending and are never published; remember to set the preference back to TRUE. EXPLAIN PLAN is an estimate; where possible run the query and check 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":"Configure incremental statistics for a partitioned table","category":"Statistics","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":"In incremental mode a synopsis is stored per partition; global (table-level) statistics, NDV in particular, are computed by merging synopses without rescanning the whole table. Only changed partitions are read again.","when":"On large (TB-scale) range/interval partitioned tables where new data lands only in recent partitions, when global statistics gathering exceeds the maintenance window.","caution":"No license of its own, but the Partitioning option (extra-cost EE) is already required. Synopses consume SYSAUX space (the 12.2+ HyperLogLog format is much smaller). The first gather is a full scan; start it in a maintenance window. Setting ESTIMATE_PERCENT to anything other than AUTO_SAMPLE_SIZE disables incremental.","since":"Oracle 11g","note":"The INCREMENTAL_STALENESS and INCREMENTAL_LEVEL preferences were added in 12c; in 12.2+ the synopsis format is controlled by APPROXIMATE_NDV_ALGORITHM (HYPERLOGLOG).","tags":["incremental stats","synopsis","partition statistics","global stats","incremental_staleness","dbms_stats","large table"]},{"id":"partition-split-merge-online","task":"Split and merge partitions online","category":"Partitioning","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 divides a partition at a boundary value into two (12c+: into several); MERGE combines adjacent partitions. With ONLINE, DML continues during the operation; with UPDATE INDEXES, local and global indexes do not become UNUSABLE.","when":"To split a MAXVALUE/\"other\" partition that has grown, to merge small old partitions before archiving, or to change the partitioning scheme (monthly to quarterly).","caution":"Requires the Partitioning option (extra-cost EE); ONLINE requires Enterprise Edition. SPLIT/MERGE physically rewrites the data: slow on large partitions, generating redo/undo and temporary space. Without ONLINE the table is locked for DML during the operation; without UPDATE INDEXES global indexes become UNUSABLE. Splitting an empty partition is fast (fast split).","since":"Oracle 12.2","note":"ONLINE SPLIT arrived in 12.2 and ONLINE MERGE in 18c; both are available in 19c. On older releases drop the ONLINE keyword and run the operation in a maintenance window.","tags":["split partition","merge partition","add partition","split a partition","merge partitions","update indexes","online","partitioning"]},{"id":"partition-exchange","task":"Bulk load or archive data with exchange partition","category":"Partitioning","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":"Swaps the segment of a partition with the segment of a regular table in the dictionary; millions of rows \"enter\" the table or \"leave\" it for archiving in seconds. FOR EXCHANGE WITH TABLE creates a staging table that guarantees column and hidden-column compatibility.","when":"For DWH/ETL bulk loads, moving an old partition out to an archive table, and preparing indexes and statistics without affecting the live table during the load.","caution":"Requires the Partitioning option (extra-cost EE). WITHOUT VALIDATION does not check that staging rows fit the partition bounds; wrong data stays in the wrong partition and queries can return wrong results; if unsure use WITH VALIDATION (full scan). Without UPDATE GLOBAL INDEXES global indexes become UNUSABLE. Unique/PK constraint states must match on both sides.","note":"CREATE TABLE ... FOR EXCHANGE WITH TABLE was introduced in 12.2; before that, create the staging table with CREATE TABLE ... AS SELECT * FROM <table> WHERE 1=0 and check hidden/virtual column differences manually.","tags":["exchange partition","staging","for exchange with table","bulk load","archive","update global indexes","partitioning"]},{"id":"partition-drop-truncate-gidx","task":"Truncate/drop a partition while keeping global indexes usable","category":"Partitioning","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":"Removes old data instantly at partition level instead of row-by-row DELETE. In 12c+, UPDATE INDEXES only marks global indexes in the dictionary (asynchronous maintenance) instead of updating every entry; the index stays usable immediately and orphaned entries are cleaned later.","when":"When purging old periods under a data retention policy, in ILM/purge jobs, and to avoid the undo/redo load of DELETE on large tables.","caution":"Requires the Partitioning option (extra-cost EE). DROP/TRUNCATE is irreversible (flashback table does not bring a partition back); back up or archive via exchange first. If UPDATE INDEXES is forgotten, global indexes become UNUSABLE and queries may hit ORA-01502. The operation fails if child tables reference it (FK). On interval tables the last range (transition) partition cannot be dropped.","since":"Oracle 12c","tags":["drop partition","truncate partition","update indexes","orphaned_entries","cleanup_gidx","global index","data purge","partitioning"]},{"id":"interval-partitioning","task":"Create interval partitioning and convert an existing range table to interval","category":"Partitioning","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":"Interval partitioning creates a new partition automatically when the first row beyond the last bound arrives, eliminating ORA-14400 errors caused by forgetting next month's partition. SET INTERVAL converts an existing range table without moving data.","when":"For tables that grow by date, such as time series, logs and orders, and to reduce partition maintenance scripts.","caution":"Requires the Partitioning option (extra-cost EE). The partition key must be a single NUMBER/DATE/TIMESTAMP column; a MAXVALUE partition must be removed first. A bad or extreme date (e.g. year 9999) opens a far-off partition and distorts partition numbering; with narrow intervals you can approach the 1,048,575-partition limit (ORA-14300), so validate dates in the application. Automatic partitions go to the table's default tablespace (direct them with 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":"Verify partition keys and partition-wise join usage","category":"Partitioning","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":"Two tables equi-partitioned on the join key can be joined partition pair by partition pair (full partition-wise join), which sharply reduces redistribution traffic and memory in parallel queries. The plan shape shows whether PWJ is used.","when":"When designing large fact-to-fact or fact-to-dimension joins in a DWH, when parallel queries show heavy PX redistribution (PX Deq Credit: send blkd), and when validating the partitioning strategy.","caution":"License: Partitioning option (extra-cost EE); parallel query requires Enterprise Edition. EXPLAIN PLAN is an estimate; use DISPLAY_CURSOR/SQL Monitor for the actual plan. Full PWJ needs the same partition counts (a power of two is recommended for hash) and matching key data types. PWJ can appear in serial plans too, but the real gain is in parallel execution.","tags":["partition-wise join","pwj","equi-partitioning","hash partition","parallel query","dba_part_key_columns","partitioning"]},{"id":"online-redefinition-flow","task":"Redefine a table online with DBMS_REDEFINITION (full flow)","category":"Online Operations","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":"Changes a table's structure (partitioning, column types/order, tablespace, compression, LOB settings) while the application keeps running: data is copied to an interim table, changes are synchronized through an MV log, and at the end the names are swapped under a very short lock.","when":"For changes that ALTER TABLE cannot do online, such as partitioning a large non-partitioned table without downtime, changing a column type, or converting BasicFile LOBs to SecureFiles.","caution":"Requires Enterprise Edition (plus the Partitioning option if the target is partitioned). The process needs twice the table's space and adds redo and MV log overhead. FINISH takes a brief exclusive lock; long-running transactions make it wait (dml_lock_timeout). Always check COPY_TABLE_DEPENDENTS errors in DBA_REDEFINITION_ERRORS; FKs may be copied as NOVALIDATE. Rehearse in a test environment first.","note":"The dml_lock_timeout parameter was added in 12c. In 12.2+ monitor progress in V$ONLINE_REDEF. For a simple move/compression change, ALTER TABLE ... MOVE ONLINE is enough instead of 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":"Move a table or partition online (ALTER TABLE MOVE ONLINE)","category":"Online Operations","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":"Rewrites the table segment: reduces fragmentation and the high water mark, changes tablespace, applies compression. With ONLINE, DML is not blocked during the operation and indexes are maintained automatically and stay VALID.","when":"To reclaim space after mass deletes, to move a table to a new tablespace/storage, and to compress old partitions into an archive tablespace.","caution":"Requires Enterprise Edition; ROW STORE COMPRESS ADVANCED requires the Advanced Compression option (extra cost). The operation needs additional space equal to the table size and generates heavy redo. Short locks are taken at the start and end; long open transactions make it wait. A MOVE without ONLINE or UPDATE INDEXES leaves all indexes UNUSABLE.","since":"Oracle 12.2","note":"Table-level MOVE ONLINE was introduced in 12.2; partition-level MOVE PARTITION ONLINE has existed since 12.1. On 12.1 and earlier use DBMS_REDEFINITION for tables.","tags":["move online","alter table move","move table","reorganization","high water mark","compression","enterprise edition"]},{"id":"tde-keystore-setup","task":"Create, open, set the master key of and back up a TDE keystore (19c+)","category":"Security","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":"Sets up a software keystore for Transparent Data Encryption in united mode (all PDBs share the CDB keystore), creates the master encryption key and enables auto-login so it opens after restart. In 19c+ the recommended method is WALLET_ROOT/TDE_CONFIGURATION instead of sqlnet.ora ENCRYPTION_WALLET_LOCATION.","when":"As the first step before tablespace/column encryption in compliance projects that require data-at-rest encryption (GDPR, PCI-DSS, ISO 27001).","caution":"On-premises requires the Advanced Security option (extra cost); it is included in Oracle Cloud (OCI/Autonomous) licensing. WALLET_ROOT is static and needs an instance restart (downtime). If the keystore and its password are lost, encrypted data is PERMANENTLY unreadable; store keystore backups securely and separately from database backups. Anyone holding the auto-login file (cwallet.sso) can open the database; restrict file permissions and use LOCAL AUTO_LOGIN where appropriate. With Data Guard, copy the keystore to the standby as well.","since":"Oracle 18c","note":"WALLET_ROOT and TDE_CONFIGURATION were introduced in 18c and are standard in 19c; the 12c-18c sqlnet.ora ENCRYPTION_WALLET_LOCATION method is deprecated in 19c and desupported in 23ai. In 23ai the default algorithm is AES256; encryption with GOST and SEED is desupported and decryption with them is deprecated. WALLET_ROOT is a static parameter and requires an instance restart.","tags":["tde","keystore","wallet","master key","encryption","advanced security","wallet_root","administer key management"],"untested":true},{"id":"tde-tablespace-encryption","task":"Encrypt tablespaces with TDE (new and existing, online/offline)","category":"Security","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":"Tablespace encryption encrypts every block written to the datafiles; data is clear in the buffer cache, so no application or SQL changes are needed. Online conversion builds an encrypted copy of the datafile and switches to it while DML continues; offline conversion encrypts blocks in place.","when":"To encrypt application tablespaces holding sensitive data at rest, to move data into a new encrypted tablespace, or to close an audit finding.","caution":"On-premises requires the Advanced Security option (extra cost). Online conversion needs extra space equal to the datafiles being encrypted and generates heavy I/O and redo; a Data Guard standby performs the same work. Offline conversion must be run separately on a Data Guard standby. SYSTEM/SYSAUX/UNDO can be encrypted online in 12.2+ but need extra care. Backups of encrypted tablespaces cannot be restored without the keystore.","since":"Oracle 12.2","note":"Online/offline tablespace conversion was introduced in 12.2. To encrypt new tablespaces automatically, 19c uses TABLESPACE_ENCRYPTION=AUTO_ENABLE (19.16+ RU) or ENCRYPT_NEW_TABLESPACES=ALWAYS. The keystore must be open with a master key set first (see 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":"Create a Unified Auditing policy, enable it conditionally, review records, remove it","category":"Security","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 collects all audit records in a single trail (AUDSYS schema, UNIFIED_AUDIT_TRAIL). Policies are defined per object action, system privilege or action; the WHEN condition and BY/EXCEPT user lists reduce noise.","when":"To record access to sensitive tables, monitor privileged DDL and privilege changes, see failed logon attempts, and meet audit requirements.","caution":"No extra license. Auditing SELECT on busy tables produces many records and SYSAUX growth; narrow it with conditions and EXCEPT, and schedule archiving/cleanup with DBMS_AUDIT_MGMT (CLEAN_AUDIT_TRAIL, SET_LAST_ARCHIVE_TIMESTAMP). An enabled policy cannot be dropped (ORA-46361); run NOAUDIT first. Policy changes take effect in new sessions. 19c runs in mixed mode by default, so traditional AUDIT_TRAIL records may also be written.","since":"Oracle 12c","note":"In 23ai traditional auditing is desupported; only unified auditing exists, and FLUSH_UNIFIED_AUDIT_TRAIL was removed because records are written directly to the table. In 19c, switching to pure unified mode requires relinking the oracle binary with 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":"Add a row-level access policy with VPD (DBMS_RLS)","category":"Security","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 automatically appends the WHERE predicate returned by the policy function to every SQL against the table, so users of the same table see/modify only the rows they are entitled to. update_check=>TRUE prevents a user from updating a row to a value they could not see.","when":"In multi-tenant applications, for region/branch-based data isolation, and to add row security without touching application code.","caution":"Included in Enterprise Edition, no extra license. CLIENT_IDENTIFIER can be set freely by the client; in production use an application context set by a trusted package (CREATE CONTEXT ... USING). SYS and users with EXEMPT ACCESS POLICY bypass the policy; Data Pump exports without that privilege silently export partial data. An error in the function blocks all access with ORA-28112. A wrong policy_type (e.g. STATIC) caches the predicate and returns wrong results.","tags":["vpd","virtual private database","dbms_rls","row security","row level security","add_policy","policy function","security"]},{"id":"password-profile-hardening","task":"Create a password policy profile and assign it to users","category":"Security","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":"A profile applies failed-login lockout, password lifetime, reuse, complexity (verify function) and inactive-account locking rules to a group of users. PASSWORD_ROLLOVER_TIME lets applications switch without an outage while a service account's password is changed.","when":"For security audit/CIS benchmark requirements, when creating new application users, and to separate the DEFAULT profile's loose values per user group.","caution":"No extra license. A FAILED_LOGIN_ATTEMPTS limit on a service account lets one app server with a wrong password lock the account and cause a production outage. PASSWORD_LIFE_TIME on application accounts can cause unexpected EXPIRED status. The verify function must exist under SYS (otherwise @?/rdbms/admin/utlpwdmg.sql; that script also modifies the DEFAULT profile). Changing the profile does not re-validate existing passwords.","note":"INACTIVE_ACCOUNT_TIME was added in 12.2; PASSWORD_ROLLOVER_TIME came in 21c and was backported to 19c with the 19.12 RU. Sessions logging in with the old password during the rollover period can be tracked through audit records.","tags":["profile","password policy","password_verify_function","failed_login_attempts","inactive_account_time","password_rollover_time","account lock","security"]},{"id":"privilege-analysis-capture","task":"Find unused privileges with privilege analysis","category":"Security","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 records at run time which system/object privileges are actually used within the chosen scope (whole database, role, condition); the result lists unused privileges so REVOKEs can be planned under the least-privilege principle.","when":"When cleaning up application accounts with DBA/ANY privileges, for privilege reviews before an audit, and to find out what a packaged application really needs.","caution":"Included in Enterprise Edition since 2018 (previously required a Database Vault license); no extra license. There is a small overhead while capturing. The result reflects only the workload in the capture window: if month-end, year-end or emergency jobs did not run, a privilege shown as \"unused\" may still be needed, so revoke gradually. Only one capture can be enabled at a time; the only exception is that a G_DATABASE capture can be enabled alongside one role or context capture.","since":"Oracle 12c","tags":["privilege analysis","dbms_privilege_capture","unused privileges","least privilege","dba_unused_privs","privilege cleanup","security"]},{"id":"opatch-inventory-prereq","task":"Inspect the inventory with OPatch and run pre-patch checks","category":"Patching & Lifecycle","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":"Lists the binary patches (RUs, one-offs) applied to the Oracle Home and checks, before applying a new patch, whether it conflicts with existing patches and whether there is enough disk space.","when":"Before a quarterly RU/security patch, when support asks for inventory output on an SR, and when comparing the patch level of two environments.","caution":"Run the commands as the OS user owning the Oracle Home (oracle); opatchauto requires root. Prereq checks are read-only and do not affect the database, but if a conflict is reported, do not apply before obtaining a merge/overlay patch from Oracle Support. Back up the Oracle Home (tar) and the inventory before applying.","note":"To view the inventory from SQL, DBMS_QOPATCH.GET_OPATCH_LSINVENTORY can be used (on installations with an OPatch inventory).","tags":["opatch","lsinventory","lspatches","prereq","patch","patch conflict","opatchauto","release update"],"untested":true},{"id":"datapatch-apply-verify","task":"Update the SQL layer with datapatch -verbose after a binary patch","category":"Patching & Lifecycle","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 applies the SQL/PL-SQL components (dictionary, packages) of the binary patches in the Oracle Home to CDB root, PDB$SEED and all open PDBs, and records the result in DBA_REGISTRY_SQLPATCH. It also reverts the SQL part of rolled-back patches.","when":"After every RU/RUR/one-off binary patch or rollback; for PDBs moved or unplugged/plugged into a newly patched home; and to rerun it when DBA_REGISTRY_SQLPATCH shows WITH ERRORS.","caution":"Closed or MOUNTED PDBs are not patched and may open in RESTRICTED mode; open all of them first. Do not run under heavy load (dictionary locks, package invalidation). On RAC run it once from one node only. With Data Guard run datapatch only on the primary; the changes reach the standby through redo (patch the standby binaries first).","note":"The datapatch shipped with recent 19c RUs offers -sanity_checks to verify dictionary and environment health (invalid objects, PDB violations, tablespace/TEMP state) before patching.","tags":["datapatch","sql patch","release update","dba_registry_sqlpatch","post-patch","utlrp","pdb","patching"],"untested":true},{"id":"sqlpatch-registry-check","task":"Verify SQL patch history, component status and invalid objects","category":"Patching & Lifecycle","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":"Shows at a glance whether the SQL layer was really updated after patching (dictionary matching the binaries), which patch failed in which PDB, whether dictionary components are VALID, and the number of invalid objects.","when":"As the verification step after every datapatch, to capture a baseline before patching or upgrading, and when a mismatch is suspected after plugging in a PDB.","caution":"Read-only; no license required. SOURCE_VERSION/TARGET_VERSION are populated in the 18c+ RU model. If STATUS='WITH ERRORS', fix the error in LOGFILE and rerun datapatch. Some components (e.g. unused XDB/JAVA) may show INVALID/REMOVED; compare with the pre-patch state.","note":"To see the binary inventory from SQL, use SELECT XMLTRANSFORM(DBMS_QOPATCH.GET_OPATCH_LIST, DBMS_QOPATCH.GET_OPATCH_XSLT) FROM dual; it raises ORA-20013 on installations without an OPatch inventory.","tags":["dba_registry_sqlpatch","patch history","datapatch","dba_registry","invalid objects","invalid object","patch verification"]},{"id":"scheduler-job-lifecycle","task":"Create, run manually, disable and drop a scheduler job","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":"Defines a scheduled PL/SQL job with DBMS_SCHEDULER; sets max_run_duration (raises an event when exceeded) and a detailed logging level, validates the calendar expression in advance, and allows a manual run without disturbing the schedule.","when":"To run recurring database work such as maintenance, ETL and cleanup inside the database instead of cron, with RAC/Data Guard awareness.","caution":"No extra license. The time zone of start_date matters: SYSTIMESTAMP uses the database server's TZ; use an explicit region (e.g. 'Europe/Istanbul') to avoid drift at DST changes. If JOB_QUEUE_PROCESSES=0 no job runs. DISABLE with force=>TRUE does not stop a running instance, it only prevents future runs; use STOP_JOB to interrupt it. Creating a job in another schema requires CREATE ANY JOB.","tags":["dbms_scheduler","job","scheduled job","create_job","run_job","repeat_interval","disable job","scheduler"]},{"id":"scheduler-job-history","task":"Monitor scheduler job run history and failures","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":"Lists, from the scheduler log views, each run's start time, duration, CPU usage, status and error message, plus running jobs and job lifecycle events.","when":"When a nightly job did not run or ran long, for job SLA tracking, and when defining failure rules in a monitoring system (OEM/Zabbix/Prometheus).","caution":"Read-only; no license required. Default log retention is 30 days, and jobs with logging_level OFF produce no records. The ERRORS column exists in 12c+; on older releases check ADDITIONAL_INFO. On RAC, RUNNING_INSTANCE shows which node ran the job.","tags":["scheduler","job history","dba_scheduler_job_run_details","failed job","dba_scheduler_running_jobs","job log","monitoring"]},{"id":"resource-plan-basic","task":"Create and activate a simple Resource Manager plan","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":"Decides which workload wins under CPU contention: gives the OLTP group 70% and the report group 20% of CPU, limits parallelism to 4 in the report group, and cancels calls exceeding 30 minutes (CANCEL_SQL). Users are mapped to consumer groups automatically.","when":"For mixed workloads where daytime reports slow down OLTP, to cap uncontrolled parallel queries, and to cancel runaway queries automatically.","caution":"Resource Manager requires Enterprise Edition (very limited on SE2); no extra license. mgmt_p1 percentages apply only under CPU contention and cannot exceed 100% in total at one level. CANCEL_SQL kills the running statement with ORA-00040; a wrong mapping can kill critical work, so test thresholds carefully. While a plan is active, CPU throttling shows up as the 'resmgr:cpu quantum' wait event. For sharing between PDBs at CDB level use 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":"Switch the WAL segment","category":"WAL & Checkpoint","command":"SELECT pg_switch_wal();","what":"Closes the current WAL segment and switches to a new one; triggers archiving.","when":"To produce an archive file or force out WAL after a backup.","caution":"Before PostgreSQL 10 it was pg_switch_xlog().","since":"PostgreSQL 10","note":"This function was renamed to pg_switch_wal() in PostgreSQL 10; before that it was pg_switch_xlog() (WAL used to be called 'xlog').","tags":["wal","switch","checkpoint","archive"]},{"id":"manual-checkpoint","task":"Run a manual CHECKPOINT","category":"WAL & Checkpoint","command":"CHECKPOINT;","what":"Writes dirty buffers to disk and creates a checkpoint record; advances the recovery starting point.","when":"Before a backup, in a maintenance window, or to sync to disk before stopping the server.","caution":"Can generate heavy I/O; do not run it in production during peak hours. Only a superuser or (PG15+) the pg_checkpoint role can run it.","tags":["checkpoint","wal","io","flush"]},{"id":"wal-status-size","task":"Show WAL size and current LSN","category":"WAL & 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":"Returns the current WAL LSN, the name of the WAL file being written and the total size of the pg_wal directory.","when":"To monitor WAL accumulation, disk usage and checkpoint spacing.","caution":"pg_ls_waldir and pg_walfile_name are available in PostgreSQL 10+; access may require the pg_monitor role.","tags":["wal","lsn","size","monitor"]},{"id":"archive-lag","task":"Show archive lag (WAL not yet archived)","category":"WAL & 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":"Shows archive lag by comparing the last archived WAL file, the WAL currently being written and the archiving failure counters.","when":"To monitor whether archive_command is falling behind and to catch archiving failures.","caution":"If failed_count keeps increasing, archive_command is failing and pg_wal can fill up; act immediately to prevent the disk from overflowing.","tags":["archive","wal","lag","monitor"]},{"id":"create-tablespace","task":"Add a new tablespace","category":"Storage","command":"CREATE TABLESPACE ssd_ts OWNER postgres LOCATION '/mnt/ssd/pgdata';","what":"Defines a new tablespace in the given directory; tables and indexes can be placed on that disk.","when":"To spread data onto a different disk (e.g., fast SSD) and separate I/O.","caution":"The directory must already exist, be empty and be owned by the postgres user. Do not spread data to other disks with symbolic links; use PostgreSQL's own tablespace mechanism.","tags":["tablespace","storage","disk","create"]},{"id":"move-table-tablespace","task":"Move a table to another tablespace","category":"Storage","command":"ALTER TABLE customer SET TABLESPACE ssd_ts;","what":"Physically moves all of the table's data to the new tablespace (disk).","when":"To move a table to a faster/separate disk.","caution":"An ACCESS EXCLUSIVE lock is taken on the table during the move; the table is inaccessible until the move completes. Do it in a maintenance window for large tables.","tags":["tablespace","table","move","storage"]},{"id":"move-index-tablespace","task":"Move an index to another tablespace","category":"Storage","command":"ALTER INDEX customer_email_idx SET TABLESPACE ssd_ts;","what":"Moves the index to the given tablespace.","when":"To place indexes on a disk separate from the data and spread I/O.","caution":"An ACCESS EXCLUSIVE lock is taken during the move; the index cannot be used. ALTER TABLE ... SET TABLESPACE does not cover the table's indexes, so it cannot move them all at once; move each index separately.","tags":["tablespace","index","move","storage"]},{"id":"db-table-size","task":"Query database and table size","category":"Storage","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":"Lists the 20 largest tables with their total size including indexes and TOAST.","when":"To analyze disk usage and find which tables take up space.","caution":"For the whole database size: SELECT pg_size_pretty(pg_database_size(current_database()));","tags":["size","table","database","storage"]},{"id":"table-bloat","task":"Estimate table bloat","category":"Storage","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":"Shows the bloat ratio based on dead tuple counts and the last autovacuum time.","when":"To determine VACUUM needs and whether autovacuum is keeping up.","caution":"n_dead_tup is an estimate continuously updated from DML counters (it drops after VACUUM; ANALYZE only corrects the estimate); for exact physical bloat the pgstattuple extension (pgstattuple('customer')) is more accurate.","tags":["bloat","dead-tuple","vacuum","storage"]},{"id":"vacuum-analyze-table","task":"VACUUM ANALYZE a specific table","category":"Maintenance","command":"VACUUM (ANALYZE, VERBOSE) customer;","what":"Cleans up dead tuples in the table, makes the space reusable and updates planner statistics.","when":"After bulk DELETE/UPDATE, or when query plans degrade.","caution":"VACUUM does not return disk space to the OS; reclaiming space requires VACUUM FULL, which takes an ACCESS EXCLUSIVE lock. Plain VACUUM does not block the table.","tags":["vacuum","analyze","maintenance","cleanup"]},{"id":"vacuum-freeze","task":"Run an aggressive VACUUM FREEZE","category":"Maintenance","command":"VACUUM (FREEZE, VERBOSE, ANALYZE) customer;","what":"Immediately marks all eligible tuples in the table as 'frozen'; reduces transaction ID wraparound risk.","when":"When a wraparound warning appears, or preventively on large tables that will be archived/become read-only.","caution":"Generates heavy I/O because it scans the entire table. In a wraparound emergency the database can effectively go read-only; monitor datfrozenxid beforehand.","tags":["vacuum","freeze","wraparound","maintenance"]},{"id":"reindex-concurrently","task":"Rebuild an index without locking (REINDEX CONCURRENTLY)","category":"Maintenance","command":"REINDEX INDEX CONCURRENTLY customer_email_idx;","what":"Rebuilds the index without blocking writes; removes index bloat.","when":"When an index has grown/become corrupted, on a live system without a maintenance window.","caution":"Requires PostgreSQL 12+. If it fails it can leave an INVALID index with the _ccnew suffix; check pg_index.indisvalid and clean up with DROP INDEX if needed. To rebuild the whole table: REINDEX TABLE CONCURRENTLY customer;","since":"PostgreSQL 12","note":"REINDEX ... CONCURRENTLY arrived in PostgreSQL 12; before that, REINDEX locked the index with an ACCESS EXCLUSIVE lock (a lock-free rebuild required CREATE INDEX CONCURRENTLY + DROP).","tags":["reindex","concurrently","index","maintenance"]},{"id":"autovacuum-per-table","task":"Tune autovacuum for a specific table","category":"Maintenance","command":"ALTER TABLE customer SET (autovacuum_vacuum_scale_factor = 0.02, autovacuum_vacuum_threshold = 5000, autovacuum_analyze_scale_factor = 0.01);","what":"Lowers the autovacuum trigger thresholds for this table only, so large/busy tables are vacuumed more often.","when":"When autovacuum falls behind on large tables with heavy UPDATE/DELETE activity.","caution":"Overly aggressive settings can lead to constant autovacuum runs and I/O load. To revert the setting: ALTER TABLE customer RESET (autovacuum_vacuum_scale_factor);","tags":["autovacuum","tuning","table","maintenance"]},{"id":"statistics-target","task":"Increase a column's statistics target","category":"Maintenance","command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500; ANALYZE customer;","what":"Increases the histogram/sample detail collected for a specific column; improves the planner's estimates.","when":"When bad query plans occur on columns with skewed distributions.","caution":"A high value (default 100, max 10000) increases ANALYZE time and planning time. The change takes effect only after ANALYZE.","tags":["statistics","planner","analyze","tuning"]},{"id":"alter-system-reload","task":"Change a parameter and reload (ALTER SYSTEM)","category":"Parameters","command":"ALTER SYSTEM SET work_mem = '64MB'; SELECT pg_reload_conf();","what":"Writes the parameter to postgresql.auto.conf and reloads the configuration (effective immediately for parameters that do not require a restart).","when":"To persistently change parameters that can be applied by reload, such as work_mem and logging settings.","caution":"Some parameters such as shared_buffers and max_connections take effect only on restart, not reload; those with pg_settings.context = 'postmaster' require a restart.","tags":["alter-system","reload","parameter","config"]},{"id":"session-set","task":"Change a parameter for the session (SET)","category":"Parameters","command":"SET work_mem = '256MB';","what":"Changes the parameter only for the current connection/session; it resets when the session ends.","when":"When a setting needs to be raised temporarily for a single heavy query or batch job.","caution":"Use SET LOCAL to limit it to a single transaction. A global change requires ALTER SYSTEM.","tags":["set","session","parameter","work-mem"]},{"id":"show-setting","task":"Show the current parameter value","category":"Parameters","command":"SELECT name, setting, unit, context, source FROM pg_settings WHERE name = 'shared_buffers';","what":"Shows the parameter's effective value, unit, how it can be applied (context) and its source.","when":"To find a parameter's running value and whether it requires a restart or a reload.","caution":"Shortcut: SHOW shared_buffers; or SELECT current_setting('shared_buffers'); If the context column is 'postmaster', a restart is required.","tags":["show","pg_settings","parameter","current-setting"]},{"id":"physical-replica-basebackup","task":"Build a physical replica (pg_basebackup)","category":"Replication","command":"pg_basebackup -h primary_host -U replicator -D /var/lib/postgresql/16/main -Fp -Xs -P -R --slot=replica_slot_1 -C","what":"Takes a full physical backup from the primary, streams WAL (-Xs), writes standby.signal and primary_conninfo (-R) and creates the replication slot (-C).","when":"To build a new streaming standby (read replica or failover target).","caution":"The target directory (-D) must be empty. On the primary, pg_hba.conf must grant 'replication' access to the replicator user, and there must be sufficient wal_keep_size / a slot. With PostgreSQL 12+, -R produces standby.signal instead of recovery.conf.","tags":["replication","basebackup","standby","physical"]},{"id":"promote-replica","task":"Promote a replica to primary","category":"Replication","command":"SELECT pg_promote(wait := true, wait_seconds := 60);","what":"Takes the standby server out of recovery mode and turns it into a writable primary.","when":"To make the standby the new primary in a planned or emergency failover.","caution":"Promotion cannot be undone; the server can no longer follow the old primary. Make sure the old primary is really down to prevent split-brain. Alternative: pg_ctl promote.","since":"PostgreSQL 12","note":"The pg_promote() SQL function arrived in PostgreSQL 12; before that, a trigger file (promote_trigger_file) or pg_ctl promote was used to promote a standby.","tags":["replication","promote","failover","standby"]},{"id":"replication-lag","task":"Show replication lag","category":"Replication","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":"On the primary, shows each standby's replay gap in bytes relative to the primary's current WAL position, plus write/flush/replay lag.","when":"To monitor how far behind streaming replication is (run on the primary).","caution":"For lag in seconds on the standby: SELECT now() - pg_last_xact_replay_timestamp(); this value also grows when there are no writes on the primary, so interpret it together with the LSN gap. An empty result means no standby is connected.","tags":["replication","lag","lsn","monitor"]},{"id":"replication-slot","task":"Create and drop a replication slot","category":"Replication","command":"SELECT pg_create_physical_replication_slot('replica_slot_1'); -- To drop: SELECT pg_drop_replication_slot('replica_slot_1');","what":"Creates a physical slot that guarantees the primary does not remove required WAL until the standby confirms it; the second command removes the slot.","when":"To ensure the standby does not lose WAL during short outages.","caution":"An unused/abandoned slot leads to WAL accumulation and a full disk. Monitor slot status: SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained FROM pg_replication_slots; An active slot cannot be dropped.","tags":["replication","slot","wal","cleanup"]},{"id":"logical-replication","task":"Set up a logical replication publication and subscription","category":"Replication","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":"Starts table-level logical replication by creating a publication for specific tables on the source and a subscription that connects to it on the target.","when":"For version upgrades, replicating selected tables to another server, or one-way data distribution.","caution":"Requires PostgreSQL 10+. The target tables' schema must already exist; the subscription performs the initial COPY automatically. wal_level must be logical (requires a restart). DDL changes and sequence values are not replicated; apply schema changes on both sides separately and sync sequences with setval at cutover.","since":"PostgreSQL 10","note":"Built-in logical replication (CREATE PUBLICATION / CREATE SUBSCRIPTION) arrived in PostgreSQL 10; before that, external extensions such as pglogical were needed for the same job.","tags":["logical","replication","publication","subscription"]},{"id":"terminate-backend","task":"Terminate a backend (pg_terminate_backend)","category":"Sessions","command":"SELECT pg_terminate_backend(12345);","what":"Forcibly terminates the backend process with the given PID; drops the connection and the running transaction.","when":"To close a stuck/hung connection or a session holding a lock.","caution":"The running transaction is rolled back; uncommitted work is lost. Try the gentler pg_cancel_backend first. Verify the PID in pg_stat_activity.","tags":["terminate","backend","session","kill"]},{"id":"cancel-backend","task":"Cancel a running query (pg_cancel_backend)","category":"Sessions","command":"SELECT pg_cancel_backend(12345);","what":"Cancels the query currently running on the given PID; the connection stays open.","when":"To stop a long-running query without dropping the connection.","caution":"It only cancels an active query; it does not close a session in idle in transaction state, which requires pg_terminate_backend.","tags":["cancel","backend","query","session"]},{"id":"active-blocking-queries","task":"Show active and blocking queries","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":"Lists all non-idle backends, which PIDs are blocking them and how long they have been running.","when":"To diagnose locking/blocking problems and long-running queries.","caution":"pg_blocking_pids is available in PostgreSQL 9.6+. If the blocking_pids array is non-empty, those PIDs hold the lock; cancel/terminate that PID if needed.","since":"PostgreSQL 9.6","note":"The pg_blocking_pids() function arrived in PostgreSQL 9.6; before that, finding blocking processes required manually joining pg_locks.","tags":["activity","blocking","lock","session"]},{"id":"create-extension","task":"Add an extension","category":"Extensions","command":"CREATE EXTENSION IF NOT EXISTS pg_stat_statements;","what":"Installs the given extension in the current database (adds functions, views, types, etc.).","when":"When additional capabilities such as pg_stat_statements, pgcrypto or postgis are needed.","caution":"For extensions that require shared_preload_libraries, such as pg_stat_statements, first add them to postgresql.conf and restart the server. The extension files (contrib package) must be installed on the server.","tags":["extension","create","pg_stat_statements","plugin"]},{"id":"list-extensions","task":"List installed extensions","category":"Extensions","command":"SELECT extname AS extension, extversion AS version FROM pg_extension ORDER BY extname;","what":"Lists all extensions installed in the current database and their versions.","when":"To see which extensions are installed and whether an update is needed.","caution":"To see installable (but not yet installed) extensions: SELECT * FROM pg_available_extensions; Upgrade: 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":"Show Patroni cluster status, topology and history","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":"Shows Patroni's cluster view from the DCS: leader/replica roles, member state (running/streaming), timeline, replication lag and pending restarts; history lists timeline changes with the LSN at which they happened.","when":"Before and after every switchover/failover/maintenance, and as the first diagnostic step when an alert fires.","caution":"The output reflects what is stored in the DCS; if the DCS is unreachable the command fails or may show stale data. Do not start a switchover before confirming all members are on the same timeline and Lag in MB is below maximum_lag_on_failover.","untested":true,"tags":["patroni","cluster status","topology","history","lag"]},{"id":"patroni-switchover","task":"Perform a planned switchover with 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":"Demotes the healthy leader in a controlled way and promotes the chosen replica (--candidate); the former leader automatically rejoins as a replica of the new leader. --scheduled defers the operation to the given time.","when":"To move the primary to another node before maintenance (OS patching, hardware replacement, minor version upgrade) or to restore the original roles after a failover.","caution":"All connections drop when the leader changes; the application must be able to reconnect. Patroni rejects the switchover if the candidate's lag exceeds maximum_lag_on_failover. With synchronous_mode enabled the candidate must be a synchronous standby.","note":"In older Patroni releases the --leader option was named --master; Patroni 4 removed --master. --primary is equivalent to --leader.","untested":true,"tags":["patroni","switchover","planned switchover","maintenance","leader"]},{"id":"patroni-failover","task":"Perform a manual failover with 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":"Promotes the specified replica regardless of the leader's state; intended for cases where automatic failover did not happen or chose the wrong node.","when":"When the leader is unreachable/unhealthy and automatic failover did not kick in (e.g. in pause mode), or when you must force a move to a specific node.","caution":"With asynchronous replication, transactions not yet received by the candidate are lost (RPO > 0). If the leader is healthy, use switchover instead of failover. When the old leader returns it may need pg_rewind (if use_pg_rewind is enabled) or a reinit.","note":"In Patroni 3.2+ --candidate is mandatory for failover; the --leader/--primary option of failover was deprecated in 3.2 and removed in Patroni 4.0. Use switchover if you need to name the leader.","untested":true,"tags":["patroni","failover","manual failover","promote","rpo"]},{"id":"patroni-reinit-replica","task":"Rebuild a broken replica with 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":"Deletes the specified replica's data directory and rebuilds it from scratch according to Patroni's create_replica_methods (pgBackRest restore or pg_basebackup).","when":"When a replica cannot rejoin replication due to a timeline mismatch, failed pg_rewind, missing WAL or data corruption.","caution":"All local data on the member is deleted; double-check that the command targets the right member (Patroni refuses reinit on the leader). On large databases this can take hours and adds I/O/network load on the leader; the cluster runs one replica short meanwhile. --from-leader exists only in recent Patroni 4.x releases (check with patronictl reinit --help); it skips create_replica_methods and takes a pg_basebackup from the leader.","untested":true,"tags":["patroni","reinit","rebuild replica","rebuild","basebackup"]},{"id":"patroni-edit-config","task":"Change cluster-wide parameters in Patroni (edit-config) and apply pending restarts","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":"Writes PostgreSQL and Patroni parameters to the dynamic configuration in the DCS; Patroni applies the change to all members, activates reloadable ones immediately and flags those that need a restart as 'Pending restart'.","when":"To change parameters such as max_connections, shared_buffers or work_mem consistently on every node of a Patroni-managed cluster.","caution":"Under Patroni, do not edit postgresql.conf manually or use ALTER SYSTEM: Patroni rewrites postgresql.conf, and postgresql.auto.conf overrides Patroni's settings, causing drift between nodes. max_connections, max_worker_processes, max_wal_senders, max_prepared_transactions and max_locks_per_transaction on a standby cannot be lower than on the primary; plan the restart order accordingly.","untested":true,"tags":["patroni","edit-config","parameter","pending restart","dcs"]},{"id":"patroni-pause-resume","task":"Put Patroni into maintenance mode and back (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":"Puts the cluster in maintenance mode: Patroni performs no automatic failover and does not restart a stopped PostgreSQL; resume returns to normal management.","when":"During DCS (etcd/Consul) maintenance, Patroni upgrades, or when PostgreSQL must be managed manually without Patroni interfering.","caution":"If the primary crashes while paused there is no automatic failover and the outage continues. Do not forget to resume after maintenance; add a 'Maintenance mode' check to your alerting. Manually starting a PostgreSQL as primary while paused can cause split-brain.","untested":true,"tags":["patroni","pause","resume","maintenance mode","maintenance"]},{"id":"patroni-dcs-health","task":"Check Patroni DCS (etcd/Consul) and REST API health","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":"Checks quorum, leader and capacity of the DCS where Patroni holds the leader lock, and each member's own view of its role through the Patroni REST API. The /primary endpoint is the basis of load balancer (HAProxy) health checks.","when":"When Patroni logs 'DCS is not accessible', after an unexpected demote/failover, and as a routine health check.","caution":"If the DCS loses quorum, Patroni cannot renew the leader lock and demotes the primary to read-only (unless failsafe_mode, Patroni 3.0+, is enabled); an etcd outage is therefore a database write outage. When the etcd DB size reaches its quota (default 2 GB) a NOSPACE alarm stops writes. Run the DCS on at least 3 nodes on separate disks.","untested":true,"tags":["patroni","etcd","consul","dcs health","rest api"]},{"id":"pgbackrest-archive-config","task":"Configure WAL archiving for pgBackRest (archive-push/archive-get)","category":"Backup & 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":"Makes PostgreSQL send every completed WAL segment to the pgBackRest repository (archive-push) and fetch it from there during recovery (archive-get); archive-async makes pushing parallel and asynchronous through a spool directory.","when":"As the first step of setting up physical backups and PITR with pgBackRest, before stanza-create.","caution":"As long as archive_command fails, WAL accumulates in pg_wal and the primary stops when the disk fills; monitor pg_stat_archiver.failed_count. With Patroni, set these parameters through patroni.yml / edit-config instead of postgresql.conf. Archiving from two different clusters into the same stanza corrupts the repository.","untested":true,"tags":["pgbackrest","archiving","archive_command","wal archive","configuration"]},{"id":"pgbackrest-stanza-create-check","task":"Create the pgBackRest stanza and verify archiving (stanza-create/check)","category":"Backup & 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 creates the cluster's metadata (system identifier, version) in the repository; check tests the PostgreSQL connection, archive_command and WAL arrival in the repository with an actual WAL switch.","when":"At initial setup, after changing archive_command or the repository, and as a monitoring/cron health check.","caution":"Run the commands as postgres (or the repository owner); running them as root breaks file ownership. After a major version upgrade, stanza-upgrade is required, not stanza-create. If check fails, PITR may be impossible even if backups complete.","untested":true,"tags":["pgbackrest","stanza","stanza-create","check","archive verification"]},{"id":"pgbackrest-backup-full-diff-incr","task":"Take full, differential and incremental backups with pgBackRest","category":"Backup & 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":"A full backup copies the whole cluster, diff copies files changed since the last full, incr copies files changed since the last backup of any type; at the end the required WAL is verified as archived and expire runs automatically according to retention.","when":"In a regular backup schedule: typically weekly full + daily diff/incr, more frequent incr on large clusters.","caution":"Without a prior full, diff/incr requests are automatically promoted to full. A longer incr chain increases restore time and dependency; losing one backup in the chain makes later ones unusable. To offload the primary, back up from a standby with backup-standby=y (the primary is still needed for coordination).","untested":true,"tags":["pgbackrest","backup","full","differential","incremental"]},{"id":"pgbackrest-info","task":"Show the pgBackRest backup catalog and WAL archive range (info)","category":"Backup & 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":"Lists the stanza status, each backup's label, type, start/stop time, size, the backup it depends on, and the min/max range of the WAL archive in the repository.","when":"Before a restore/PITR to confirm the target time is covered, to monitor that backups run regularly, and to check retention.","caution":"info shows that a backup exists, not that it is restorable; validate with regular test restores and pgbackrest verify. If there is a gap in the WAL min/max range, PITR cannot cross it.","untested":true,"tags":["pgbackrest","info","backup list","catalog","monitoring"]},{"id":"pgbackrest-restore-latest","task":"Restore the latest backup with pgBackRest","category":"Backup & 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":"Restores the latest backup and writes recovery.signal and restore_command; on startup PostgreSQL replays all archived WAL to the most recent consistent point and promotes on a new timeline.","when":"When the primary's data directory is lost/corrupted, or when regularly testing that backups are restorable.","caution":"If the restored cluster and the old cluster archive to the same stanza at the same time, the repository is corrupted; use --archive-mode=off for test restores. Under Patroni, pause the cluster or stop Patroni first, otherwise Patroni interferes. If tablespaces or paths outside PGDATA exist, review the --tablespace-map and link options.","untested":true,"tags":["pgbackrest","restore","restore","recovery","disaster recovery"]},{"id":"pgbackrest-pitr-restore","task":"Restore to a point in time with pgBackRest (PITR, --type=time)","category":"Backup & 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":"Restores a backup that finished before the target time and replays WAL only up to the target; target-action=pause stops at the target so you can validate the data, and pg_wal_replay_resume() promotes.","when":"To return to the moment just before a logical error such as an accidental DELETE/DROP/UPDATE.","caution":"All changes after the target are lost on this cluster; where possible, run PITR on a separate server and move back only the affected data with pg_dump. State the time zone explicitly in the target. pgBackRest 2.24+ selects a suitable backup before the target automatically; on older releases specify a backup that finished before the target with --set. If the target is earlier than the backup's stop time, the restore cannot reach consistency.","untested":true,"tags":["pgbackrest","pitr","point in time recovery","point in time","recovery"]},{"id":"pgbackrest-delta-restore-standby","task":"Quickly rebuild a standby with pgBackRest delta restore","category":"Backup & 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 compares files in the existing PGDATA with the backup (size/timestamp and checksum) and copies only the differing ones; --type=standby writes standby.signal and recovery-option adds primary_conninfo. The standby first catches up from the archive, then switches to streaming.","when":"To turn a lagging or timeline-diverged standby, or an old primary where pg_rewind failed, back into a replica without a full copy.","caution":"Delta replaces/removes differing or extra files in the target directory with the backup's; running it on the wrong server destroys that server's data. pgBackRest refuses to restore over a running PostgreSQL, but verify yourself that the service is stopped. Keep the password in .pgpass rather than in primary_conninfo.","untested":true,"tags":["pgbackrest","delta restore","rebuild standby","rebuild","replica"]},{"id":"pgbackrest-retention-expire","task":"Configure pgBackRest retention and run expire","category":"Backup & 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":"Defines how many full/diff backups (count) or how many days of full backups (time) to keep; expire deletes backups outside the policy, the diff/incr backups depending on them, and WAL archive no longer needed.","when":"To keep repository size under control and guarantee the RPO/PITR window requirement (e.g. being able to go back 14 days).","caution":"The PITR window is bounded by the oldest retained full backup; with weekly fulls and retention-full=2, you can go back at most about 2 weeks. repo1-retention-archive follows full retention by default; setting it lower leaves older backups usable only up to their consistency point. Expire cannot be undone.","untested":true,"tags":["pgbackrest","retention","retention period","expire","backup cleanup"]},{"id":"standby-manual-config","task":"Configure a standby manually (standby.signal + primary_conninfo)","category":"Replication","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":"The standby.signal file makes the server start as a standby; primary_conninfo defines the streaming connection and primary_slot_name the replication slot to use. On PG13+ primary_conninfo and primary_slot_name can be changed with a reload.","when":"To turn a data directory prepared without pg_basebackup -R (pgBackRest, snapshot, rsync) into a standby, or to point an existing standby to a new primary.","caution":"The slot must be created on the primary beforehand (pg_create_physical_replication_slot), otherwise the walreceiver keeps failing. Put the password in ~/.pgpass instead of conninfo. application_name must exactly match the name in synchronous_standby_names. If standby.signal is forgotten, the server starts as a primary and moves to a separate timeline.","since":"PostgreSQL 12","note":"Before PostgreSQL 12 these settings lived in recovery.conf (standby_mode = on); PG12 moved them to standby.signal and postgresql.conf. Before PG13, changing primary_conninfo required a restart.","tags":["standby","standby.signal","primary_conninfo","replica setup","streaming"]},{"id":"standby-wal-receiver-lag","task":"Monitor WAL receive and replay lag on the standby (pg_stat_wal_receiver)","category":"Replication","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":"Shows how much WAL the standby has received and how much it has applied (replay backlog), the age of the last replayed transaction, whether replay is paused, and the walreceiver connection state (which primary, slot, timeline, time of last message).","when":"To diagnose lag when the primary is not reachable or on cascading standbys; on a 'standby behind' alert, to separate a network (receive) problem from a replay (I/O, conflict) problem.","caution":"If pg_stat_wal_receiver returns no rows, the standby is not streaming (fed from the archive or disconnected). last_replay_age grows even when the primary has no writes; interpret it together with the receive_lsn/replay_lsn difference. If replay backlog grows while receive advances, the standby has long queries (max_standby_streaming_delay) or an I/O bottleneck.","since":"PostgreSQL 14","note":"pg_get_wal_replay_pause_state() was added in PostgreSQL 14; before that only pg_is_wal_replay_paused() existed. The rest of the query works on PG10+.","tags":["standby","replication lag","wal receiver","replay","lag"]},{"id":"pg-rewind-old-primary","task":"Rejoin the old primary as a replica after failover with pg_rewind","category":"Replication","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":"Makes the old primary's data directory consistent with the new primary by copying the blocks changed after the timeline divergence point from the new primary; turns it into a replica within minutes without a full basebackup.","when":"When the old primary comes back after a failover/promote and lags behind the new timeline with diverged changes.","caution":"Transactions left on the old primary after the failover that never reached the new primary are permanently removed. pg_rewind also copies config files (postgresql.conf, postgresql.auto.conf, pg_hba.conf) from the source; fix the port and slot settings before starting. If WAL needed since the divergence point is no longer on the source, fetch it from the archive with -c (--restore-target-wal). The source connection needs a superuser or a role with the function privileges listed in the documentation. In Patroni this is done automatically with use_pg_rewind: true.","since":"PostgreSQL 13","note":"-R/--write-recovery-conf and -c/--restore-target-wal were added in PostgreSQL 13; on PG13+ pg_rewind runs crash recovery itself on an uncleanly stopped target (disable with --no-ensure-shutdown). pg_rewind has existed since 9.5.","tags":["pg_rewind","after failover","old primary","timeline","rejoin"]},{"id":"sync-standby-quorum","task":"Configure synchronous replication with quorum (ANY) or priority (FIRST)","category":"Replication","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":"Defines which standbys must confirm before a commit completes: ANY N is quorum-based (sync_state = quorum), FIRST N makes the first N connected standbys in list order synchronous (sync) and the rest standbys-in-waiting (potential). The confirmation level is set by synchronous_commit (remote_write, on, remote_apply).","when":"On systems that require no data loss on failover (RPO = 0); with several standbys, ANY is preferred so that a single standby failure does not stop writes.","caution":"If the required number of synchronous standbys is not connected, commits hang without an error and the application freezes. Names in the list must match the standbys' application_name. In an emergency a single session can bypass it with SET synchronous_commit = local. Under Patroni, manage this with synchronous_mode instead of setting it manually.","since":"PostgreSQL 10","note":"The FIRST syntax was added in PostgreSQL 9.6 and ANY (quorum commit) in PostgreSQL 10; the keyword-less 'N (a, b)' form means FIRST.","tags":["synchronous replication","synchronous_standby_names","quorum","rpo","sync"]},{"id":"slot-retained-wal-monitor","task":"Monitor WAL retained by replication slots and safely drop an abandoned slot","category":"Replication","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":"Shows how much WAL each slot retains on the primary, wal_status (reserved/extended/unreserved/lost), the remaining safe margin under max_slot_wal_keep_size (safe_wal_size), and the xmin/catalog_xmin values that hold back VACUUM; the second query drops the slot only if it is not in use.","when":"When the pg_wal directory grows, on a disk alert and in routine monitoring; for slot cleanup after a standby or subscriber has been permanently removed.","caution":"An inactive slot retains WAL without limit and can fill the disk and stop the primary; a slot holding xmin/catalog_xmin also blocks dead tuple cleanup and creates bloat/wraparound risk. Dropping a slot makes it impossible for its standby/subscriber to resume where it left off (a rebuild is required); verify its owner before dropping. Run on the primary (pg_current_wal_lsn() fails on a standby).","since":"PostgreSQL 13","note":"wal_status and safe_wal_size were added in PostgreSQL 13. PG16 added conflicting for logical slots and PG17 added inactive_since; inactive_since is the most direct indicator of an abandoned slot.","tags":["replication slot","wal accumulation","disk full","drop slot","retained wal"]},{"id":"slot-max-wal-keep-size","task":"Limit the WAL that slots can retain (max_slot_wal_keep_size)","category":"Replication","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":"Sets an upper bound on the WAL that replication slots can keep in pg_wal (default -1 = unlimited); when exceeded, checkpoint removes old WAL and the slot becomes wal_status = 'lost'. The parameter takes effect with a reload.","when":"As a safeguard on every cluster that uses slots, against the risk of a disconnected standby or subscriber filling the primary's disk and stopping the whole system.","caution":"The limit favors keeping the primary alive at the expense of the slot owner: a standby with a 'lost' slot must be rebuilt and a logical subscription recreated. Choose a value below the free space of the pg_wal disk but larger than the WAL generated during the longest expected outage. If wal_status is 'unreserved' the slot is about to be lost; raise an alert.","since":"PostgreSQL 13","note":"max_slot_wal_keep_size was added in PostgreSQL 13; before that there was no way to cap WAL retained by slots and the only protection was monitoring plus dropping slots manually.","tags":["max_slot_wal_keep_size","slot limit","wal accumulation","disk protection","replication slot"]},{"id":"logical-add-table-refresh","task":"Add a table to a publication and refresh the subscription (REFRESH PUBLICATION)","category":"Logical Replication","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":"Adds a new table to the publication on the publisher; on the subscriber, REFRESH PUBLICATION includes the new tables in the subscription and starts the initial copy (tablesync) with copy_data = true. srsubstate: i = init, d = data copy, f = copy finished, s = synchronized, r = ready.","when":"When a new table is added to a running logical replication, or a table is removed from the publication.","caution":"Even with FOR ALL TABLES or TABLES IN SCHEMA publications, the subscriber does not pick up a new table until REFRESH is run. REFRESH fails if the table does not exist on the subscriber; DDL is not replicated. If a published table has no PRIMARY KEY or REPLICA IDENTITY, UPDATE/DELETE fail on the publisher. The initial copy of a large table holds a slot and snapshot on the publisher for a long time.","since":"PostgreSQL 10","note":"ADD TABLES IN SCHEMA was added in PostgreSQL 15 (PG15 also added row filters with WHERE and column lists).","tags":["logical replication","publication","add table","refresh publication","subscription"]},{"id":"logical-subscription-monitor","task":"Monitor logical replication (pg_stat_subscription, apply errors, slot lag)","category":"Logical Replication","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":"On the subscriber, shows apply/tablesync workers, received and confirmed LSN, time of last message and cumulative apply/sync error counts; on the publisher, shows the logical slot's unconfirmed (confirmed_flush) WAL and the total WAL it retains.","when":"During logical replication setup, to confirm lag approaches zero during an upgrade/migration cutover, and for error alerting.","caution":"If pid is NULL, the apply worker is not running (subscription disabled or repeatedly failing and restarting); check the server log. A rising apply_error_count indicates a conflict. A stalled subscription accumulates WAL and catalog_xmin on the publisher; monitor publisher disk and catalog bloat.","since":"PostgreSQL 15","note":"pg_stat_subscription_stats was added in PostgreSQL 15 and pg_stat_subscription.leader_pid in PG16 (parallel apply); PG17 added the worker_type column. The basic pg_stat_subscription columns exist since PG10.","tags":["logical replication","subscription monitoring","pg_stat_subscription","apply error","lag"]},{"id":"logical-conflict-skip","task":"Resolve a logical replication conflict (disable_on_error, SKIP)","category":"Logical Replication","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":"When an error such as a duplicate key, missing row or permission problem stops apply on the subscriber: disable_on_error disables the subscription automatically; either fix the conflicting data and ENABLE, or SKIP the entire remote transaction identified by the 'finished at' LSN in the log.","when":"When the apply worker fails and keeps restarting because of direct writes on the subscriber, an incomplete initial copy or bidirectional writes.","caution":"SKIP also discards the non-conflicting changes in that transaction and the two sides diverge permanently; prefer fixing the data, and compare the table after a SKIP. Until the conflict is resolved, the slot on the publisher keeps accumulating WAL. Prevent application writes to subscriber tables with privileges.","since":"PostgreSQL 15","note":"SKIP and disable_on_error were added in PostgreSQL 15; before that the same was done by advancing the origin on the subscriber with pg_replication_origin_advance() (riskier).","tags":["logical replication","conflict","conflict","skip","duplicate key"]},{"id":"logical-sync-sequences","task":"Synchronize sequence values to the subscriber in logical replication","category":"Logical Replication","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":"Because logical replication does not carry sequence (including identity) values, this reads the last value of every sequence on the publisher and generates and directly applies setval commands that set the same value on the subscriber.","when":"At the cutover of an upgrade/migration via logical replication, right before pointing the application to the subscriber.","caution":"If run while the publisher is still taking writes, the values are stale immediately and duplicate key errors follow after cutover; run it after stopping writes or add a safety margin. DDL is not replicated either: apply schema changes (ALTER TABLE, new indexes) to both sides separately, subscriber first. pg_sequences returns NULL last_value for sequences you cannot read; run the command as the owner or a superuser.","since":"PostgreSQL 10","tags":["logical replication","sequence","setval","ddl not replicated","cutover"]},{"id":"logical-orphan-slot-cleanup","task":"Remove a subscription and clean up an orphaned logical slot on the publisher","category":"Logical Replication","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":"Drops the subscription without touching the remote slot (slot_name = NONE); then finds and removes logical slots on the publisher that no longer have a consumer, plus leftover tablesync slots.","when":"When a subscription must be removed while the publisher is unreachable, after a migration is finished, or when a logical slot with active = false is accumulating WAL on the publisher.","caution":"An orphaned logical slot retains both WAL and catalog_xmin: the disk fills and system catalogs bloat. However, dropping the slot makes it impossible for that subscription to resume; before dropping, verify that no subscriber (including one on another server) uses this slot. An active slot cannot be dropped; the connected walsender must be terminated first.","since":"PostgreSQL 10","tags":["logical replication","slot cleanup","drop subscription","orphaned slot","catalog_xmin"]},{"id":"pg-upgrade-link-runbook","task":"Major version upgrade with pg_upgrade --link (end-to-end runbook)","category":"Upgrade","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":"First validates compatibility with --check, then performs the major upgrade in minutes regardless of data size by hard-linking the old cluster's data files to the new version instead of copying them; afterwards gathers statistics in stages and removes the old cluster.","when":"To move a large cluster in place to a new major version with an acceptably short outage (minutes).","caution":"With --link, once the new cluster has been started the old cluster can no longer be used safely; the only way back is a backup taken before the upgrade. The old and new data directories must be on the same file system. Plans may be poor on the new cluster until analyze finishes. If you use pgBackRest, run stanza-upgrade and take a new full backup immediately; backups of the old version cannot be restored into the new cluster.","note":"Physical standbys are not upgraded with pg_upgrade: after the primary is upgraded, update them with the rsync --hard-links --size-only method from the documentation or rebuild them. Logical slots are not migrated before PG17 (they are when the old cluster is PG17+); planner statistics are not migrated before PG18.","tags":["pg_upgrade","major upgrade","version upgrade","link","check"]},{"id":"logical-upgrade-near-zero-downtime","task":"Near-zero-downtime major version upgrade with logical replication","category":"Upgrade","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":"Builds an empty cluster on the new version, moves the schema and continuously streams data from the old version with logical replication; only the final cutover step (stopping writes, syncing sequences, redirecting) causes a short outage. A second replication in the reverse direction can keep a rollback path open.","when":"When the outage required by pg_upgrade is unacceptable, when moving to new hardware/OS at the same time, or when skipping several major versions.","caution":"DDL, sequences, large objects and materialized view contents are not replicated; freeze the schema during the migration. On tables without a primary key/replica identity, UPDATE/DELETE fail on the publisher. On large databases the initial copy takes long and holds WAL and catalog_xmin on the old server. Each database needs its own publication/subscription. Compare row counts/checksums before cutover.","since":"PostgreSQL 10","note":"pg_createsubscriber, added in PostgreSQL 17, converts a physical standby into a logical subscriber without an initial copy, eliminating initial copy time on large databases.","tags":["upgrade","logical replication","zero downtime migration","major upgrade","cutover"]},{"id":"pgbouncer-show-pools-stats","task":"Show PgBouncer pools, statistics and connections (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":"For each database/user pool, shows active and waiting clients (cl_active, cl_waiting), active/idle server connections (sv_active, sv_idle), the longest wait time (maxwait) and query/transaction counters.","when":"When the application reports connection failures/timeouts, for pool sizing, and to see active connections before maintenance.","caution":"The admin console does not accept SQL; it runs PgBouncer commands only. If cl_waiting stays high, first check the server side for long transactions or locks; blindly raising default_pool_size pushes PostgreSQL toward its max_connections limit and memory pressure. SHOW STATS values are cumulative; averages refer to the stats_period interval.","untested":true,"tags":["pgbouncer","show pools","connection pooling","statistics","connection pool"]},{"id":"pgbouncer-pause-resume-reload","task":"Hold connections during maintenance with PgBouncer (PAUSE/RESUME) and reload 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 drains connections on the PostgreSQL side without dropping client connections and queues new queries; RESUME lets the queue flow again. A short primary restart or switchover thus appears to the application as a brief delay rather than errors.","when":"During short maintenance windows (switchover, minor version restart, parameter restart) and when applying pgbouncer.ini/userlist changes.","caution":"PAUSE returns only after all active transactions finish; a long-running query holds PAUSE (terminate that session in PostgreSQL if needed). Waiting clients get an error when query_wait_timeout (default 120 s) expires; finish the maintenance within that time. Forgetting RESUME after PAUSE is a full outage. Some settings (listen_addr, listen_port, etc.) do not change with RELOAD and require a restart.","untested":true,"tags":["pgbouncer","pause","resume","reload","maintenance"]},{"id":"pgbouncer-pool-mode","task":"PgBouncer pool_mode choice and basic sizing","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":"Session mode binds a client to one server connection for the lifetime of its connection; transaction mode assigns a server connection only for the duration of a transaction and serves thousands of clients with dozens of PostgreSQL connections; statement mode returns the connection after every query and disallows multi-statement transactions. pool_mode can also be overridden per database.","when":"Transaction mode for OLTP/microservice applications that open many short-lived connections; session mode for work that depends on session state (reporting, migrations, services using LISTEN).","caution":"In transaction mode, session state is not preserved between transactions: SET (except SET LOCAL), session-level advisory locks, LISTEN, WITH HOLD cursors, temp tables across transactions and SQL-level PREPARE are unreliable. Protocol-level prepared statements are supported only with PgBouncer 1.21+ and max_prepared_statements > 0 (default 200 since 1.24, 0 before). default_pool_size times the number of (database, user) pairs must not exceed PostgreSQL max_connections (minus the superuser_reserved_connections margin).","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":"Find the most expensive queries (pg_stat_statements: total and mean time)","category":"Performance","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":"Lists the normalized queries that consume the most server time in the current database, with their share of the total; the second query lists the slowest ones per call.","when":"First stop when CPU/IO load rises or users report 'the database is slow'; to decide where tuning should start.","caution":"pg_stat_statements collects nothing until it is added to shared_preload_libraries, the server is restarted and CREATE EXTENSION is run in the database. Values are cumulative since the last reset. A stddev_ms that is high relative to mean_ms indicates that the plan or data distribution varies between calls. mean_plan_time is populated only with pg_stat_statements.track_planning = on (default off; enabling it adds overhead). Seeing other users' query texts requires superuser or the pg_read_all_stats role.","since":"PostgreSQL 13","note":"In PostgreSQL 13 the total_time/mean_time columns were renamed to total_exec_time/mean_exec_time and planning times (total_plan_time) got separate columns; use the old names on PG12 and earlier.","tags":["pg_stat_statements","slow query","most expensive query","top sql","performance","query tuning"]},{"id":"pgss-io-temp-heavy","task":"Find queries driving disk I/O, temp files and WAL (pg_stat_statements)","category":"Performance","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":"Shows the queries that read the most blocks from outside shared buffers, write the most to temp files (sorts/hashes that do not fit in work_mem) and generate the most WAL, together with their cache hit ratio.","when":"To find the responsible query when disk read latency rises, temp file alerts fire (log_temp_files) or WAL volume suddenly grows.","caution":"shared_blks_read also counts reads served from the OS page cache; real disk time requires track_io_timing = on (measure its overhead with pg_test_timing first). If temp_written is high, the fix is usually raising work_mem per role/session or fixing the query/index; raising work_mem globally increases memory risk.","since":"PostgreSQL 13","note":"The wal_bytes/wal_records columns arrived in PG13. In PG17 blk_read_time/blk_write_time were renamed to shared_blk_read_time/shared_blk_write_time and local_blk_*_time was added.","tags":["pg_stat_statements","io","temp files","temp_blks_written","work_mem","wal"]},{"id":"pgss-reset","task":"Reset pg_stat_statements statistics (all or a single query)","category":"Performance","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":"Resets pg_stat_statements counters entirely or per user/database/queryid; pg_stat_statements_info shows the last reset time and how many entries were evicted for lack of space.","when":"To start a clean measurement before a tuning change (index, parameter, deploy) or to measure a single query's new plan in isolation.","caution":"A reset cannot be undone; monitoring/APM tools that rely on cumulative counters will show a gap, so take a snapshot first (see the plan regression recipe). If dealloc keeps growing, pg_stat_statements.max is too small (changing it requires a restart). By default only superusers can run it; grant it with GRANT EXECUTE ON FUNCTION.","since":"PostgreSQL 14","note":"Parameterized reset (userid, dbid, queryid) arrived in PG12, the pg_stat_statements_info view in PG14. PG17 added a fourth parameter, minmax_only: pg_stat_statements_reset(0, 0, 0, true) resets only min/max times; the stats_since and minmax_stats_since columns are also PG17.","tags":["pg_stat_statements","reset","clear statistics","queryid","statistics","baseline"]},{"id":"auto-explain-setup","task":"Log execution plans of slow queries with auto_explain","category":"Performance","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":"Writes the execution plan of queries exceeding the threshold to the server log (with actual row counts and buffer data when log_analyze is on); captures the plan produced with the application's real parameters.","when":"When a query is slow only from the application or with specific parameters and cannot be reproduced with a manual EXPLAIN; to catch intermittent plan changes.","caution":"log_analyze = on adds instrumentation overhead to every sampled query, including those below the threshold; log_timing = off and sample_rate < 1 reduce it. log_min_duration = 0 floods the log in production. ALTER SYSTEM SET session_preload_libraries overwrites the existing value; check SHOW session_preload_libraries first. session_preload_libraries does not affect existing sessions; loading it in every process requires shared_preload_libraries plus a restart.","note":"auto_explain is a contrib module and does not need CREATE EXTENSION. The log_wal (PG13+) and log_settings (PG12+) options are also available.","tags":["auto_explain","execution plan","slow query","log","plan capture","explain"]},{"id":"explain-analyze-buffers-wal","task":"Read a plan in depth with EXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL)","category":"Performance","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":"Returns the plan with real execution statistics: BUFFERS shows shared hit/read/dirtied and temp blocks per node, SETTINGS shows non-default parameters that affect planning, WAL shows the WAL records/bytes generated.","when":"To analyze the gap between estimated and actual rows, sorts/hashes spilling to disk (Sort Method: external merge, temp read/written) and the WAL cost of DML.","caution":"ANALYZE really executes the statement; always run DML inside BEGIN/ROLLBACK (triggers and sequence increments still happen). Reading tips: a 10x or larger gap between estimated 'rows' and 'actual rows' points to a statistics problem; for nodes inside loops, time and rows are per loop, multiply by loops for the total; high 'Heap Fetches' on an Index Only Scan means the visibility map is stale, and 'Workers Planned' > 'Workers Launched' means the worker pool is exhausted. Use TIMING OFF where timing overhead is high; COSTS OFF also hides the JIT section.","since":"PostgreSQL 13","note":"The SETTINGS option arrived in PG12, WAL in PG13. PG16 added GENERIC_PLAN, PG17 added SERIALIZE and MEMORY; in PG18 BUFFERS is on by default with ANALYZE.","tags":["explain analyze","buffers","execution plan","reading plans","wal","performance"]},{"id":"plan-regression-queryid","task":"Catch plan regressions by queryid (pg_stat_statements snapshot comparison)","category":"Performance","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":"Saves pg_stat_statements' cumulative counters to a table and compares them with the later state per queryid; shows the mean time and buffers per call of calls made after the snapshot next to the old values.","when":"To find the query whose plan changed after a deploy, major upgrade, statistics refresh or parameter change ('it was fast yesterday, slow today').","caution":"If pg_stat_statements_reset() runs between the snapshot and the comparison, or entries are evicted for lack of space (dealloc), the deltas are meaningless; check pg_stat_statements_info.stats_reset. An increase in buffers per call is a more reliable sign of a plan change than an increase in time, because it is independent of caching. To confirm the plan, EXPLAIN the query with real parameters or use auto_explain; PostgreSQL has no built-in plan pinning (the pg_hint_plan extension is required). Drop the snapshot table when done.","since":"PostgreSQL 14","note":"pg_stat_activity.query_id and the compute_query_id parameter arrived in PG14 (default auto: computed when pg_stat_statements is loaded). The 'Query Identifier' line in EXPLAIN VERBOSE output shows the same value. Steps 1 and 2 also work on PG13.","tags":["plan regression","queryid","pg_stat_statements","snapshot","plan change","performance"]},{"id":"parallel-query-usage","task":"Check parallel query capacity and current worker usage","category":"Performance","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":"Shows parallel query limits and how many parallel workers each leader process is using right now; demonstrates the per-table worker count setting.","when":"When analytic queries are slower than expected, the plan shows 'Workers Planned' > 'Workers Launched', or CPU cores sit idle.","caution":"max_parallel_workers is capped by max_worker_processes (restart required), and that pool is shared with logical replication workers and other background workers. In OLTP, a high max_parallel_workers_per_gather saturates CPU under concurrency; each worker also uses its own work_mem. Queries that write data or lock rows (except CREATE TABLE AS / SELECT INTO), cursors and queries calling PARALLEL UNSAFE functions do not get parallel plans. ALTER TABLE ... SET (parallel_workers) takes a brief ShareUpdateExclusiveLock.","since":"PostgreSQL 13","note":"pg_stat_activity.leader_pid arrived in PG13; before that, parallel workers only appear as backend_type = 'parallel worker' and cannot be tied to their leader.","tags":["parallel query","parallel worker","leader_pid","max_parallel_workers","gather","cpu"]},{"id":"jit-impact-check","task":"Measure the impact of JIT compilation on query time","category":"Performance","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":"Shows, per query, the share of JIT compilation time (generation, inlining, optimization, emission) in execution time; measures the net effect by running the same query with JIT on and off.","when":"When short OLTP or reporting queries show unexplained delays of tens to hundreds of ms; especially on heavily partitioned tables where inflated cost estimates trigger JIT.","caution":"JIT kicks in only when the plan cost exceeds jit_above_cost (default 100000); with inflated cost estimates, compilation can take longer than execution for short queries. Rather than disabling it globally, first consider ALTER ROLE ... SET jit = off for the affected role/database or raising jit_above_cost. EXPLAIN (ANALYZE, COSTS OFF) does not print the JIT section. If PostgreSQL was built without LLVM support, JIT is never used; check with SELECT pg_jit_available();","since":"PostgreSQL 15","note":"The jit_* columns were added to pg_stat_statements in PG15; PG17 added jit_deform_count/jit_deform_time. JIT was introduced in PG11 and is on by default since PG12.","tags":["jit","llvm","jit_above_cost","pg_stat_statements","compilation time","performance"]},{"id":"blocking-tree","task":"Build the blocking tree (find the root blocker)","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":"Follows the wait chain recursively with pg_blocking_pids(); starting from the root blocker (which waits for nobody), shows every waiting session as an indented tree with the PIDs directly blocking it and its transaction age.","when":"When the application sees mass timeouts or queues piling up; to find the single root session that needs intervention among dozens of waiters.","caution":"The root is often a forgotten 'idle in transaction' session holding the lock; the query column shows that session's LAST statement, not the one that took the lock. A DDL waiting for ACCESS EXCLUSIVE also blocks simple SELECTs queued behind it and shows up as an intermediate node. pg_blocking_pids() briefly locks the lock manager; do not run this query in a tight loop. A session reachable through several branches is shown once, at its deepest level.","since":"PostgreSQL 9.6","tags":["blocking","lock tree","lock chain","pg_blocking_pids","blocking session","lock"]},{"id":"lock-wait-detail","task":"Inspect lock waits in detail by object and lock mode (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":"For every ungranted lock request (granted = false), shows the target object (table, transaction id, etc.), the requested mode, the blocking PID and its mode, whether the blocker actually holds the lock, and the approximate wait time.","when":"To understand which table and lock mode conflict causes the blocking; e.g. a DDL waiting for AccessExclusiveLock, or a row lock showing up as a transactionid wait.","caution":"Because matching uses pg_blocking_pids(), only truly conflicting requests are listed. blocker_holds = false means the blocker does not hold the lock yet but is queued ahead with a conflicting mode (lock queue). locktype = 'transactionid' is a row-level conflict: the waiter waits until the transaction that modified the row ends; the row itself is not visible in pg_locks. pg_locks can contain thousands of rows on large systems; do not run this frequently. waiting_for is an approximation based on state_change.","since":"PostgreSQL 9.6","tags":["pg_locks","lock wait","lock waiting","accessexclusivelock","transactionid","lock"]},{"id":"long-idle-transactions","task":"Find long-running and 'idle in transaction' transactions","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":"Shows sessions whose open transaction is older than 5 minutes or that sit idle inside a transaction; with transaction age, time in the current state, assigned XID and the age of the xmin they hold.","when":"When bloat grows, VACUUM reports 'dead row versions cannot be removed yet', during lock waits, or when a connection pool leak is suspected.","caution":"Long transactions (especially REPEATABLE READ/SERIALIZABLE or those holding open cursors) hold back VACUUM's cleanup horizon (xmin horizon) via backend_xmin, causing bloat and XID aging across the database. 'idle in transaction (aborted)' is a session that hit an error and is waiting for ROLLBACK. Permanent fix: per-role idle_in_transaction_session_timeout and correct transaction boundaries in the application.","tags":["long transaction","idle in transaction","xmin","bloat","session","long running"]},{"id":"terminate-idle-in-transaction","task":"Terminate long 'idle in transaction' sessions in bulk, in a controlled way","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":"First lists sessions that have left a transaction open and idle for a given time, then terminates them; the timeout parameter waits until the process has actually exited (or the timeout expires).","when":"For emergency intervention when forgotten sessions holding locks cause blocking, or hold back the xmin horizon and stall VACUUM.","caution":"pg_cancel_backend does nothing for an 'idle in transaction' session (there is no running query to cancel); pg_terminate_backend is required and all uncommitted changes are rolled back. Narrow the filter by usename/application_name; exclude replication, backup and monitoring sessions. Connection pools will reopen terminated connections. If the timeout expires, the function emits a warning and returns false. As a permanent fix, set idle_in_transaction_session_timeout per role.","since":"PostgreSQL 14","note":"The two-argument pg_terminate_backend(pid, timeout) arrived in PG14; before that only pg_terminate_backend(pid) exists and it returns right after sending the signal.","tags":["pg_terminate_backend","idle in transaction","terminate session","kill session","bulk","lock"]},{"id":"role-timeouts","task":"Set statement_timeout / lock_timeout per role and database","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":"Makes timeout parameters persistent at the role (and optionally database) level, so each workload runs with its own limits without touching the global setting.","when":"To keep runaway queries from exhausting resources, DDL/lock queues from freezing the application, and forgotten transactions from producing bloat.","caution":"Settings apply to NEW sessions only; recycle existing pooled connections. Users can override the value with SET in their session; this is not a security boundary. Do not set statement_timeout globally in postgresql.conf: maintenance jobs such as pg_dump, VACUUM and CREATE INDEX get cancelled too. Precedence: role+database > role > database > postgresql.conf.","note":"idle_in_transaction_session_timeout arrived in PG9.6, idle_session_timeout in PG14, transaction_timeout in PG17.","tags":["statement_timeout","lock_timeout","idle_in_transaction_session_timeout","alter role","query timeout","timeout"]},{"id":"ddl-lock-timeout-retry","task":"Apply DDL safely with lock_timeout and retries (avoid lock queues)","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":"Keeps a DDL that needs ACCESS EXCLUSIVE from waiting indefinitely when it cannot get the lock, and from blocking every query that queues up behind it (lock queue); retries at short intervals.","when":"When applying quick but heavily locking changes such as ALTER TABLE, CREATE/DROP TRIGGER, ATTACH PARTITION or RENAME on busy tables in production.","caution":"Without lock_timeout, an ALTER TABLE queued behind a single long SELECT also makes every subsequent read/write wait and can exhaust the connection pool within minutes. lock_timeout only limits the lock wait; it does not limit the runtime of DDL that rewrites the table (e.g. changing a column type). The DO block is a single transaction and holds a snapshot across attempts; for long retry scenarios prefer an external shell loop.","tags":["lock_timeout","ddl","alter table","lock queue","retry","lock"]},{"id":"connection-usage","task":"Measure connection usage against 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":"Shows used connection slots and those left for regular users relative to max_connections, the breakdown by database/user/application/state, and configured CONNECTION LIMITs.","when":"On 'too many clients already' / 'remaining connection slots are reserved' errors, during capacity planning, or when evaluating the need for a pooler.","caution":"max_connections limits client backends only; autovacuum workers, background workers and walsenders use separate slots. The last superuser_reserved_connections slots (plus reserved_connections on PG16+) are closed to regular users. Instead of raising max_connections into the hundreds or thousands, use a pooler such as PgBouncer: every connection is a separate process, and memory and snapshot costs grow with the connection count. Many 'idle' sessions indicate an oversized application pool. Prevent a runaway application from consuming all slots with ALTER ROLE ... CONNECTION LIMIT; changing max_connections requires a restart.","note":"The reserved_connections parameter and the pg_use_reserved_connections role arrived in PG16; the query also works on older versions (the parameter counts as 0 when absent).","tags":["max_connections","connection count","connection","pooler","superuser_reserved_connections","session"]},{"id":"autovacuum-progress","task":"Monitor the progress of running autovacuum/VACUUM operations","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":"Shows, for every running VACUUM (autovacuum worker or manual), the table, phase, heap percentage scanned/vacuumed, number of index vacuum passes and wait event; plus the number of busy autovacuum workers.","when":"To estimate when a VACUUM on a large table will finish, whether all autovacuum workers are busy (tables waiting in the queue), and whether vacuum is waiting because of throttling.","caution":"index_vacuum_count > 1 means the dead tuple list did not fit in memory and indexes were scanned multiple times; increase maintenance_work_mem/autovacuum_work_mem (before PG17 at most 1GB is used for this). wait = Timeout:VacuumDelay is cost-based throttling (autovacuum_vacuum_cost_delay/cost_limit). Do not terminate an autovacuum whose query text says 'to prevent wraparound'; it restarts immediately. VACUUM FULL and CLUSTER appear in pg_stat_progress_cluster, not in this view.","since":"PostgreSQL 9.6","note":"In PG17 the max_dead_tuples/num_dead_tuples columns changed to max_dead_tuple_bytes/num_dead_item_ids and indexes_total/indexes_processed were added; the query therefore uses version-independent columns.","tags":["autovacuum","vacuum progress","pg_stat_progress_vacuum","vacuum","maintenance","progress"]},{"id":"xid-wraparound-risk","task":"Measure transaction ID wraparound risk (XID age per database and 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":"Shows each database's oldest unfrozen XID and multixact age relative to autovacuum_freeze_max_age and the wraparound limit; in the current database, the oldest tables (including TOAST) with size and last vacuum time.","when":"In routine monitoring (typical thresholds: pct_of_wraparound 50% warning / 75% critical), when the log shows 'must be vacuumed within N transactions', and to prioritize freeze work.","caution":"For tables where pct_of_freeze_max_age exceeds 100, autovacuum starts a mandatory (anti-wraparound) vacuum; it is I/O-heavy and restarts even if terminated. If the age does not drop, the cause is almost always something holding back the xmin horizon (long transaction, prepared transaction, replication slot, hot_standby_feedback); fix that first, otherwise even VACUUM FREEZE cannot lower the age. Close to the limit the server first warns, then refuses to assign new XIDs (writes) when only a few million remain. For a targeted freeze: VACUUM (FREEZE, VERBOSE) <table>;","note":"PG14 added vacuum_failsafe_age / vacuum_multixact_failsafe_age (default 1.6 billion): when a table reaches that age, VACUUM switches to an emergency mode that skips cost delay and index cleanup.","tags":["wraparound","xid age","datfrozenxid","relfrozenxid","freeze","multixact"]},{"id":"xmin-horizon-holders","task":"Find the xmin horizon holders that stop VACUUM from removing 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":"Lists, ordered by xmin age, the four sources that can block dead tuple cleanup: sessions holding snapshots, prepared transactions, replication slots, and standbys sending hot_standby_feedback.","when":"When VACUUM VERBOSE reports 'dead row versions cannot be removed yet', bloat keeps growing and the XID age does not drop.","caution":"The oldest xmin defines the cleanup horizon; replication slots and standby feedback affect all databases. Prepared transactions survive restarts; verify the owner and close them with COMMIT PREPARED / ROLLBACK PREPARED '<gid>'. Dropping an unused slot breaks its downstream (standby, CDC); verify the owner first. Run on a standby, it shows only that standby's sessions.","tags":["xmin horizon","vacuum blocker","dead tuple","prepared transaction","replication slot","bloat"]},{"id":"pgstattuple-bloat","task":"Measure table and index bloat with 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":"Shows the live/dead tuple and free space percentages of a table (fast estimate and exact) and the reclaimable space; for B-tree indexes, leaf density and fragmentation.","when":"When the n_dead_tup estimate in pg_stat_user_tables is not enough; to back a pg_repack, VACUUM FULL or REINDEX decision with data.","caution":"pgstattuple and pgstatindex read the whole relation and generate heavy I/O on large tables (they take only AccessShareLock). Use pgstattuple_approx first. By default superuser or the pg_stat_scan_tables role is required. Some free space is normal (fillfactor, room for HOT updates); VACUUM makes it reusable but does not shrink the file; reclaiming it requires pg_repack or VACUUM FULL (ACCESS EXCLUSIVE lock!). Rebuild an index with low avg_leaf_density using REINDEX CONCURRENTLY.","since":"PostgreSQL 12","note":"pgstattuple is a contrib extension; pgstattuple_approx arrived in PG9.5. MATERIALIZED in the CTE (PG12+) prevents pgstatindex from being called, and failing, on non-B-tree indexes.","tags":["bloat","pgstattuple","pgstatindex","index bloat","dead tuple","free space"]},{"id":"pg-repack-online","task":"Reorganize a bloated table without long locks (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":"Builds a compacted copy of the table in the background, captures concurrent changes into a log table via a trigger, and swaps the tables at the end under a brief ACCESS EXCLUSIVE lock; unlike VACUUM FULL it does not block reads/writes for the duration.","when":"When severe bloat that VACUUM cannot return (verified with pgstattuple) must be reclaimed on a live system.","caution":"The table must have a PRIMARY KEY or a UNIQUE index on NOT NULL columns. It temporarily needs extra disk space equal to the table plus its indexes and generates heavy WAL (replica lag, archive volume). ACCESS EXCLUSIVE locks are needed at the start and at the end; when --wait-timeout expires the default is to cancel conflicting queries and, if needed, terminate their sessions, while --no-kill-backend skips the table instead. Do not run DDL on the target table while it runs. Client and extension versions must match. If interrupted, clean up leftovers with DROP EXTENSION pg_repack CASCADE; CREATE EXTENSION pg_repack;","note":"pg_repack is not part of core PostgreSQL (separate package, e.g. postgresql-17-repack).","tags":["pg_repack","bloat","online","vacuum full alternative","table reorganization","repack"]},{"id":"invalid-indexes","task":"Find and clean up INVALID indexes","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":"Lists invalid indexes left behind by failed CREATE INDEX CONCURRENTLY or REINDEX CONCURRENTLY runs, with their size, and generates the drop command.","when":"After failed or cancelled online index operations, in routine health checks and before a major upgrade.","caution":"An in-progress CREATE INDEX CONCURRENTLY / REINDEX CONCURRENTLY also shows as INVALID; check pg_stat_progress_create_index before dropping. An INVALID index is not used by queries, but if indisready = true it is still maintained on every INSERT/UPDATE, i.e. it is pure overhead. It can be rebuilt with REINDEX INDEX CONCURRENTLY, but this fails again unless the cause (e.g. a unique violation) is fixed. REINDEX CONCURRENTLY leftovers carry the _ccnew/_ccold suffix. DROP INDEX CONCURRENTLY cannot run inside a transaction block.","tags":["invalid index","indisvalid","create index concurrently","broken index","_ccnew","index"]},{"id":"extended-statistics","task":"Create extended statistics for correlated columns (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":"Teaches the planner about cross-column correlation (functional dependencies, combined distinct counts, joint most-common values); fixes row estimates for multi-column WHERE and GROUP BY conditions.","when":"When EXPLAIN ANALYZE shows estimated rows far below actual for multi-column filters (e.g. 12 vs. 570) and a wrong join method/index is chosen as a result.","caution":"Takes effect only after ANALYZE. When the number of column combinations is far above the statistics target (default 100), the mcv list cannot cover most of them, and mcv may then fail to fix an estimate that dependencies would fix. Verify with EXPLAIN; if needed drop mcv from the definition or raise the target with ALTER STATISTICS customer_city_district_stx SET STATISTICS 1000;. dependencies is used only for equality/IN conditions, ndistinct for GROUP BY estimates. Every definition lengthens ANALYZE; add it only to column groups with a proven misestimate.","since":"PostgreSQL 12","note":"CREATE STATISTICS arrived in PG10 (ndistinct, dependencies), the mcv kind in PG12, ALTER STATISTICS ... SET STATISTICS in PG13, expression statistics in PG14.","tags":["extended statistics","create statistics","correlation","row estimate","cardinality","analyze"]},{"id":"analyze-staleness","task":"Find tables with stale statistics (ANALYZE strategy)","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":"Shows the number of rows modified since the last ANALYZE, its ratio to live rows, and the approximate modification threshold at which autovacuum will trigger ANALYZE.","when":"After bulk loads/deletes, when plans show wrong row estimates, or when autoanalyze is suspected of lagging on large tables.","caution":"The computed threshold uses the global settings; if table-specific autovacuum_analyze_* reloptions exist (pg_class.reloptions), the real threshold differs. On large tables the default scale_factor (0.1) triggers far too late; lower it per table. Autovacuum does not ANALYZE the parent of a partitioned table; parent-level statistics need a manual ANALYZE. Temp tables are never seen by autovacuum. ANALYZE reads a sample and takes ShareUpdateExclusiveLock (does not block DML).","tags":["analyze","stale statistics","n_mod_since_analyze","autoanalyze","statistics","planner"]},{"id":"visibility-map-index-only","task":"Check visibility map coverage and Index Only Scan efficiency","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":"Shows the all-visible page ratio of tables, rows inserted since the last vacuum, and how often an Index Only Scan had to visit the heap (Heap Fetches).","when":"When an Index Only Scan underperforms; on insert-heavy/append-only tables and when validating a covering (INCLUDE) index design.","caution":"relpages/relallvisible are estimates recorded by the last VACUUM/ANALYZE. Only VACUUM sets visibility map bits; every UPDATE/DELETE clears the bit of the affected page. For a low ratio on frequently updated tables, lower autovacuum_vacuum_scale_factor per table. pg_visibility_map_summary reads the small VM fork, not the heap.","since":"PostgreSQL 13","note":"n_ins_since_vacuum and insert-triggered autovacuum (autovacuum_vacuum_insert_threshold/scale_factor) arrived in PG13; before that, append-only tables may see no vacuum until an anti-wraparound vacuum, so the visibility map is not set.","tags":["visibility map","index only scan","heap fetches","relallvisible","pg_visibility","vacuum"]},{"id":"range-partition-monthly","task":"Create a monthly range-partitioned table (generate partitions in a loop)","category":"Partitioning","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":"Creates a RANGE-partitioned parent on a date column, an index that propagates to every partition, and 12 monthly partitions in a loop; then lists the hierarchy with bounds and sizes.","when":"For large, ever-growing tables that drop old data periodically (DETACH/DROP) or whose queries mostly filter by date range (orders, logs, time series).","caution":"On a partitioned table, PRIMARY KEY/UNIQUE constraints must include the partition key (there are no global unique indexes). The lower bound is inclusive, the upper bound exclusive (FROM inclusive, TO exclusive). Without a job that pre-creates future partitions (pg_cron, pg_partman), out-of-range INSERTs fail. Thousands of partitions increase planning time and lock overhead. CREATE INDEX CONCURRENTLY is not supported on the parent: use CREATE INDEX ... ON ONLY on the parent, CREATE INDEX CONCURRENTLY on each partition, then ALTER INDEX ... ATTACH PARTITION.","since":"PostgreSQL 12","note":"Declarative partitioning arrived in PG10, indexes/PRIMARY KEY on the parent and default partitions in PG11, pg_partition_tree() in PG12.","tags":["partition","range partition","declarative partitioning","monthly partition","pg_partition_tree","partitioning"]},{"id":"attach-partition-safely","task":"Attach a prepared table as a partition with a short lock (ATTACH PARTITION)","category":"Partitioning","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":"Turns data loaded into a separate table into a partition, without a full scan or long locks, by using a pre-validated CHECK constraint that matches the bounds and pre-built indexes.","when":"For bulk loads (ETL) or restores from archive: prepare the data without touching the live table, then bring it online in one step.","caution":"ATTACH takes SHARE UPDATE EXCLUSIVE on the parent and ACCESS EXCLUSIVE on the table being attached. Without a matching valid CHECK, the table is scanned in full under that lock to validate the bounds; if indexes matching the parent's are missing, ATTACH builds them under the lock, so prepare both in advance. If a default partition exists, it is also scanned to prove it holds no rows in the new range (see the default partition recipe). Column names and types must match the parent exactly; creating the table with LIKE ... INCLUDING is the safest way.","since":"PostgreSQL 12","note":"ATTACH PARTITION taking only SHARE UPDATE EXCLUSIVE on the parent arrived in PG12; in PG11 and earlier the parent is locked with ACCESS EXCLUSIVE.","tags":["attach partition","add partition","check constraint","not valid","etl","partitioning"]},{"id":"detach-partition-concurrently","task":"Detach an old partition without blocking live traffic (DETACH CONCURRENTLY)","category":"Partitioning","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":"Detaches the partition in two transactions; takes only SHARE UPDATE EXCLUSIVE on the parent so running queries are not blocked, and adds a CHECK constraint equivalent to the partition bound to the detached table.","when":"When detaching old periods on a live system for retention/archiving (followed by DROP, a move to another tablespace, or a pg_dump archive).","caution":"Cannot run inside a BEGIN ... COMMIT block and cannot be used when the parent has a default partition. It waits for all transactions that use the parent and started before it; long transactions prolong it. If interrupted, the partition stays hidden from queries but not detached ('detach pending') and must be completed with FINALIZE. A plain DETACH without CONCURRENTLY is faster but needs ACCESS EXCLUSIVE on the parent; try it with lock_timeout.","since":"PostgreSQL 14","tags":["detach partition","concurrently","finalize","retention","archiving","partitioning"]},{"id":"default-partition-pitfall","task":"Default partition pitfall: new partition fails with 'would be violated by some row'","category":"Partitioning","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":"Counts rows that landed in the default partition per period; moves rows within the new partition's range into a new table and attaches it, all in one transaction.","when":"When CREATE TABLE ... PARTITION OF or ATTACH fails with 'updated partition constraint for default partition would be violated by some row', or when the default partition is found to be growing.","caution":"With a default partition present, every new partition scans the default in full and holds ACCESS EXCLUSIVE on it meanwhile; a large default partition means an outage at every maintenance. The default is closed to writes while rows are moved; do it during low traffic. Permanent fix: pre-create partitions and keep the default empty with monitoring (alert on row count > 0), or do not use one at all, so out-of-range INSERTs fail and the problem surfaces early. A default partition also prevents DETACH ... CONCURRENTLY.","since":"PostgreSQL 11","tags":["default partition","partition error","violated by some row","partition constraint","pitfall","partitioning"]},{"id":"partition-pruning-check","task":"Verify that partition pruning works (plan time and run time)","category":"Partitioning","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":"Compares plan-time pruning, run-time pruning with a generic plan ('Subplans Removed: N'), and how pruning is lost when a function is applied to the partition key.","when":"When queries on a partitioned table are slower than expected, the plan scans every partition, or the application uses an ORM/prepared statements.","caution":"Applying a function or cast to the partition key (date_trunc(created_at), created_at::text) disables pruning; write the condition as a range on the key. With a generic plan, pruning happens at executor startup and shows only as 'Subplans Removed'; for STABLE expressions such as now() it is also visible in EXPLAIN without ANALYZE. With enable_partition_pruning = off no pruning happens at all. With many partitions, measure Planning Time too.","since":"PostgreSQL 12","note":"Run-time pruning arrived in PG11; plan_cache_mode arrived in PG12, which also greatly improved planning performance with many partitions.","tags":["partition pruning","subplans removed","generic plan","plan_cache_mode","explain","partitioning"]},{"id":"pg-partman-setup","task":"Automate partition creation and retention with pg_partman","category":"Partitioning","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":"Registers a parent table with pg_partman; pre-creates future partitions, drops those past the retention period and reports rows that landed in the default partition.","when":"When you need standard, observable automation instead of managing daily/monthly partitions by hand or with custom scripts (log, metric, event tables).","caution":"The API changed between pg_partman 4.x and 5.x: in 5.x p_type accepts only 'range'/'list' (default 'range'), and the 4.x p_type := 'native' is invalid; check the installed version with SELECT extversion FROM pg_extension WHERE extname = 'pg_partman';. create_parent also creates a default partition by default. retention_keep_table = false permanently DROPs partitions; if you need an archive, keep it true and use retention_schema. If run_maintenance_proc() is not scheduled, no new partitions are created and data lands in the default. pg_partman_bgw requires shared_preload_libraries plus a restart.","note":"pg_partman is not part of core PostgreSQL (separate package, e.g. postgresql-17-partman). The commands use pg_partman 5.x syntax.","tags":["pg_partman","partition maintenance","retention","create_parent","automatic partitioning","partitioning"]},{"id":"least-privilege-roles","task":"Build a least-privilege role hierarchy (GRANT + ALTER DEFAULT PRIVILEGES)","category":"Security","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":"Builds a hierarchy of owner, read-only and read-write group roles; grants the right privileges on existing and future tables/sequences, and authorizes application users only through group membership.","when":"When setting up a new application schema, fixing applications that connect as superuser or table owner, and simplifying privileges before an audit.","caution":"ALTER DEFAULT PRIVILEGES applies only to objects created LATER by the role named in FOR ROLE; if the migration tool creates tables as a different user, the privileges are not applied, so run DDL with SET ROLE app_owner. GRANT ... ON ALL TABLES covers only objects that exist at that moment. Databases created on PG15+ already do not grant CREATE on the public schema to PUBLIC; databases upgraded from older versions need the REVOKE. REVOKE ALL ON DATABASE ... FROM PUBLIC also removes CONNECT and TEMP; grant them separately to other roles that need them. Use psql \\password instead of putting passwords on the command line (they stay out of logs and history).","note":"The pg_read_all_data / pg_write_all_data roles arrived in PG14, the INHERIT/SET options on role membership (GRANT ... WITH INHERIT FALSE) in PG16.","tags":["least privilege","role hierarchy","alter default privileges","grant","privilege","security"]},{"id":"row-level-security","task":"Set up tenant isolation with row-level security","category":"Security","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":"Enables RLS on the table and defines a policy that filters rows by the tenant id in a session variable (USING) and blocks writes for another tenant (WITH CHECK).","when":"To enforce data isolation in the database for multi-tenant applications instead of relying on application code.","caution":"Superusers and roles with the BYPASSRLS attribute always bypass policies; the table owner is subject to them only with FORCE ROW LEVEL SECURITY. With RLS enabled and no matching policy, the result is empty (no error). current_setting(..., true) returns NULL when the variable is undefined; NULLIF prevents the empty string left after RESET from failing as ''::int. Users can change app.tenant_id with SET; only a trusted application layer should set it and end users must not get SQL access. With a pooler in transaction mode, use SET LOCAL instead of SET. Index the tenant_id column used in the policy. pg_dump fails when run as a user subject to RLS; take backups with a BYPASSRLS role.","since":"PostgreSQL 9.5","tags":["row level security","rls","create policy","multi-tenant","tenant isolation","security"]},{"id":"pg-hba-validate-reload","task":"Validate pg_hba.conf rule order and reload safely","category":"Security","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":"Shows where pg_hba.conf lives, how PostgreSQL interprets it (line, type, database, user, address, method) and any syntax errors before the reload; then reloads the configuration.","when":"When adding a new client network/application, migrating from md5 to scram-sha-256, or investigating 'no pg_hba.conf entry for host' errors.","caution":"pg_hba_file_rules shows the file as it is now, not the active rules. If any line has a non-empty error, a reload does NOT load pg_hba.conf at all and the old rules stay in effect (the log says 'pg_hba.conf was not reloaded'). Changes affect new connections only. A broad rule placed early (e.g. host all all 0.0.0.0/0 trust) neutralizes every restriction after it. With SSL off, hostssl lines cannot match and show a message in the error column. By default only superusers can read the view. Make sure tools editing the file as root do not change its ownership; if the postgres user cannot read it, the reload fails.","since":"PostgreSQL 10","note":"pg_hba_file_rules arrived in PG10; newer versions also have the rule_number and file_name columns. PG16 supports include directives in pg_hba.conf and regex matching in the user/database fields.","tags":["pg_hba.conf","authentication","rule order","reload","pg_hba_file_rules","security"]},{"id":"ssl-enable-verify","task":"Enable SSL/TLS and verify encrypted connections (pg_stat_ssl)","category":"Security","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":"Enables TLS with a server certificate without a restart, sets the minimum protocol version, and lists whether current connections are encrypted, with TLS version and cipher.","when":"For security audit/compliance requirements (PCI, GDPR), to protect passwords and data in transit over the network.","caution":"ssl = on alone does not enforce encryption; clients can still connect unencrypted with sslmode=prefer/disable. To enforce it, use hostssl instead of host in pg_hba.conf (and add hostnossl ... reject). sslmode=require does not verify the server identity (open to MITM); use verify-full for certificate verification. The key file must be owned by the postgres user with mode 0600 (or root-owned with 0640); otherwise SSL is not loaded, so check the log and SHOW ssl after the reload. Certificate renewals are also picked up by a reload.","since":"PostgreSQL 10","note":"Changing SSL settings with a reload instead of a restart arrived in PG10, ssl_min_protocol_version in PG12; its default is TLSv1.2 since PG13.","tags":["ssl","tls","pg_stat_ssl","hostssl","certificate","encryption"]},{"id":"scram-migration","task":"Migrate from md5 passwords to scram-sha-256","category":"Security","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":"Makes new passwords stored as SCRAM-SHA-256, finds login roles that still carry md5 hashes, and gives the steps to switch pg_hba.conf to scram-sha-256 once passwords are renewed.","when":"When a security audit flags md5, after upgrading to PG14+, or when preparing for versions where md5 is deprecated.","caution":"SCRAM cannot be derived from existing md5 hashes; every password must be set again in clear text (requires coordination with application teams). The 'md5' method in pg_hba.conf automatically performs SCRAM authentication for roles whose password is SCRAM, so keeping md5 during the migration is a safe intermediate step, but once the method is 'scram-sha-256' roles with md5 passwords can no longer connect. Old client drivers (libpq < 10, old JDBC/.NET drivers) may not support SCRAM; upgrade drivers first. ALTER ROLE ... PASSWORD may write the password to the log via log_statement; psql \\password hashes it on the client before sending.","since":"PostgreSQL 10","note":"SCRAM-SHA-256 arrived in PG10, and the password_encryption default became scram-sha-256 in PG14. PG18 deprecates md5 passwords and emits a warning.","tags":["scram-sha-256","md5","password_encryption","password","authentication","security"]},{"id":"pgaudit-object-audit","task":"Keep role- and object-level audit logs with pgaudit","category":"Security","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":"Records server-wide DDL and privilege changes, all reads/writes of a specific role, and access to sensitive tables/columns granted to the auditor role, as structured log lines prefixed with 'AUDIT:'.","when":"To prove who accessed sensitive data in compliance audits such as GDPR, PCI-DSS or SOX.","caution":"Enabling pgaudit.log = 'all' or 'read' server-wide sharply increases log volume and I/O; narrow the scope with role or object auditing. A superuser can switch auditing off (ALTER SYSTEM/ALTER ROLE); ship the records to a central log system (syslog/SIEM) and store them independently of the database server. log_parameter = on also logs bind parameters (personal data). Role settings take effect in new sessions. The pgaudit major version must match the PostgreSQL major version (pgaudit 17.x = PG17).","note":"pgaudit is not part of core PostgreSQL (separate package, e.g. postgresql-17-pgaudit).","tags":["pgaudit","audit","auditing","object audit","log","security"]},{"id":"memory-settings-check","task":"Review memory settings (shared_buffers, work_mem, maintenance_work_mem)","category":"Parameters","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":"Shows memory-related parameters with their value, source (default/configuration file) and change context (postmaster = restart); rough memory ceilings for work_mem and autovacuum; and temp file volume per database.","when":"When building a new server, after adding RAM, after an OOM killer event, or when heavy temp file usage is seen.","caution":"work_mem is allocated not per connection but per sort/hash NODE (times hash_mem_multiplier for hashes) and separately for each parallel worker; a complex query can use several multiples of work_mem, so the computed product is a rough warning, not an upper bound. Typical starting points: shared_buffers ~25% of RAM (restart required), effective_cache_size ~50-75% (a planner hint only, allocates nothing), maintenance_work_mem 512MB-2GB. With autovacuum_work_mem = -1, each autovacuum worker may use maintenance_work_mem. If temp_bytes is high, raise work_mem per role or query, not globally. With large shared_buffers on Linux, consider huge_pages.","note":"hash_mem_multiplier arrived in PG13 (default 2.0 since PG15).","tags":["shared_buffers","work_mem","maintenance_work_mem","memory","memory tuning","temp_bytes"]},{"id":"pending-restart-config-errors","task":"Show parameters pending restart and configuration file errors","category":"Parameters","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":"Shows parameters changed in the files but waiting for a restart, configuration file lines that were not applied or have errors, and which file/line a parameter's effective value comes from.","when":"To verify after an ALTER SYSTEM or postgresql.conf change that the reload had the expected effect, and to list pending changes before a planned restart.","caution":"pg_file_settings reads the files as they are now; if a parameter is defined in several places, only the last one has applied = true (postgresql.auto.conf is read last, so ALTER SYSTEM always wins). A line with applied = false and an empty error is shadowed by another definition; it is not an error. A parameter that needs a restart shows error 'setting could not be applied'. A faulty file may be applied partially on reload; after a change, run pg_reload_conf() and then check these queries and the server log. Do not edit postgresql.auto.conf by hand; use ALTER SYSTEM RESET.","since":"PostgreSQL 9.5","tags":["pending_restart","pg_file_settings","restart","configuration error","alter system","config"]},{"id":"checkpoint-stats","task":"Review checkpoint behavior (PG17+ pg_stat_checkpointer, PG16 and earlier pg_stat_bgwriter)","category":"Parameters","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":"Shows the ratio of scheduled checkpoints to checkpoints forced by WAL volume, average write/sync time per checkpoint and the volume of buffers written (on PG16 and earlier, together with bgwriter and backend writes).","when":"When write-heavy periods show I/O spikes, WAL generation is high, or the log warns 'checkpoints are occurring too frequently'.","caution":"A high req_pct (typically above 10-20%) means checkpoints are forced because max_wal_size fills up; increase max_wal_size and watch for 'checkpoint starting: wal' lines with log_checkpoints = on. A longer checkpoint_timeout spreads I/O but lengthens crash recovery. On PG16 and earlier, a high written_by_backends means backends must write dirty pages themselves (shared_buffers/bgwriter insufficient); on PG17 this is in pg_stat_io. Averages also count scheduled checkpoints skipped for lack of work, so read them as rough figures. Counters are cumulative since stats_reset.","since":"PostgreSQL 17","note":"In PG17 checkpointer statistics moved from pg_stat_bgwriter to the new pg_stat_checkpointer view (checkpoints_timed → num_timed, checkpoints_req → num_requested, buffers_checkpoint → buffers_written); buffers_backend and buffers_backend_fsync were removed. PG18 added the num_done and slru_written columns.","tags":["checkpoint","pg_stat_checkpointer","pg_stat_bgwriter","max_wal_size","checkpoint_timeout","wal"]},{"id":"relation-size-breakdown","task":"List table size broken down into heap / index / TOAST, and the largest indexes","category":"Storage","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":"Shows the largest tables broken down into total, heap, index and TOAST (+FSM/VM) with their share of the database; and the largest indexes with scan counts and unique/primary flags.","when":"To find where growth comes from (data, indexes or TOAST) as disk usage rises, and to spot large unused indexes.","caution":"Size functions touch the file system for every relation and can be slow in databases with tens of thousands of tables. A partitioned parent (relkind 'p') is 0 bytes itself and is excluded; for the total of a hierarchy: SELECT pg_size_pretty(sum(pg_total_relation_size(relid))) FROM pg_partition_tree('sales'); Large indexes with idx_scan = 0 are drop candidates, but counters cover only the time since the last statistics reset and only this server (usage on replicas is not visible); unique/primary indexes enforce constraints and are not dropped.","tags":["table size","index size","toast","disk usage","pg_total_relation_size","storage"]},{"id":"extension-update-check","task":"Find extensions with pending updates and update them","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":"Compares each extension's installed version in the database with the default version of the package on the server, and generates the ALTER EXTENSION command for those with pending updates.","when":"After PostgreSQL minor/major upgrades and extension package updates, and during periodic maintenance.","caution":"Upgrading the OS package does not update the SQL side automatically; ALTER EXTENSION ... UPDATE must be run in every database separately (especially important after pg_upgrade). The update script may take locks and alter objects (e.g. PostGIS); test it first and read the release notes. To move to a specific version: ALTER EXTENSION pgstattuple UPDATE TO '1.5'; Some extensions (e.g. pg_stat_statements) also require a restart to reload the shared library.","tags":["alter extension update","extension version","extension","pg_available_extensions","upgrade","update"]},{"id":"pg-stat-io-analysis","task":"Analyze where I/O comes from with pg_stat_io (backend type, object, context)","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":"Shows reads, writes, extends, evictions and fsyncs by backend type (client backend, autovacuum, checkpointer...), object (relation/temp relation) and context (normal, vacuum, bulkread, bulkwrite); and which process types the writes come from.","when":"During an I/O bottleneck, to tell whether the load comes from queries, autovacuum or checkpoints, and to validate shared_buffers/bgwriter settings.","caution":"pg_stat_io is cluster-wide and cumulative since the last reset; take the difference between two samples to see change (to reset: SELECT pg_stat_reset_shared('io');). read_time/write_time are populated only with track_io_timing = on. reads also includes reads served from the OS page cache. High client backend writes or evictions mean backends write buffers themselves to free space; review shared_buffers, bgwriter_lru_maxpages and checkpoint settings. Rows with context = 'vacuum'/'bulkread' reflect ring buffer usage; reuses there are normal.","since":"PostgreSQL 16","note":"pg_stat_io arrived in PG16. PG18 removed the op_bytes column, added read_bytes/write_bytes/extend_bytes and also reports WAL I/O in this view; on PG18 use read_bytes instead of 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":"Rotate the binary log","category":"Binlog & Log","command":"FLUSH BINARY LOGS;","what":"Closes the current binary log file and switches to a new one.","when":"To create a clean binlog starting point before a backup, or to limit file size.","caution":"Frequent rotation produces many small binlog files. The same command works in MariaDB; FLUSH LOGS rotates all logs.","tags":["binlog","rotate","flush","log"]},{"id":"purge-binlog","task":"Purge old binary logs (PURGE)","category":"Binlog & Log","command":"PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;","what":"Deletes binary log files older than the given date from disk.","when":"When binlogs are filling the disk, to clean up old logs that are no longer needed.","caution":"If a replica still needs a binlog being deleted, replication breaks; first verify with SHOW REPLICA STATUS that all replicas have moved past it. To delete up to a specific file: PURGE BINARY LOGS TO 'mysql-bin.000123'; For automatic cleanup, use the binlog_expire_logs_seconds setting.","tags":["binlog","purge","cleanup","log"]},{"id":"show-binlogs","task":"View binary log status","category":"Binlog & Log","command":"SHOW BINARY LOGS;","what":"Lists all binary log files on the server and their sizes.","when":"To see binlog disk usage and which files exist.","caution":"For the binlog file currently being written and its position: SHOW BINARY LOG STATUS; (MySQL 8.2+). Older versions use SHOW MASTER STATUS (deprecated in 8.2–8.3, removed in 8.4).","tags":["binlog","status","show","log"]},{"id":"enable-general-slow-log","task":"Enable the general and slow query logs","category":"Binlog & Log","command":"SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL general_log = 'ON';","what":"Enables, at run time, the slow query log (queries longer than 1 second) and the general log that records every query.","when":"To temporarily analyze slow queries or all query traffic.","caution":"general_log adds significant performance and disk overhead; always turn it off when diagnostics are done (SET GLOBAL general_log='OFF'). These settings reset on restart; use SET PERSIST to make them permanent.","tags":["slow-log","general-log","log","diagnostics"]},{"id":"innodb-redo-capacity","task":"Change the InnoDB redo log size","category":"Storage","command":"SET GLOBAL innodb_redo_log_capacity = 2147483648;","what":"Dynamically sets the total InnoDB redo log capacity (in bytes, here 2 GB) at run time.","when":"To reduce checkpoint frequency and improve performance under write-heavy workloads.","caution":"innodb_redo_log_capacity arrived in MySQL 8.0.30 and replaces the old innodb_log_file_size + innodb_log_files_in_group settings. On older versions/MariaDB, innodb_log_file_size is changed in my.cnf and requires a restart. To make it permanent: SET PERSIST innodb_redo_log_capacity = 2147483648;","since":"MySQL 8.0.30","note":"innodb_redo_log_capacity arrived in MySQL 8.0.30 and enabled dynamic (restart-free) resizing of the redo log; previously innodb_log_file_size + innodb_log_files_in_group had to be set and the server restarted.","tags":["innodb","redo-log","capacity","storage"]},{"id":"innodb-undo-tablespace","task":"Add / manage an InnoDB undo tablespace","category":"Storage","command":"CREATE UNDO TABLESPACE undo_003 ADD DATAFILE 'undo_003.ibu';","what":"Creates a new InnoDB undo tablespace; undo logs are managed separately from data files and become truncatable.","when":"To manage space when long-running transactions bloat the undo log, or to move undo to a separate disk.","caution":"The file name must end in .ibu. An undo tablespace must be made inactive before it can be dropped: ALTER UNDO TABLESPACE undo_003 SET INACTIVE; then DROP UNDO TABLESPACE undo_003; This is a MySQL 8.0+ feature.","since":"MySQL 8.0","note":"Creating/dropping undo tablespaces with SQL (CREATE/DROP UNDO TABLESPACE) arrived in MySQL 8.0; previously the number of undo tablespaces was fixed by innodb_undo_tablespaces and could not be managed at run time.","tags":["innodb","undo-log","tablespace","storage"]},{"id":"create-tablespace","task":"Add a general tablespace","category":"Storage","command":"CREATE TABLESPACE ssd_ts ADD DATAFILE 'ssd_ts.ibd' ENGINE=InnoDB;","what":"Creates an InnoDB general tablespace that multiple tables can share.","when":"To group tables in a specific file/disk, or to reduce the number of files.","caution":"If the DATAFILE path is relative, it is created under datadir; placing it on a different disk requires an absolute path and the necessary permissions. To move a table here: ALTER TABLE customer TABLESPACE ssd_ts; A tablespace can be dropped only when it is empty.","tags":["tablespace","innodb","storage","create"]},{"id":"optimize-table","task":"OPTIMIZE a table","category":"Storage","command":"OPTIMIZE TABLE customer;","what":"Rebuilds the table to remove fragmentation, reclaim unused space and update index statistics.","when":"To reclaim space in an InnoDB table bloated by many DELETEs/UPDATEs.","caution":"On InnoDB, OPTIMIZE actually runs as an online ALTER that rebuilds the table, but it still needs I/O and temporary disk space; large tables require free disk equal to the table size. Run it during low traffic to reduce locking.","tags":["optimize","table","defragment","storage"]},{"id":"table-schema-size","task":"Query table and schema sizes","category":"Storage","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":"Lists the 20 largest tables by combined data and index size (MB).","when":"To analyze disk usage and find which tables take up space.","caution":"information_schema sizes are approximate for InnoDB. For per-schema totals, GROUP BY table_schema only, instead of table_name.","tags":["size","table","schema","information-schema"]},{"id":"analyze-table","task":"Update table statistics (ANALYZE TABLE)","category":"Maintenance","command":"ANALYZE TABLE customer;","what":"Recomputes the table's index/key distribution statistics so the optimizer can choose better plans.","when":"When bad query plans appear after bulk data changes.","caution":"A brief metadata lock is taken on the table while it runs. On InnoDB it is sample-based; increase innodb_stats_persistent_sample_pages for more accuracy.","tags":["analyze","statistics","optimizer","maintenance"]},{"id":"rebuild-table","task":"Rebuild a table/indexes","category":"Maintenance","command":"ALTER TABLE customer ENGINE=InnoDB;","what":"Rebuilds the table with the same engine; recreates indexes and data pages to remove fragmentation and index bloat.","when":"To refresh a table with a null ALTER, or to repair a damaged page layout.","caution":"Large tables require temporary disk equal to the table size. To reduce locking: ALTER TABLE customer ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;","tags":["rebuild","alter","innodb","maintenance"]},{"id":"check-table","task":"Check table integrity (CHECK TABLE)","category":"Maintenance","command":"CHECK TABLE customer;","what":"Checks the table and its indexes for corruption and returns a status.","when":"On unexpected errors or when corruption is suspected.","caution":"A read lock is taken on the table during the check. If corruption is found, InnoDB is usually repaired via dump/restore; REPAIR TABLE works only for engines such as MyISAM/Archive.","tags":["check","integrity","corruption","maintenance"]},{"id":"set-global-persist","task":"Change a global variable and persist it","category":"Parameters","command":"SET PERSIST innodb_buffer_pool_size = 4294967296;","what":"Sets the variable both on the running server and persistently across restarts (mysqld-auto.cnf).","when":"To change a server variable permanently without a restart.","caution":"SET PERSIST is a MySQL 8.0+ feature; MariaDB does not support it (there, edit my.cnf and restart). Use SET GLOBAL for a runtime-only change. Restart-only variables require SET PERSIST_ONLY.","since":"MySQL 8.0","note":"SET PERSIST arrived in MySQL 8.0 and makes a variable persistent by writing it to mysqld-auto.cnf; previously (and in MariaDB) persistence required editing my.cnf manually and restarting.","tags":["set-persist","global","variable","parameter"]},{"id":"show-variables","task":"View the current value of a variable","category":"Parameters","command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","what":"Shows the effective value of the specified server variable (or those matching the pattern).","when":"To check the running value of a parameter.","caution":"Use SHOW GLOBAL VARIABLES for the global rather than the session value. To see where the value comes from (file, command line, 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":"Reload the grant tables (FLUSH PRIVILEGES)","category":"Parameters","command":"FLUSH PRIVILEGES;","what":"Reloads grant tables such as mysql.user into memory after they have been modified directly.","when":"To activate changes made with manual INSERT/UPDATE on the grant tables.","caution":"GRANT/REVOKE/CREATE USER already apply privileges immediately; FLUSH PRIVILEGES after them is unnecessary. It is needed only when you modify the grant tables directly.","tags":["flush-privileges","grants","privileges","reload"]},{"id":"setup-replica","task":"Set up a replica (CHANGE REPLICATION SOURCE)","category":"Replication","command":"CHANGE REPLICATION SOURCE TO SOURCE_HOST='source_host', SOURCE_PORT=3306, SOURCE_USER='replicator', SOURCE_PASSWORD='secret', SOURCE_AUTO_POSITION=1;","what":"Configures this server as a replica that follows the specified source using GTID-based auto-positioning.","when":"To set up a new replica or change the replication source.","caution":"This is MySQL 8.0.23+ terminology; older versions use CHANGE MASTER TO ... MASTER_HOST (an alias in 8.0.23–8.3, removed in 8.4). SOURCE_AUTO_POSITION=1 requires GTID to be enabled (gtid_mode=ON); otherwise SOURCE_LOG_FILE and SOURCE_LOG_POS must be specified. Replication must be stopped before running the command. If the replication user was created with the 8.0+ default caching_sha2_password, the connection requires TLS: add SOURCE_SSL=1 or, without TLS, set GET_SOURCE_PUBLIC_KEY=1; otherwise the I/O thread fails to connect with error 2061.","since":"MySQL 8.0.23","note":"CHANGE REPLICATION SOURCE TO (SOURCE_HOST etc.) arrived in MySQL 8.0.23; previously CHANGE MASTER TO ... MASTER_HOST / MASTER_AUTO_POSITION was used.","tags":["replication","change-source","replica","setup"]},{"id":"start-stop-replica","task":"Start / stop a replica","category":"Replication","command":"STOP REPLICA; START REPLICA;","what":"Stops and restarts the replication I/O and SQL threads.","when":"For maintenance, configuration changes or to fix a replication error.","caution":"This is MySQL 8.0.22+ terminology; older versions use STOP SLAVE / START SLAVE. For the SQL thread only: STOP REPLICA SQL_THREAD; After STOP REPLICA you can run CHANGE REPLICATION SOURCE.","since":"MySQL 8.0.22","note":"START REPLICA / STOP REPLICA arrived in MySQL 8.0.22; previously START SLAVE / STOP SLAVE was used (the old syntax works as an alias in 8.0.22–8.3 and was removed in 8.4).","tags":["replication","start","stop","replica"]},{"id":"replica-status","task":"View replica status (SHOW REPLICA STATUS)","category":"Replication","command":"SHOW REPLICA STATUS\\G","what":"Shows in detail the replica's connection to the source, thread states, lag and any errors.","when":"To check whether replication is healthy and how far behind it is.","caution":"This is MySQL 8.0.22+ terminology; older versions use SHOW SLAVE STATUS (an alias in 8.0.22–8.3, removed in 8.4). Key fields: Replica_IO_Running and Replica_SQL_Running must be 'Yes', Seconds_Behind_Source shows the lag, Last_Error shows the error.","since":"MySQL 8.0.22","note":"SHOW REPLICA STATUS arrived in MySQL 8.0.22; previously SHOW SLAVE STATUS was used and fields were named like Slave_IO_Running / Seconds_Behind_Master.","tags":["replication","status","replica","lag"]},{"id":"skip-gtid-transaction","task":"Skip a broken transaction (GTID)","category":"Replication","command":"STOP REPLICA; SET GTID_NEXT='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:15'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC'; START REPLICA;","what":"Commits an empty transaction for the specified GTID so that transaction counts as 'applied', and lets replication move on.","when":"When a duplicate/unappliable event on the replica stops replication, to skip that event.","caution":"Skipping a transaction creates data inconsistency between the replica and the source; use it only when you know the effect of the skipped event. Take the GTID value from Last_Error / Retrieved_Gtid_Set in SHOW REPLICA STATUS. If GTID is off, use SET GLOBAL sql_replica_skip_counter = 1; (8.0.26+; previously sql_slave_skip_counter, a deprecated alias in 8.0.26+).","tags":["replication","gtid","skip","recovery"]},{"id":"kill-query-connection","task":"Kill a query or connection (KILL)","category":"Session","command":"KILL QUERY 4567;","what":"Terminates the query currently running on the given connection ID without closing the connection.","when":"To stop a long-running/stuck query.","caution":"To close the entire connection (session): KILL CONNECTION 4567; (or simply KILL 4567). If a running write transaction is killed, it is rolled back, which can take a long time on large tables.","tags":["kill","query","connection","session"]},{"id":"process-list","task":"View the process list","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":"Lists all non-idle connections, the query they are running and the elapsed time.","when":"To diagnose active sessions and long-running queries.","caution":"Shortcut: SHOW FULL PROCESSLIST; performance_schema.processlist provides all columns and can be filtered with WHERE. The time column is in seconds.","tags":["processlist","session","activity","performance-schema"]},{"id":"blocking-locks","task":"View blocking locks (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":"Shows which transaction is blocked by which, with the related queries and thread IDs.","when":"To diagnose lock waits and slowdowns caused by deadlocks.","caution":"performance_schema.data_lock_waits arrived in MySQL 8.0; older versions use information_schema.innodb_lock_waits. To stop the blocking thread, use KILL blocking_thread;.","since":"MySQL 8.0","note":"performance_schema.data_lock_waits arrived in MySQL 8.0; in MySQL 5.7 and earlier, information_schema.innodb_lock_waits was used for the same information.","tags":["blocking","lock","data-lock-waits","deadlock"]}]},"recipes/mysql-ext.json":{"engineId":"mysql","engine":"MySQL","order":2,"recipes":[{"id":"replica-applier-health","task":"Monitor replica lag and applier worker errors (performance_schema)","category":"Replication","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":"Breaks down what SHOW REPLICA STATUS summarizes in a single row, per channel and per worker: receiver thread state, each worker's last error, the applied / currently applying GTID and the real lag computed from the original commit timestamp.","when":"When Seconds_Behind_Source looks unreliable (jumping between 0 and large values, NULL), to find which worker is stuck on a multi-threaded replica, and to feed an accurate lag metric into monitoring.","caution":"current_lag_sec is meaningful only while a transaction is being applied; an idle replica returns 0. To see whether the receiver is also behind, compare LAST_QUEUED_TRANSACTION with the source's gtid_executed. The original commit timestamp comes from the source clock; clock skew between servers (NTP) distorts the result. Seconds_Behind_Source stays near zero at the start of a large transaction and jumps at the end; long transactions make that metric misleading.","note":"The ORIGINAL_COMMIT_TIMESTAMP / END_APPLY_TIMESTAMP columns arrived in 8.0.2 and cover the whole chain (source → intermediate → replica); 5.7 only has Seconds_Behind_Master.","since":"MySQL 8.0","tags":["replication","lag","applier","worker","performance_schema","delay"]},{"id":"replica-parallel-applier","task":"Tune the multi-threaded replica (replica_parallel_workers)","category":"Replication","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":"Sets the number of workers that apply transactions in parallel on the replica; replica_preserve_commit_order keeps the commit order identical to the source. WRITESET dependency tracking on the source lets transactions that touch different rows be applied in parallel even if they were committed serially on the source.","when":"When the replica cannot keep up with the source write rate and lag builds up while the applier is effectively single-threaded.","caution":"A new worker count takes effect when the applier restarts. Running more workers than CPU cores adds coordination overhead; start in the 8–16 range and watch lag and worker errors. A single huge transaction or heavy DML on a table without a PK cannot be parallelized, which is usually the real cause of persistent lag. replica_preserve_commit_order=OFF gives more parallelism but allows brief out-of-order commits on the replica; keep it ON on replicas that serve reads.","note":"Since 8.0.27 the defaults are replica_parallel_workers=4, replica_parallel_type=LOGICAL_CLOCK and replica_preserve_commit_order=ON. Before 8.0.26 the variables were named slave_parallel_workers / slave_preserve_commit_order. binlog_transaction_dependency_tracking was deprecated in 8.0.35 and removed in 8.4, where WRITESET behavior is the default.","since":"MySQL 8.0.26","tags":["replication","parallel","replica_parallel_workers","writeset","lag","mta"]},{"id":"replica-fix-row-conflict","task":"Repair a broken replica: 1062 / 1032 row conflicts","category":"Replication","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":"Fixes the two most common row-based replication breaks (1062 duplicate key, 1032 row not found) without skipping the transaction, by bringing the replica row to the state the source expects; data consistency is preserved.","when":"When Replica_SQL_Running = No with error 1062 / 1032; as a proper repair instead of skipping the GTID with an empty transaction.","caution":"The fix on the replica must run with sql_log_bin=0; otherwise it creates an errant GTID that bites at failover. If super_read_only=ON, disable it temporarily and re-enable it afterwards. The before-image values assume binlog_row_image=FULL; with MINIMAL only the PK is logged and the rest of the row must be read from the source. One conflict in a table usually means there are more; after the repair compare the table with the source using pt-table-checksum. Relay log path: SELECT @@relay_log_basename;","note":"replica_exec_mode=IDEMPOTENT silently swallows these errors; it is designed for NDB and special cases and must not be left on permanently. If the transaction really has to be skipped, see the skip-gtid-transaction recipe.","since":"MySQL 8.0.22","tags":["replication","1062","1032","mysqlbinlog","relay log","broken replica"]},{"id":"gtid-errant-transactions","task":"Find and neutralize errant GTID transactions","category":"Replication","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":"Detects transactions executed directly on a replica with no counterpart on the source (errant) via GTID set difference, and aligns the GTID sets across the topology by marking them with empty transactions on the source.","when":"As a routine pre-failover / switchover check; when Orchestrator or MySQL Shell refuses to promote a replica because of errant GTIDs, or a replica fails with 1236 when attaching to a new source.","caution":"First find out what the errant transaction did from the replica's binlog (mysqlbinlog --include-gtids=...). If it changed data, the empty transaction only papers over the GTID and the data difference remains; rebuild the replica (CLONE) or align the data manually. Empty transactions committed on the source propagate to every replica; that is intended. Keep super_read_only=ON on replicas to prevent recurrence.","note":"Once the replica is promoted, the other replicas request these GTIDs from it; if that binlog has been purged, replication breaks with error 1236. That is why errant GTIDs are a risk that stays invisible until failover.","since":"MySQL 5.6","tags":["gtid","errant","gtid_subtract","failover","replication","empty transaction"]},{"id":"replica-rebuild-clone","task":"Rebuild a replica from scratch with the CLONE plugin","category":"Replication","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":"Physically copies the donor's InnoDB data over the network, restarts the recipient and carries over the GTID state; produces a consistent replica without separate backup, transfer and restore steps.","when":"To rebuild a corrupted replica, one with errant GTIDs or one so far behind that its binlogs were purged, or to add a new replica.","caution":"All data on the recipient (users included) is replaced by the donor's copy; the root password becomes the donor's. Donor and recipient need the same release series, platform and plugin set (before 8.0.37 the exact same point release was required). The recipient user running the command needs CLONE_ADMIN. When the clone finishes mysqld restarts itself; without a supervisor such as systemd / mysqld_safe it returns 3707 'Restart server failed (mysqld is not managed by supervisor process)'. That is not a failure: the clone is complete, start the server manually. The donor sees heavy IO / network load; cap it with clone_max_concurrency and clone_max_data_bandwidth. With GTID off, use BINLOG_FILE / BINLOG_POSITION from clone_status as SOURCE_LOG_FILE / SOURCE_LOG_POS.","note":"Only InnoDB data is cloned; MyISAM and CSV tables arrive empty. In InnoDB Cluster, addInstance(..., {recoveryMethod: 'clone'}) uses the same mechanism.","since":"MySQL 8.0.17","tags":["clone","rebuild","replica","provisioning","gtid","reseed"]},{"id":"delayed-replica-recover","task":"Recover from a bad DROP / DELETE with a delayed replica (SOURCE_DELAY + UNTIL)","category":"Replication","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":"Makes the replica apply events a fixed time behind; when a bad DROP / DELETE hits the source, you roll the replica forward to just before that transaction and recover the lost data from it.","when":"As a fast recovery layer against human error on large databases where restoring from backup would take hours.","caution":"When the coordinator is OFF, the data is in the state right before the bad operation; dump the affected table with mysqldump and load it into the source. Afterwards put the replica back into delayed mode; if it applies the bad transaction too, the data is gone here as well. A delayed replica must not be a failover candidate (exclude it in Orchestrator / Router / ProxySQL). Relay logs accumulate for the whole delay; size the disk accordingly.","note":"Before 8.0.23 use CHANGE MASTER TO MASTER_DELAY. The delay is measured from the commit on the immediate source, so delayed intermediate replicas in a chain add up. SQL_BEFORE_GTIDS stops before the given GTID, SQL_AFTER_GTIDS applies it too; before 8.2 (including 8.0), using SQL_AFTER_GTIDS with a multi-threaded applier raises a warning and the replica switches to single-threaded mode.","since":"MySQL 8.0.23","tags":["delayed replica","source_delay","until","sql_before_gtids","recovery","drop table"]},{"id":"mysqldump-consistent","task":"Take a consistent logical backup with GTID info using mysqldump","category":"Backup & 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":"Dumps InnoDB tables within a single consistent snapshot and writes the binlog file / position (as a comment) and a SET @@GLOBAL.GTID_PURGED statement at the top, so the dump can seed a replica or serve as the starting point for binlog-based PITR.","when":"For databases up to a few tens of GB, for moving schemas / tables, and for logical migration between versions.","caution":"--single-transaction is consistent for InnoDB only; MyISAM tables come out inconsistent. DDL (ALTER / RENAME / TRUNCATE) on a table during the dump makes the dump fail or miss the table. --source-data / --set-gtid-purged take a brief global read lock at the start (RELOAD privilege required); if a long query is running at that moment, all writes stall while the lock waits. A partial dump (--databases) still writes all GTIDs; use --set-gtid-purged=OFF when moving a subset to another server. The dump is single-threaded; for large data prefer MySQL Shell dump or XtraBackup.","note":"--source-data replaced --master-data in 8.0.26. mysqlpump was deprecated in 8.0.34 and removed in 8.4; use MySQL Shell util.dumpInstance for parallel logical backups.","since":"MySQL 8.0.26","tags":["mysqldump","backup","single-transaction","gtid","logical backup","dump"]},{"id":"mysqlsh-dump-load","task":"Parallel dump / load with MySQL Shell (util.dumpInstance, loadDump)","category":"Backup & 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":"Splits tables into chunks and dumps / loads them with many threads; both dump and restore are many times faster than mysqldump. The dump directory stores GTID and binlog coordinates in @.json.","when":"For logical backups of hundreds of GB, logical migration during upgrades, and on-prem → cloud (RDS, HeatWave) moves.","caution":"--consistent=true takes a brief global read lock / backup lock; writes wait if a long query is running. loadDump uses LOAD DATA LOCAL, so the target needs local_infile=ON, which should be turned off afterwards. An interrupted load resumes from where it stopped (progress file); use --resetProgress to start over. User accounts are not loaded by default (--loadUsers=true). --skipBinlog=true disables binlogging during the load; keep it false if the target has replicas.","note":"This is not a physical backup; for PITR apply binlogs from the gtidExecuted value in the dump's @.json. The ocimds / compatibility options produce dumps that respect HeatWave / RDS restrictions.","since":"MySQL Shell 8.0.21","tags":["mysqlsh","dumpinstance","loaddump","parallel","logical backup","migration"]},{"id":"xtrabackup-full-incremental","task":"Full + incremental physical backup and restore with XtraBackup","category":"Backup & 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":"Takes a hot physical backup by copying InnoDB files and the redo log while the server runs; an incremental backup contains only pages changed since the previous backup. The prepare step applies redo and makes the backup consistent.","when":"For large databases that need fast restores (many times faster than logical), for seeding replicas, and for daily full + hourly incremental strategies.","caution":"The XtraBackup version must match the server release series (8.0 server → XtraBackup 8.0, 8.4 → 8.4); if the server uses a newer redo / data dictionary format than the XtraBackup build, the backup is refused. An unprepared backup is inconsistent. Forgetting --apply-log-only on intermediate steps makes later incrementals unusable. The datadir must be empty for restore; --move-back saves disk but consumes the backup. Backup user: BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT, and SELECT on performance_schema.log_status, keyring_component_status, replication_group_members.","note":"xtrabackup_binlog_info holds the backup's binlog file / position and GTID set; PITR replays binlogs from that point. To stream to another host use --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":"Point-in-time recovery with binlogs: find and skip the bad transaction","category":"Backup & 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":"After restoring the backup, replays binlogs in order to bring the database to the desired point. With GTID on, transactions already in the backup are skipped automatically; --exclude-gtids removes just the bad operation and keeps every change after it.","when":"After a bad DROP / DELETE / UPDATE, to recover without losing changes made between the backup and the incident.","caution":"Restore on a separate server, not production; verify, then move only the affected table. --start/--stop-datetime are interpreted in the time zone of the machine running mysqlbinlog. --stop-position applies only to the last file given; for multiple files replay the earlier files fully in a separate command. --exclude-gtids is safe only for an independent operation; if later transactions write to the dropped table, they fail too. The required binlogs must not be purged on the source; keep binlog retention longer than at least one backup interval.","note":"Run mysqlbinlog and mysql in a single pipe with all files in one command; replaying files one by one breaks temporary tables and transactions that span files. For very large binlog sets an alternative is to place the binlogs as relay logs on the restore server and apply them with the (parallel) SQL thread.","since":"MySQL 8.0","tags":["pitr","mysqlbinlog","binlog","point in time","exclude-gtids","recovery"]},{"id":"innodb-cluster-status","task":"Check InnoDB Cluster / Group Replication health","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":"Shows overall cluster state (OK / OK_NO_TOLERANCE / NO_QUORUM), the primary, each member's state and the certification / applier queues; the queues reveal which member lags and the flow-control risk.","when":"Before and after every intervention; on alerts, write slowdowns, or when a member shows RECOVERING / ERROR / UNREACHABLE.","caution":"statusText should say 'can tolerate up to ONE failure' (3 members). With NO_QUORUM writes stop; use forceQuorumUsingPartitionOf only when you are certain the other partition is down, otherwise you get split-brain. A member whose apply_queue keeps growing triggers flow control and slows writes on the primary. A long RECOVERING means clone / incremental recovery is running; for ERROR check the error log and try cluster.rejoinInstance(). Special characters in passwords (#, @) must be URL-encoded in the URI (# → %23).","note":"MEMBER_COMMUNICATION_STACK arrived in 8.0.27; the MYSQL stack carries GR traffic over 3306 with normal TLS and user authentication (XCOM uses a separate port, usually 33061). In 8.4 group_replication_consistency defaults to BEFORE_ON_PRIMARY_FAILOVER and exit_state_action defaults to OFFLINE_MODE.","since":"MySQL 8.0","tags":["innodb cluster","group replication","mysqlsh","status","flow control","quorum"]},{"id":"innodb-cluster-switch-primary","task":"Planned primary switchover in 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":"Moves the primary role to another member in a controlled way in single-primary mode; the old primary becomes super_read_only, the new one applies its backlog and opens for writes. MySQL Router learns the new primary automatically.","when":"Before maintenance, OS patching or upgrading the primary, and for hardware moves.","caution":"Open transactions on the old primary are killed when the timeout expires; without a timeout the switch waits for them and takes longer. If the application does not connect through Router, you must redirect it yourself. With BEFORE_ON_PRIMARY_FAILOVER writes wait until the new primary has drained its backlog; confirm apply_queue is empty in cluster status first. In InnoDB Cluster prefer the Shell command; it stays consistent with the metadata.","note":"group_replication_set_as_primary arrived in 8.0.13; the timeout parameter was added in 8.0.29. On unplanned failover GR elects the new primary itself (group_replication_member_weight sets preference).","since":"MySQL 8.0.13","tags":["innodb cluster","switchover","primary","group replication","set_as_primary","maintenance"]},{"id":"innodb-cluster-reboot-outage","task":"Bring the cluster back after a complete outage (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":"Re-bootstraps the group after a power loss or simultaneous restart of all nodes, when no quorum exists; picks the member with the highest GTID set and rejoins the others.","when":"When cluster status reports 'Cluster is not reachable' and every member shows OFFLINE.","caution":"Use only when every member is OFFLINE; if one member is still ONLINE the group is alive and the others join with rejoinInstance. Start every node before running it: if an unreachable member is more up to date, its transactions are lost. force: true ignores missing members; it is a last resort. The primary: 'host:port' option forces a specific member as primary.","note":"The dryRun, primary and force options arrived in Shell 8.0.31; earlier versions worked through interactive prompts. Even with group_replication_start_on_boot=ON the group does not form by itself after a full outage; bootstrapping must be done deliberately from one place.","since":"MySQL Shell 8.0.31","tags":["innodb cluster","outage","reboot","bootstrap","mysqlsh","disaster"]},{"id":"mysql-router-bootstrap","task":"Bootstrap MySQL Router against an InnoDB Cluster","category":"Router & 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":"Registers Router in the cluster metadata and generates the account and configuration; the application reaches the current primary through the RW port and secondaries round-robin through the RO port.","when":"When putting a transparent failover layer in front of an InnoDB Cluster / ClusterSet / ReplicaSet; typically one Router per application server.","caution":"Router is stateless; running it next to the application avoids a single point of failure, a single central Router does not. The bootstrap account is used only during bootstrap; Router reads metadata with its own account (routeruser). It learns topology changes through the metadata cache without re-bootstrapping. The RW split port (6450) sends reads inside a transaction to the primary and others to secondaries; be careful with applications that expect read-your-writes. The Router version must not be older than the MySQL servers in the cluster.","note":"The read/write splitting port (6450) arrived in 8.2. Existing connections drop on failover; the application must have reconnect (retry) logic.","since":"MySQL Router 8.0","tags":["mysql router","bootstrap","innodb cluster","failover","read write split","6446"]},{"id":"proxysql-rw-split","task":"Read/write split and replica health checks with ProxySQL","category":"Router & 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":"Manages writer (10) and reader (20) hostgroups automatically based on the read_only flag; routes SELECTs to replicas and locking reads and writes to the source; temporarily removes a replica that exceeds max_replication_lag (SHUNNED).","when":"To spread read load in an async replication topology without touching the application; for connection pooling and per-query routing / caching / firewalling.","caution":"Configuration has 3 layers (MEMORY → RUNTIME → DISK): without LOAD ... TO RUNTIME nothing changes, without SAVE ... TO DISK it is lost on restart. The read_only check requires super_read_only=ON on replicas; otherwise replicas are moved into the writer hostgroup. Sending every SELECT to replicas causes read-after-write errors due to lag; writing rules only for heavy / reporting queries based on stats_mysql_query_digest is safer. The admin interface is SQLite: MySQL functions such as LEFT() do not exist, use substr(). Change the default admin:admin account (admin-admin_credentials).","note":"The max_replication_lag check requires REPLICATION CLIENT for the monitor user. For Group Replication / InnoDB Cluster use the mysql_group_replication_hostgroups table.","since":"ProxySQL 2.0","tags":["proxysql","read write split","hostgroup","query rules","connection pool","replication lag"]},{"id":"online-ddl-instant-inplace","task":"Online DDL: ALGORITHM=INSTANT / INPLACE, LOCK=NONE and progress tracking","category":"Online Operations","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 changes only the data dictionary and finishes immediately regardless of table size. INPLACE + LOCK=NONE processes the table in place while allowing concurrent reads and writes. The last query shows which stage a running ALTER is in and its percentage.","when":"Before schema changes on production tables; to know which operations can run without downtime and to estimate when a long ALTER will finish.","caution":"INSTANT and INPLACE still take a brief exclusive metadata lock at start and end; if a long-running transaction holds the table, the ALTER waits and every query behind it queues up (see metadata lock diagnosis). Lower lock_wait_timeout in the session (SET SESSION lock_wait_timeout = 5). DML arriving during INPLACE is logged up to innodb_online_alter_log_max_size; if exceeded, the ALTER rolls back at the end. Column type changes, charset conversion and PK changes need COPY and block writes; on large tables use gh-ost / pt-online-schema-change. On a replica the ALTER runs single-threaded for the same duration; a large INPLACE ALTER causes replica lag, INSTANT does not.","note":"INSTANT ADD COLUMN arrived in 8.0.12 for the last column only; since 8.0.29 columns can be added at any position and INSTANT DROP COLUMN is supported. Extending a VARCHAR is metadata-only when the number of length bytes does not change (the 255-byte boundary is not crossed); it completes immediately with INPLACE, not INSTANT. An indexed column cannot be dropped with INSTANT; drop the index first.","since":"MySQL 8.0.29","tags":["online ddl","instant","inplace","alter table","lock=none","progress"]},{"id":"pt-online-schema-change","task":"Non-blocking ALTER on a large table with pt-online-schema-change","category":"Online Operations","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":"Creates an empty copy with the new structure, copies rows in chunks, mirrors changes during the copy with triggers and finally swaps the tables with an atomic RENAME. It slows down / pauses on its own when load or replica lag crosses the thresholds.","when":"For ALTERs that need COPY (column type, charset, PK change) or that would cause long replica lag.","caution":"The table needs a PK or unique key. Triggers add overhead to every write during the migration; on very write-heavy tables gh-ost has less impact. If child tables reference this table via foreign keys, --alter-foreign-keys-method=auto is required. For caching_sha2_password accounts add s=1 (TLS) to the DSN; otherwise the replica connection fails with 'Authentication requires secure connection'. The --check-replica-lag DSN does not inherit user / password from the main DSN; specify them. The swap takes a brief metadata lock; keep lock_wait_timeout low, it retries if the lock cannot be acquired.","note":"Replicas are discovered via processlist by IP by default; if those IPs are unreachable use a DSN table with --recursion-method=dsn=D=percona,t=dsns. In 3.7 --check-slave-lag was renamed to --check-replica-lag (old name deprecated).","since":"Percona Toolkit 3.7","tags":["pt-online-schema-change","pt-osc","percona toolkit","alter","trigger","online schema change"]},{"id":"gh-ost-migration","task":"Triggerless online schema change with gh-ost","category":"Online Operations","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":"Builds a ghost table and copies rows; applies concurrent changes by reading the binlog instead of triggers. Throttling, pausing and cut-over timing are controlled at runtime via the socket or flag files.","when":"On large, write-heavy tables where trigger overhead is unacceptable and you want to choose the cut-over moment (e.g. a low-traffic window) yourself.","caution":"Requires binlog_format=ROW and binlog_row_image=FULL; the replica it connects to needs log_replica_updates=ON. The user needs SUPER or REPLICATION CLIENT / REPLICATION SLAVE plus ALL on the table. Tables with foreign keys (parent or child) are not supported. After cut-over the old table remains as _<table>_del; drop it manually once verified (--ok-to-drop-table drops it automatically). gh-ost releases before 1.1.8 use SHOW SLAVE STATUS and stop with error 1064 on MySQL 8.4; 8.4 support arrived in 1.1.8. gh-ost creates the file given in --postpone-cut-over-flag-file itself; cut-over does not happen until the file is removed.","note":"The --test-on-replica and --migrate-on-replica options let you try and verify the change on a replica only first. The throttle reason and ETA appear in the output of the 'status' command.","tags":["gh-ost","online schema change","binlog","cut-over","alter","triggerless"]},{"id":"ps-top-digests","task":"Find the most expensive queries (performance_schema digest)","category":"Performance","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":"Ranks normalized queries by total time with call count, average and p95 latency, share of total, rows examined / sent per call, executions without an index and temp tables spilled to disk. The second query shows which waits (IO, locks, redo) the time is spent on.","when":"First stop when CPU / IO load rises or users report 'the database is slow'; works even when the slow log is off.","caution":"Values are cumulative since the last restart or TRUNCATE TABLE performance_schema.events_statements_summary_by_digest; take two snapshots and diff them to see current activity. The table is capped at performance_schema_digests_size rows; once full, new queries are aggregated into a single row with a NULL DIGEST. DIGEST_TEXT is cut at performance_schema_max_digest_length (1024) bytes. Times are in picoseconds. A high rows_exam_per_call / rows_sent_per_call ratio is the clearest sign of a missing index.","note":"The QUANTILE_95 / QUANTILE_99 and QUERY_SAMPLE_TEXT columns arrived in 8.0. The same data in readable units is available in the sys.statement_analysis and sys.statements_with_full_table_scans views.","since":"MySQL 8.0","tags":["performance_schema","digest","top sql","slow query","sys","wait"]},{"id":"sys-session-activity","task":"What is running right now: active sessions, latency, locks and transactions (sys.session)","category":"Performance","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":"Enriches SHOW PROCESSLIST with performance_schema data: running statement latency, progress percentage, lock wait time, rows examined, full scan / disk temp table info and open transaction state in one row. It also lists sessions that are in Sleep but keep a transaction open.","when":"In the first 30 seconds of an incident: which query has been running for how long, who is waiting on locks, which idle session holds a transaction open.","caution":"sys.session performs several joins over performance_schema; with thousands of connections the query itself may take seconds, so use the x$ views and LIMIT for frequent monitoring. progress is populated only with stage instruments enabled and for operations that report progress, such as ALTER. A session in Sleep with trx_state=ACTIVE is a transaction forgotten by an application / connection pool using autocommit=0, the most common cause of metadata lock and purge problems.","note":"sys.processlist returns the same data including background threads. To terminate: KILL QUERY conn_id (statement only) or KILL conn_id (connection, the transaction is rolled back).","since":"MySQL 8.0","tags":["sys.session","processlist","active sessions","incident","trx","session"]},{"id":"metadata-lock-diagnose","task":"Diagnose 'Waiting for table metadata lock' and find the real blocker","category":"Performance","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":"Shows which session's metadata lock a DDL such as ALTER / TRUNCATE / RENAME is waiting on, and the queries queued behind the DDL. The blocker is usually a session in Sleep that read the table and never closed its transaction.","when":"When, after an ALTER starts, every query on that table piles up in 'Waiting for table metadata lock'.","caution":"A waiting DDL also blocks the SELECTs that arrive after it; decide quickly during the incident: KILL the blocking session or cancel the ALTER (KILL QUERY). The blocker's PROCESSLIST_INFO may be NULL (idle); use performance_schema.events_statements_history or sys.session.last_statement to see its last statement. Prevention: run DDL after SET SESSION lock_wait_timeout = 5 so the ALTER gives up quickly, and avoid long open transactions in the application.","note":"The wait/lock/metadata/sql/mdl instrument is enabled by default in 8.0; in 5.7 it must be enabled in setup_instruments. sys.schema_table_lock_waits also lists an ALTER's own SHARED_UPGRADABLE lock as a blocker; the real culprit is the session holding a GRANTED SHARED_READ / SHARED_WRITE.","since":"MySQL 8.0","tags":["metadata lock","mdl","alter","waiting for table metadata lock","lock","blocking"]},{"id":"innodb-deadlock-analysis","task":"Analyze InnoDB deadlocks and log all of them","category":"Performance","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":"Shows the two transactions in the deadlock, the lock each holds and waits for (index, lock_mode, record) and which one InnoDB rolled back; the counter tracks frequency and the error log keeps past deadlocks.","when":"When the application gets error 1213 'Deadlock found when trying to get lock' or deadlock alerts increase.","caution":"LATEST DETECTED DEADLOCK keeps only the last event and is lost on restart. The index name and lock_mode (X, S, gap, insert intention) in the 'HOLDS THE LOCK(S)' / 'WAITING FOR THIS LOCK TO BE GRANTED' lines explain the cause; 'WE ROLL BACK TRANSACTION (n)' shows the victim. On 1213 the application must retry the whole transaction. Permanent fixes: access rows in a consistent order, keep transactions short, use the right index to reduce gap locks and READ COMMITTED where appropriate. log_error_verbosity=3 also enables every Note message; watch error log growth. The Innodb_deadlocks status variable does not exist in MySQL Community; use INNODB_METRICS for the counter.","note":"Under very high concurrency deadlock detection itself can become a CPU bottleneck; innodb_deadlock_detect=OFF is considered only with a low innodb_lock_wait_timeout. A lock wait timeout (1205) is not a deadlock; use the blocking-locks recipe to find the blocker.","since":"MySQL 8.0","tags":["deadlock","innodb","1213","innodb status","lock","error log"]},{"id":"long-trx-history-list","task":"Long-running transactions and history list length (purge lag)","category":"Performance","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":"Shows how far behind purge is (history list length) and the oldest transactions holding it back: owner, age, rows modified and the last SQL they ran.","when":"When the undo tablespace grows, when the same queries get slower over time (they scan old row versions), or on disk space alerts.","caution":"The most common cause is a session in Sleep with an open transaction, or a consistent snapshot forgotten under REPEATABLE READ (e.g. an abandoned mysqldump / report). After KILL, rollback takes as long as the rows the transaction changed; killing a transaction with a high trx_rows_modified means a long, IO-heavy rollback, and a restart does not shorten it. Undo is truncated automatically once purge catches up (innodb_undo_log_truncate=ON).","note":"performance_schema.processlist arrived in 8.0.22 and replaces the deprecated information_schema.PROCESSLIST. 'History list length' in SHOW ENGINE INNODB STATUS gives the same value.","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":"Capture a short full slow log and analyze it with pt-query-digest","category":"Performance","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":"Captures every query for a short window with long_query_time=0; pt-query-digest groups them by fingerprint and produces a ranked report with total / average / p95 time, rows examined and a sample query.","when":"When performance_schema digests are not enough: analysis with full statement text and real parameters, before / after comparison around a deploy, offline review on another host.","caution":"long_query_time is copied from the global value when a session starts; existing connections in a pool do not see the new threshold until they reconnect. long_query_time=0 writes thousands of entries per second on a busy server; watch disk space and IO, keep the window short and always revert. Copy the file off production for analysis. The user running pt-query-digest needs read access to the file (mode 640, mysql:mysql).","note":"log_slow_extra (8.0.14) writes extra fields such as Bytes_sent, Read_first / Read_next and Created_tmp_disk_tables; pt-query-digest shows them in the report. For permanently enabling / disabling see the enable-general-slow-log recipe.","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":"Debug a bad plan with EXPLAIN ANALYZE and the optimizer trace","category":"Performance","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 shows estimated (cost, rows) and actual (actual time, rows, loops) values side by side for each plan step; the optimizer trace shows the access paths considered, their costs and why each was chosen or rejected.","when":"When a query suddenly slows down (plan change), the optimizer picks the wrong index, or to find the source of an estimated vs. actual row mismatch.","caution":"EXPLAIN ANALYZE really executes the query; be careful with heavy queries on production. It works with SELECT, TABLE and multi-table UPDATE / DELETE. A gap of more than 10x between estimated and actual rows points to a statistics / histogram problem (ANALYZE TABLE, histograms). On the inner side of a nested loop, actual rows is per loop; total = rows × loops. If MISSING_BYTES_BEYOND_MAX_MEM_SIZE > 0 the trace was truncated; raise optimizer_trace_max_mem_size. The trace only holds the last statement of the same session.","note":"EXPLAIN FORMAT=TREE arrived in 8.0.16, EXPLAIN ANALYZE in 8.0.18. To pin a plan, optimizer hints (/*+ INDEX(t ix) */, JOIN_ORDER) give finer control than FORCE INDEX.","since":"MySQL 8.0.18","tags":["explain analyze","optimizer trace","execution plan","query tuning","index","plan"]},{"id":"histogram-statistics","task":"Create and manage histograms for non-indexed columns","category":"Index & Statistics","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":"Stores the value distribution of a column (singleton or equi-height) in the data dictionary; the optimizer estimates the selectivity of filters on non-indexed columns correctly, improving join order and plan quality.","when":"When EXPLAIN ANALYZE shows a large estimated vs. actual row gap on skewed columns (e.g. status, type) where adding an index is not desirable.","caution":"On indexed columns the optimizer uses index dives, so a histogram usually has no effect (unless eq_range_index_dive_limit is exceeded). In the default MANUAL mode the histogram is not refreshed as data changes; schedule ANALYZE ... UPDATE HISTOGRAM. Large tables are sampled according to histogram_generation_max_mem_size (sampling-rate < 1). ANALYZE TABLE takes a brief metadata lock; updating a histogram does not change table statistics (index cardinality).","note":"Histograms arrived in 8.0; the AUTO UPDATE option was added in 8.4 and refreshes along with automatic statistics recalculation. The maximum is 1024 buckets; if there are fewer distinct values than buckets, a singleton histogram is created.","since":"MySQL 8.0","tags":["histogram","statistics","optimizer","analyze table","selectivity","cardinality"]},{"id":"invisible-redundant-index","task":"Find unused / redundant indexes, make them invisible, drop safely","category":"Index & Statistics","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":"Lists unused indexes and indexes covered by another one; making an index invisible before dropping it lets you test production behavior without it and revert instantly (no rebuild) if needed.","when":"When cleaning up indexes to reduce write cost, buffer pool pressure and disk usage.","caution":"schema_unused_indexes relies on statistics collected since the last restart only; infrequent queries such as monthly reports may not show up. If replicas serve different workloads, check each server separately. An invisible index is still maintained on DML (write cost remains) but the optimizer ignores it; a unique index still enforces uniqueness when invisible. A PRIMARY KEY cannot be made invisible. Queries that name the index in FORCE INDEX / USE INDEX fail once it is invisible.","note":"Toggling index visibility is metadata-only and completes immediately. The redundant list is based on column order; index (a) is covered by (a,b) but (b) is not.","since":"MySQL 8.0","tags":["invisible index","unused index","redundant index","sys","drop index","index cleanup"]},{"id":"buffer-pool-sizing","task":"Is the buffer pool big enough: hit ratio, wait_free and online resize","category":"Memory","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":"Shows buffer pool size, cache hit ratio, free / dirty page counts and the free-page wait (wait_free) counter; resizes it without a restart and tracks progress.","when":"When read IO is high, after adding / removing RAM, or during capacity planning.","caution":"Hit ratio alone is misleading; even 99% can mean thousands of disk reads per second. Look at the per-second growth of Innodb_buffer_pool_reads; wait_free > 0 means the buffer pool is too small or the page cleaners cannot keep up. The new size is rounded to a multiple of innodb_buffer_pool_chunk_size × instances. Brief stalls are possible during a resize, more so when shrinking; avoid peak hours. On a dedicated server start at 60–75% of RAM; leave room for per-connection memory, OS cache and other processes, since swapping is always worse than a smaller buffer pool.","note":"Innodb_buffer_pool_resize_status_code / _progress arrived in 8.0.31. innodb_dedicated_server=ON sizes the buffer pool and redo capacity automatically from RAM. sys.innodb_buffer_stats_by_table shows per-table buffer pool usage; it scans INNODB_BUFFER_PAGE and is heavy on large buffer pools.","since":"MySQL 8.0","tags":["buffer pool","innodb","memory","hit ratio","resize","sizing"]},{"id":"partition-maintenance","task":"RANGE partition maintenance: add future partitions, archive / drop old ones","category":"Partitioning","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":"On a date-based RANGE-partitioned table, adds the next period's partition, moves an old period into a separate table within milliseconds via EXCHANGE, and drops the partition far more cheaply than DELETE.","when":"For monthly retention and archiving on tables that grow over time, such as logs, events and audit trails.","caution":"The partitioning column must be part of every unique key (PK included). REORGANIZE PARTITION pmax rewrites the data in pmax; keep pmax empty and create partitions ahead of time (e.g. 3 months). DROP PARTITION cannot be undone and is not logged row by row; on replicas it runs as DDL. The EXCHANGE target must have the same structure and no partitioning; rows outside the range cause an error (WITHOUT VALIDATION is faster but can let wrong data in). Partition DDL takes a table-level metadata lock. For pruning, use the partitioning column in WHERE without wrapping it in a function.","note":"In 8.0 partitioning is supported only by InnoDB (and NDB). Adding future partitions / dropping old ones is usually automated with the Event Scheduler or cron; ALTER TABLE ... TRUNCATE PARTITION removes the data but keeps the partition.","since":"MySQL 8.0","tags":["partition","range","reorganize","exchange partition","retention","archive"]},{"id":"roles-least-privilege","task":"Build a least-privilege model with roles (CREATE ROLE, DEFAULT ROLE)","category":"Security","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":"Grants privileges to roles instead of individual users; accounts get privileges through role membership. Changes happen in one place and auditing and offboarding get easier.","when":"When creating new application / team accounts and when cleaning up legacy servers with tangled privileges.","caution":"A role that is not a default role is not active in the session (CURRENT_ROLE() NONE) and the user appears to lack privileges; use SET DEFAULT ROLE ALL or activate_all_roles_on_login=ON. A role is also an account (locked, no password) and shows up in mysql.user. Use a network range instead of '%' for application accounts. Never grant ALL, SUPER, FILE, PROCESS or CONNECTION_ADMIN to an application account. Revoking a privilege from a role affects active sessions too.","note":"mandatory_roles assigns roles to every account automatically. CREATE USER ... DEFAULT ROLE both grants the roles and makes them default.","since":"MySQL 8.0","tags":["role","grant","least privilege","default role","security","privilege"]},{"id":"password-policy-lockout","task":"Password policy, failed-login lockout and zero-downtime password rotation","category":"Security","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":"Enables a policy that rejects weak passwords, temporarily locks accounts after consecutive failed logins, sets password lifetime and reuse rules; with dual passwords a service account password is changed without downtime while applications are updated one by one.","when":"For security audits / compliance (PCI, ISO 27001) and service account password rotation.","caution":"The policy applies only to new / changed passwords; it does not detect existing weak ones. On service accounts FAILED_LOGIN_ATTEMPTS lets a single misconfigured client lock the account and cause an outage; use it for human accounts. PASSWORD_LOCK_TIME is in days (UNBOUNDED: until unlocked manually); unlock with ALTER USER ... ACCOUNT UNLOCK. PASSWORD EXPIRE causes unexpected outages on service accounts; prefer PASSWORD EXPIRE NEVER plus dual password rotation there.","note":"Dual passwords arrived in 8.0.14, FAILED_LOGIN_ATTEMPTS / PASSWORD_LOCK_TIME in 8.0.19. The 5.7 validate_password plugin was replaced by a component in 8.0; variable names use a dot (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":"Enforce TLS, find non-TLS connections, renew certificates without restart","category":"Security","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":"Shows the server TLS configuration, the certificate expiry date and whether each connection uses TLS; forbids unencrypted TCP connections entirely and renews certificates without stopping the server.","when":"For security audits / compliance, before certificate expiry, and when tracking down legacy clients connecting without encryption.","caution":"require_secure_transport=ON does not affect Unix socket and shared memory connections; those count as secure. Find clients connecting without TLS before enabling it, otherwise they fail with error 3159. ALTER INSTANCE RELOAD TLS does not affect existing connections, only new ones; if the certificate file is invalid it errors out and keeps the old certificate. The auto-generated self-signed certificate cannot be verified by clients with VERIFY_CA / VERIFY_IDENTITY; use a certificate signed by your CA in production.","note":"ALTER INSTANCE RELOAD TLS arrived in 8.0.16, performance_schema.tls_channel_status in 8.0.21. For per-account enforcement use ALTER USER ... REQUIRE SSL (or REQUIRE X509).","since":"MySQL 8.0.21","tags":["tls","ssl","require_secure_transport","certificate","encryption","cert"]},{"id":"risky-accounts-audit","task":"Audit risky accounts: empty password, '%' host, SUPER, mysql_native_password","category":"Security","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":"Lists security findings per account in one row: no password, connectable from anywhere, legacy authentication plugin, SUPER / GRANT / FILE privileges and no TLS requirement; the second query shows dangerous dynamic privileges.","when":"During periodic security audits, on a newly inherited server and before an 8.4 upgrade.","caution":"The query reads mysql.user directly and needs SELECT on it. Role accounts are locked with no password, so EMPTY PASSWORD is reported only for unlocked accounts. Super_priv is the static SUPER privilege; since 8.0 splits it into dynamic privileges, check the second query too. Accounts flagged NATIVE AUTH must move to caching_sha2_password before 8.4: ALTER USER 'u'@'h' IDENTIFIED WITH caching_sha2_password BY '...'; old clients / connectors may not support caching_sha2_password, test them first.","note":"mysql_native_password was deprecated in 8.0.34, is disabled by default in 8.4 (can be enabled with mysql_native_password=ON) and was removed in 9.0.","since":"MySQL 8.0","tags":["security audit","mysql.user","mysql_native_password","super","privileges","audit"]},{"id":"upgrade-checker-84","task":"8.0 → 8.4 LTS upgrade pre-check (util.checkForServerUpgrade)","category":"Upgrade","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":"Checks the current server against the target version: removed variables and privileges, parameters with new defaults, legacy authentication methods, invalid column definitions, foreign key and partition issues. Error-level findings block the upgrade.","when":"Before every major / LTS upgrade; rerun after fixing the findings.","caution":"Review the warnings too: in 8.4 defaults such as innodb_io_capacity (10000), innodb_log_buffer_size (64 MB), innodb_adaptive_hash_index (OFF), innodb_change_buffering (none), innodb_buffer_pool_instances and group_replication_consistency change and may affect performance; pin the values whose current behavior you want to keep in my.cnf. Removed privileges such as SET_USER_ID are dropped during the upgrade. Upgrade a replica first, watch it under load, then switch over; binary downgrade from 8.4 to 8.0 is not supported, so the fallback is a logical backup or a replica left on the old version.","note":"Removed in 8.4: the old replication syntax such as START / STOP SLAVE, SHOW SLAVE STATUS and CHANGE MASTER TO, default_authentication_plugin (replaced by authentication_policy), binlog_transaction_dependency_tracking and mysqlpump. mysql_native_password is disabled by default.","since":"MySQL Shell 8.0","tags":["upgrade","8.4","checkforserverupgrade","mysqlsh","lts","version upgrade"]},{"id":"charset-utf8mb4-convert","task":"Convert latin1 / utf8mb3 tables to 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":"Finds columns and tables that are not utf8mb4 and generates the conversion DDL; CONVERT TO changes both the table default and every character column, enabling emoji / 4-byte characters.","when":"On schemas migrated from 5.x, on 'Incorrect string value' (1366) errors, and when joins between columns with different collations cannot use indexes.","caution":"CONVERT TO rewrites the table with COPY and blocks writes for the duration (INPLACE is not supported); on large tables run the same --alter through pt-online-schema-change / gh-ost. utf8mb4 uses 4 bytes per character: an indexed VARCHAR(255) becomes 1020 bytes; the limit is 3072 bytes with the DYNAMIC / COMPRESSED row format and 767 with COMPACT / REDUNDANT, beyond which you get error 1071. VARCHAR / TEXT columns are promoted to a larger type (TEXT → MEDIUMTEXT) when needed to keep the same character capacity. A collation change affects sorting and comparison: utf8mb4_0900_ai_ci is accent and case insensitive, so values that used to differ may collide on a unique index (1062). Convert joined tables together. The client connection must use utf8mb4 too.","note":"utf8 (utf8mb3) is deprecated in 8.0; since 8.0.30 it is shown as utf8mb3 in SHOW CREATE TABLE. For Turkish collation rules (dotted and dotless I) use utf8mb4_tr_0900_ai_ci. The 8.0 default is 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":"Take a transaction log backup","category":"Backup & Log","command":"BACKUP LOG [Customer]\nTO DISK = N'D:\\Backup\\Customer_log_20260706_1200.trn'\nWITH COMPRESSION, CHECKSUM, STATS = 10;","what":"Takes a transaction log backup and (in FULL/BULK_LOGGED recovery model) truncates the used portion of the log so it can be reused.","when":"Regularly in FULL recovery model (e.g. every 15 min), for the point-in-time recovery chain, and when the log file grows.","caution":"Only meaningful in FULL/BULK_LOGGED recovery model (in SIMPLE the log is truncated automatically). At least one FULL backup must exist first so the log chain is not broken. Set up a correct delete/overwrite strategy for backup files.","tags":["backup log","log backup","transaction log","trn","recovery","backup"]},{"id":"backup-full-diff","task":"Take FULL and DIFFERENTIAL backups","category":"Backup & Log","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 backs up the entire database; DIFFERENTIAL backs up the extents changed since the last FULL.","when":"FULL: daily/weekly base backup. DIFF: fast intermediate backups between FULLs.","caution":"Restoring a DIFF requires its base FULL backup. INIT overwrites the existing file; with a wrong file name you lose a backup. CHECKSUM catches corruption early.","tags":["backup","full","differential","differential backup","bak","backup"]},{"id":"log-reuse-wait","task":"Why the log file grew / why it is not truncating","category":"Backup & Log","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":"The log_reuse_wait_desc column tells why the transaction log cannot be truncated (e.g. LOG_BACKUP, ACTIVE_TRANSACTION, REPLICATION, AVAILABILITY_REPLICA).","when":"To find the root cause when the log file keeps growing or the log is full (error 9002).","caution":"If LOG_BACKUP is shown, the fix is to take a log backup, not to shrink the file. Query only; no risk.","tags":["log_reuse_wait_desc","log growth","sys.databases","log backup","recovery","diagnostics"]},{"id":"shrink-log","task":"Shrink the log file","category":"Backup & Log","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":"Reduces the physical size of the transaction log file and returns space to the operating system.","when":"When the log has stayed permanently bloated after a one-off abnormal growth (large transaction/index rebuild).","caution":"DANGEROUS/CAUTION: Routine shrinking is BAD practice; performance drops while the log grows again and VLF fragmentation increases. Take a log backup first, otherwise the size will not drop. Shrink only an unnecessarily grown log file, not the data file. Then grow it back to an appropriate size in a single step.","tags":["shrink","dbcc shrinkfile","shrink log","log","vlf","caution"]},{"id":"vlf-count","task":"Show the VLF (Virtual Log File) count","category":"Backup & Log","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":"Returns the number of virtual log files (VLFs) inside the transaction log. Too many VLFs degrade startup/recovery/log backup performance.","when":"When the log is slow or database startup takes long; when reviewing the log growth setting.","caution":"Hundreds/thousands of VLFs are a warning sign; the fix is to shrink the log and grow it again in suitably large increments (e.g. 8GB steps). Query only; no risk.","since":"SQL Server 2016 SP2","note":"The sys.dm_db_log_info DMV arrived with SQL Server 2016 SP2 and 2017; before that, VLFs were counted from the row count of the undocumented DBCC LOGINFO output.","tags":["vlf","dm_db_log_info","dbcc loginfo","log","virtual log file","performance"]},{"id":"open-transaction","task":"Find the oldest open transaction (ACTIVE_TRANSACTION)","category":"Backup & Log","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":"Shows the oldest open (not committed/rolled back) transaction and the session holding it. An open transaction prevents log truncation (LOG_REUSE_WAIT = ACTIVE_TRANSACTION).","when":"To find the root cause when the log does not shrink despite log backups and log_reuse_wait_desc = ACTIVE_TRANSACTION.","caution":"Confirm with the business owner before KILLing the session holding a long open transaction; the rollback may take a long time. Query only; no risk.","tags":["dbcc opentran","open transaction","active_transaction","dm_tran_active_transactions","log","diagnostics"]},{"id":"add-data-file","task":"Add a data file to a database","category":"Storage","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":"Adds a new data file (.ndf) to an existing filegroup to increase capacity and spread I/O.","when":"When the disk is full, or when you want to spread I/O across multiple disks.","caution":"Writes to the new file are balanced by proportional fill; the emptiest file fills first. Specify FILEGROWTH as fixed MB, not a percentage. Setting MAXSIZE prevents uncontrolled growth.","tags":["add file","add data file","ndf","alter database","filegroup","storage"]},{"id":"add-filegroup","task":"Add a filegroup","category":"Storage","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":"Creates a new filegroup with a file in it; lets you place tables/indexes on a separate disk/filegroup.","when":"To separate archive/cold data, for filegroup-level backup/restore, and for I/O separation.","caution":"An empty filegroup cannot be backed up/used; add at least one file to it. Changing the default affects where new objects are placed.","tags":["filegroup","filegroup","add filegroup","alter database","ndf","storage"]},{"id":"modify-file-growth","task":"Change file size / autogrowth","category":"Storage","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":"Pre-grows a data/log file or sets its autogrowth increment/upper limit.","when":"To fix uncontrolled small growth increments, pre-allocate space, or set a MAXSIZE.","caution":"Percentage-based autogrowth produces very large increments on big files; use fixed MB. Without Instant File Initialization, data file growth blocks. SIZE cannot shrink a file (shrink is required).","tags":["modify file","autogrowth","filegrowth","size","alter database","storage"]},{"id":"move-file","task":"Move a data/log file to another location","category":"Storage","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":"Changes the physical location of a database file: the new path is defined in metadata, the file is moved physically while the database is offline, then the database is brought online.","when":"When replacing a disk or moving to another drive to gain space.","caution":"OFFLINE + ROLLBACK IMMEDIATE drops all active connections; plan for downtime. Do not bring the database ONLINE before actually copying the file to the new path in the OS, or the database becomes SUSPECT. tempdb requires a different method (MODIFY FILE + SQL Server service restart).","tags":["move file","move file","modify file","offline","online","mdf","ldf","storage"]},{"id":"add-tempdb-file","task":"Add a data file to tempdb","category":"Storage","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":"Adds an extra data file to tempdb to reduce allocation (PFS/GAM/SGAM) contention and spread I/O.","when":"On systems with tempdb contention (PAGELATCH_UP waits) or heavy tempdb usage.","caution":"All tempdb data files must have the SAME size and growth increment (for proportional fill). The added file is used immediately without a restart; if existing files differ in size, equalize them all first, otherwise the new (empty) file takes a disproportionate load. The file count is usually matched to the core count (up to 8); SQL Server 2016+ setup does this by default.","tags":["tempdb","add file","tempdb file","ndf","contention","pagelatch","storage"]},{"id":"index-rebuild-reorg","task":"Index rebuild / reorganize","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 recreates the index from scratch (removes fragmentation completely, refreshes statistics); REORGANIZE compacts the leaf level in place (lightweight, non-blocking).","when":"REORGANIZE for 5-30% fragmentation, REBUILD above 30%. During maintenance windows for large tables.","caution":"ONLINE = ON is available only in Enterprise/Developer; use OFF in Standard (the table is locked). REBUILD generates a lot of log and needs space. MAXDOP and SORT_IN_TEMPDB affect resource usage.","tags":["index","rebuild","reorganize","alter index","fragmentation","maintenance","online"]},{"id":"update-statistics","task":"Update statistics","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":"Updates the column/index statistics the optimizer uses; ensures correct execution plans and cardinality estimates.","when":"After large data changes/loads, on plan regressions; an index rebuild already fully refreshes statistics (not needed separately).","caution":"FULLSCAN is accurate but expensive; it creates I/O load on large tables. sp_updatestats updates only changed statistics and uses default sampling. A repeat UPDATE STATISTICS on the same index after an index REBUILD is unnecessary.","tags":["update statistics","statistics","sp_updatestats","fullscan","optimizer","maintenance"]},{"id":"dbcc-checkdb","task":"Integrity check (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":"Checks the physical and logical integrity of the database (page structures, allocation, index consistency) and reports corruption.","when":"In regular maintenance (weekly), on suspected hardware/IO problems, after 823/824 errors.","caution":"Uses heavy I/O and tempdb; run it in a maintenance window. If errors are found, REPAIR_ALLOW_DATA_LOSS must be the LAST resort and CAUSES DATA LOSS; consider restoring from a good backup first. PHYSICAL_ONLY is faster but skips logical checks.","tags":["dbcc checkdb","integrity","corruption","corruption","integrity","maintenance"]},{"id":"index-fragmentation","task":"Show index fragmentation","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":"Lists the fragmentation percentage and page count of indexes; the basis for the rebuild/reorganize decision.","when":"Before maintenance, to decide which indexes to address.","caution":"Fragmentation is irrelevant on small indexes (< 1000 pages); they are filtered out. 'DETAILED' mode is more accurate but very expensive; 'LIMITED' is usually sufficient. Query only; no risk.","tags":["fragmentation","dm_db_index_physical_stats","index","frag","maintenance","diagnostics"]},{"id":"kill-spid","task":"Kill a 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":"Terminates the specified session_id (SPID); its open transaction is rolled back.","when":"To stop a session that holds locks, runs a runaway/long query, or does not release its connection.","caution":"On a large open transaction the rollback can take a long time and cannot be canceled; monitor progress with KILL WITH STATUSONLY. Do not kill system SPIDs (is_user_process=0). Confirm the correct SPID.","tags":["kill","spid","kill session","dm_exec_sessions","dm_exec_requests","session"]},{"id":"blocking-chain","task":"Find the blocking (lock) chain","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":"Lists waiting sessions and the sessions blocking them, with wait time and the query they run; makes the lock chain visible.","when":"To find the source of lock contention on \"the application hangs / times out\" complaints.","caution":"Confirm with the business owner before killing the root blocker (the SPID that is not blocked by anyone but blocks others). Query only; no risk.","tags":["blocking","lock","lock","dm_exec_requests","blocking_session_id","session","chain"]},{"id":"active-queries","task":"Show active queries and their cost","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":"Shows currently running user queries with CPU, elapsed time, reads/writes and the exact statement text.","when":"To find which query is consuming resources during a live slowdown.","caution":"statement_start/end_offset extracts the exact running statement (not the whole batch). Query only; no risk.","tags":["active queries","active query","dm_exec_requests","dm_exec_sql_text","cpu","session"]},{"id":"sp-configure-memory","task":"Configure with 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":"Caps the maximum memory SQL Server can use for the buffer pool; leaves RAM for the operating system and other components.","when":"When the OS/other services on the server are starved of memory, or new RAM has been added.","caution":"Leave enough RAM for the operating system, SSAS/SSIS, CLR and thread stacks (usually at least 4-6 GB for the OS). Setting it too low cripples SQL Server performance. The change is not applied without RECONFIGURE; this setting is dynamic (no restart required).","tags":["sp_configure","max server memory","reconfigure","memory","configuration","memory"]},{"id":"reconfigure-general","task":"General sp_configure setting and 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":"Sets server-level configuration values; RECONFIGURE applies them to the running value.","when":"When changing server settings such as MAXDOP, the parallelism threshold, or optimize for ad hoc workloads.","caution":"Settings with is_dynamic = 0 require a SQL Server restart. RECONFIGURE WITH OVERRIDE skips validation for some settings; use it carefully. Note value_in_use before changing anything.","tags":["sp_configure","reconfigure","maxdop","cost threshold","sys.configurations","configuration"]},{"id":"ag-failover","task":"Availability Group failover","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":"Moves the Availability Group primary role to another replica; a planned failover to a synchronous replica happens without data loss.","when":"Role transfer for maintenance/patching, failover when the primary node fails.","caution":"The normal FAILOVER command is run on the SECONDARY replica that will take over. FORCE_FAILOVER_ALLOW_DATA_LOSS is used only when the primary is unreachable or on an asynchronous replica and can CAUSE DATA LOSS; be very careful if synchronization is not healthy.","tags":["availability group","ag","failover","hadr","failover","alwayson","configuration"]},{"id":"change-recovery-model","task":"Change the database recovery model","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":"Changes the database recovery model; determines how the log is managed and which recovery options exist.","when":"FULL: when point-in-time recovery/log backups/AG are required. SIMPLE: on systems that need no recovery point (dev/test, DW).","caution":"After switching to FULL, take a FULL backup to start the log chain and set up regular log backups, otherwise the log keeps growing. Switching from FULL to SIMPLE breaks the log chain; you lose point-in-time recovery. AG members must be in FULL recovery model.","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":"Monitor availability group health and lag (log send / redo queue, RPO)","category":"Always On & 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":"Shows replica connection and synchronization health; per database the log send queue, redo queue, send/redo rates, estimated redo catch-up time and the last-commit gap to the primary (potential data loss window); also returns the WSFC quorum state.","when":"On a NOT_HEALTHY / NOT SYNCHRONIZING alert in the AG dashboard, when checking RPO/RTO before a planned failover, or when users report stale data on a readable secondary.","caution":"A growing log_send_queue is the amount of log lost if the primary fails (RPO); a growing redo_queue is how long the database takes to come online after failover (RTO). On a secondary only the local replica is visible; run it on the primary for the full picture. data_loss_window_s is meaningful only while there is write traffic. Read-only query; safe.","since":"2016","note":"secondary_lag_seconds is a 2016+ column. If the redo queue keeps growing, check whether the redo thread on the secondary is blocked (long read queries, DDL) and look for REDO_THREAD_PENDING_WORK / PARALLEL_REDO_* waits.","tags":["availability group","ag","log send queue","redo queue","synchronization","rpo"],"untested":true},{"id":"ag-forced-failover-dr","task":"Forced failover (FORCE_FAILOVER_ALLOW_DATA_LOSS) and post-failover recovery","category":"Always On & 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":"Makes a DR replica the primary, accepting data loss, when the primary is completely lost; then resumes the suspended data movement on the other replicas and, when the old primary returns, takes a snapshot to inspect transactions that never reached the new primary.","when":"When the primary data center is down, only an asynchronous replica is left, or a synchronous replica is not SYNCHRONIZED and a failover is still required.","caution":"DATA LOSS: transactions after the last hardened LSN are lost. Get RPO sign-off from the business first. After a forced failover all secondary databases are SUSPENDED; the log is not truncated and keeps growing until HADR RESUME. If WSFC quorum is lost, start the cluster with forced quorum first (PowerShell: Start-ClusterNode -FixQuorum), otherwise the command fails. HADR RESUME on the old primary permanently discards the lost transactions; do not resume without taking the snapshot.","note":"For a planned failover without data loss use the FAILOVER command in the ag-failover recipe. AGs with CLUSTER_TYPE = NONE / EXTERNAL also use FORCE_FAILOVER_ALLOW_DATA_LOSS; on EXTERNAL (Pacemaker) the failover is driven by the cluster manager. A snapshot needs every logical data file of the source; for multi-file databases add one ON entry per data file.","tags":["force failover","allow data loss","dr","availability group","hadr resume","data loss"],"untested":true},{"id":"ag-add-db-autoseed","task":"Add a database to an AG (automatic seeding) and monitor seeding progress","category":"Always On & 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":"Adds the database to the AG by streaming it to the secondaries over the endpoint (VDI) without a manual backup/restore; shows seeding throughput, estimated completion and failure reason.","when":"When adding a new database to an AG, adding a new replica, or rebuilding a broken secondary database.","caution":"Seeding saturates the endpoint link and is uncompressed by default; for very large databases or a weak WAN a manual backup/restore + JOIN is more predictable. The primary log is not truncated during seeding (log_reuse_wait_desc = AVAILABILITY_REPLICA). On 2016 the secondary needs the same data/log folder paths as the primary; on 2017+, if the primary files are in the instance default paths, the secondary uses its own default paths. For a TDE database the certificate must be restored on every secondary first. Without GRANT CREATE ANY DATABASE on the secondary, seeding stays pending.","since":"2016","note":"Seeding compression is enabled with trace flag 9567 (costs CPU). Seeding start, completion and failures are written to the SQL Server error log; for detailed tracking create a separate Extended Events session with the hadr_automatic_seeding_* and hadr_physical_seeding_* events. Switch back with 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":"Check the listener, its IPs and read-only routing configuration","category":"Always On & 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":"Shows the listener DNS name, port and the cluster state of each subnet IP; for each primary scenario in the read-only routing list, which replica and URL read-intent connections are routed to.","when":"When applications cannot connect after a failover, ApplicationIntent=ReadOnly connections land on the primary, or a new subnet/replica was added.","caution":"Read-only routing works only for connections made through the listener with ApplicationIntent=ReadOnly and an explicit database; direct connections to an instance name are not routed. A replica with an empty read_only_routing_url cannot be a routing target. On a multi-subnet listener, clients without MultiSubnetFailover=True see 20-30 s connection timeouts after failover. Read-only query; safe.","note":"For 2016+ load balancing the routing list uses nested parentheses: READ_ONLY_ROUTING_LIST = (('SQL02','SQL03'),'SQL01'). If listener IPs show OFFLINE, check the resource dependencies on the cluster side (Failover Cluster Manager / Get-ClusterResource).","tags":["listener","read-only routing","applicationintent","multisubnetfailover","availability group","connection"],"untested":true},{"id":"logshipping-monitor","task":"Monitor log shipping status and latency","category":"Always On & 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":"Shows the age of the last log backup on the primary, the last copied/restored file and restore latency on the secondary together with their thresholds, and the backup/copy/restore job errors from the last 24 hours.","when":"When the log shipping alert job fires (errors 14420/14421), before a DR test, or when reporting how far behind the secondary is.","caution":"Monitor tables contain only the side the server sees: the primary table is populated on the primary/monitor, the secondary table on the secondary/monitor. A manual (non-log-shipping) log backup breaks the chain and the restore job keeps failing; use COPY_ONLY for ad-hoc log backups. In STANDBY mode user connections are dropped before each restore. Read-only query; safe.","note":"Date columns are in server local time; use the *_utc columns when comparing servers in different time zones. If the restore job logs \"skipping log backup file\", that file was already applied; to find a real gap, check the FirstLSN/LastLSN sequence with RESTORE HEADERONLY on the secondary.","tags":["log shipping","sp_help_log_shipping_monitor","restore latency","dr","secondary","latency"],"untested":true},{"id":"backup-copy-only","task":"Take an ad-hoc backup without breaking the chain (COPY_ONLY, striping, throughput)","category":"Backup & Log","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":"Takes a standalone FULL or log backup that does not affect the regular backup chain (differential base and log chain); for large databases, striping across files and a larger transfer size shorten the backup.","when":"When handing a copy to test/dev, before a risky change, or when a backup is requested for an audit; whenever you need a backup without touching the scheduled backup strategy.","caution":"An ad-hoc FULL without COPY_ONLY becomes the base of subsequent DIFF backups; if that file is deleted, the DIFFs are useless. COPY_ONLY has no effect together with DIFFERENTIAL. Restoring a striped backup needs every file; losing one makes the whole backup unusable. BUFFERCOUNT x MAXTRANSFERSIZE of memory is allocated outside the buffer pool (256 MB in the example); lower it on a server under memory pressure.","note":"For a TDE database, backup compression requires MAXTRANSFERSIZE greater than 65536 (2016+). Server defaults: sp_configure 'backup compression default' and 'backup checksum default'. On 2022 with Intel QAT hardware, WITH COMPRESSION (ALGORITHM = QAT_DEFLATE) is available.","tags":["copy_only","ad-hoc backup","stripe","maxtransfersize","buffercount","backup"]},{"id":"backup-to-url","task":"Back up to URL (Azure Blob SAS / S3-compatible storage)","category":"Backup & Log","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":"Writes the backup directly to object storage: to Azure Blob as block blobs with a SAS credential, or on 2022 to S3 API-compatible storage (AWS S3, MinIO etc.) with an access key credential. The credential is picked automatically by matching the URL prefix.","when":"For off-site backup requirements, cloud migration/hybrid DR scenarios, or when local backup storage is insufficient.","caution":"On Azure a single block blob holds at most 50,000 blocks; with 4 MB MAXTRANSFERSIZE that is roughly 195 GB per file, so stripe large databases across several URLs. On S3 a file has at most 10,000 parts; for large databases raise MAXTRANSFERSIZE (up to 20 MB, requires COMPRESSION) and stripe across multiple URLs (up to 64). S3 does not support INIT/append; FORMAT overwrites an existing backup with the same name. When the SAS expires, backup jobs fail with an error; put the expiry date in the calendar. The credential secret is stored in master; principals with CONTROL SERVER can use it.","since":"2022","note":"Block blob backup with SAS is 2016+, S3 support is 2022+. The legacy storage account key method (IDENTITY = account name) produces page blobs and is not recommended. For S3 the endpoint TLS certificate must be trusted by the server; for on-prem solutions such as MinIO the port goes into the URL (s3://minio.local:9000/bucket/...).","tags":["backup to url","azure blob","s3","credential","sas","cloud backup"],"untested":true},{"id":"backup-history-msdb","task":"Backup history from msdb and missing backup check","category":"Backup & Log","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":"Lists the last FULL/DIFF/LOG backup time and log backup age for every database; for one database, the backup chain with LSN sequence, compressed size, checksum flag and file path.","when":"In the morning check, to find which files a restore needs, to answer \"when was the last backup\" in an audit, or to see whether log backups are running during a log-full alert.","caution":"msdb only knows backups taken by this instance; backups taken on another server or on another AG replica are not visible (check every replica in an AG). A log backup's first_lsn must equal the previous one's last_lsn; a gap means the chain is broken. In FULL recovery a NULL last_log means the log is never backed up and will keep growing. Read-only query; safe.","note":"msdb history grows without limit and slows backup jobs; purge it regularly with EXEC msdb.dbo.sp_delete_backuphistory @oldest_date = '2026-01-01';. type values: D = FULL, I = DIFF, L = LOG, F = file/filegroup.","tags":["backupset","msdb","backup history","last backup","lsn","backup chain"]},{"id":"backup-restore-progress","task":"See backup / restore / CHECKDB progress and estimated completion","category":"Backup & Log","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":"Shows percentage progress, estimated finish time and current wait for long operations that report percent_complete (BACKUP, RESTORE, DBCC CHECKDB/SHRINK, ALTER INDEX REORGANIZE, rollback after KILL).","when":"When the nightly backup runs into business hours, to answer \"when will it finish\" during a large restore, or to follow the rollback of a killed session.","caution":"The estimate assumes linear progress; during the redo/undo phases of a restore and during log zero-initialization the percentage can stall for a long time. Log files do not benefit from instant file initialization (except growths up to 64 MB on 2022); restores with large logs spend a long time in the first phase. Read-only query; safe.","note":"If wait_type is mostly BACKUPIO / ASYNC_IO_COMPLETION, the bottleneck is the target/source disk; high BACKUPTHREAD or CPU points to compression cost. To see the long zeroing phase of a restore in detail in the error log, use trace flags 3004 + 3605.","tags":["percent_complete","backup progress","restore progress","eta","dm_exec_requests","progress"]},{"id":"tail-log-backup","task":"Take a tail-log backup before a restore (NORECOVERY / NO_TRUNCATE)","category":"Restore & Recovery","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":"Backs up the part of the log written since the last log backup (the tail), so the restore chain reaches the moment of failure and the last minutes of transactions are not lost.","when":"Before restoring over the same database, when the data file disk fails, or at the final cutover of a migration.","caution":"NORECOVERY puts the database into RESTORING and ends user access; do not run it on the wrong database. Restoring WITH REPLACE without a tail-log backup permanently discards the transactions after the last log backup. A backup taken with NO_TRUNCATE may contain incomplete metadata; run DBCC CHECKDB after the restore. A tail-log backup is not possible in SIMPLE recovery.","note":"To bring the database back from RESTORING without a restore: RESTORE DATABASE [Musteri] WITH RECOVERY;. NO_TRUNCATE is equivalent to COPY_ONLY + CONTINUE_AFTER_ERROR and tries to read the log even if the database cannot be opened.","tags":["tail-log","backup log","norecovery","no_truncate","restore","recovery"]},{"id":"restore-pitr-stopat","task":"Restore to a point in time (point-in-time restore, STOPAT)","category":"Restore & Recovery","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":"Applies FULL + (optional) DIFF + log backups WITH NORECOVERY and stops log replay at the given time; returns the database to just before the bad operation (wrong DELETE/UPDATE, DROP).","when":"To recover accidentally deleted/updated data, to inspect the data at a specific moment, or for an audit.","caution":"Restore under a side name rather than over the original, then copy the needed rows back; if you must restore over it, take a tail-log backup first. STOPAT is in server local time. The recovery model must be FULL; if the log backup containing the STOPAT time includes minimally logged operations under BULK_LOGGED, you cannot stop inside that backup. After RESTORE ... WITH RECOVERY no more logs can be applied.","note":"Specifying STOPAT on every log restore is the recommended pattern; the informational message \"contains records that were logged before the designated point in time\" on earlier files is normal. To stop at a named mark (BEGIN TRAN ... WITH MARK) use STOPATMARK / STOPBEFOREMARK. Use fn_dump_dblog or application logs to find the exact time.","tags":["stopat","point-in-time","restore log","norecovery","standby","rollback to time"]},{"id":"restore-verify-header","task":"Inspect and verify a backup file (VERIFYONLY, HEADERONLY, FILELISTONLY)","category":"Restore & Recovery","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":"Verifies that the backup is readable and its page checksums are valid without restoring it; shows the backup sets inside (Position, BackupType, FirstLSN/LastLSN, DatabaseVersion), the logical file names and stripe information.","when":"Before restoring a backup received from another team, to learn file names for MOVE, as a quick check after a backup job, or to pick the right FILE = n when a file holds several sets.","caution":"VERIFYONLY does not guarantee that the database is restorable and consistent; it only checks the backup structure and (if taken WITH CHECKSUM) page checksums. For real assurance run regular test restores + DBCC CHECKDB. A backup from a newer version cannot be restored on an older one (check DatabaseVersion). Read operations; safe.","note":"If HEADERONLY shows HasBackupChecksums = 0, the backup was taken without CHECKSUM and VERIFYONLY WITH CHECKSUM cannot validate pages. Files written without INIT accumulate multiple sets; pass WITH FILE = <Position> on restore.","tags":["restore verifyonly","headeronly","filelistonly","labelonly","backup verification","checksum"]},{"id":"page-restore-suspect","task":"Find corrupt pages (suspect_pages) and restore single pages","category":"Restore & Recovery","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":"Lists pages that hit I/O (823/824), checksum or torn page errors from msdb.dbo.suspect_pages; repairs individual corrupt pages by taking them from the FULL backup and rolling them forward with logs, without restoring the whole database.","when":"On 823/824 errors in the error log, consistency errors limited to a few pages in CHECKDB, or a user query failing with \"incorrect checksum\".","caution":"Requires FULL or BULK_LOGGED recovery and an unbroken log chain. Online page restore is Enterprise/Developer only; on other editions the database is offline during the restore. Allocation pages (GAM/SGAM/PFS), file headers and the boot page (1:9) cannot be restored this way. 823/824 usually means a storage problem; do not close the case without checking the disk/controller/SAN, or it will recur. With many bad pages a full database restore is faster.","note":"suspect_pages holds at most 1,000 rows; when full, new errors are not recorded, so delete old rows (event_type 4/5/7). Multiple pages are comma separated: PAGE = '1:336, 1:337'. In an AG, a corrupt page on the primary is fixed automatically from a secondary by automatic page repair (sys.dm_hadr_auto_page_repair).","tags":["suspect_pages","page restore","823","824","corruption","bad page"]},{"id":"top-queries-cpu","task":"Find the most expensive queries in the plan cache (CPU, reads, duration)","category":"Performance","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":"Groups statements in the plan cache by query_hash (ad-hoc queries that differ only in literals collapse into one row) and returns total and average CPU, duration, logical reads, memory grant and plan count.","when":"When CPU is pegged, on a \"server is slow\" complaint, for databases without Query Store, or when you need a server-wide view across all databases.","caution":"Only sees plans still in cache: queries whose plans were evicted by restart, DBCC FREEPROCCACHE, memory pressure or RECOMPILE are missing. Use Query Store for persistent, time-windowed analysis. plan_count > 1 hints at parameter sensitivity or different SET options. On a very large plan cache the query can take a few seconds; safe.","since":"2016","note":"total_grant_kb is a 2016+ column. For per-stored-procedure totals use sys.dm_exec_procedure_stats.","tags":["dm_exec_query_stats","top queries","cpu","query_hash","plan cache","expensive query"]},{"id":"wait-stats-top","task":"Wait statistics: what the server is waiting on","category":"Performance","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":"Filters out benign background waits and breaks down total server wait time by wait type with percentage and resource vs. signal (CPU queue) share; also shows user requests waiting right now.","when":"As the first step of a performance investigation to classify the bottleneck (I/O, CPU, lock, log, memory, parallelism); for before/after baseline comparisons.","caution":"Values are cumulative since restart; for a current problem take two snapshots and compare the delta. DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR) resets the counters for the whole server and breaks monitoring tools; avoid it. Interpretation: PAGEIOLATCH_* = disk reads, WRITELOG = log disk, LCK_M_* = blocking, CXPACKET/CXCONSUMER = parallelism, RESOURCE_SEMAPHORE = memory grants, SOS_SCHEDULER_YIELD + high signal = CPU pressure. Read-only query; safe.","note":"For per-session waits use sys.dm_exec_session_wait_stats (2016+); for per-query wait categories in Query Store use sys.query_store_wait_stats (2017+).","tags":["wait stats","dm_os_wait_stats","waits","pageiolatch","writelog","performance"]},{"id":"deadlock-system-health","task":"Read past deadlocks from the system_health session (deadlock graph)","category":"Performance","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":"Reads xml_deadlock_report events from the event_file target of the system_health Extended Events session (on by default); for each deadlock returns time (UTC), victim process, participant count, database and the full deadlock graph XML.","when":"When the application reports error 1205, when analysing deadlock frequency and the objects involved; on servers without trace flag 1222.","caution":"system_health files roll over; on a busy server older deadlocks may be gone, so set up a dedicated XE session for a permanent archive. Reading many files costs CPU; keep TOP during business hours. Events are written with a delay, so a new deadlock appears after a few seconds. Read-only; safe.","note":"Click the deadlock_graph value and save it as .xdl to open it graphically in SSMS. The system_health event_file default is 10 x 100 MB on 2016 SP2 CU12 / 2017 CU20 / 2019 CU3 and later (Standard/Enterprise); 4 x 5 MB on older builds and other editions. In the graph, inputbuf, waitresource (KEY/PAGE/OBJECT) and lockMode are the key fields; most deadlocks are fixed with a missing index or a consistent access order.","tags":["deadlock","system_health","xml_deadlock_report","extended events","1205","deadlock graph"]},{"id":"xe-long-query-session","task":"Create an Extended Events session: capture slow queries to file and read them","category":"Performance","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":"Creates a lightweight Extended Events session that writes statements and RPC calls longer than 1 second to disk with application, host, user, database and SQL text; turns the .xel files into rows with T-SQL.","when":"For intermittent slowness complaints, when you need client details (app/host) that Query Store does not show, and as a replacement for SQL Trace/Profiler.","caution":"A very low threshold (e.g. 0) fills files quickly and adds overhead on a busy server; filter in the WHERE predicate, not afterwards. The sql_text action is expensive at high volume. The folder must be writable by the SQL Server service account. duration and cpu_time are in microseconds. Stop/drop the session when done; with STARTUP_STATE = OFF it does not start after a restart.","note":"To narrow to one database add sqlserver.database_name = N'Musteri' to the WHERE clause. For a quick look use SSMS > Management > Extended Events > Watch Live Data.","tags":["extended events","xe","event session","slow query","sql_statement_completed","event_file"]},{"id":"tempdb-contention","task":"Diagnose tempdb contention and space usage","category":"Performance","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":"Shows PAGELATCH waits on tempdb allocation pages (PFS/GAM/SGAM) and system tables, the sessions using the most tempdb (temp tables vs. sort/hash spills), which database is inflating the version store, and the file configuration.","when":"When PAGELATCH_UP/EX waits rise under heavy temp table workloads, when tempdb fills up (1105/9002), or when the version store grows.","caution":"Pages 2:1:1, 2:1:2, 2:1:3 and PFS pages at multiples of 8088 mean allocation contention: the fix is multiple equally sized data files with equal growth (one per core up to 8, then add 4 at a time). For contention on system table pages, 2019+ offers memory-optimized tempdb metadata (requires a restart). If the version store grows, find the oldest open snapshot transaction; shrinking tempdb is not a fix. Read-only query; safe.","note":"Memory-optimized metadata: ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; + restart (2019+). 2022 added latch-free GAM/SGAM updates, which greatly reduces contention. The transaction holding the 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":"Enable Query Store, apply recommended settings and check its state","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":"Enables Query Store in the database; starts persisting query texts, plans, runtime and wait statistics in time intervals. The options define the storage limit, cleanup and capture mode.","when":"When a new database goes live, before a version/compatibility level upgrade (for a baseline), or when a plan regression is suspected.","caution":"When MAX_STORAGE_SIZE_MB is reached, Query Store silently switches to READ_ONLY (readonly_reason = 65536) and stops collecting; monitor current_storage_size_mb. QUERY_CAPTURE_MODE = ALL fills storage quickly and adds overhead under heavy ad-hoc workloads. Very busy OLTP systems may show QDS_* waits. Turning it OFF keeps the collected data; CLEAR deletes it.","since":"2016","note":"On 2022, newly created databases have Query Store on in READ_WRITE by default. 2019+ QUERY_CAPTURE_MODE = CUSTOM accepts thresholds (EXECUTION_COUNT, TOTAL_COMPILE_CPU_TIME_MS, TOTAL_EXECUTION_CPU_TIME_MS). Query Store for readable secondaries (ALTER DATABASE ... FOR SECONDARY SET QUERY_STORE = ON) is a limited preview on 2022, enabled only with trace flag 12606 and not supported in production; 2025 needs no trace flag (still preview).","tags":["query store","enable","max_storage_size_mb","query_capture_mode","read_only","qds"]},{"id":"qs-regressed-queries","task":"Find plan regressions with Query Store (same query, different plans)","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":"Finds queries that ran with more than one plan in the last 24 hours; puts each plan's execution count, weighted average duration/CPU/reads and how many times slower it is than the best plan side by side.","when":"When a query \"was fast yesterday and slow today\", after a statistics update, index change, compatibility level upgrade or patch.","caution":"Average duration also depends on data distribution; when comparing plans that ran with different parameter values, look at avg_reads too (parameter sniffing). Time windows are UTC. Results are statistically weak for plans with few executions. Read-only query; safe.","since":"2016","note":"SSMS > Query Store > Regressed Queries does the same graphically. On 2022 with compatibility level 160, Parameter Sensitive Plan (PSP) optimization creates multiple variant query_ids for one statement; map them to the parent with sys.query_store_query_variant (parent_query_id). 2017+ automatic tuning (FORCE_LAST_GOOD_PLAN) can fix such regressions automatically.","tags":["query store","plan regression","regressed queries","parameter sniffing","query_store_runtime_stats","plan"]},{"id":"qs-force-plan-hints","task":"Force a good plan or attach a hint with 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":"Forces a specific Query Store plan for the chosen query; the optimizer uses that plan on every compilation. On 2022 you can alternatively attach hints such as RECOMPILE, MAXDOP or OPTIMIZE FOR without changing application code.","when":"When a plan regression needs an urgent fix in production, until a permanent fix (index, query rewrite, statistics) is ready.","caution":"A forced plan can degrade as data grows and is not a permanent fix; review it regularly. If an index used by the plan is dropped/renamed, forcing fails (last_force_failure_reason_desc = NO_INDEX etc.) and the query compiles normally. After forcing, a \"morally equivalent\" plan may appear under a new plan_id; this is expected. Forcing works while Query Store is READ_ONLY, but verify after a restore or AG failover.","since":"2016","note":"Query Store hints are 2022+ (earlier in Azure SQL). Hints replace plan guides; if a hint cannot be applied the query runs without it and the reason appears in last_query_hint_failure_reason_desc. Plan forcing on readable secondaries (@replica_group_id) requires Query Store for secondary replicas, which on 2022 is a limited preview enabled with trace flag 12606.","tags":["force plan","sp_query_store_force_plan","query store hints","plan forcing","regression","recompile"]},{"id":"missing-index-dmv","task":"Read missing index suggestions from DMVs (with caveats)","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":"Scores the missing index suggestions the optimizer recorded at compile time by usage, estimated cost and impact, and generates a draft CREATE INDEX statement; on 2019+ also shows the query behind each suggestion.","when":"After a new application release, for a quick scan of index needs of read-heavy queries, or as a starting list for an index design review.","caution":"Do not apply suggestions blindly: the optimizer ignores existing indexes, often produces near-duplicate (overlapping) suggestions and overly wide INCLUDE lists, and orders columns by equality/inequality rather than selectivity. Every new index costs writes and storage; consider widening an existing index first. The DMVs reset on restart, database offline and index changes on the table. Read-only query; safe.","note":"Before creating a suggested index compare it with the table's existing indexes (sp_helpindex or sys.index_columns) and review it together with the unused index recipe. The MissingIndex element in plan XML gives the same information per query.","tags":["missing index","missing index dmv","dm_db_missing_index_details","index suggestion","create index","tuning"]},{"id":"unused-indexes","task":"Find unused / write-only indexes","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":"Lists nonclustered indexes that have not been read (seek/scan/lookup = 0) since restart but still incur write cost, with their size, and generates a draft DROP INDEX statement.","when":"For slow INSERT/UPDATE on write-heavy tables, to reduce storage and maintenance (rebuild, CHECKDB) time, or before index consolidation.","caution":"Counters reset on restart, database offline/detach and (on some older 2012/2014 builds) index rebuild; indexes used only by month-end/year-end reports look \"unused\" in a short window. Observe for at least one full business cycle before dropping, and in an AG check usage on readable secondaries separately (counters are per replica). Unique indexes enforce constraints even when not read; they are excluded. Read-only query; safe.","note":"Disabling with ALTER INDEX ... DISABLE before dropping makes rollback easy (the definition stays; REBUILD brings it back). Searching for the index name in Query Store or the plan cache shows whether it is referenced by a hint.","tags":["unused index","write-only index","dm_db_index_usage_stats","drop index","write overhead","index"]},{"id":"stats-health","task":"Statistics health: when updated, how many rows changed","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":"Shows for each statistic the last update time, sampling rate, rows modified since the last update and the threshold at which auto update fires (dynamic threshold for compat 130+).","when":"For plans with a large gap between estimated and actual rows, after a large data load, or when checking whether the statistics maintenance job is effective.","caution":"The threshold formula applies to compat level 130+ (MIN(500 + 20%, SQRT(1000 x rows))); lower compat levels use the 20% + 500 rule. Statistics never updated (empty tables) return NULL. Skewed columns with low sample_pct may need FULLSCAN or PERSIST_SAMPLE_PERCENT. Read-only query; safe.","since":"2016","note":"persisted_sample_percent reflects UPDATE STATISTICS ... WITH PERSIST_SAMPLE_PERCENT = ON (2016 SP1 CU4 / 2017 CU1+). See the update-statistics recipe for the update commands.","tags":["statistics","stats health","dm_db_stats_properties","modification_counter","auto update","cardinality"]},{"id":"checkdb-strategy","task":"DBCC CHECKDB strategy: last good check, splitting the work, interpreting results","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":"Shows the last successful CHECKDB time for every database, estimates the tempdb needed, lets you split the check for large databases into CHECKALLOC + CHECKCATALOG + CHECKTABLE pieces, and returns the results as rows (object, index, page).","when":"When auditing maintenance, for very large databases that do not fit in the maintenance window, or when determining the extent of corruption after 823/824.","caution":"The phrase \"repair_allow_data_loss is the minimum repair level\" in the result means repair will delete data; consider a restore from a good backup (or page restore) first. If only nonclustered indexes (index_id > 1) are affected, dropping and recreating the index fixes it without data loss. After finding corruption, investigate the disk subsystem. PHYSICAL_ONLY skips logical checks; a full CHECKDB is still required periodically. When splitting with CHECKTABLE, coverage is incomplete unless CHECKALLOC/CHECKCATALOG also run.","note":"DATABASEPROPERTYEX(..., 'LastGoodCheckDbTime') is available on 2016 SP2, 2017 CU9 and 2019+; on older builds read dbi_dbccLastKnownGood from DBCC DBINFO WITH TABLERESULTS. To offload, running CHECKDB on a copy restored from backup (e.g. on a test server) also validates the production database. The MAXDOP option is 2014 SP2 / 2016+.","tags":["dbcc checkdb","lastgoodcheckdbtime","checktable","corruption","repair_allow_data_loss","integrity"]},{"id":"resource-governor","task":"Limit reporting load with 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":"Routes sessions from specific logins or applications to a separate workload group at connect time; protects the OLTP workload by capping that group's CPU, memory grants, DOP and CPU time per request.","when":"When reporting/BI queries starve OLTP, when ETL overlaps business-hours load, or for resource sharing on a multi-tenant server.","caution":"The classifier function runs on every new connection; a slow or failing function slows down or breaks every login (no queries or table access in it). If it misbehaves, connect via the DAC and set CLASSIFIER_FUNCTION = NULL. Classification happens only at connect time; existing sessions are not affected. CAP_CPU_PERCENT is a hard cap applied even when CPU is idle. MAX_CPU_PERCENT only applies under CPU contention. Resource Governor is an Enterprise/Developer feature.","note":"Exceeding REQUEST_MAX_CPU_TIME_SEC only raises an event by default (cpu_threshold_exceeded); with trace flag 2422 (2016 SP2 / 2017 CU3+) the request is aborted. For I/O limits use MAX_IOPS_PER_VOLUME on the pool (2014+). Statistics: sys.dm_resource_governor_resource_pools / _workload_groups.","tags":["resource governor","resource pool","workload group","classifier","cpu limit","resource management"]},{"id":"tde-enable","task":"Set up TDE: master key, certificate, certificate backup and encryption progress","category":"Security","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":"Creates a database master key and certificate in master, backs up the certificate with its private key, creates an AES-256 database encryption key and encrypts the data and log files at rest; shows the progress of the background encryption scan.","when":"For data-at-rest encryption requirements such as GDPR/PCI, and against theft of backup files and MDF/LDF files.","caution":"If the certificate + private key backup or its password is lost, the TDE database and all its backups are PERMANENTLY unreadable; keep the backup in a secure vault away from the server. Enabling TDE on any database also encrypts tempdb (a small server-wide CPU cost). The encryption scan is I/O heavy; on 2019+ it can be paused with ALTER DATABASE ... SET ENCRYPTION SUSPEND/RESUME. For AG/log shipping the certificate must be installed on every target server first. TDE does not encrypt data in memory or on the network.","note":"Certificate expiry does not stop existing TDE, but for rotation create a new certificate and re-encrypt with ALTER DATABASE ENCRYPTION KEY ENCRYPTION BY SERVER CERTIFICATE [new] (only the DEK is re-encrypted, not the data). encryption_state_desc and encryption_scan_state_desc are 2019+ columns; on older versions use encryption_state (3 = encrypted, 2 = in progress). TDE is available in Standard edition from 2019.","tags":["tde","transparent data encryption","certificate","backup certificate","encryption","dek"]},{"id":"sql-server-audit","task":"Set up SQL Server Audit: logins, permission changes and table access","category":"Security","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":"Creates a server audit that writes to file; records failed logins, role membership, principal and permission changes server-wide and SELECT/DML access to a chosen table at database level, and reads the records with T-SQL.","when":"For audit/compliance requirements (GDPR, PCI, SOX), for \"who read/changed this table\" questions, or when privilege escalation is suspected.","caution":"SELECT auditing on a busy table generates records very fast and costs performance; narrow it by object and principal. ON_FAILURE = SHUTDOWN stops the server when the audit cannot be written; use it only when mandated. The audit folder must be writable by the SQL Server service account and not deletable by non-DBAs. event_time is UTC. A database audit specification is restored with the database but stays orphaned if the server audit GUID does not match.","note":"Database audit specifications are available in Standard edition from 2016 SP1. Always include AUDIT_CHANGE_GROUP so that changes to the audit itself, including turning it off, are recorded.","tags":["sql server audit","server audit","audit specification","fn_get_audit_file","auditing","compliance"]},{"id":"orphaned-users-fix","task":"Find orphaned users and remap them to logins (after restore / AG)","category":"Security","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":"Finds database users whose SID does not match any server login (orphaned); remaps a user to an existing login and generates a script to recreate logins on another server with the same SID and password hash.","when":"After restoring/attaching a database on another server, when the application gets \"Login failed for user\" after an AG/log shipping failover, or during a server migration.","caution":"ALTER USER ... WITH LOGIN changes the user's SID to the login's; in an AG this change flows from the primary to the secondaries, but if logins on the secondary servers have different SIDs the problem returns on the next failover. The permanent fix is creating logins with the same SID on every replica. The password hash is sensitive; store the script securely. Contained database users (authentication_type = DATABASE) need no login and are not listed.","note":"sp_change_users_login is deprecated; use ALTER USER WITH LOGIN. For an orphaned dbo (database owner login missing) use ALTER AUTHORIZATION ON DATABASE::[Musteri] TO [sa];. For bulk login migration use 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":"Login and permission audit: sysadmin members, risky permissions, weak passwords","category":"Security","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":"Lists server role memberships, risky permissions such as CONTROL SERVER/IMPERSONATE, SQL logins with password policy off, locked, or with a password equal to the login name or empty; in the database, role memberships and explicit non-system object permissions.","when":"For periodic access reviews, before an audit, after someone leaves the team, or when a security incident is suspected.","caution":"Permissions granted through Windows groups appear under the group name; to see the people use EXEC xp_logininfo N'DOMAIN\\Group', 'members';. Verify application dependencies before revoking anything. PWDCOMPARE works only for SQL logins and requires CONTROL SERVER. Read-only query; safe.","note":"For a principal's effective permissions: EXECUTE AS USER = N'app_user'; SELECT * FROM fn_my_permissions(NULL, 'DATABASE'); REVERT;. On 2022 the new granular roles (##MS_ServerStateReader##, ##MS_DefinitionReader## etc.) grant monitoring rights without sysadmin.","tags":["permission audit","sysadmin","server_role_members","database_permissions","pwdcompare","security"]},{"id":"ledger-tables","task":"Create a ledger table, read its change history and verify integrity","category":"Security","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":"Creates a ledger table that records row changes in a cryptographic hash chain; the _Ledger view shows who made each INSERT/UPDATE/DELETE and when, and verification against a stored digest proves the data was not altered afterwards (including by DBAs).","when":"For data whose immutability must be provable: financial records, audit trails, contract/approval records.","caution":"Ledger cannot be turned off after the table is created; a ledger table cannot be converted to a regular table, and a dropped ledger table is kept as a dropped ledger table. Updatable ledger writes a history row on every change and storage grows. Unless digests are stored outside the database in immutable storage (WORM, Azure immutable blob), verification has no evidential value. sp_verify_database_ledger fails with error 37498 without snapshot isolation.","since":"2022","note":"sp_generate_database_ledger_digest returns JSON; sp_verify_database_ledger accepts a single digest or a JSON array and can verify a single table with @table_name. For automatic digest storage: ALTER DATABASE SCOPED CONFIGURATION SET LEDGER_DIGEST_STORAGE_ENDPOINT = '<blob url>'. CREATE DATABASE ... WITH LEDGER = ON makes every table a ledger table by default.","tags":["ledger","append-only","sp_verify_database_ledger","digest","immutable","tamper-evident"]},{"id":"agent-job-failures","task":"SQL Agent job failure history and last run status","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":"Shows job steps that failed or were cancelled in the last 24 hours with their error message; each job's last outcome, whether it is running now and its next scheduled run; and the Agent service state.","when":"In the morning check, on a backup/maintenance job alert, for \"did the nightly job run\", or after a failover to verify jobs run on the right replica.","caution":"Job history in msdb is limited (default 1,000 rows total, 100 per job); frequently running jobs quickly push out old failures, so raise the limit in Agent properties. The message column is truncated at 4,000 characters; define an output file on the job step for long output. If the Agent service is stopped, no scheduled job runs and none reports an error. Read-only query; safe.","note":"msdb.dbo.agent_datetime is undocumented but widely used. In an AG, jobs must exist on every replica and check the role in the first step with 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":"Add a member to the replica set","category":"Replica Set","command":"rs.add({ host: \"mongo2:27017\", priority: 1, votes: 1 })","what":"Adds a new member (secondary) to the existing replica set and starts an initial sync.","when":"When adding a new node to increase read capacity or for redundancy.","caution":"The new member pulls data from the primary during initial sync; with large data sets this creates network and disk load.","tags":["replica","rs.add","member","member","add"]},{"id":"rs-remove-member","task":"Remove a member from the replica set","category":"Replica Set","command":"rs.remove(\"mongo2:27017\")","what":"Removes the specified host from the replica set configuration.","when":"When taking a node out of service or replacing hardware.","caution":"Do not remove the primary; removing members in a way that breaks the majority can make the set unavailable for reads and writes.","tags":["replica","rs.remove","member","member","remove"]},{"id":"rs-step-down","task":"Step down the primary (stepDown)","category":"Replica Set","command":"rs.stepDown(60)","what":"Demotes the current primary to secondary for the specified number of seconds and triggers a new election.","when":"To hand the primary role over to another node before planned maintenance.","caution":"A brief write outage occurs during stepDown; the application must reconnect.","tags":["replica","stepdown","primary","failover","step down"]},{"id":"rs-set-priority","task":"Change member priority","category":"Replica Set","command":"cfg = rs.conf(); cfg.members[1].priority = 2; rs.reconfig(cfg)","what":"Changes the specified member's priority for becoming primary in elections.","when":"When you want a specific node to be the preferred primary.","caution":"reconfig can trigger an election and cause a brief primary change/write outage.","tags":["replica","priority","reconfig","priority","election"]},{"id":"rs-status","task":"View replica set status","category":"Replica Set","command":"rs.status()","what":"Shows the state, roles, replication lag and health of the set members.","when":"When checking replication health and lag.","caution":"Read-only command; no risk.","tags":["replica","status","status","health","rs.status"]},{"id":"rs-hidden-member","task":"Configure a hidden / non-voting member","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":"Makes a member hidden (invisible to the application), non-voting and priority 0, using it only for backup/analytics.","when":"When you want to isolate backups or analytical queries.","caution":"A hidden member must have priority 0; otherwise reconfig fails.","tags":["replica","hidden","votes","hidden","non-voting"]},{"id":"rs-resync","task":"Resync a member from scratch","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 has no separate resync command; stopping the secondary, emptying its data directory and restarting it starts a new initial sync from the primary (or a sync source).","when":"When a member falls so far behind that it drops outside the oplog window (RECOVERING / too stale) or its data becomes inconsistent.","caution":"All local data on the member is deleted and re-pulled; do this only on a secondary and make sure the majority stays up. Sync time and network/disk load are high; for very large data sets, seeding from a file system snapshot of a healthy member is faster.","tags":["replica","resync","initialsync","sync","resync"]},{"id":"sh-add-shard","task":"Add a shard to the cluster","category":"Sharding","command":"sh.addShard(\"shardRS/mongoShard1:27018\")","what":"Adds a new shard (replica set) to the sharded cluster.","when":"When scaling storage or throughput capacity horizontally.","caution":"After adding a shard, the balancer starts migrating chunks; this creates additional IO load.","tags":["sharding","addshard","shard","add","cluster"]},{"id":"sh-shard-collection","task":"Shard a collection","category":"Sharding","command":"sh.shardCollection(\"shop.customer\", { customerId: \"hashed\" })","what":"Shards the specified collection on the given shard key.","when":"When you want to distribute a growing collection across multiple shards.","caution":"The shard key can be changed with reshardCollection in 5.0+ (and extended with refineCollectionShardKey in 4.4+), but this is expensive because it redistributes all of the data; a poor key choice leads to uneven distribution.","tags":["sharding","shardcollection","key","customer","shard key"]},{"id":"sh-start-balancer","task":"Start the balancer","category":"Sharding","command":"sh.startBalancer()","what":"Enables the balancer process that distributes chunks.","when":"When you want to re-enable balancing after maintenance.","caution":"The balancer creates IO and network load while migrating chunks; define a balancing window outside peak hours.","tags":["sharding","balancer","start","start","rebalance"]},{"id":"sh-stop-balancer","task":"Stop the balancer","category":"Sharding","command":"sh.stopBalancer()","what":"Disables the balancer that performs chunk migrations.","when":"To stop migrations before a backup, maintenance or a bulk data load.","caution":"If it stays off for a long time, shards become unbalanced; do not forget to re-enable it afterwards.","tags":["sharding","balancer","stop","stop","maintenance"]},{"id":"sh-status","task":"View sharding status","category":"Sharding","command":"sh.status()","what":"Summarizes shards, databases, sharded collections and chunk distribution.","when":"When examining the cluster's distribution state and balancer health.","caution":"Read-only command; no risk.","tags":["sharding","status","status","chunk","sh.status"]},{"id":"sh-add-zone","task":"Add a zone (tag) and assign a range","category":"Sharding","command":"sh.addShardToZone(\"shard0000\", \"TR\"); sh.updateZoneKeyRange(\"shop.customer\", { region: \"TR\" }, { region: \"TR~\" }, \"TR\")","what":"Assigns a shard to a zone and binds a specific key range to that zone (zone sharding).","when":"When you want to keep data on specific shards by geography or hardware.","caution":"Zone ranges must match the shard key; a wrong range prevents chunks from being migrated.","tags":["sharding","zone","tag","region","range"]},{"id":"sh-move-chunk","task":"Move a chunk manually","category":"Sharding","command":"sh.moveChunk(\"shop.customer\", { customerId: 1000 }, \"shard0001\")","what":"Manually moves the chunk containing the specified key to the target shard.","when":"To correct uneven distribution or place specific data.","caution":"A manual move can conflict with the balancer; large chunk migrations create IO load and brief locking.","tags":["sharding","movechunk","chunk","move","manual"]},{"id":"compact-collection","task":"Compact a collection","category":"Maintenance","command":"db.runCommand({ compact: \"customer\" })","what":"Rewrites the collection and its indexes on disk, reclaiming free space.","when":"When you want to reclaim disk space after many deletes/updates.","caution":"In 4.4+, compact does not block reads/writes; it only blocks certain DDL operations on the same collection (drop, createIndex, dropIndex, etc.); it still generates heavy IO and does not always return space to the operating system. Run it on a secondary and in a maintenance window when possible.","tags":["compact","storage","maintenance","disk","storage"]},{"id":"create-index-background","task":"Create an index (non-blocking)","category":"Maintenance","command":"db.customer.createIndex({ email: 1 }, { name: \"idx_email\" })","what":"Creates an index on the specified field; since MongoDB 4.2, index builds do not block writes by default.","when":"When adding a new index to improve query performance.","caution":"On large collections, index builds consume CPU and IO; load on the primary can increase.","since":"MongoDB 4.2","note":"MongoDB 4.2 introduced a new index build process, and index creation no longer blocks writes by default; therefore the old { background: true } option is unnecessary and is ignored from 4.2 onward.","tags":["index","createindex","background","create","performance"]},{"id":"collection-stats","task":"View index and collection sizes","category":"Maintenance","command":"db.customer.stats({ indexDetails: true })","what":"Reports the collection size, storage size and the size of each index.","when":"When analyzing storage usage and index cost.","caution":"Read-only command; no risk.","tags":["stats","index","size","collection","storage"]},{"id":"log-rotate","task":"Rotate the log file","category":"Maintenance","command":"db.adminCommand({ logRotate: 1 })","what":"Closes the current log file and opens a new one (log rotation).","when":"When the log file has grown or before archiving.","caution":"If used together with OS log rotation, check the configuration to avoid conflicts.","tags":["log","logrotate","rotate","maintenance","archive"]},{"id":"current-op-long","task":"Find long-running operations","category":"Maintenance","command":"db.currentOp({ \"active\": true, \"secs_running\": { $gt: 5 } })","what":"Lists active operations that have been running for more than 5 seconds.","when":"When identifying problem queries during slowness or lockups.","caution":"Read-only command; no risk.","tags":["currentop","long","long-running","operation","diagnostics"]},{"id":"kill-op","task":"Kill an operation","category":"Maintenance","command":"db.killOp(12345)","what":"Terminates the running operation with the specified opid.","when":"When a query locks up the system or runs far too long.","caution":"Killing a write operation can leave partial changes; avoid killing system/replication operations.","tags":["killop","kill","operation","kill","diagnostics"]},{"id":"set-parameter","task":"Change a runtime parameter","category":"Parameters","command":"db.adminCommand({ setParameter: 1, cursorTimeoutMillis: 600000 })","what":"Changes a runtime parameter without restarting the server.","when":"When you want to adjust a server behavior temporarily.","caution":"The change is not persistent; on restart it reverts to the value in the config file. A wrong value can degrade performance.","tags":["setparameter","parameter","runtime","setting","admin"]},{"id":"set-profiling","task":"Set the profiling level","category":"Parameters","command":"db.setProfilingLevel(1, { slowms: 100 })","what":"Records slow queries (over 100 ms) in the system.profile collection (level 1).","when":"When you want to diagnose slow queries.","caution":"Level 2 records all operations and adds significant overhead; profiling data takes up disk space.","tags":["profiling","slowms","profiler","slow","diagnostics"]}]},"recipes/mongodb-ext.json":{"engineId":"mongodb","engine":"MongoDB","order":2,"recipes":[{"id":"rs-oplog-window","task":"Measure the oplog window and replication lag","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":"Shows how many hours of writes the oplog holds (oplog window) and how many seconds each secondary is behind the primary, together with its sync source.","when":"On a replication lag alert, before planned maintenance to calculate how long a member can stay down, and after bulk write jobs.","caution":"If a member stays down longer than the oplog window it becomes 'too stale' and needs an initial sync. Bulk update/delete jobs can shrink the window within hours; judge the window by its value at peak load. The lag calculation is based on the optimeDate difference; if the primary receives no writes, lag does not look high but the window does not shrink either.","since":"MongoDB 4.4","note":"rs.printSlaveReplicationInfo() was renamed to rs.printSecondaryReplicationInfo() in 4.4. If the window is too small, grow the oplog with replSetResizeOplog (see rs-oplog-resize). Delayed members appear behind by secondaryDelaySecs (slaveDelay in 4.4); exclude them from lag alerts.","tags":["oplog","oplog window","replication lag","delay","secondary","printReplicationInfo"],"untested":true},{"id":"rs-oplog-resize","task":"Resize the oplog online (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":"Grows or shrinks the oplog of a running member without a restart; minRetentionHours keeps oplog entries for a minimum period even if the size limit is exceeded.","when":"When the oplog window does not cover the maintenance or initial sync duration, or bulk write jobs shrink the window.","caution":"The command affects only the member it runs on and is not replicated; apply it on every member. The window must also be sufficient on members that act as sync sources. Shrinking does not return disk space to the OS immediately; compact on oplog.rs is needed for that. With minRetentionHours the oplog can grow beyond size depending on write volume; plan disk accordingly.","since":"MongoDB 4.4","note":"The new size persists across restarts; minRetentionHours set by the command does not, so also set storage.oplogMinRetentionHours to keep it. size must be at least 990 MB. Changing replication.oplogSizeMB in the configuration only takes effect when the oplog is first created.","tags":["oplog","resize","replSetResizeOplog","minRetentionHours","size","oplog window"],"untested":true},{"id":"rs-force-reconfig","task":"Force a replica set reconfiguration after losing the majority (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":"When the majority of voting members is permanently lost (e.g. a whole data center is gone), builds a new config from the surviving members so the set can elect a primary again.","when":"Only in a disaster scenario: the majority will not come back and the set is stuck read-only.","caution":"This is a last resort. If the lost members return later with the old config, split-brain and rollback can occur; isolate them from the network and, when bringing them back, wipe their data and add them with an initial sync. Pick the member with the most recent optime; otherwise writes not majority-committed on other members fall into rollback. Do not use force for routine membership changes.","since":"MongoDB 4.4","note":"force reconfig bumps the config version by a large random increment and is the only reconfig form that can run on a secondary. In 4.4+ normal reconfigs can add or remove at most one voting member at a time; force is not subject to this limit.","tags":["force reconfig","majority","disaster","recovery","split-brain","rs.reconfig"],"untested":true},{"id":"rs-rolling-maintenance","task":"Rolling maintenance on a replica set (restart, OS patch, minor upgrade)","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":"Gives the order for taking members into maintenance one at a time, secondaries first and the primary last, so restarts, OS patches or binary updates are done without taking the set out of service.","when":"During planned OS/kernel updates, minor version upgrades, mongod parameter changes or disk/hardware maintenance.","caution":"Never shut down more than one member at a time; in a 3-member set losing two members loses the majority. After each member, confirm SECONDARY state and that lag has closed. stepDown causes a short write interruption; make sure drivers reconnect with retryable writes. If there is an arbiter, handle it in the same sequence.","since":"MongoDB 4.4","note":"rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs): the second parameter is how long the primary waits for a secondary to catch up; if none catches up, stepDown fails and the primary stays. To keep a specific member from becoming primary during maintenance, run rs.freeze(seconds) on it.","tags":["rolling","maintenance","restart","stepDown","upgrade","patching"],"untested":true},{"id":"sh-balancer-window","task":"Balancer state, active window and per-collection balancing","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":"Shows whether the balancer is enabled and running a round, restricts chunk migrations to a time window and disables or enables balancing for a single collection.","when":"When chunk migrations hurt daytime performance, during the backup window or while isolating a migration problem on one collection.","caution":"A window that is too narrow cannot keep up with data growth and shards become unbalanced; check balance regularly with balancerCollectionStatus. A migration started before the window closes runs until it finishes. If per-collection balancing is left disabled, that collection keeps growing on a single shard.","since":"MongoDB 6.0","note":"activeWindow times are evaluated in the local time zone of the config server replica set primary. In 6.0.3+ the balancer decides based on the data size difference between shards, not the chunk count; chunk counts in sh.status() can look uneven while the data is balanced.","tags":["balancer","activeWindow","window","migration","sharding","balancerCollectionStatus"],"untested":true},{"id":"sh-jumbo-chunks","task":"Find jumbo chunks, split them and clear the flag","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":"Lists chunks flagged as jumbo because the balancer could not move them, splits those that can be split and clears the jumbo flag; shows how to refine the shard key for huge single-value chunks.","when":"When one shard stays noticeably larger than the others, jumbo appears in sh.status() output, or the balancer reports migration errors.","caution":"A chunk whose documents all share the same shard key value cannot be split; the only lasting fix is to change the shard key with refineCollectionShardKey or reshardCollection. refineCollectionShardKey requires a supporting index for the new key to exist beforehand. Clearing the jumbo flag without splitting makes the balancer retry the same chunk and fail again.","since":"MongoDB 5.0","note":"Low-cardinality shard keys or keys concentrated on one value (e.g. status, country) are the most common cause of jumbo chunks. Before 5.0, config.chunks documents are queried by the ns field. clearJumboFlag arrived in 4.2.3/4.0.15.","tags":["jumbo","chunk","split","clearJumboFlag","shard key","refineCollectionShardKey"],"untested":true},{"id":"sh-shard-key-check","task":"Check shard key choice and data distribution (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":"Shows the percentage of a collection's data on each shard; analyzeShardKey reports the cardinality, most frequent values and monotonicity of the current or candidate shard key, and whether sampled queries target a single shard or all shards (scatter-gather).","when":"Before sharding a new collection, when a hot shard or uneven growth is noticed, and before deciding on reshardCollection.","caution":"analyzeShardKey needs an index that supports the key and samples documents on large collections; it adds read load at peak time. While query sampling is on, samples are written to config.sampledQueries; turn it off when the analysis is done. A monotonically increasing key (ObjectId, timestamp) piles all inserts onto one shard.","since":"MongoDB 7.0","note":"analyzeShardKey and configureQueryAnalyzer arrived in 7.0 and also work on replica sets. A good shard key has high cardinality, low frequency, is not monotonic and appears in the filter of frequent queries. A hashed key fixes distribution but turns range queries into scatter-gather.","tags":["shard key","analyzeShardKey","getShardDistribution","cardinality","distribution","hot shard"],"untested":true},{"id":"explain-execstats","task":"Inspect a query plan with real execution statistics (explain executionStats)","category":"Performance","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":"Runs the query and shows the winning plan (IXSCAN/COLLSCAN, SORT stage), the number of returned documents, examined index keys and documents, and the time taken.","when":"When analyzing a query that appears slow in the profiler or log, and when verifying that a new index is actually used.","caution":"executionStats really runs the query; explaining a heavy query at peak time adds load too. For write commands (update/delete/findAndModify) explain does not modify data but does run the plan. A high totalDocsExamined/nReturned ratio or an in-memory SORT stage in the plan means the index is not suitable.","since":"MongoDB 4.4","note":"Apply the ESR rule when designing compound indexes: Equality fields first, then Sort, then Range. Use explain(\"allPlansExecution\") to see rejected plans as well. In 7.0+, when a query runs on the Slot-Based Engine, winningPlan contains queryPlan and slotBasedPlan fields.","tags":["explain","executionStats","IXSCAN","COLLSCAN","plan","index"]},{"id":"profiler-analyze","task":"Extract the slowest queries and query shapes from profiler data","category":"Performance","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":"From records in the system.profile collection, lists recent slow operations, total time per query shape (queryHash) and COLLSCAN queries that use no index.","when":"On a slowness complaint, to find which query consumes server time and to determine index needs.","caution":"system.profile is a 1 MB capped collection by default and is per database; on a busy system records roll over within minutes, so analyze while profiling is on or right after. Profiling level 2 or slowms 0 writes every operation and adds significant overhead. Records are kept only on the member where they ran and are not replicated.","since":"MongoDB 4.4","note":"Even with profiling off, operations above slowms (default 100 ms) are written to the mongod log as 'Slow query'; attr.durationMillis and attr.planSummary in the log give the same diagnosis. In 8.0+ a planCacheShapeHash field is also written for the query shape.","tags":["profiler","system.profile","slow query","top queries","COLLSCAN","queryHash"]},{"id":"index-usage-hide","task":"Find unused indexes and hide them before dropping (hideIndex)","category":"Performance","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 shows how many times each index has been used; hideIndex hides an index from the query planner without dropping it, so the effect of removing it can be tested reversibly.","when":"When cleaning up unnecessary indexes that add write overhead and storage.","caution":"Counters are per member and reset on restart; if reads go to secondaries, check all members and observe a long enough period (including month-end reports). Unique or TTL indexes with ops 0 may still be required: the unique constraint and TTL deletes do not show up in the counter. A hidden index is still maintained on writes, and a hidden unique index still enforces the constraint.","since":"MongoDB 4.4","note":"hideIndex requires featureCompatibilityVersion 4.4 or higher. The _id index cannot be hidden. Unhiding with unhideIndex needs no rebuild and takes effect immediately.","tags":["indexStats","hideIndex","unused index","index cleanup","dropIndex","index"]},{"id":"connections-by-app","task":"Count connections by application and client (connection storm diagnosis)","category":"Performance","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":"Shows the total and available connection counts on the server, then groups open connections by appName and client IP together with how many are active.","when":"When the connection count jumps suddenly, on 'connection refused' or pool timeout errors, to find which application server leaks connections.","caution":"idleConnections: true also returns all idle connections, so on systems with thousands of connections the output is large; summarizing with $group matters for that reason. Rows with a null client are internal threads. Each connection consumes roughly 1 MB of memory; a connection storm also grows memory outside the WiredTiger cache.","since":"MongoDB 4.4","note":"The lasting fix is usually on the driver side: maxPoolSize, maxIdleTimeMS and the number of application instances. If applications do not set appName, telling them apart is hard; ask for appName=<service> in the connection string. net.maxIncomingConnections and the OS ulimit -n value set the hard upper limit.","tags":["connections","connection count","currentOp","appName","connection storm","pool"]},{"id":"index-build-monitor","task":"Monitor and abort an in-progress index build","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":"Lists running index builds with the number of documents scanned and the percentage progress (progress.done / progress.total); if needed, aborts the build with dropIndex.","when":"To estimate when a createIndex started on a large collection will finish, or to stop a build that creates load at peak time.","caution":"Abort an index build with dropIndex/dropIndexes, not killOp; in a replica set run it on the primary so the abort propagates to all members. Some DDL operations on the collection wait during the build. A short exclusive lock is taken at the end of the build; under very heavy writes that moment can cause latency.","since":"MongoDB 4.4","note":"In 4.4+ replica sets an index build runs simultaneously on all data-bearing members and commits once commitQuorum (default votingMembers) is satisfied; if a secondary lags, the build does not finish on the primary either. In 5.0+ a build interrupted by a clean shutdown resumes where it left off after restart.","tags":["index build","createIndex","progress","monitor","dropIndex","commitQuorum"]},{"id":"rolling-index-build","task":"Rolling index build on a replica set (member by member)","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":"Builds the index on each member separately by temporarily running the member standalone outside the set; the primary never carries the build load.","when":"When the impact of the default distributed build on the primary and on replication lag is unacceptable for a very large collection.","caution":"Each member's maintenance time must be shorter than the oplog window; otherwise the member is too stale when it returns. When building a unique index the rolling way, stop writes to the collection for the whole build; otherwise some members may end up with violating documents. Start the standalone member on a different port so applications do not connect to it.","since":"MongoDB 4.4","note":"In 4.4+ the default build is already non-blocking and runs simultaneously on all members; the rolling method is needed only when that load is unacceptable. In a sharded cluster the procedure is done for each shard replica set separately, with the balancer stopped beforehand.","tags":["rolling index","index build","standalone","replica","createIndex","maintenance"],"untested":true},{"id":"ttl-index","task":"Create a TTL index, change its expiry and monitor TTL deletes","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":"Creates a TTL index that automatically deletes documents a set time after a date field, changes the expiry with collMod without a rebuild, and shows how many documents the TTL monitor has deleted.","when":"To clean up time-bound data such as sessions, logs or temporary tokens without writing a delete job in the application, and to investigate why a growing collection is not shrinking.","caution":"The TTL monitor runs every 60 seconds by default; deletion is not instant. The field must be of type Date (or an array of Dates); string dates or documents without the field are never deleted. Shortening the expiry causes many documents to be deleted at once, write load and replication lag. Deletes happen only on the primary and are written to the oplog.","since":"MongoDB 5.1","note":"A TTL index must be single-field (compound TTL is not supported). Adding expireAfterSeconds to an existing non-TTL single-field index with collMod is possible in 5.1+; changing an existing TTL expiry also works on older versions. To delete at a specific time, use expireAfterSeconds: 0 and store the deletion time in the field.","tags":["ttl","expireAfterSeconds","collMod","auto delete","index","metrics.ttl"]},{"id":"wt-cache-pressure","task":"Measure WiredTiger cache pressure (fill, dirty ratio, application eviction)","category":"Performance","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":"Shows the WiredTiger cache size, its fill and dirty percentages, how many pages application threads were forced to evict, and the read/write ticket status.","when":"When disk reads increase, latency spikes suddenly or write throughput drops; when sizing the cache.","caution":"Once fill exceeds 80% (eviction_target) background eviction starts; at 95% (eviction_trigger) application threads also take part in eviction and queries slow down. For dirty data 5% is the target and 20% the trigger; constantly near 20% means disk write capacity is insufficient. A growing pages evicted by application threads counter is latency felt directly by users. Values are cumulative counters; look at the difference between two samples.","since":"MongoDB 7.0","note":"The default cache size is the larger of (RAM - 1 GB) / 2 or 256 MB; inside containers it is computed from the cgroup limit. Increasing storage.wiredTiger.engineConfig.cacheSizeGB reduces the OS page cache; going beyond half of RAM rarely helps. In 7.0 the ticket count is adjusted dynamically; in 8.0+ ticket metrics also appear under serverStatus().queues.execution.","tags":["wiredtiger","cache","eviction","dirty","tickets","memory"]},{"id":"dump-restore-archive","task":"Take an archive backup with mongodump/mongorestore and restore a single collection","category":"Backup","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":"Backs up a database into a single gzip archive file; restores the whole database or a single collection from the archive, optionally under a different name (nsFrom/nsTo).","when":"For logical backups, moving data between environments or recovering a single collection that was dropped or corrupted by mistake.","caution":"mongodump is a logical backup: it is slow on large data sets and indexes are rebuilt during restore. A dump taken without --oplog is not point-in-time consistent on a live system. --drop deletes the target collection before the restore starts; restore under a different name with nsTo first and verify. The restore writes to the primary and can cause replication lag.","since":"MongoDB 4.4","note":"Since 4.4 the database tools (mongodump/mongorestore) are versioned separately from the server (100.x). Add readPreference=secondary to the URI to take read load off the primary during the backup. To move users and roles as well, use a full instance dump with --restoreDbUsersAndRoles or back up the admin database separately.","tags":["mongodump","mongorestore","backup","archive","nsFrom","restore"]},{"id":"pitr-oplog-replay","task":"Point-in-time restore with oplog replay (return to before a bad drop/delete)","category":"Backup","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":"Makes the full backup consistent with --oplog, then replays the oplog accumulated after the backup up to the timestamp of the bad operation (drop, bulk delete), bringing the data back to that moment.","when":"When a collection was dropped by mistake or a faulty bulk update/delete ran and you need the last state before the damage.","caution":"Restore to a separate target, not the live system, and move the needed data after verification. The oplog from the start of the backup to the bad operation must still be within the oplog window; operations that fell out of the window cannot be recovered. --oplog only works with a full instance dump (it cannot be combined with --db). Consistent PITR with mongodump is not possible on a sharded cluster; a file system snapshot or a backup product is required.","since":"MongoDB 4.4","note":"--oplogLimit=<seconds>:<ordinal> does not apply entries at or after the given timestamp; passing the ts of the bad operation directly leaves it out. ts values appear in the oplog as Timestamp(t, i). Dump the oplog right after the bad operation, before the window closes.","tags":["pitr","point in time","oplogReplay","oplogLimit","rollback","mongorestore"],"untested":true},{"id":"user-role-admin","task":"Define a custom role, create a user, grant/revoke roles and change the password","category":"Security","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":"Defines a custom role with specific database and cluster actions, creates a user with that role and built-in roles, grants and revokes roles, changes the password and lists users across all databases.","when":"When creating application and monitoring accounts under least privilege, during access reviews or password rotation.","caution":"The database where the user is defined (admin here) is used as authSource; it must match the application's connection string. Do not grant broad roles such as root/dbOwner to application accounts. Role changes apply to existing sessions immediately; a password change does not drop open connections and applies to new ones.","since":"MongoDB 4.4","note":"passwordPrompt() asks for the password without leaving it in shell history or the process list. In replica sets and sharded clusters users are managed through the primary/mongos and are replicated. Use showPrivileges: true in getUser to see a user's effective privileges.","tags":["createUser","createRole","grantRolesToUser","privileges","rbac","password"]},{"id":"fcv-upgrade","task":"Check featureCompatibilityVersion (fCV) and the major upgrade sequence","category":"Upgrade","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":"Shows the running fCV; gives the order in which the binary upgrade and the fCV upgrade, which are separate steps, are performed.","when":"When planning a major version upgrade (e.g. 6.0 -> 7.0), when new features are unavailable after an upgrade, or before moving to the next major.","caution":"Major versions cannot be skipped: for 5.0 -> 7.0 first move to 6.0 and set fCV to 6.0. After fCV is raised, data files may switch to the new format and binary downgrade becomes harder; raise fCV only after the new version has run cleanly for a few days. Binary downgrade is not officially supported on 7.0+ Community; base the fallback plan on a backup. Check driver compatibility before upgrading.","since":"MongoDB 7.0","note":"Since 7.0, setFeatureCompatibilityVersion requires confirm: true. If an fCV transition fails, targetVersion/previousVersion appears in the getParameter output; rerun the same command to complete it. The fCV value is replicated within the replica set.","tags":["fcv","featureCompatibilityVersion","upgrade","major upgrade","major","downgrade"]}]},"recipes/redis.json":{"engineId":"redis","engine":"Redis","order":6,"recipes":[{"id":"bgrewriteaof","task":"Rewrite (compact) the AOF file","category":"Persistence","command":"BGREWRITEAOF","what":"Rewrites the AOF file in the background in a more compact form.","when":"When the AOF file has bloated, to shorten restart time.","caution":"Memory and IO usage increase during the fork; be careful at peak hours.","tags":["aof","rewrite","persistence","persistence"]},{"id":"bgsave","task":"Take an RDB snapshot in the background","category":"Persistence","command":"BGSAVE","what":"Writes an RDB snapshot to disk in the background using a forked child process.","when":"When you want to take a backup or create a snapshot.","caution":"At fork time, memory usage can rise temporarily because of copy-on-write.","tags":["rdb","bgsave","snapshot","persistence","backup"]},{"id":"save","task":"Take a synchronous RDB snapshot","category":"Persistence","command":"SAVE","what":"Writes an RDB snapshot to disk synchronously on the main thread.","when":"Only before maintenance/shutdown, when no clients are being served.","caution":"SAVE blocks the entire server and no commands are processed until it finishes; prefer BGSAVE in production.","tags":["rdb","save","snapshot","blocking","persistence"]},{"id":"lastsave","task":"View the last successful snapshot time","category":"Persistence","command":"LASTSAVE","what":"Returns the Unix timestamp of the last successful RDB save.","when":"To check whether a BGSAVE has completed.","caution":"Read-only command; no risk.","tags":["lastsave","rdb","timestamp","check","persistence"]},{"id":"config-rewrite","task":"Write the running configuration to disk","category":"Persistence","command":"CONFIG REWRITE","what":"Persists changes made at runtime with CONFIG SET to the redis.conf file.","when":"When you want runtime changes to survive a restart.","caution":"Overwrites the existing redis.conf file; the file must be defined and writable.","tags":["config","rewrite","persistent","conf","setting"]},{"id":"config-appendonly","task":"Enable or disable AOF","category":"Persistence","command":"CONFIG SET appendonly yes","what":"Enables (yes) or disables (no) AOF persistence at runtime.","when":"When you want to turn AOF persistence on or off without a restart.","caution":"Disabling AOF weakens the durability guarantee; when you enable it, the initial AOF rewrite creates IO load.","tags":["aof","appendonly","config","persistence","setting"]},{"id":"config-save-rdb","task":"Set RDB snapshot rules","category":"Persistence","command":"CONFIG SET save \"900 1 300 10 60 10000\"","what":"Sets the rules (seconds/number of changes) that trigger automatic RDB snapshots.","when":"When you want to change the automatic snapshot frequency.","caution":"If you pass an empty value (\"\"), automatic RDB saving is disabled entirely and persistence may be lost.","tags":["rdb","save","config","snapshot","setting"]},{"id":"replicaof","task":"Make a node a replica","category":"Replication","command":"REPLICAOF redis-master 6379","what":"Makes this node a replica of the master at the specified host and port and starts synchronization.","when":"When adding a new read replica or assigning roles.","caution":"The existing data on the node becoming a replica is discarded and a full sync is pulled from the master.","since":"Redis 5.0","note":"The REPLICAOF command replaced SLAVEOF in Redis 5.0; the old SLAVEOF command still works as an alias.","tags":["replicaof","replica","replication","master","sync"]},{"id":"replicaof-no-one","task":"Promote a replica to master","category":"Replication","command":"REPLICAOF NO ONE","what":"Breaks the node's replica link, turning it into a standalone master.","when":"When you want to promote a replica in a failover scenario.","caution":"After the promotion, manage the topology carefully so there is no data divergence (split-brain) with the old master.","since":"Redis 5.0","note":"REPLICAOF NO ONE replaced SLAVEOF NO ONE in Redis 5.0; the old SLAVEOF NO ONE still works as an alias.","tags":["replicaof","promote","master","failover","replication"]},{"id":"cluster-failover","task":"Perform a manual failover","category":"Cluster","command":"CLUSTER FAILOVER","what":"When run on a replica, starts a controlled failover by promoting that replica to master.","when":"When you want to hand the master role over to a replica before maintenance.","caution":"This command must be run on the target replica, not on the master itself.","tags":["cluster","failover","promote","maintenance","replication"]},{"id":"cluster-meet","task":"Add a node to the cluster","category":"Cluster","command":"CLUSTER MEET 10.0.0.12 6379","what":"Joins the node at the specified IP and port to the cluster gossip network.","when":"When adding a new node to the cluster (scaling).","caution":"If no slot assignment (ADDSLOTS) or reshard is done after adding the node, it receives no data.","tags":["cluster","meet","node","add","node"]},{"id":"cluster-addslots","task":"Assign cluster slots","category":"Cluster","command":"CLUSTER ADDSLOTS 0 1 2 3 4 5","what":"Assigns the specified hash slots to this master node.","when":"When configuring a new master node manually or fixing slot distribution.","caution":"The same slot cannot be assigned to more than one node; if all 16384 slots are not fully assigned, the cluster goes into the down state.","tags":["cluster","addslots","slot","assign","node"]},{"id":"info-replication","task":"View replication status","category":"Cluster","command":"INFO replication","what":"Shows the role (master/replica), connected replicas and replication offset information.","when":"When checking replication lag and link status.","caution":"Read-only command; no risk.","tags":["info","replication","status","replication","lag"]},{"id":"cluster-info","task":"View cluster status","category":"Cluster","command":"CLUSTER INFO","what":"Shows the overall cluster state (ok/fail), the number of assigned slots and the number of known nodes.","when":"When verifying cluster health and slot integrity.","caution":"Read-only command; no risk.","tags":["cluster","info","status","slot","health"]},{"id":"config-maxmemory","task":"Set maxmemory and the policy","category":"Memory","command":"CONFIG SET maxmemory 4gb maxmemory-policy allkeys-lru","what":"Sets the memory limit and the eviction policy applied when the limit is exceeded. Redis 7.0+ accepts multiple parameters in a single CONFIG SET; on older versions run two separate CONFIG SET commands.","when":"When you want to cap memory usage and define eviction behavior.","caution":"With the noeviction policy, writes fail once the limit is reached; a wrong limit can lead to data loss/downtime.","tags":["maxmemory","policy","memory","eviction","config"]},{"id":"memory-doctor","task":"Analyze memory","category":"Memory","command":"MEMORY DOCTOR","what":"Analyzes memory usage and produces a human-readable report on possible problems.","when":"For diagnosis when high memory usage or fragmentation is suspected.","caution":"Read-only analysis; for details you can also use the MEMORY STATS command.","tags":["memory","doctor","memory","analysis","diagnostics"]},{"id":"slowlog-get","task":"View the slow command log","category":"Maintenance","command":"SLOWLOG GET 10","what":"Returns the 10 most recent slow command entries (duration, time, arguments) from the slowlog.","when":"When identifying slow commands and investigating performance bottlenecks.","caution":"Read-only command; use SLOWLOG RESET to clear the entries.","tags":["slowlog","get","slow","diagnostics","performance"]},{"id":"slowlog-config","task":"Set the slow command threshold","category":"Maintenance","command":"CONFIG SET slowlog-log-slower-than 10000","what":"Sets the duration threshold, in microseconds, for a command to be logged in the slowlog (10000 = 10 ms).","when":"When you want to adjust slowlog sensitivity.","caution":"Setting it to 0 logs every command and adds overhead; -1 disables the slowlog.","tags":["slowlog","threshold","threshold","config","slow"]},{"id":"slowlog-reset","task":"Reset the slow command log","category":"Maintenance","command":"SLOWLOG RESET","what":"Clears all entries in the slowlog.","when":"When clearing entries to start a new observation window.","caution":"The existing slow command history is permanently deleted.","tags":["slowlog","reset","reset","clear","maintenance"]},{"id":"client-kill","task":"Kill a client connection","category":"Maintenance","command":"CLIENT KILL ADDR 10.0.0.5:53210","what":"Terminates the client connection at the specified address.","when":"When you want to terminate a stuck or misbehaving client.","caution":"The connection is dropped immediately; in-flight operations may be left incomplete. Make sure you pick the correct address.","tags":["client","kill","client","kill","maintenance"]},{"id":"client-list","task":"List connected clients","category":"Maintenance","command":"CLIENT LIST","what":"Lists all connected clients with details such as address, idle time and last command.","when":"When inspecting connections or finding a problematic client.","caution":"Read-only command; no risk.","tags":["client","list","client","list","connection"]},{"id":"config-get","task":"View a parameter value","category":"Runtime","command":"CONFIG GET maxmemory-policy","what":"Returns the current value of one or more configuration parameters (glob patterns supported).","when":"When verifying the runtime value of a setting.","caution":"Read-only command; no risk.","tags":["config","get","parameter","view","runtime"]},{"id":"config-set","task":"Change a parameter value","category":"Runtime","command":"CONFIG SET appendfsync everysec","what":"Changes a configuration parameter at runtime without a restart.","when":"When you want to adjust a server behavior immediately.","caution":"The change is temporary; run CONFIG REWRITE to persist it. A wrong value can degrade performance/durability.","tags":["config","set","parameter","runtime","setting"]}]},"recipes/redis-ext.json":{"engineId":"redis","engine":"Redis","order":2,"recipes":[{"id":"mem-fragmentation","task":"Analyze memory usage and fragmentation, enable active defrag","category":"Memory","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":"Shows the difference between the memory Redis allocates for data (used_memory) and the RSS seen by the OS, the fragmentation ratio and the memory breakdown; enables active defrag at runtime if needed.","when":"When RSS is clearly larger than the dataset, memory does not drop after bulk deletes/expiry, or an OOM risk appears.","caution":"mem_fragmentation_ratio is misleading on small datasets (e.g. 4.0 on 2 MB of data); look at mem_fragmentation_bytes for the absolute value. A ratio below 1 means swapping, not fragmentation, and causes latency. Active defrag consumes CPU; cap it with active-defrag-cycle-max. CONFIG REWRITE or redis.conf is needed to make it persistent.","since":"Redis 4.0","note":"Active defrag only works on builds compiled with the jemalloc shipped with Redis. allocator_frag_ratio shows internal fragmentation that defrag can fix; allocator_rss_ratio shows space the allocator has not returned to the OS. Fragmentation grows faster on workloads that frequently update values of varying size.","tags":["memory","ram","fragmentation","activedefrag","rss","info memory"]},{"id":"bigkeys-memkeys","task":"Find big keys (--bigkeys, --memkeys, MEMORY USAGE)","category":"Memory","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":"Scans the keyspace with SCAN and reports the keys with the most elements and the most memory per type; shows the real memory consumption and encoding of a suspicious key.","when":"When memory grows unexpectedly, a single command (HGETALL, SMEMBERS, DEL) causes latency, or one node in a cluster uses more memory than the others.","caution":"The scan uses SCAN and does not block the server but walks the entire keyspace; -i 0.01 sleeps every 100 SCAN calls to reduce load. Deleting a big key with DEL blocks the main thread; use UNLINK. MEMORY USAGE with SAMPLES 0 is O(N) on large collections. Running it on a replica does not load the master.","since":"Redis 6.0","note":"--bigkeys looks at element counts, --memkeys at MEMORY USAGE; for types like list/hash they can highlight different keys. Small hashes/zsets/lists use listpack encoding and take much less memory; once thresholds such as hash-max-listpack-entries are exceeded they convert to hashtable. In a cluster run it on each master separately.","tags":["bigkeys","memkeys","big key","memory usage","keystats","large key"]},{"id":"hotkeys-lfu","task":"Find hot keys (--hotkeys, OBJECT FREQ)","category":"Performance","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":"Lists the most frequently accessed keys using LFU counters; shows the access frequency counter of a single key.","when":"When one node's CPU approaches 100%, a slot/node in a cluster receives far more traffic than the others, or network bandwidth is saturated by a single key.","caution":"--hotkeys only works when maxmemory-policy is *-lfu. Changing the policy also changes eviction behavior: if maxmemory is set and the previous policy was volatile-* or noeviction, allkeys-lfu can also evict keys without a TTL. Keep the analysis short and revert the policy. LFU counters are logarithmic and decay over time; they are not absolute access counts.","since":"Redis 4.0","note":"After the policy change, some traffic is needed before LFU counters become meaningful. If the policy cannot be changed, a short MONITOR sample is an alternative, but MONITOR seriously reduces throughput on busy systems. For hot read keys, client-side caching (CLIENT TRACKING) or splitting the key is the lasting fix.","tags":["hotkeys","hot keys","lfu","object freq","hot key","cpu"]},{"id":"scan-unlink-pattern","task":"Find keys matching a pattern without KEYS and delete them safely (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":"Scans the keyspace with a cursor without blocking the server, counts keys matching a pattern and type, and bulk deletes them with UNLINK, freeing memory in the background.","when":"When cleaning up keys left by an old application version, finding out how many keys exist under a prefix, or when KEYS would otherwise be needed in production.","caution":"KEYS returns the whole keyspace at once and blocks the server for seconds on large data; do not use it in production. SCAN gives no full guarantee for keys added/removed during the scan and may return the same key more than once. Key names containing spaces or quotes break with xargs. Check the count and a few samples before deleting; there is no undo.","since":"Redis 4.0","note":"The TYPE option of SCAN arrived in 6.0. COUNT is a hint and does not guarantee the number of returned elements. In a cluster, --scan only scans the connected node; run it on each master. UNLINK frees large values in a separate thread via lazyfree; for small keys it costs the same as DEL.","tags":["scan","unlink","keys","bulk delete","pattern","match"]},{"id":"latency-diagnosis","task":"Diagnose the source of latency (LATENCY DOCTOR, histogram, commandstats)","category":"Performance","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":"The latency monitor records latency spikes from events such as fork, AOF fsync, the expire cycle and slow commands; shows per-command latency percentiles and the most expensive commands by total CPU time; measures baseline latency at host level.","when":"When the application sees p99 latency spikes and slowness complaints continue even though SLOWLOG is clean.","caution":"latency-monitor-threshold defaults to 0 (off); restore the old value after the diagnosis. SLOWLOG only measures command execution time and does not see network or queue waits; LATENCY events also capture in-server stalls such as fork and fsync. --intrinsic-latency consumes CPU and must run on the machine hosting the Redis server.","since":"Redis 7.0","note":"LATENCY HISTOGRAM and INFO latencystats arrived in 7.0 (latency-tracking is on by default). If fork events dominate LATENCY HISTORY, look at dataset size and THP (transparent huge pages); if aof-fsync-always/aof-write events are high, look at disk latency. Commands with high usec_per_call in commandstats indicate O(N) usage.","tags":["latency","slowness","latency doctor","histogram","commandstats","intrinsic-latency"]},{"id":"eviction-oom-diag","task":"Diagnose eviction, expiry and OOM errors","category":"Memory","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":"Shows evicted and expired key counts, the cache hit ratio, the share of keys with a TTL, and the memory value that triggers eviction relative to maxmemory.","when":"When the application gets 'OOM command not allowed when used memory > maxmemory', the cache hit ratio drops, or keys are reported to disappear unexpectedly.","caution":"With the noeviction policy, write commands get an OOM error once maxmemory is reached while reads keep working. With volatile-* policies keys without a TTL are never evicted; if there are no keys with a TTL it behaves like noeviction (check expires in INFO keyspace). Do not set maxmemory too close to physical RAM: leave room for replication buffers and copy-on-write during fork.","since":"Redis 6.0","note":"Hit ratio = keyspace_hits / (keyspace_hits + keyspace_misses). If evicted_keys grows steadily while the hit ratio drops, memory is insufficient. A high expired_stale_perc means a high share of expired keys not yet removed; the expire cycle can be sped up with active-expire-effort. Replica buffers and the AOF buffer are included in mem_not_counted_for_evict.","tags":["eviction","oom","maxmemory","expire","hit ratio","evicted_keys"]},{"id":"client-memory","task":"Find connections bloating memory (output buffer, query buffer, maxmemory-clients)","category":"Memory","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":"Sorts connections by total memory (tot-mem), output buffer (omem) and query buffer (qbuf); shows pub/sub connections and buffer limits; sets an overall upper limit for client memory.","when":"When used_memory grows without data growth, mem_clients_normal looks high, or a slow-consuming pub/sub/MONITOR client fills memory.","caution":"A large omem usually points to a client reading results slowly (large KEYS/HGETALL reply, slow pub/sub subscriber, a MONITOR left open). When maxmemory-clients is exceeded, the clients holding the most memory are closed without warning; replica and master links are excluded but application connections may drop. Use CLIENT NO-EVICT on for critical connections.","since":"Redis 7.0","note":"maxmemory-clients arrived in 7.0 and defaults to 0 (unlimited). client-output-buffer-limit applies hard/soft limits for the pubsub and replica classes; normal clients are unlimited by default. Close a problematic connection with CLIENT KILL ID <id> (see client-kill).","tags":["client list","omem","output buffer","maxmemory-clients","connections","pubsub"]},{"id":"persistence-health","task":"Diagnose RDB/AOF error state, fork time and the cause of MISCONF","category":"Persistence","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":"Shows whether the last RDB snapshot and AOF rewrite/write succeeded, how many keys changed since the last save, the delayed fsync count and the fork duration.","when":"When the application gets 'MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk', BGSAVE/BGREWRITEAOF fails, or fork-related latency is seen.","caution":"With stop-writes-on-bgsave-error yes (default), all writes are rejected after a failed BGSAVE. Silencing the error by setting it to 'no' hides the risk of data loss; fix the cause first: disk full, no write permission on dir, or fork cannot get memory (vm.overcommit_memory=0). Fork time can reach hundreds of milliseconds on a large dataset, and Redis does not respond during that time.","since":"Redis 7.0","note":"vm.overcommit_memory=1 and disabled transparent huge pages on Linux are the official Redis recommendations. A growing aof_delayed_fsync means the disk cannot keep up with fsync (appendfsync everysec). Error details are in the Redis log ('Background saving error', 'Can't save in background: fork').","tags":["misconf","bgsave","aof","fork","persistence","durability"]},{"id":"aof-repair","task":"Repair Redis that will not start because of a corrupt AOF (redis-check-aof, redis-check-rdb)","category":"Persistence","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":"Validates the AOF files (base + incr + manifest) and the RDB file; truncates the incomplete or corrupt tail of the AOF so Redis can start again.","when":"When Redis fails to start with 'Bad file format reading the append only file' or 'Unexpected EOF' after a power cut, a full disk or a sudden shutdown.","caution":"--fix deletes everything from the first corrupt point to the end of the file; if the corruption is in the middle of the file, all later writes are lost. Check how many bytes will be truncated in the output (ok_up_to, diff) before confirming. An incomplete last command (e.g. a large HSET) is dropped entirely. Always take a copy first.","since":"Redis 7.0","note":"With aof-load-truncated yes (default), an AOF truncated only at the end is trimmed automatically on startup; redis-check-aof is usually needed when the corruption is in the middle of the file. In 7.0+ multi-part AOF the files are in the appenddirname directory and the base file can be in RDB format. For a corrupt RDB, redis-check-rdb only diagnoses and does not repair.","tags":["aof","redis-check-aof","corrupted","corrupt","repair","redis-check-rdb"]},{"id":"replication-backlog","task":"Fix replication drops and an endless full resync loop (backlog, replica buffer)","category":"Replication","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":"Shows link state, offset difference and partial/full sync counters on master and replica; grows the replication backlog so short disconnects do not turn into full syncs, and the replica output buffer limit so a full sync is not cut off midway.","when":"When a replica keeps looping on 'MASTER <-> REPLICA sync started', the sync_full counter grows, or the master log shows 'scheduled to be closed ASAP for overcoming of output buffer limits'.","caution":"Every full sync means a fork + RDB generation on the master; the loop strains master memory and disk. repl-backlog-size is allocated from master memory. Raising the replica buffer limit means the master may use that much extra memory during a full sync; include it in the maxmemory calculation. CONFIG REWRITE or redis.conf is needed to make changes persistent.","since":"Redis 4.0","note":"Rough backlog sizing: the master's write volume per second (growth of master_repl_offset) x the disconnect duration to tolerate. The 'replica' class name in client-output-buffer-limit is accepted instead of 'slave' since 5.0; CONFIG GET output may still show slave. If the disk is slow, repl-diskless-sync yes (default in 7.0+) streams the RDB over the socket without writing it to disk.","tags":["replication","backlog","full sync","psync","replica","output buffer"]},{"id":"sentinel-status","task":"Check Sentinel state, the current master and 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":"Lists the masters monitored by Sentinel, the current master address, the known replicas and other sentinels; verifies that the quorum and majority needed for failover are available.","when":"When the application connects to the wrong node, when verifying topology after a failover, and before every maintenance.","caution":"Each sentinel reports its own view; GET-MASTER-ADDR-BY-NAME must return the same address on all sentinels, otherwise there is a network partition or a configuration mismatch. s_down/o_down in the flags field or disconnected on a replica needs urgent investigation. The Sentinel port (26379) should be open in the firewall only between sentinels and clients.","since":"Redis 5.0","note":"SENTINEL REPLICAS replaced SENTINEL SLAVES in 5.0 (the old name still works). The number of sentinels should be odd and at least 3, spread across different failure domains (host/rack/AZ). If Redis uses ACL/passwords, sentinel auth-user/auth-pass must be set in sentinel.conf.","tags":["sentinel","master","quorum","ckquorum","topology","failover"],"untested":true},{"id":"sentinel-failover","task":"Perform a manual failover with Sentinel and clean up the topology","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":"Hands the master role to the most suitable replica even when the master is healthy; then verifies the new master and clears replicas that no longer exist from the sentinels' memory.","when":"Before planned maintenance on the master's host, during a failover drill, or when moving the master to another data center.","caution":"There is a write interruption of a few seconds during failover, and data written to the master but not yet replicated can be lost. Replicas with replica-priority 0 are never chosen; if no eligible replica exists the command returns an error. If SENTINEL RESET runs on all sentinels at once, topology information is lost temporarily; run it on each one separately and spaced out. Make sure clients use a driver that asks Sentinel for the master address.","since":"Redis 5.0","note":"SENTINEL FAILOVER does not ask other sentinels for agreement but does propagate the new config epoch. Selection order: replica-priority (lower first), replication offset (larger first), run ID. When the old master comes back after the failover, Sentinel automatically reconfigures it as a replica of the new master.","tags":["sentinel","failover","manual failover","master","reset","switchover"],"untested":true},{"id":"cluster-check-fix","task":"Check cluster health and repair slot inconsistencies (--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":"Checks that all nodes see the same slot map, that all 16384 slots are assigned and that there are no incomplete (migrating/importing) slots; fixes problems found and shows which slot/node a key lives on.","when":"When cluster_state:fail appears, clients get CLUSTERDOWN or constant MOVED/ASK errors, and after an interrupted reshard.","caution":"--cluster fix may assign unowned slots to arbitrary nodes and move keys in open slots; review the check output first. If a master and all its replicas are lost, the data in those slots is gone; fix only assigns the slot empty to another node. With cluster-require-full-coverage yes (default), a single unowned slot stops writes on the whole cluster.","since":"Redis 7.0","note":"CLUSTER SHARDS arrived in 7.0 and replaces CLUSTER SLOTS. If authentication is enabled, add -a <password> or --user/--pass to --cluster commands. fail/pfail flags in CLUSTER NODES output mean the node is seen as unreachable by the others.","tags":["cluster","check","fix","slot","clusterdown","cluster nodes"],"untested":true},{"id":"cluster-reshard","task":"Add nodes, move slots and remove nodes in a 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":"Adds a new master or replica to the cluster, moves slots and their keys between nodes online, rebalances the slot distribution automatically and removes an emptied node from the cluster.","when":"When scaling memory/throughput horizontally, spreading the load of a hot node, or decommissioning a node.","caution":"Slot migration transfers keys one by one with MIGRATE; while big keys move, both nodes can block briefly. During migration clients get ASK redirects; a cluster-aware client is required. If a reshard is interrupted, slots stay in migrating/importing state; complete it with --cluster fix. Do not remove a master that still owns slots with del-node; move its slots first.","since":"Redis 5.0","note":"Node IDs come from CLUSTER NODES or CLUSTER MYID. --cluster-pipeline sets how many keys each MIGRATE call moves (default 10). --cluster rebalance with --cluster-weight can give nodes different weights. The redis-cli --cluster commands replaced redis-trib.rb in 5.0.","tags":["cluster","reshard","rebalance","add-node","slot","del-node"],"untested":true},{"id":"acl-users","task":"Create ACL users, test permissions and make them persistent","category":"Security","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":"Creates application and monitoring users authorized by key prefix, pub/sub channel and command category; tests whether a command is permitted with DRYRUN without running it, and writes the ACLs to disk.","when":"When moving from a single shared password (requirepass) to per-user access, isolating applications from each other's keys, or creating a read-only account for a monitoring tool.","caution":"Rule order matters: resetchannels removes any &channel rules given before it. -@dangerous also blocks commands such as KEYS, FLUSHALL, CONFIG and DEBUG; verify the commands the application uses with DRYRUN. If the default user stays enabled with nopass and +@all, ACLs provide no protection; run ACL SETUSER default off after creating an admin user. In redis-cli, if AUTH with --user/--pass fails, the connection continues as the default user and the command still runs; use DRYRUN for permission tests for that reason.","since":"Redis 7.0","note":"ACLs arrived in 6.0; ACL DRYRUN, selectors and subcommand rules (client|list) were added in 7.0. aclfile and user lines in redis.conf cannot be used together. Passwords are stored as SHA-256 hashes; ACL GETUSER does not show the plain password.","tags":["acl","setuser","users","permissions","dryrun","security"]},{"id":"acl-log-denied","task":"Investigate NOPERM and WRONGPASS errors (ACL LOG)","category":"Security","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":"Lists denied command, key and channel accesses and failed AUTH attempts with user name, object, repeat count and client information (IP, last command); shows cumulative denial counters.","when":"When the application gets NOPERM or WRONGPASS after a deploy, to find the command broken by a tightened ACL rule, or to detect password guessing.","caution":"ACL LOG lives in memory, is cleared on restart and is limited to acllog-max-len (default 128) entries; when the same error repeats, count increases instead of a new row. The client-info field contains the client IP and the user in use; record what you need before resetting.","since":"Redis 7.0","note":"The acl_access_denied_* counters were added to INFO stats in 7.2; wire them into monitoring to alert on increases. entry-id and timestamp-created/last-updated fields arrived in 7.2. Wrong password attempts appear as reason=auth, missing command permission as reason=command.","tags":["acl log","noperm","wrongpass","access denied","auth","security"]},{"id":"keyspace-notifications","task":"Monitor key deletion and expiry events with 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":"Publishes events on keys (expire, del, set, etc.) to pub/sub channels so you can watch live which key was deleted or expired and when.","when":"To prove which command or expiry removes keys when they 'disappear on their own', or to feed workflows that depend on expiry events (session close).","caution":"Events add CPU and network load; 'A' (all classes) is especially expensive on busy systems, so enable only the classes you need and turn them off afterwards. Pub/sub is fire-and-forget: a subscriber that is not connected misses the event, and nothing is persisted. The expired event is generated when the key is actually deleted (on access or by the background expire cycle), not at the TTL moment.","since":"Redis 2.8","note":"Add the e (evicted) class for keys removed by eviction. In a cluster, events are published only on the node holding the key; subscribe to each master separately. Set notify-keyspace-events in redis.conf for a persistent setting.","tags":["keyspace notifications","notify-keyspace-events","expired","pubsub","events","psubscribe"]}]},"recipes/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"recipes":[{"id":"flush","task":"Flush an index","category":"Index Lifecycle","command":"POST /customer/_flush","what":"Performs a Lucene commit: durably writes in-memory indexing data to disk as segments and trims translog entries that are no longer needed.","when":"To shrink the translog before maintenance or a restart.","caution":"Usually done automatically; a manual flush causes a slight increase in IO.","tags":["flush","translog","index","customer","maintenance"]},{"id":"force-merge","task":"Merge index segments (force merge)","category":"Index Lifecycle","command":"POST /customer/_forcemerge?max_num_segments=1","what":"Merges index segments to reduce the segment count and improve read performance.","when":"Only on indices that are no longer written to (read-only), to improve query performance.","caution":"Consumes heavy IO and CPU; never run it on actively written indices. It can take a long time and cannot be cancelled.","tags":["forcemerge","segment","merge","merge segments","performance"]},{"id":"rollover","task":"Roll over via an alias","category":"Index Lifecycle","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":"When the write index the alias points to meets the conditions, creates a new index and points the alias to it.","when":"To manage index size for time-series data (an alternative/complement to ILM).","caution":"The alias must have a single write index via is_write_index; a wrong configuration blocks the rollover.","tags":["rollover","alias","index","time series","ilm"]},{"id":"set-refresh-interval","task":"Change the refresh interval","category":"Index Lifecycle","command":"PUT /customer/_settings\n{\n  \"index\": { \"refresh_interval\": \"30s\" }\n}","what":"Sets how often the index makes new documents searchable (refresh).","when":"When increasing the interval to speed up indexing during bulk loads.","caution":"-1 disables refresh completely; documents do not become searchable. Set it back to a reasonable value after the load.","tags":["refresh","interval","settings","bulk","refresh interval"]},{"id":"set-replicas","task":"Change the replica count","category":"Index Lifecycle","command":"PUT /customer/_settings\n{\n  \"index\": { \"number_of_replicas\": 2 }\n}","what":"Dynamically changes the number of replica shards for the index.","when":"To increase/decrease redundancy or read capacity.","caution":"Adding replicas creates extra disk usage and network traffic (recovery); dropping to 0 removes redundancy.","tags":["replica","settings","number_of_replicas","redundancy","capacity"]},{"id":"open-close-index","task":"Open or close an index","category":"Index Lifecycle","command":"POST /customer/_close\nPOST /customer/_open","what":"Closes the index to reads/writes (freeing resources) or opens it again.","when":"To reduce resource usage of rarely used indices or to change certain settings.","caution":"A closed index cannot be queried or written to; while closed it is not accessible except via snapshots.","tags":["open","close","index","open index","close index"]},{"id":"node-join-note","task":"Add a node to the cluster","category":"Cluster/Node","command":"GET /_cat/nodes?v","what":"Starting the new node with the same cluster.name and correct discovery.seed_hosts in elasticsearch.yml is enough; this command verifies that the node has joined.","when":"To verify membership when adding a new node to the cluster.","caution":"In 7.0+ there is no minimum_master_nodes; the voting configuration is managed automatically. Add and remove master-eligible nodes one at a time, and never remove half or more of them at once; when permanently removing more than one master-eligible node, first use POST _cluster/voting_config_exclusions?node_names=....","tags":["node","join","cluster","cat","add node"]},{"id":"node-decommission","task":"Drain a node (decommission)","category":"Cluster/Node","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.exclude._name\": \"node-3\"\n  }\n}","what":"Safely drains the specified node by moving its shards to other nodes.","when":"To move a node's data off before taking it out of service.","caution":"Shard relocation creates heavy network/IO load; make sure the remaining nodes have enough disk and capacity. Remove the setting with null after the node is removed. transient settings are deprecated since 7.16; use persistent.","tags":["decommission","exclude","allocation","drain","node"]},{"id":"reroute-shard","task":"Move a shard manually (reroute)","category":"Cluster/Node","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":"Manually moves a specific shard from one node to another.","when":"To fix an unbalanced shard distribution or to drain a specific node.","caution":"It can conflict with automatic rebalancing; moving large shards creates network and IO load.","tags":["reroute","shard","move","relocate","cluster"]},{"id":"allocation-enable","task":"Enable or disable shard allocation","category":"Cluster/Node","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.enable\": \"primaries\"\n  }\n}","what":"Restricts shard allocation cluster-wide (primaries: only primaries are allocated, none: nothing) or returns it to the default (all / null).","when":"Before a rolling restart, to prevent unnecessary replica relocations (the officially recommended value is \"primaries\").","caution":"Remember to revert the setting with null (or \"all\") afterwards; if it stays restricted, replicas remain unassigned. transient settings are deprecated; use persistent.","tags":["allocation","enable","restart","shard","cluster"]},{"id":"cluster-settings","task":"Change a cluster setting","category":"Cluster/Node","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"indices.recovery.max_bytes_per_sec\": \"100mb\"\n  }\n}","what":"Changes a persistent or transient cluster-wide setting.","when":"When tuning cluster behavior such as recovery speed.","caution":"persistent settings survive restarts; transient settings are lost and are deprecated since 7.16, so do not use them. To reset a setting to its default, set its value to null. A wrong value affects cluster stability.","tags":["cluster","settings","persistent","transient","setting"]},{"id":"snapshot-repo","task":"Register a snapshot repository","category":"Snapshot","command":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": {\n    \"location\": \"/mnt/es_backups\"\n  }\n}","what":"Registers a repository where snapshots will be stored.","when":"When setting up the backup infrastructure for the first time.","caution":"For an fs repository, the path must be defined in path.repo and accessible on all master and data nodes.","tags":["snapshot","repository","repo","backup","register"]},{"id":"snapshot-create","task":"Take a snapshot","category":"Snapshot","command":"PUT /_snapshot/backup_repo/snap1?wait_for_completion=false\n{\n  \"indices\": \"customer\",\n  \"include_global_state\": false\n}","what":"Takes a point-in-time backup (snapshot) of the specified indices into the repository.","when":"For regular backups or as a safety backup before a major change.","caution":"wait_for_completion=true keeps the HTTP connection waiting for a long time on large indices; use false for large snapshots.","tags":["snapshot","create","backup","backup snapshot","take"]},{"id":"snapshot-restore","task":"Restore from a 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":"Restores indices from a snapshot into the cluster (optionally renaming them).","when":"For recovery after data loss or when moving a backup to a test environment.","caution":"To restore under the same name, the target index must be closed or deleted; you cannot restore over an open index.","tags":["snapshot","restore","restore snapshot","recovery","backup"]},{"id":"cache-clear","task":"Clear the cache","category":"Maintenance","command":"POST /customer/_cache/clear","what":"Clears the index's query, request and fielddata caches.","when":"To reduce memory pressure or resolve fielddata problems.","caution":"After clearing the cache, the first queries run slower until it warms up again.","tags":["cache","clear","clear cache","fielddata","maintenance"]},{"id":"pending-tasks","task":"View pending cluster tasks","category":"Maintenance","command":"GET /_cluster/pending_tasks","what":"Lists cluster-level tasks waiting to be processed on the master node.","when":"To diagnose master load when the cluster slows down or updates get stuck.","caution":"Read-only command; no risk.","tags":["pending","tasks","master","diagnostics","pending tasks"]},{"id":"hot-threads","task":"Get a hot threads analysis","category":"Maintenance","command":"GET /_nodes/hot_threads","what":"Shows the stack traces of the threads consuming the most CPU on the nodes.","when":"When diagnosing the source of high CPU usage.","caution":"Read-only command; the output can be long, narrow it down with the thread_type parameter.","tags":["hotthreads","cpu","diagnostics","thread","performance"]},{"id":"cat-shards","task":"View shard distribution","category":"Maintenance","command":"GET /_cat/shards?v&s=state","what":"Lists all shards with index, type (primary/replica), state and the node they reside on.","when":"When reviewing shard placement and unassigned shards.","caution":"Read-only command; no risk.","tags":["cat","shards","distribution","state","shard"]},{"id":"allocation-explain","task":"Find why a shard is unassigned","category":"Maintenance","command":"GET /_cluster/allocation/explain\n{\n  \"index\": \"customer\",\n  \"shard\": 0,\n  \"primary\": true\n}","what":"Explains in detail why a specific shard could not be allocated (unassigned).","when":"When looking for the reason behind unassigned shards in a yellow/red cluster.","caution":"Read-only diagnostic command; no risk.","tags":["allocation","explain","unassigned","diagnostics","shard"]},{"id":"cluster-health","task":"View cluster health","category":"Maintenance","command":"GET /_cluster/health","what":"Shows overall cluster health (green/yellow/red) and the number of active and unassigned shards.","when":"When quickly checking cluster status.","caution":"Read-only command; no risk.","tags":["cluster","health","health check","status","green"]},{"id":"refresh-index","task":"Manually refresh an index","category":"Index Lifecycle","command":"POST /customer/_refresh","what":"Makes pending changes in the index immediately searchable.","when":"When you want data to be queryable right after a bulk load.","caution":"Frequent manual refreshes can produce many small segments and degrade performance.","tags":["refresh","index","search","segment","customer"]}]},"recipes/elasticsearch-ext.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":2,"recipes":[{"id":"red-yellow-triage","task":"Red/yellow cluster triage flow (which index, which shard, why)","category":"Cluster/Node","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":"Gives an ordered triage flow from cluster status down to the problem indices, then to unassigned shards and the reason they are not allocated (unassigned.reason, allocation explain); _health_report summarizes which indicator (disk, shard availability, master) is unhealthy.","when":"As the first response when the cluster turns red or yellow.","caution":"Red: at least one primary shard is unassigned; writes to that index and searches hitting that shard fail. Yellow: all primaries are present but replicas are missing; data is reachable but there is no redundancy. On a single-node cluster, indices with replicas > 0 stay yellow permanently (same_shard decision). If a node left recently, delayed_unassigned_shards are held on purpose until index.unassigned.node_left.delayed_timeout (default 1m) expires.","since":"Elasticsearch 8.7","note":"_health_report arrived in 8.7; on older versions use the first four steps. unassigned.reason values: NODE_LEFT (node left), ALLOCATION_FAILED (retry limit reached, see retry-failed-allocation), INDEX_CREATED, CLUSTER_RECOVERED. For a specific shard, use the allocation-explain recipe with an index/shard/primary body.","tags":["red","yellow","cluster health","unassigned","health report","triage"]},{"id":"retry-failed-allocation","task":"Reallocate shards that hit the retry limit (reroute retry_failed)","category":"Cluster/Node","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":"Shows failure details for shards whose allocation was stopped after consecutive failures (ALLOCATION_FAILED, max_retry) and triggers allocation again; monitors recovery progress.","when":"When allocation explain shows 'shard has exceeded the maximum number of retries'; after a full disk, corrupt translog or transient network problem has been resolved.","caution":"Running retry_failed without fixing the cause makes the shard try 5 more times and fail again; resolve the error in unassigned_info.details first. If many shards enter recovery at once, network and disk load rises; it is limited by cluster.routing.allocation.node_concurrent_recoveries and indices.recovery.max_bytes_per_sec.","since":"Elasticsearch 7.0","note":"The retry limit is index.allocation.max_retries (default 5). Elasticsearch also retries periodically, but retry_failed removes the wait. metric=none suppresses the large cluster state in the response.","tags":["reroute","retry_failed","allocation failed","max_retries","unassigned","recovery"]},{"id":"allocate-stale-primary","task":"Allocate a lost primary shard as a last resort (allocate_stale_primary)","category":"Cluster/Node","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":"When the node holding the up-to-date primary copy is permanently lost, assigns an older (not in-sync) copy on another node as primary to take the index out of red.","when":"Only when the node hosting the primary cannot be recovered and restoring from a snapshot is not an option or would take longer.","caution":"The stale copy does not contain the latest writes; all writes made after that copy are permanently lost. If the lost node returns later, the more recent copy on it is deleted. Consider recovering the node and restoring from snapshot first. allocate_empty_primary creates the shard completely empty and deletes all data in that shard; use it only when no copy remains.","since":"Elasticsearch 7.0","note":"In _shard_stores output the allocation type appears as 'primary', 'replica' or 'unused'; do not choose copies with a store_exception. You can run the command with ?dry_run=true&explain=true first to see the decisions. After the operation, close the data gap by reindexing from the application side.","tags":["allocate_stale_primary","red","primary","data loss","shard_stores","reroute"],"untested":true},{"id":"disk-watermark","task":"Manage disk watermarks and remove the flood-stage write block","category":"Cluster/Node","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":"Shows disk usage per node and the effective watermark values; adjusts the low/high/flood_stage thresholds and removes the read_only_allow_delete block placed on indices at flood stage.","when":"When writes are rejected with 'TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block', new shards are not allocated, or shards start moving off a full node.","caution":"Above low, no new shards are allocated to the node; above high, shards are moved off the node; above flood_stage, indices with a shard on that node become read-only. Raising thresholds only buys time; the lasting fix is deleting old indices, reducing replicas or adding disk/nodes. If the disk fills completely, shards can be corrupted.","since":"Elasticsearch 8.5","note":"In 7.4+ the block is removed automatically once disk usage drops below the high watermark; manual removal shortens the wait. In 8.5+ large disks use max_headroom (low 200GB, high 150GB, flood 100GB) as absolute free space instead of the percentage; these default headrooms apply only while the watermarks are left at their defaults, so setting percentages explicitly as above disables them. Watermarks also apply on single data node clusters.","tags":["disk watermark","flood_stage","read_only_allow_delete","disk full","allocation","cat allocation"]},{"id":"rolling-restart","task":"Rolling restart / rolling upgrade (node by node, without unnecessary shard moves)","category":"Cluster/Node","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":"Gives the order for restarting or upgrading nodes one at a time while preventing replicas from being recopied to other nodes when a node is down; when the node returns, shards recover quickly from the local copy.","when":"When restarting nodes in sequence for an OS patch, JVM/heap change, elasticsearch.yml change or a minor/major version upgrade.","caution":"Do not move to the next node without waiting for green; with two nodes down at once, all copies of a shard may be gone. Restart master-eligible nodes last and one at a time; if the majority is lost the cluster cannot elect a master. If you forget to re-enable allocation.enable, replicas stay unassigned. During an upgrade, data nodes are upgraded first (frozen/cold first, hot last), then masters.","since":"Elasticsearch 7.16","note":"If there are ML jobs, run POST _ml/set_upgrade_mode?enabled=true at the start and enabled=false at the end. Synced flush was removed in 8.0; a normal _flush is enough. Use persistent since transient settings are deprecated as of 7.16. For short restarts, raising index.unassigned.node_left.delayed_timeout instead of disabling allocation also works.","tags":["rolling restart","rolling upgrade","allocation","primaries","maintenance","upgrade"],"untested":true},{"id":"shard-sizing","task":"Audit shard sizes and shard count (oversharding / oversized shard detection)","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":"Lists the largest shards, primary sizes of indices, shard count per node, total shard count and the cluster.max_shards_per_node limit.","when":"During capacity planning, when recovery times grow or heap pressure rises, and on the error 'this action would add [x] shards, but this cluster currently has [y]/[z] maximum normal shards open'.","caution":"Many very small shards (oversharding) strain the master and heap; very large shards stretch recovery and rebalancing to hours. The primary shard count cannot change after index creation; shrink, split or reindex is needed to fix it. Raising max_shards_per_node removes the limit but does not solve the problem.","since":"Elasticsearch 8.3","note":"Elastic's general guidance: 10-50 GB per shard and at most around 200 million documents. In 8.3+ the old '20 shards per GB of heap' rule was replaced by a heap estimate based on the number of mapped fields. For time series, pin shard size with max_primary_shard_size in rollover.","tags":["shard size","oversharding","cat shards","max_shards_per_node","capacity","sizing"]},{"id":"shrink-index","task":"Shrink an index with too many shards","category":"Index Lifecycle","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":"Reduces the primary shard count of an index that is no longer written to by hard-linking its segments into a new index; lowers heap and shard count overhead.","when":"When daily indices were created with more shards than needed, the shard count limit is near, or before moving old indices to the warm tier.","caution":"The source index is closed to writes during the shrink; apply it only to indices that are no longer written to. The target shard count must be a factor of the source count (8 -> 4, 2 or 1). The node where all copies are gathered needs as much free disk as the source index. With replicas 0 the source shard has a single copy; do not delete the source until the operation finishes and the new index is green.","since":"Elasticsearch 7.0","note":"After the shrink, delete the source or switch the alias to the new index. The operation is usually fast because the same node can hard-link files on the same file system; otherwise files are copied. Use _split to increase the shard count, and the shrink action in the warm phase to automate it with ILM.","tags":["shrink","shard count","oversharding","index","blocks.write","warm"]},{"id":"ilm-policy-bootstrap","task":"Define an ILM policy, attach it via a template and bootstrap the rollover chain","category":"Index Lifecycle","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":"Creates an ILM policy with hot (rollover), warm (shrink + force merge) and delete phases, applies the policy and rollover alias to new indices through a template, and creates the first index of the chain with the write alias.","when":"When automating index size, retention and deletion for time series data such as logs or metrics.","caution":"min_age in the warm and delete phases is counted from the rollover time, not from index creation. Policy changes apply to existing indices only after their current phase completes. Shrink and forcemerge in the warm phase generate heavy IO; the index must not be written to until the force merge finishes. ILM runs every 10 minutes by default, so conditions are not evaluated instantly.","since":"Elasticsearch 7.8","note":"For new projects, a data stream achieves the same more simply: add \"data_stream\": {} to the index template, and no rollover alias or bootstrap index is needed. Rollover conditions are evaluated against refreshed statistics. Composable index templates (_index_template) arrived in 7.8; the legacy _template API is deprecated.","tags":["ilm","lifecycle","rollover","policy","index template","retention"]},{"id":"ilm-explain-retry","task":"Diagnose a stuck ILM step and retry it (_ilm/explain, _ilm/retry)","category":"Index Lifecycle","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":"Shows indices waiting in the ERROR step, which phase/action/step they are stuck in and the failure reason (step_info); reruns the failed step after the cause is fixed.","when":"When rollover does not happen, old indices are not deleted, or the disk fills with indices ILM should have removed.","caution":"_ilm/retry only works for indices in the ERROR step; otherwise it returns 'has not encountered an error'. Common causes: rollover_alias setting missing or the alias has is_write_index on another index, not enough disk on a single node for shrink, an allocation rule (_tier_preference) cannot be satisfied. Deleting the policy does not remove ILM management from indices; remove the policy setting from the indices first.","since":"Elasticsearch 7.0","note":"Errors with is_auto_retryable_error true are retried by ILM on its own (failed_step_retry_count grows). The ILM check interval is set by indices.lifecycle.poll_interval (default 10m). Use POST <index>/_ilm/remove to take an index out of ILM.","tags":["ilm","explain","retry","error step","rollover","lifecycle"]},{"id":"index-template-component","task":"Build an index template from component templates and simulate the result","category":"Index Lifecycle","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":"Defines a reusable mapping component template, composes it into an index template, and shows the final settings and mapping that would apply to a given index name without creating the index.","when":"When new indices are created with the wrong mapping or shard count (dynamic mapping explosion, text instead of keyword), and before rolling out a template change.","caution":"A template applies only to newly created indices; the mapping of existing indices does not change (see reindex-alias-swap). If two templates match the same pattern, only the one with the highest priority is used; they are not merged. dynamic: strict rejects documents containing undefined fields; if the application sends a new field, indexing fails.","since":"Elasticsearch 7.8","note":"The overlapping field in _simulate_index output shows other templates matching the same pattern that are overridden. Keep priority above 200 to avoid clashing with built-in templates (logs-*-*, metrics-*-*). Composable templates take precedence over the legacy _template API.","tags":["index template","component template","simulate","mapping","priority","dynamic strict"]},{"id":"reindex-alias-swap","task":"Change a mapping with zero downtime (reindex + atomic alias switch)","category":"Index Lifecycle","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":"Creates an index with the new mapping, copies the data in the background with reindex, compares document counts, and switches the alias used by the application to the new index in one atomic operation.","when":"When an existing field's type must change (text -> keyword, integer -> long), an analyzer must change or the shard count must be redefined; these cannot be done in place on the existing index.","caution":"The application must connect to the alias, not the index name; otherwise the switch needs a deploy. New writes to the source during the reindex do not reach the target: stop writes, or rerun the reindex with op_type create + conflicts proceed just before the switch to close the gap (updates and deletes are not carried over this way). Reindex creates read load on the source and write load on the target; throttle with requests_per_second.","since":"Elasticsearch 7.0","note":"Documents loaded while refresh_interval is -1 do not show up in counts until a refresh; run _refresh before comparing. If _cat/count gets several comma-separated indices, it returns their total; use _cat/indices for a per-index comparison. The speed of a running reindex can be changed with POST _reindex/<task_id>/_rethrottle?requests_per_second=500. wait_for_completion=false writes the result to the .tasks index; check response.failures with GET _tasks/<task_id>.","tags":["reindex","alias","mapping change","zero downtime","_aliases","no downtime"]},{"id":"tasks-cancel","task":"Find, cancel or throttle long-running search and reindex tasks","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":"Sorts tasks running in the cluster by duration, shows searches and bulk operations with their query descriptions; cancels a selected task or all searches, and throttles reindex/by-query jobs.","when":"When the search thread pool fills, CPU suddenly rises, a dashboard strains the cluster with a heavy aggregation, or an update_by_query affects production.","caution":"Cancellation is cooperative: the task stops at its next checkpoint; some phases (e.g. force merge) cannot be cancelled. Cancelling all searches returns errors to users. A cancelled reindex/by-query does not roll back changes made so far; the target contains partial data. When a parent task is cancelled, its child tasks are cancelled too.","since":"Elasticsearch 7.0","note":"The detailed parameter writes the query body of search tasks into the description field. For lasting protection use search.default_search_timeout, a timeout in the search request and search.max_buckets for large aggregations. In 7.x+ searches whose HTTP connection closes are cancelled automatically.","tags":["tasks","cancel","kill query","rethrottle","long query","search"]},{"id":"search-slowlog","task":"Enable the search and indexing slow log (per-index thresholds)","category":"Performance","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":"For a given index, writes requests exceeding the threshold in the query and fetch phases and in indexing to the slow log, together with the query body.","when":"To find which query causes slow searches, or to catch heavy queries from a specific dashboard or client.","caution":"The threshold is measured per shard: on an index with many shards each shard can write its own line. A very low threshold (0ms) significantly increases log volume and IO; turn it off with null after the diagnosis. indexing.slowlog.source writes the document source to the log; set it to false if it contains sensitive data. The setting applies only to the given index; add it to the template for new indices.","since":"Elasticsearch 8.0","note":"Slow log files are written in the logs directory as <cluster>_index_search_slowlog.json and <cluster>_index_indexing_slowlog.json; if log4j2 is routed to the console they go to stdout. The index.search.slowlog.level setting was removed in 8.0; the level is determined by the thresholds (warn/info/debug/trace). In 8.14+, index.search.slowlog.include.user: true also writes the requesting user to the log. To inspect a single query in detail, add \"profile\": true to the search request.","tags":["slowlog","slow log","slow search","search","indexing","threshold"]},{"id":"rejections-heap-breaker","task":"Diagnose 429 rejections, heap and circuit breaker pressure","category":"Performance","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":"Shows requests rejected because the write/search thread pool queues were full, heap/CPU state per node, circuit breaker trips, old GC count and fielddata memory usage.","when":"When clients get 429 Too Many Requests or es_rejected_execution_exception, CircuitBreakingException ([parent] Data too large) appears, or nodes suffer long GC pauses.","caution":"rejected counters are cumulative since the node started; look at the increase between two samples. Raising queue_size hides rejections but increases heap and latency; the real fix is reducing bulk size/concurrency or adding capacity. If heap stays above 85% and old GC runs often, the node is approaching OOM. Enabling fielddata on a text field consumes heap quickly.","since":"Elasticsearch 7.0","note":"The parent breaker defaults to 95% of heap (real memory circuit breaker, 7.0+). Write rejections usually concentrate on a single hot node: check shard distribution and routing (_routing). To free fielddata temporarily, use POST _cache/clear?fielddata=true (see cache-clear).","tags":["429","rejected","thread pool","circuit breaker","heap","fielddata"]},{"id":"slm-snapshot-monitor","task":"Automate snapshots and retention with SLM, monitor snapshot progress","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":"Defines an SLM policy that takes snapshots on a cron schedule and deletes old ones according to a retention rule; shows per-shard progress of a running snapshot, past snapshots and repository reachability.","when":"When setting up regular backups, on an alert that the nightly snapshot failed, or when estimating when a long-running snapshot will finish.","caution":"The schedule is evaluated in UTC. If more than one cluster writes to the same repository, or the repository is registered as writable on another cluster, it can be corrupted; use readonly: true on the other clusters. Retention deletes only snapshots taken by SLM. include_global_state true can also restore templates and persistent cluster settings during a restore.","since":"Elasticsearch 7.5","note":"Snapshots are incremental: only segments missing from the previous snapshot are copied, so frequent snapshots do not take proportional space in the repository. SLM arrived in 7.4, retention in 7.5. Without a repository name, _status returns all running snapshots in the cluster; with the name of a past snapshot it reads from the repository and can be slow.","tags":["slm","snapshot","retention","backup","schedule","snapshot status"],"untested":true},{"id":"security-user-role","task":"Create a role and native user, change the password, issue an API key","category":"Security","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":"Creates a role with read access on an index pattern and a native realm user holding that role, changes the password, verifies the identity of the caller and issues a time-limited API key with narrowed privileges.","when":"When creating reporting, application or monitoring accounts with least privilege, during password rotation, and when moving services from users to API keys.","caution":"Do not use the elastic superuser in applications. An API key's privileges cannot exceed those of the user who created it; role_descriptors only narrow them. An API key is limited by a point-in-time snapshot of its owner's privileges at creation; later role changes do not affect existing keys. After a password change, services using the old password get 401; plan the cutover. The encoded value in the API key response is returned only once; store it.","since":"Elasticsearch 8.0","note":"Security is enabled by default in 8.0+ and the elastic password is generated on first startup. The elasticsearch-reset-password tool arrived in 8.0. List API keys with GET _security/api_key?name=rapor-key and revoke them with DELETE _security/api_key. Role mapping for users in LDAP/AD/SAML realms is done with _security/role_mapping.","tags":["security","users","roles","api key","password","rbac"],"untested":true}]},"solver/slow-query.json":{"id":"slow-query","title":"Slow query: in-depth diagnosis","icon":"🐢","category":"Performance","intro":"A query or the whole system is slow. Narrow it down step by step: run a check at each node and choose the branch based on the result. You end with a root cause + fix.","start":"scope","nodes":{"scope":{"kind":"step","title":"What is slow?","detail":"Is one or a few specific queries slow, or the whole system? This splits the investigation into one of two tracks: 'query-focused' or 'resource/bottleneck-focused'.","choices":[{"label":"One or a few specific queries are slow","next":"running"},{"label":"The whole system is slow","next":"system"}]},"running":{"kind":"step","title":"Is the query running right now?","detail":"Is it a query running/stuck right now, or one that is 'generally slow'? Look at the active sessions.","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":"Running / stuck right now","next":"blocked"},{"label":"Generally slow; I want to look at its plan","next":"plan"}]},"blocked":{"kind":"step","title":"Is the query waiting on something?","detail":"If it is active but not progressing, it is most likely waiting. Is blocking_session / wait_event populated?","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":"Blocked by another session","next":"sol_blocking"},{"label":"Waiting on a resource (IO / log / latch)","next":"wait"},{"label":"No wait; actively burning CPU","next":"plan"}]},"wait":{"kind":"step","title":"Which wait dominates?","detail":"The dominant wait event tells you the type of bottleneck.","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":"IO wait (db file read / PAGEIOLATCH)","next":"sol_io"},{"label":"Log / commit wait (log file sync / WRITELOG)","next":"sol_log"},{"label":"Lock / enqueue (enq: TX / LCK)","next":"sol_blocking"}]},"plan":{"kind":"step","title":"Get the execution plan (EXPLAIN)","detail":"Pull the plan. Is there a full table scan, an unexpected join order, or a gap between estimated and actual rows?","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 / missing index","next":"sol_index"},{"label":"It was fast yesterday; the plan changed suddenly","next":"sol_planchange"},{"label":"Processes too many rows / N+1","next":"sol_rewrite"},{"label":"The plan looks good but it is slow","next":"stats"}]},"stats":{"kind":"step","title":"Are statistics up to date?","detail":"A large gap between estimated and actual rows (E-rows vs A-rows) is a sign of stale statistics.","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":"Statistics are stale / old","next":"sol_stats"},{"label":"Statistics are current but it is still slow","next":"sol_deep"}]},"system":{"kind":"step","title":"System-wide: where is the bottleneck?","detail":"If everything is slow rather than a single query, look for a resource bottleneck: CPU, IO, memory, or widespread blocking?","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 close to 100%","next":"sol_cpu"},{"label":"High IO wait","next":"sol_io"},{"label":"Memory pressure / low cache hit","next":"sol_mem"},{"label":"Lots of blocking","next":"sol_blocking"}]},"sol_blocking":{"kind":"solution","title":"Root cause: Blocking","detail":"The query is waiting on a lock held by another session. Find the session at the head of the chain (root blocker) and resolve it.","steps":["Find the head of the blocking chain (the session that is not waiting itself but makes others wait).","Understand what the root blocker is doing: a long uncommitted transaction, or 'idle in transaction'?","If the application can commit/rollback, trigger that; if not and work is stalled, terminate the root blocker.","Fix the root cause: a missing index may be causing broad locking; shorten transactions and review the isolation level."],"related":["blocking-sessions","kill-session"],"playbook":"blocking"},"sol_io":{"kind":"solution","title":"Root cause: IO bottleneck","detail":"The query reads a lot from disk. Usually due to full scans (missing index) or insufficient memory (buffer cache).","steps":["Add a suitable index for the filter causing the full scan (the plan should switch to an index scan, IXSCAN).","Check the buffer cache / buffer pool hit ratio; increase memory if it is low.","Reduce unnecessary column/row reads (needed columns instead of SELECT *, pagination).","If storage really is slow (high IO latency), improve the disk layer."],"related":["cache-hit-ratio","create-index"]},"sol_log":{"kind":"solution","title":"Root cause: Commit / log bottleneck","detail":"If 'log file sync' / 'WRITELOG' dominates, commits are waiting on redo/WAL writes.","steps":["Turn frequent single-row commits into batched commits; reduce unnecessary commits.","Speed up the redo/WAL disk (separate, fast disk); review log file size/count.","Evaluate synchronous_commit / commit_write settings against the workload and durability trade-off."],"related":["show-config-param"]},"sol_index":{"kind":"solution","title":"Root cause: Missing index","detail":"The plan does a full scan. A suitable index on filter/join columns gives a large speedup.","steps":["Add a suitable index (composite if needed) on the WHERE/JOIN columns online.","After adding the index, get the plan again and verify that it switched to an index scan (IXSCAN).","If the write load is heavy, weigh the index cost; drop unnecessary indexes.","An index does not help on columns with low selectivity; check the selectivity."],"related":["create-index","explain-plan"]},"sol_planchange":{"kind":"solution","title":"Root cause: Plan regression","detail":"The query was fast yesterday but not today; the plan changed. Usually stale statistics or a parameter change.","steps":["First gather fresh statistics (stale statistics are the most common cause).","Pin the good plan: Oracle SQL Plan Baseline / hint, MSSQL Query Store 'force plan', PG settings.","If there was a recent upgrade/patch/parameter change, review it (optimizer behavior)."],"related":["update-statistics"]},"sol_rewrite":{"kind":"solution","title":"Root cause: Query design","detail":"The plan is reasonable but the query inherently does too much work (too many rows, N+1, unnecessary joins).","steps":["Shrink the result set: pagination (LIMIT/OFFSET or keyset), early filtering.","Turn the N+1 pattern into a single batch query; simplify unnecessary joins/subqueries.","Consider a materialized view / summary table for recurring heavy reports."],"related":["explain-plan","materialized-view"]},"sol_stats":{"kind":"solution","title":"Root cause: Stale statistics","detail":"The optimizer misestimates because statistics are not up to date.","steps":["Gather statistics manually for the affected table(s).","Verify that the automatic gathering job (autovacuum/auto stats/gather job) is running.","Add statistics gathering to the workflow after sudden large data loads."],"related":["update-statistics"]},"sol_deep":{"kind":"solution","title":"In-depth analysis","detail":"If the plan and statistics are fine but it is still slow, dig deeper.","steps":["Enable a session-level trace: Oracle 10046, PG auto_explain, MySQL profiling, MSSQL Extended Events.","Examine parallelism, memory grants and sort/hash spills (tempdb/temp).","Rule out the hardware/OS layer (CPU steal, IO latency, network).","Evaluate contention caused by connection count and concurrency."],"related":["session-trace","top-wait-events"]},"sol_cpu":{"kind":"solution","title":"Root cause: CPU bottleneck","detail":"The whole system is CPU-bound. Usually a few badly planned queries are burning the CPU.","steps":["Find the top 10 CPU-consuming queries and fix their plans (indexes/statistics).","Throttle runaway queries (Resource Manager / Resource Governor).","If utilization stays high, scale (add CPU, spread reads to read replicas)."],"related":["top-slow-queries"],"playbook":"high-cpu"},"sol_mem":{"kind":"solution","title":"Root cause: Memory pressure","detail":"Low cache hit and/or swapping: memory is insufficient or misallocated.","steps":["Size the buffer pool / SGA and session memory (work_mem/PGA) correctly (Tools > Memory calculator).","Find and fix the runaway query consuming memory.","If the system is swapping, getting out of swap is the priority; add RAM if needed."],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"}}},"solver/deadlock.json":{"id":"deadlock","title":"Deadlock / lock conflict (ORA-00060, 1213, 40P01)","icon":"♻️","category":"Locks","intro":"You hit a deadlock (Oracle ORA-00060, MySQL 1213, PG 40P01, MSSQL 1205). Capture the graph, work out the pattern, prevent recurrence. Deadlocks are resolved by design — the goal is to reduce their frequency.","start":"capture","nodes":{"capture":{"kind":"step","title":"First capture the deadlock graph","detail":"Which two (or more) sessions, which resources, which lock modes collided? Do not apply blind fixes without seeing this.","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":"Got the graph — two transactions lock resources in reverse order","next":"sol_order"},{"label":"Many transactions pile up on a single row/table (hotspot)","next":"sol_hotspot"},{"label":"There are wide range locks (many rows get locked)","next":"index_check"}]},"index_check":{"kind":"step","title":"Does the locking query use an index?","detail":"Without an index, an UPDATE/DELETE locks many rows (even the whole table) to satisfy the WHERE clause → a source of deadlocks. Check its 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":"No index / it does a full scan","next":"sol_index"},{"label":"There is an index, but it still conflicts","next":"sol_order"}]},"sol_order":{"kind":"solution","title":"Root cause: reverse lock order","detail":"The most common cause of deadlock: two transactions access resources in different orders. When A→B and B→A happen at the same time, they deadlock.","steps":["Access resources in the SAME order on every code path (e.g. always lowest id → highest id).","Keep transactions short; move user interaction/long work outside the transaction.","If needed, take locks early in a deterministic order (ordered SELECT ... FOR UPDATE).","Add automatic retry (retry + backoff) for the deadlock victim in the application — this is mandatory."],"related":["blocking-sessions","open-transactions"]},"sol_index":{"kind":"solution","title":"Root cause: missing index → wide locks","detail":"An UPDATE/DELETE without an index locks more rows than necessary and makes deadlocks far more likely.","steps":["Add an index on the column in the WHERE clause; the lock is then limited to the affected rows.","Get the plan again and verify that the full scan (Seq Scan / type=ALL) has turned into index access.","Run bulk updates in small batches (chunks)."],"related":["create-index","list-indexes"]},"sol_hotspot":{"kind":"solution","title":"Root cause: hotspot","detail":"Many transactions pile up at once on a single resource such as a counter row/key.","steps":["Spread the hot row: use a sequence/identity, split counters (sharded counter).","Shorten transaction duration so the lock is held for less time.","If possible, use optimistic locking (version/timestamp) or a queue pattern."],"related":["sequence","open-transactions"]}}},"solver/disk-full.json":{"id":"disk-full","title":"Disk / tablespace full","icon":"💽","category":"Capacity","intro":"You got 'unable to extend', 'disk full', 'could not extend' or 'log full'. Don't panic: first find WHAT is full, reclaim space safely, then fix it permanently.","start":"what","nodes":{"what":{"kind":"step","title":"What is full?","detail":"Cleaning the wrong place does not help. Measure usage first: is it a datafile, the transaction log, archive/redo, temp, or the OS disk?","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 is full","next":"data"},{"label":"Transaction log / archive-redo is full","next":"log"},{"label":"Temp / undo is full","next":"sol_temp"},{"label":"OS disk is full (not the DB)","next":"sol_os"}]},"data":{"kind":"step","title":"What is the biggest consumer?","detail":"Most of the time a single runaway table/segment is the culprit. Find the largest ones.","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":"There is an unexpectedly large/runaway table","next":"sol_runaway"},{"label":"Normal growth, space has genuinely run out","next":"sol_extend"},{"label":"Too many dead rows / bloat","next":"sol_reclaim"}]},"log":{"kind":"step","title":"Why is the log growing?","detail":"The log/archive cannot be cleaned up until the oldest active transaction/backup completes. Why has it accumulated?","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: log backups are not being taken (LOG_BACKUP)","next":"sol_logbackup"},{"label":"PG: a stalled replication slot is retaining WAL","next":"sol_slot"},{"label":"Oracle: archived log area (FRA) is full","next":"sol_archive"},{"label":"A long open transaction is holding the log","next":"sol_longtxn"}]},"sol_runaway":{"kind":"solution","title":"Root cause: Runaway table/segment","detail":"A single object has grown unexpectedly (uncontrolled inserts, a queue/log table that is never purged).","steps":["Verify whether the object is actually needed; if it is a log/queue table, apply retention.","Purge unneeded old data with a partition drop / TRUNCATE (after backup/approval).","Stop the uncontrolled growth in the application; set up a growth alert."],"related":["largest-tables","create-partition"]},"sol_extend":{"kind":"solution","title":"Root cause: Genuine capacity need","detail":"Normal growth; space must be added.","steps":["Add space to the datafile or enable AUTOEXTEND (Oracle: ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE).","If necessary, add a new datafile/tablespace or grow the disk.","Set up 80%/90% usage alerts so there are no more surprises."],"related":["free-space"]},"sol_reclaim":{"kind":"solution","title":"Root cause: Bloat / dead rows","detail":"The space of deleted/updated rows has not been reclaimed.","steps":["Reclaim the space: PG VACUUM (pg_repack if needed), Oracle SHRINK SPACE, MySQL OPTIMIZE TABLE, Mongo compact.","Make autovacuum/automatic maintenance settings more aggressive.","VACUUM FULL / OPTIMIZE lock the table — run them outside peak hours."],"related":["reclaim-space","free-space"]},"sol_temp":{"kind":"solution","title":"Root cause: Temp / undo full","detail":"A large sort/hash or a long open transaction filled the temporary space.","steps":["Find the query that blew up temp (large sort/hash spill) and optimize it.","Find the long open transaction; it is the one holding the undo (Oracle).","If necessary, grow the temp/undo space; review the retention setting."],"related":["open-transactions","reclaim-space"]},"sol_os":{"kind":"solution","title":"Root cause: OS disk full","detail":"The problem is on the operating system disk, not the DB. Logs, core dumps, old backups or trace files may be taking up space.","steps":["Find the full filesystem with df -h; scan for the largest directories (du -sh *).","Clean up old log/trace/core dump/backup files (with approval).","Put the DB diagnostic directories (Oracle ADR, PG logs) under rotation."]},"sol_logbackup":{"kind":"solution","title":"Root cause: Log backups not being taken (SQL Server)","detail":"In the FULL recovery model, the log grows indefinitely if no log backups are taken.","steps":["Take a log backup (BACKUP LOG) — this makes the log reusable.","If the recovery model really should be SIMPLE, change it.","Shrink the log file only if necessary (DBCC SHRINKFILE) and set up a regular log backup job."],"related":["last-backup-status"],"playbook":"log-growth"},"sol_slot":{"kind":"solution","title":"Root cause: Abandoned replication slot (PG)","detail":"A stalled (inactive) slot prevents WAL from being removed and bloats pg_wal.","steps":["Verify whether the slot is actually in use.","If it is unneeded, drop it: SELECT pg_drop_replication_slot('slot_name');","Fix the active replica so the slot advances; set up an alert for slot lag."],"related":["free-space"],"playbook":"log-growth"},"sol_archive":{"kind":"solution","title":"Root cause: Archived log area (FRA) full (Oracle)","detail":"Archived logs have accumulated and the FRA is full; once the archiver stalls, the database can stop accepting writes.","steps":["Delete old archived logs that have already been backed up, using RMAN (NEVER delete them without a backup).","Increase the FRA size (db_recovery_file_dest_size) if necessary.","Set up the archived log cleanup/backup job to run regularly."],"related":["last-backup-status"]},"sol_longtxn":{"kind":"solution","title":"Root cause: Long open transaction","detail":"The oldest active transaction is preventing log/undo from being reclaimed.","steps":["Find the oldest open transaction and have it committed/rolled back (or talk to its owner).","Fix the application behavior that leaves sessions 'idle in transaction'; set a timeout.","Move long reports to a separate replica/window."],"related":["open-transactions"],"playbook":"log-growth"}}},"solver/connection-storm.json":{"id":"connection-storm","title":"Connection storm (too many connections)","icon":"🌊","category":"Capacity","intro":"You got 'too many connections', 'remaining connection slots reserved' or ORA-00018; new connections are being rejected. Find the source, relieve the pressure now, fix it for good.","start":"measure","nodes":{"measure":{"kind":"step","title":"Measure the situation: how many connections, and how many are active?","detail":"First look at total connections, the limit and the active/idle split.","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":"Most connections are idle / idle in transaction","next":"sol_idle"},{"label":"Most are active; I need to find the source","next":"who"}]},"who":{"kind":"step","title":"Who/from where? Group connections by source","detail":"Usually a single application server's pool has blown up or there is a connection leak. Look at the distribution by host/user/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":"A single application/host has blown up the connections (pool)","next":"sol_pool"},{"label":"Load really increased; all are legitimate and active","next":"sol_scale"}]},"sol_idle":{"kind":"solution","title":"Root cause: Pile-up of idle connections / leak","detail":"Connections are opened and never closed, or left 'idle in transaction'; they hold slots for nothing.","steps":["Clean up long idle / idle-in-transaction connections; use the slot reserved for admins.","Set server-side timeouts: PG idle_in_transaction_session_timeout, MySQL wait_timeout, Oracle profile idle_time.","Make sure the application closes connections (and transactions); fix the leak at its source."],"related":["open-transactions","kill-session","connection-count"],"playbook":"connection-storm"},"sol_pool":{"kind":"solution","title":"Root cause: Application pool has blown up","detail":"An application server's connection pool is unbounded/too large or leaking. It is the one producing the connections.","steps":["Immediate: kill the offending application's idle connections and restart its pool.","Set a reasonable pool size on the application side (Tools > Connection Pool calculator).","Put a pooler between the server and the DB (PgBouncer / ProxySQL / Oracle DRCP / Connection Manager (CMAN)).","Do not blindly increase max_connections; it consumes memory and only postpones the problem."],"related":["connection-count","kill-session"],"playbook":"connection-storm"},"sol_scale":{"kind":"solution","title":"Root cause: Genuine load increase","detail":"The connections are legitimate and active; capacity really is insufficient.","steps":["Reduce the number of connections reaching the DB by multiplexing them through a connection pooler.","Spread read load across read replicas.","If needed, increase max_connections in a controlled way according to the memory budget; scale server memory as well."],"related":["connection-count"],"playbook":"connection-storm"}}},"solver/replication-lag.json":{"id":"replication-lag","title":"Replication lag / stopped","icon":"🔁","category":"Replication","intro":"The standby/replica is falling behind the primary, or replication has stopped entirely. This tree narrows down the type of lag step by step (growing, constant, or stopped) to reach the root cause, such as an apply error, a single-thread bottleneck, a recovery conflict, or corruption.","start":"root","nodes":{"root":{"kind":"step","title":"How is the lag behaving?","detail":"Measure the situation first. Look at the replica stream and the lag metric: is the lag growing over time, stuck at a constant level, or has apply stopped completely? This splits the investigation into three main branches.","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":"Apply stopped completely / there is an error","next":"stopped"},{"label":"Lag keeps growing (cannot catch up)","next":"growing"},{"label":"Lag is constant but a long query is suspected","next":"standby_query"},{"label":"Far behind / appears inconsistent","next":"sol_rebuild"}]},"stopped":{"kind":"step","title":"What does the apply error say?","detail":"If replication has stopped, there is usually an error in an event being applied. Look at the error message: is it a row/table missing on the target, a primary key conflict, or a connection/network drop?","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":"Conflicting/missing row or table error","next":"sol_apply_error"},{"label":"Network/connection dropped, IO thread stopped","next":"sol_connection"}]},"growing":{"kind":"step","title":"Why can it not catch up?","detail":"If the lag keeps growing, the replica cannot keep up with the primary's write rate. Is single-thread apply the bottleneck, or is the replica's disk/IO slow? Look at the apply worker state and IO waits.","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":"Single-thread apply + heavy write load","next":"sol_parallel"},{"label":"The replica's disk/IO is slow","next":"sol_io"}]},"standby_query":{"kind":"step","title":"Is there a long query on the standby?","detail":"A long query running on a read-only standby can block apply (recovery). In PostgreSQL, this is how a recovery conflict arises. Look at active queries and conflict counters on the 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":"Yes, a long query is blocking apply","next":"sol_recovery_conflict"},{"label":"No, no query, but it is still behind","next":"sol_parallel"}]},"sol_apply_error":{"kind":"solution","title":"Root cause: Apply error (data conflict)","detail":"The replica is stuck on an event it cannot apply: a row missing on the target, a primary key conflict, or a missing table. Fix the cause of the inconsistency instead of blindly skipping.","steps":["Examine the error and the stuck event: which table and which operation (delete/update/insert) failed?","Find the root cause: data written manually to the target, missing DDL (table/column), or a corrupt binlog/WAL entry.","Align the schema: add the missing table/column; compare the conflicting row with the primary and fix it.","If you must skip the event (e.g. a single event in MySQL), document it and verify the replica's data consistency afterwards.","Prevent recurrence: disable direct writes to the target and run DDL through the primary."],"related":["replication-status"],"playbook":"replication-lag"},"sol_connection":{"kind":"solution","title":"Root cause: Connection / network drop","detail":"The receiver (IO/receiver) thread cannot connect to the primary. The network, authentication, or accumulated/purged log segments may be the problem.","steps":["Verify network access between primary and replica and the replication user's privileges.","Check that the required WAL/binlog segments still exist on the primary (retention/expire).","In PostgreSQL, verify that the replication slot exists and is active; without a slot, WAL may have been removed early.","Restart the connection; once the logs start flowing again, the lag should close."],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_parallel":{"kind":"solution","title":"Root cause: Single-thread apply bottleneck","detail":"While the primary writes in parallel, the replica applies changes in a single sequence and cannot keep up. Parallelize apply.","steps":["Enable parallel apply: in MySQL, replica_parallel_workers + replica_parallel_type=LOGICAL_CLOCK.","In PostgreSQL physical replication, apply is a single process; reduce the generation rate on the primary by splitting large transactions.","Move heavy batch jobs to low-traffic hours or split them up.","Monitor the lag metric after the change; it should decrease to zero."],"related":["replication-status"],"playbook":"replication-lag"},"sol_io":{"kind":"solution","title":"Root cause: Replica IO bottleneck","detail":"The apply thread is waiting on disk, not CPU. The replica's storage cannot keep up with the primary's write rate.","steps":["Measure the IO latency/saturation of the replica's disk; compare it with the primary.","If the replica's hardware is weaker (slow disk, less RAM), match it to the primary; asymmetric hardware is a common cause.","Speed up the disk for redo/WAL apply; size the buffer/cache adequately.","If it does not improve, reduce the write load or distribute read traffic to another replica."],"related":["replication-status"],"playbook":"replication-lag"},"sol_recovery_conflict":{"kind":"solution","title":"Root cause: Standby recovery conflict","detail":"A long read-only query on the standby is blocking the application of changes from the primary (vacuum/lock conflict). PostgreSQL resolves this by delaying apply or canceling the query.","steps":["Find the long blocking query; if it is really needed, wait for it to finish, otherwise terminate it.","With hot_standby_feedback=on, make the primary respect standby queries (at the cost of bloat on the primary).","Alternatively, tune max_standby_streaming_delay to the workload (balance between lag tolerance and query cancellation).","Move long reports to a separate replica where they will not block apply, or to a different time."],"related":["replication-status","open-transactions"],"playbook":"replication-lag"},"sol_rebuild":{"kind":"solution","title":"Root cause: Replica too far behind / corrupt - rebuild it","detail":"The replica is too far behind to recover, the required logs have been removed, or the data is inconsistent. The safest path is to rebuild it from a fresh backup.","steps":["Take a current base backup from the primary: PG pg_basebackup, MySQL clone/xtrabackup, Oracle RMAN duplicate.","Restore the backup to the replica and re-establish the replication configuration (slot/user/position).","Start replication and monitor the initial synchronization (catch-up) progress.","After the lag reaches zero, verify data consistency and read traffic, then put it into 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":"The Oracle 'ORA-01555: snapshot too old' error indicates that the old (undo) data a query needs for a consistent read has already been overwritten. This tree narrows down the source of the error (long query + insufficient UNDO, high undo turnover, or LOB) and leads you to the right fix.","start":"root","nodes":{"root":{"kind":"step","title":"Where does the error occur?","detail":"First determine the context of the error. Does the classic ORA-01555 occur during a long-running SELECT/report, or while accessing a LOB (CLOB/BLOB) column? LOB-related ORA-01555 is a different mechanism (PCTVERSION/RETENTION) and is resolved separately.","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":"During a long-running query/report","next":"undo_size"},{"label":"While accessing a LOB column","next":"sol_lob"}]},"undo_size":{"kind":"step","title":"Is UNDO sufficient, is retention appropriate?","detail":"A long query reads the old image of blocks changed since it started from UNDO. If the UNDO tablespace is small or undo_retention is shorter than the query duration, old undo is overwritten. Measure UNDO usage and retention.","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":"UNDO is small / retention shorter than the query","next":"sol_undo"},{"label":"UNDO is sufficient but commits are very frequent","next":"sol_commits"},{"label":"The query runs excessively long","next":"sol_query"}]},"sol_undo":{"kind":"solution","title":"Root cause: insufficient UNDO / retention","detail":"The old undo needed by the long query was overwritten because the retention period expired or the tablespace filled up. Grow UNDO and set retention to cover the query duration.","steps":["Based on the longest query duration (v$undostat.maxquerylen), raise undo_retention above it: ALTER SYSTEM SET undo_retention=10800;","Grow the UNDO tablespace enough to accommodate the retention or enable autoextend: ALTER DATABASE DATAFILE '...' AUTOEXTEND ON MAXSIZE ...;","Guarantee it for critical long queries: ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;  (caution: if UNDO fills up, DML may fail).","After the change, rerun the same report and confirm that ORA-01555 is gone."],"related":["show-config-param"],"playbook":"ora-01555"},"sol_commits":{"kind":"solution","title":"Root cause: high undo turnover (frequent commits)","detail":"Heavy DML with very frequent commits recycles undo blocks quickly and wipes out the old image the long query needs. The 'commit cleanout' effect makes this worse.","steps":["Reduce unnecessarily frequent commits: use batch commits instead of committing every row inside a loop.","To slow undo turnover, do not schedule heavy DML workloads at the same time as the long report.","Increase undo_retention and, if needed, protect old undo with RETENTION GUARANTEE.","Move the report outside the heavy write window or pin it to a consistent read point."],"related":["open-transactions","show-config-param"],"playbook":"ora-01555"},"sol_query":{"kind":"solution","title":"Root cause: excessively long query duration","detail":"The query runs so long that even a reasonable undo_retention is not enough. The permanent fix is to speed up the query or offload its workload.","steps":["Optimize the query: fix missing indexes, bad plans or unnecessary full scans; shorten its duration.","Split the large job (by date/range) so that each chunk fits within the undo window.","Move the heavy report to a standby/replica or a separate read time — so it does not collide with active DML.","Once the duration is acceptable, set undo_retention accordingly."],"related":["explain-plan"],"playbook":"ora-01555"},"sol_lob":{"kind":"solution","title":"Root cause: LOB retention / PCTVERSION","detail":"ORA-01555 while reading a LOB indicates that the space holding old LOB versions (RETENTION or PCTVERSION) is insufficient. LOBs have a separate versioning mechanism.","steps":["View the LOB column's current setting: SELECT retention, pctversion FROM dba_lobs WHERE table_name='...' AND column_name='...';","Raise the appropriate setting depending on AUTO/MANUAL undo mode: ALTER TABLE t MODIFY LOB (my_column) (RETENTION);  or increase PCTVERSION in MANUAL mode.","LOBs using RETENTION are based on the database undo_retention value — keep it sufficient too.","Shorten long operations accessing the LOB; retest the error after the change."],"related":["show-config-param"],"playbook":"ora-01555"}}},"solver/backup-restore.json":{"id":"backup-restore","title":"Backup & restore decision","icon":"💾","category":"Backup & Recovery","intro":"A decision tree that walks you step by step through what you need to do with backups (take a backup, restore, return to a specific point in time - PITR, or verify a backup) and leads you to the right commands and steps for each database.","start":"root","nodes":{"root":{"kind":"step","title":"What do you want to do?","detail":"Choose your goal. Are you taking a new backup, restoring an existing backup, returning to a specific time (just before an outage), or verifying that the backup you have is sound?","choices":[{"label":"I want to TAKE a backup","next":"take"},{"label":"I want to RESTORE a backup","next":"restore"},{"label":"Return to a specific point in time (PITR)","next":"pitr"},{"label":"I want to VERIFY a backup","next":"verify"}]},"take":{"kind":"step","title":"Which type of backup?","detail":"A logical backup (dump) is portable and suits selective restores but is slow for large data. A physical backup (file/block level) suits large databases and fast recovery. Choose according to your needs.","choices":[{"label":"Logical backup (dump / export)","next":"sol_logical_backup"},{"label":"Physical / full backup","next":"sol_physical_backup"}]},"sol_logical_backup":{"kind":"solution","title":"Taking a logical backup (dump)","detail":"Exports data as SQL/BSON statements. Ideal for moving across versions/platforms and restoring a single table/collection.","steps":["PostgreSQL: pg_dump -Fc -d mydb -f backup.dump  (pg_dumpall for the whole 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: take an RDB snapshot in the background with BGSAVE (dump.rdb).","Elasticsearch: first register a snapshot repository, then PUT _snapshot/my_repo/snap1?wait_for_completion=true","Move the backup to a safe location and record its size/completion status."],"related":["logical-dump","last-backup-status"],"playbook":"backup-restore"},"sol_physical_backup":{"kind":"solution","title":"Taking a physical / full backup","detail":"A block/file-level copy of the datafiles. Fast for large databases and the foundation for PITR and full recovery.","steps":["Oracle: RMAN> BACKUP DATABASE PLUS ARCHIVELOG;  (together with archived logs).","PostgreSQL: pg_basebackup -D /backup/base -Fp -Xs -P  (with WAL archiving enabled).","SQL Server: BACKUP DATABASE [mydb] TO DISK='D:\\backup\\db.bak' WITH CHECKSUM, COMPRESSION;","MySQL/MariaDB: xtrabackup --backup --target-dir=/backup/base  (consistent physical copy).","MongoDB: take a file system snapshot or a consistent snapshot from a replica set member.","Copy the backup to an offline/remote location and record the last backup status."],"related":["last-backup-status"],"playbook":"backup-restore"},"restore":{"kind":"solution","title":"Restoring a backup","detail":"Restore the most suitable backup you have to the target. First verify the backup's integrity and timestamp, then load it with the engine-appropriate command.","steps":["Verify the date and soundness of the backup to be restored (know your data loss window).","PostgreSQL: pg_restore -d new_db backup.dump   (for an SQL dump: psql -d new_db -f backup.sql).","Oracle: RMAN> RESTORE DATABASE; RECOVER DATABASE; ALTER DATABASE OPEN;  (logical import with impdp).","MySQL/MariaDB: mysql mydb < backup.sql   (physical: xtrabackup --copy-back).","SQL Server: RESTORE DATABASE [mydb] FROM DISK='D:\\backup\\db.bak' WITH RECOVERY, REPLACE;","MongoDB: mongorestore --db mydb /backup/dir","After the restore, validate the data with row counts/application checks."],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"pitr":{"kind":"solution","title":"Returning to a specific point in time (PITR)","detail":"You want to return, with second-level precision, to just before an accidental delete/corruption. You need a full backup + the transaction logs up to that point (archived logs / WAL / binlog).","steps":["Determine the target time precisely (just before the incident) and verify that the required logs are available.","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: restore the base backup, then set recovery_target_time='2026-07-04 10:00:00' and apply WAL up to that point.","SQL Server: RESTORE ... WITH NORECOVERY; then RESTORE LOG ... WITH STOPAT='2026-07-04 10:00:00', RECOVERY;","MySQL/MariaDB: restore the full backup, then apply the binlog up to that point with mysqlbinlog --stop-datetime='2026-07-04 10:00:00'.","After opening, verify that the data is at the expected point in time."],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"verify":{"kind":"solution","title":"Verifying a backup","detail":"A backup is only a backup once it has been proven restorable. Test integrity and actual restorability regularly.","steps":["Oracle: RMAN> RESTORE DATABASE VALIDATE;  and for block consistency VALIDATE CHECK LOGICAL DATABASE;","SQL Server: take backups WITH CHECKSUM and run RESTORE VERIFYONLY ... WITH CHECKSUM (checks only the backup file); for real proof, restore the backup to a test server and run DBCC CHECKDB([mydb]) WITH NO_INFOMSGS; there (CHECKDB checks a live/restored database, not the backup file).","PostgreSQL: actually restore the backup to a separate/temporary server and confirm that pg_restore completes without errors.","MySQL/MongoDB: restore the dump to a test environment and compare row/collection counts and sample data.","Schedule regular 'restore drills' — taking backups is not enough, prove the restore too.","Record the results and the date of the last successful verification."],"related":["last-backup-status","restore-dump"],"playbook":"backup-restore"}}},"solver/upgrade-wizard.json":{"id":"upgrade-wizard","title":"Version upgrade wizard","icon":"⬆️","category":"Lifecycle","intro":"Patch or major, in place or by migration? Plan the upgrade step by step; each branch covers prechecks, method and post-upgrade work. Rehearse it here before touching production.","start":"kind","nodes":{"kind":{"kind":"step","title":"What kind of upgrade are you doing?","detail":"A patch/minor version (within the same major version, e.g., 16.2 -> 16.4, or a SQL Server CU) is low risk and is usually done in place. A major version (e.g., 15 -> 16) may involve data format changes and requires more careful planning.","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":"Minor version / patch (same major version, CU)","next":"patch_prep"},{"label":"Major version (the major version changes)","next":"major_method"},{"label":"I don't know the difference; explain","next":"explain"}]},"explain":{"kind":"step","title":"Patch or major?","detail":"Patch: the major version stays the same (16.2 -> 16.4); the datafile format does not change, usually swap binaries + restart. Major: the major version changes (15 -> 16); the catalog/format may change, pg_upgrade / AutoUpgrade / dump-restore is required, and application compatibility must be tested.","choices":[{"label":"Mine is a patch (same major version)","next":"patch_prep"},{"label":"Mine is a major (the major version changes)","next":"major_method"}]},"patch_prep":{"kind":"step","title":"Are your backup and rollback plan ready?","detail":"Even though a patch is low risk, the golden rule of every upgrade is: full backup first (and a snapshot if possible). You need a point to return to if something goes wrong.","choices":[{"label":"Yes, backup + rollback are ready","next":"sol_patch"},{"label":"No, not yet","next":"sol_backupfirst"}]},"sol_backupfirst":{"kind":"solution","title":"Backup + rollback plan first","detail":"Set up the safety net before starting the upgrade.","steps":["Take a full backup (and if possible a storage snapshot / Oracle guaranteed restore point).","Write down the rollback steps: if something goes wrong, how do I get back to the old version?","Finalize the maintenance window and communication plan, then move on to the patch step."],"related":["logical-dump","last-backup-status"]},"sol_patch":{"kind":"solution","title":"Apply the patch","detail":"A patch within the same major version. Applying it rolling across a cluster (starting with replicas) reduces downtime.","steps":["Oracle: install the new RU with opatch, then apply the SQL changes with 'datapatch -verbose'.","PostgreSQL: minor upgrade = install the new binaries and restart the service (no initdb/dump needed).","MySQL: update the package; in 8.0 the server performs the upgrade automatically at startup.","SQL Server: run the relevant CU/GDR installer, then restart.","MongoDB/Redis/Elasticsearch: rolling restart; secondary nodes first, the primary last.","Then verify the version and the application; if you see errors, execute the rollback plan."],"related":["apply-patch","check-version"],"playbook":"upgrade-rollback"},"major_method":{"kind":"step","title":"Major upgrade method","detail":"Two main paths: (1) In place: convert on the existing server (pg_upgrade, Oracle AutoUpgrade/DBUA, MySQL in-place). Fast but hard to roll back. (2) By migration: build a fresh server on the new version, move the data (with dump/restore or replication), then cut over. Safer, with minimal downtime.","choices":[{"label":"Let me run a preparation/compatibility check first","next":"precheck"},{"label":"In place","next":"sol_inplace"},{"label":"By migrating to a new server (minimal downtime)","next":"sol_migrate"}]},"precheck":{"kind":"step","title":"Compatibility / readiness check","detail":"Before upgrading, collect the target version's warnings: removed (deprecated) features, invalid objects, compatibility level.","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":"Clean output, continue","next":"major_method"},{"label":"There are warnings/incompatibilities","next":"sol_fixwarn"}]},"sol_fixwarn":{"kind":"solution","title":"Fix the warnings first","detail":"Resolve every warning in the report BEFORE upgrading; otherwise the upgrade may fail midway or the application may break.","steps":["Update objects and queries that use removed (deprecated) features/syntax.","Compile/fix invalid objects; resolve missing dependencies.","Plan the compatibility level / feature version steps for the target version.","Once the warnings are cleared, rerun the precheck and return to the method choice."],"related":["check-upgrade-readiness","set-compatibility"]},"sol_inplace":{"kind":"solution","title":"In-place major upgrade","detail":"Convert on the existing server. Fast, but rollback usually means restoring from backup, so a snapshot is a must.","steps":["Take a full backup + snapshot/guaranteed restore point (the only way back).","Oracle: AutoUpgrade (deploy mode) or DBUA. PostgreSQL: pg_upgrade (--link is fast but has no way back). MySQL: new binaries + automatic in-place upgrade. SQL Server: setup /ACTION=upgrade.","Run the post-upgrade tasks (see related below).","Validate the application under real load; if a plan regression occurs, pin the optimizer behavior."],"related":["upgrade-major","post-upgrade-tasks"],"playbook":"upgrade-rollback"},"sol_migrate":{"kind":"solution","title":"Upgrade by migration (minimal downtime)","detail":"Build a fresh server on the new version, move the data, then switch over with a short cutover. The safest method; the old system stays intact.","steps":["Build the new server on the target version and prepare the schema.","Transfer the initial load with dump/restore or replication; then keep it in sync with continuous replication (CDC / logical replication).","In a short maintenance window: stop writes, wait for the remaining delta, and point the application to the new server (cutover).","Verify the migration (row counts/checksums); if there is a problem, switch back to the old server immediately."],"related":["setup-cdc-replication","verify-migration","export-for-migration"]}}},"solver/migration-wizard.json":{"id":"migration-wizard","title":"Migration wizard (data/schema migration)","icon":"🚚","category":"Lifecycle","intro":"You are migrating data/schema. Is it homogeneous or heterogeneous, and what is your downtime tolerance? Plan it step by step: assess → migrate → verify. Rehearse the cutover here first.","start":"kind","nodes":{"kind":{"kind":"step","title":"What are you migrating from and to?","detail":"Homogeneous: the same database engine (e.g. Oracle 19 → Oracle 23, or PostgreSQL to another server). Heterogeneous: a different engine (e.g. Oracle → PostgreSQL, MySQL → SQL Server). Heterogeneous migrations require much more compatibility work (data types, functions, PL/SQL).","choices":[{"label":"Same engine, new server/version (homogeneous)","next":"downtime"},{"label":"To a different engine (heterogeneous)","next":"hetero"},{"label":"I am in the planning phase; how should I approach it?","next":"downtime"}]},"downtime":{"kind":"step","title":"What is your downtime tolerance?","detail":"This determines the migration method. If there is a maintenance window, the simplest path is dump/restore. If near-zero downtime is required, you load the initial data, keep it in sync with continuous replication (CDC) and perform a short cutover.","choices":[{"label":"There is a maintenance window (downtime is acceptable)","next":"sol_offline"},{"label":"Near-zero downtime is required","next":"sol_online"}]},"hetero":{"kind":"step","title":"Have you assessed schema and type compatibility?","detail":"In a heterogeneous migration, the real work is not copying the data but resolving the differences: data types (VARCHAR2↔TEXT), functions (NVL↔COALESCE), auto-increment values, PL/SQL ↔ PL/pgSQL, removed/different features.","choices":[{"label":"I have mapped the schema/type differences","next":"sol_hetero"},{"label":"I have not assessed them yet","next":"sol_assess"}]},"sol_assess":{"kind":"solution","title":"Assess compatibility first","detail":"Before migrating, map the differences between source and target; if this step is skipped, the migration stalls halfway.","steps":["Produce the schema diff: tables, constraints, indexes, views, procedures.","Map data types and functions (the DBAtlas > Types & Functions module is built exactly for this).","Decide how engine-specific features (identity/sequence, partitioning, PL/SQL, triggers) will be handled on the target.","Pick a conversion tool (ora2pg, pgloader, AWS SCT/DMS) and rehearse with a small sample."],"related":["compare-schema","heterogeneous-migration"]},"sol_offline":{"kind":"solution","title":"Offline migration (dump / restore)","detail":"The simplest and most reliable homogeneous method: take a consistent backup, load it into the target, verify, switch over.","steps":["Take a CONSISTENT export from the source (pg_dump -Fc, expdp FLASHBACK_SCN, mysqldump --single-transaction, BACKUP, mongodump --oplog).","Load it into the target (pg_restore -j, impdp, mysql <, RESTORE, mongorestore).","Verify the migration: do row counts and critical checksums match between source and target?","During the maintenance window, point the application to the target (cutover); if something goes wrong, fall back to the source."],"related":["export-for-migration","import-for-migration","verify-migration"]},"sol_online":{"kind":"solution","title":"Minimal downtime (CDC / replication)","detail":"Load the initial data, then keep the target in sync with the source via continuous replication; downtime occurs only during the short cutover.","steps":["Load the initial (baseline) data with dump/restore or a snapshot.","Set up continuous replication: PG logical replication (PUBLICATION/SUBSCRIPTION), MySQL binlog replication, Oracle GoldenGate, MSSQL transactional replication/CDC, Mongo change streams/mongosync.","Wait and monitor until the target catches up with the source and lag is ~0.","Short cutover: stop writes to the source, wait for the remaining delta, point the application to the target, verify."],"related":["setup-cdc-replication","verify-migration"]},"sol_hetero":{"kind":"solution","title":"Heterogeneous migration (to a different engine)","detail":"Convert the schema, move the data, adapt the logic, verify. Proceed in phases; do not migrate everything in one shot.","steps":["Convert the schema: with a tool (ora2pg, pgloader, AWS SCT) or by hand; adapt types and constraints to the target.","Move the data: bulk load or a tool; partition/chunk large tables.","Adapt server-side logic: PL/SQL → PL/pgSQL, function/trigger equivalents.","Verify thoroughly: row counts, sample data, results of critical queries; test the application on the target.","If downtime must be reduced, keep the target in sync with a CDC layer after conversion, then cut over."],"related":["heterogeneous-migration","verify-migration","setup-cdc-replication"]}}},"solver/high-cpu.json":{"id":"high-cpu","title":"High CPU","icon":"🔥","category":"Performance","intro":"Server CPU is pegged. First verify that the database is really the one consuming it, then follow the branches to the root cause: badly planned queries, a plan regression, excessive concurrency or a non-DB process.","start":"root","nodes":{"root":{"kind":"step","title":"Is the database really consuming the CPU?","detail":"Check on the operating system side which process is burning CPU. Is most of the load in DB processes, or in a neighboring process (backup, antivirus, another application)? This splits the investigation in two.","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":"Yes, DB processes are consuming the CPU","next":"db_confirm"},{"label":"No, a neighboring process / backup job is consuming it","next":"sol_os"}]},"db_confirm":{"kind":"step","title":"How did the load appear?","detail":"If the DB is consuming the CPU, identify the pattern: are a few heavy queries dominant, did the problem start suddenly after a change, or is there high concurrency/connection contention?","choices":[{"label":"A few heavy queries are burning the CPU","next":"topcpu"},{"label":"Started suddenly; there was a recent deploy / parameter change","next":"sol_regression"},{"label":"Many connections / concurrency contention","next":"sol_concurrency"}]},"topcpu":{"kind":"step","title":"Find the queries consuming the most CPU","detail":"Pull the top queries by total CPU consumption. Then decide whether each is heavy per call (bad plan) or cheap per call but called very frequently.","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":"Heavy per call; bad plan / full scan","next":"sol_query"},{"label":"Cheap per call but called very frequently","next":"sol_frequency"}]},"sol_query":{"kind":"solution","title":"Root cause: Badly planned queries are burning CPU","detail":"A few queries consume excessive CPU per call because of full scans or a bad join order. Fixing their plans is the core solution.","steps":["Get the plans (EXPLAIN) of the top CPU-consuming queries; look for full scans, a bad join order or gaps between estimated and actual rows.","Add suitable indexes on filter/join columns; verify that the plan switches to an index scan (IXSCAN).","If the optimizer misestimates, gather fresh statistics.","Throttle runaway queries (Oracle Resource Manager / MSSQL Resource Governor) and rewrite the query if needed."],"related":["top-slow-queries","explain-plan","create-index","update-statistics"],"playbook":"high-cpu"},"sol_frequency":{"kind":"solution","title":"Root cause: Excessive call frequency","detail":"The queries are individually cheap but called thousands of times per second; that is where the total CPU cost comes from. Usually an N+1 pattern or an uncached hot path.","steps":["Identify the most frequently called statements (calls / execution_count) and find the calling application path.","Turn the N+1 pattern into a batch query; reduce repeated reads with an application/result cache.","Use prepared statements to cut the cost of repeated parsing (hard parse).","Consider a materialized view / summary table for frequent, unchanging heavy queries."],"related":["top-slow-queries","explain-plan"],"playbook":"high-cpu"},"sol_regression":{"kind":"solution","title":"Root cause: Plan regression / stale statistics","detail":"The system was normal yesterday; CPU spiked after a deploy, parameter change or data load. The optimizer may have switched to a worse plan.","steps":["Identify the change: recent deploy, upgrade/patch, parameter change or large data load.","Gather fresh statistics for the affected tables (stale statistics are the most common cause).","Pin the good plan: Oracle SQL Plan Baseline / hint, MSSQL Query Store 'force plan'.","If the change came from a parameter, revert it and re-evaluate in a controlled way."],"related":["update-statistics","explain-plan","show-config-param"],"playbook":"high-cpu"},"sol_concurrency":{"kind":"solution","title":"Root cause: High concurrency / connection contention","detail":"Individual queries are acceptable, but too many sessions run at the same time; more active sessions than CPU cores cause context switching and latch/spin contention.","steps":["Compare the active session count with the CPU core count; with more active sessions than cores, a run queue builds up.","Put a connection pooler between the application and the DB (PgBouncer / ProxySQL) and set a reasonable pool size.","If there is excessive parallelism, limit the degree of parallelism (Oracle parallel_degree_limit / parallel_max_servers, MSSQL MAXDOP).","Relieve the primary node's CPU by spreading read load across read replicas."],"related":["connection-count","active-sessions"],"playbook":"connection-storm"},"sol_os":{"kind":"solution","title":"Root cause: A non-DB process is consuming CPU","detail":"It is not the database but a neighboring process consuming the CPU: a backup/compression job, an antivirus scan, a log shipper or another application on the same machine.","steps":["Pinpoint the culprit process with an OS tool (top / Task Manager).","If it is a backup, statistics gathering or maintenance job, reschedule it outside peak hours.","Add the data directories to the exclusion list of antivirus/scanning processes.","If the conflict is permanent, move the DB to a separate server with its own resources."],"related":["os-resource"],"playbook":"high-cpu"}}},"solver/memory-pressure.json":{"id":"memory-pressure","title":"Memory pressure / OOM","icon":"🧠","category":"Performance","intro":"Server memory is running out, the system is swapping, or the OOM killer is active. First determine who is consuming the memory: allocated memory (buffer pool/SGA), session memory (work_mem/PGA), the OS overall, or a Redis-specific situation?","start":"root","nodes":{"root":{"kind":"step","title":"Who is consuming the memory?","detail":"Separate the source of memory pressure by layer. Is the allocated (fixed) memory too large, is per-session memory exploding, is a non-DB process eating it, or is Redis's own memory management at play?","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":"Allocated memory (buffer pool / SGA) dominates","next":"allocated"},{"label":"Session memory (work_mem / PGA) is exploding","next":"sol_session"},{"label":"A non-DB process / the OS overall","next":"sol_os"},{"label":"Redis-specific (maxmemory / eviction / fragmentation)","next":"sol_redis"}]},"allocated":{"kind":"step","title":"Does the total configured memory exceed physical RAM?","detail":"If fixed allocated memory is large, there are two possibilities: either the configured total (buffer pool + sessions + OS) exceeds physical RAM (over-commit), or the size is reasonable but the cache hit ratio is still low. Add up the settings and compare them with 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":"Yes, the configured total exceeds RAM (over-commit)","next":"sol_overcommit"},{"label":"No, the size is reasonable but the cache hit ratio is low","next":"sol_cache"}]},"sol_overcommit":{"kind":"solution","title":"Root cause: Memory over-commit","detail":"The sum of buffer pool/SGA + session memory + OS needs exceeds physical RAM. The system is swapping, or the OOM killer is killing the process.","steps":["Redo the memory budget: reserve a share for the OS, then size the allocated memory correctly (Tools > Memory calculator).","Lower the buffer pool/SGA target and the session memory ceiling so they fit within the RAM limit.","Apply the changes in a controlled way and verify that swap usage and OOM events have stopped.","If capacity is permanently insufficient, add RAM or split the load."],"related":["show-config-param","cache-hit-ratio"],"playbook":"memory-pressure"},"sol_cache":{"kind":"solution","title":"Root cause: Insufficient cache (low cache hit ratio)","detail":"Allocated memory does not exceed RAM but is small relative to the working set; the buffer cache hit ratio is low and the DB constantly reads from disk.","steps":["Measure the buffer cache / buffer pool hit ratio; if it is persistently low (e.g. below 90%), the cache is too small.","If RAM is available, grow the buffer pool/SGA in a controlled way and measure the hit ratio again.","If the hot data really is large, reduce unnecessary reads: add indexes, select only the needed columns instead of SELECT *, paginate.","If growing it strains RAM, rebalance session memory against allocated memory."],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"},"sol_session":{"kind":"solution","title":"Root cause: Session memory explosion","detail":"Per-session memory such as work_mem/PGA, multiplied by high concurrency, exhausts RAM. Each sort/hash operation can allocate this memory separately.","steps":["Estimate the worst case: session memory × concurrent sessions × number of operations; compare it with RAM.","Lower the global session memory setting (work_mem / sort_buffer_size / PGA target).","For heavy queries that truly need large sorts/hashes, raise the setting only at the session level.","Limit concurrency with a connection pooler to reduce total memory demand."],"related":["show-config-param","connection-count"],"playbook":"memory-pressure"},"sol_redis":{"kind":"solution","title":"Root cause: Redis memory management","detail":"In Redis, used_memory has hit maxmemory, eviction is active, or fragmentation is high (used_memory_rss >> used_memory). The policy and fragmentation must be addressed.","steps":["Read used_memory, maxmemory and mem_fragmentation_ratio with INFO memory.","Set an eviction policy that fits the purpose: CONFIG SET maxmemory-policy allkeys-lru (cache) or noeviction (persistent data).","Use TTLs so expired keys are cleaned up, and shrink big keys (BIG KEYS).","If fragmentation is high, enable active defrag (CONFIG SET activedefrag yes) or restart in a suitable maintenance window."],"related":["show-config-param"],"playbook":"memory-pressure"},"sol_os":{"kind":"solution","title":"Root cause: Non-DB memory consumption","detail":"The memory is consumed not by the database but by the OS overall or a neighboring process: another application, file system cache pressure, or a leaking helper process.","steps":["Identify the process consuming the most memory with an OS tool (top / free / Task Manager).","If the culprit is a non-DB process, move it, limit it (cgroup / job object) or fix the memory leak.","If the OOM killer is killing the DB process, review the OOM settings and guarantee memory for the DB.","If the conflict is persistent, move the DB to a separate server with its own memory."],"related":["os-resource"],"playbook":"memory-pressure"}}},"solver/install-setup.json":{"id":"install-setup","title":"New installation & secure baseline","icon":"🛠️","category":"Lifecycle","intro":"Steps skipped while preparing a newly installed database for production lead to the most expensive failures. Choose the stage you are at and get an ordered checklist for initial configuration, security hardening, durability and monitoring.","start":"root","nodes":{"root":{"kind":"step","title":"Which stage are you at?","detail":"Preparing a new installation for production has four stages: initial configuration, security hardening, durability (backup/replication) and monitoring. Which one are you working on now? Going through all of them in order is recommended.","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":"Initial configuration after installation","next":"firstconfig"},{"label":"Security hardening","next":"sol_security"},{"label":"Backup & durability setup","next":"durability"},{"label":"Monitoring & alerting setup","next":"sol_monitoring"}]},"firstconfig":{"kind":"step","title":"What is the priority in initial configuration?","detail":"Initial configuration splits into two main topics: resource/parameter sizing (memory, connections) and permanent foundations (character set/collation, data directory/file layout). These are the hardest decisions to change later.","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":"Memory / parameter sizing","next":"sol_sizing"},{"label":"Character set, collation and file layout","next":"sol_layout"}]},"durability":{"kind":"step","title":"Which part of durability are you setting up?","detail":"Durability stands on two legs: a verified backup strategy (first backup + restore test) and continuity (archive/log mode, replication). Saying 'I have a backup' without testing it is the most common misconception.","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":"Backup strategy + first backup + restore test","next":"sol_backup"},{"label":"Archive/log mode + replication","next":"sol_replication"}]},"sol_sizing":{"kind":"solution","title":"Initial configuration: Memory and parameter sizing","detail":"Defaults are conservative for most engines; size them to the server's RAM and CPU, but avoid over-commit.","steps":["Divide total RAM: leave a share for the OS, then set the buffer pool/SGA as the main cache.","Set session memory (work_mem/PGA) conservatively based on expected concurrency; do not exceed total RAM.","Set max_connections to the actual need; plan a connection pooler for high concurrency.","Review core engine-specific parameters (WAL/redo, checkpoint, parallelism) and document the changes."],"related":["show-config-param","connect-cli"]},"sol_layout":{"kind":"solution","title":"Initial configuration: Character set, collation and file layout","detail":"Character set/collation and data directory layout are set at installation time; changing them later usually requires a reinstall or migration.","steps":["Choose a Unicode character set at server level (e.g. UTF-8 / AL32UTF8) and set a consistent collation.","Place data, log/WAL and backup directories on separate disks/volumes; reduce IO contention and single points of failure.","Harden the file system and permissions: only the DB user should be able to read the data directory.","Document layout and character set decisions so that nodes added later match them."],"related":["show-config-param","connect-cli"]},"sol_security":{"kind":"solution","title":"Security hardening","detail":"A new installation comes with default accounts, loose network access and open ports. Reducing the attack surface before going to production is mandatory.","steps":["Remove or lock default/sample accounts; disable unused administrative accounts.","Enforce a strong password policy and change all default passwords.","Least privilege: give each application only the privileges it needs and avoid broad roles.","Enable TLS, restrict the listener/port to trusted networks and enable audit logging."],"related":["create-user-grant","change-password","enable-audit"]},"sol_backup":{"kind":"solution","title":"Durability: Backup strategy and restore test","detail":"A backup is only as good as its ability to be restored. Take the first backup, set up continuity and always perform a restore test.","steps":["Define recovery objectives (RPO/RTO) and set up a full + incremental backup schedule.","Take the first full backup; store backups in a location separate from the DB server.","Test the backup by restoring it to a separate environment; a backup whose integrity has not been proven does not count.","Schedule the backup job, feed its output into monitoring and check the last backup status regularly."],"related":["logical-dump","restore-dump","last-backup-status"]},"sol_replication":{"kind":"solution","title":"Durability: Archive/log mode and replication","detail":"For point-in-time recovery and high availability, archive/log mode must be enabled and there must be at least one replica.","steps":["Enable archive/log mode (PG archive_mode/wal_level, Oracle ARCHIVELOG, MySQL binlog, MSSQL FULL recovery).","Set up at least one replica (standby/replica) and complete the initial synchronization.","Monitor replication lag and define a lag alert.","Validate failover by running a controlled failover/switchover drill."],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_monitoring":{"kind":"solution","title":"Monitoring & alerting setup","detail":"The last step before production: seeing problems before users do. Set up health checks and alerts with the right thresholds.","steps":["Collect basic health checks: cache hit ratio, active/waiting sessions, slowest queries.","Define capacity alerts: disk usage, connection count approaching the limit, replication lag.","Route alerts to a channel (email/chat) and calibrate thresholds to the real load.","Also add backup success and error logs to monitoring to catch silent failures."],"related":["cache-hit-ratio","connection-count","free-space"]}}},"solver/corruption-recovery.json":{"id":"corruption-recovery","title":"Data corruption recovery","icon":"🧯","category":"Recovery","intro":"A step-by-step decision tree for data corruption (block/page checksum errors, unreadable tables, corrupted RDB/AOF): first confirm the scope of the corruption, then choose correctly between block-level recovery, a full restore/PITR from backup and last-resort repair tools.","start":"root","nodes":{"root":{"kind":"step","title":"What is the scope of the corruption?","detail":"First run an integrity check to determine how far the corruption has spread. Are only a few blocks/pages corrupted, is a single object (table/index) affected, or is the corruption widespread? Scan with the commands below to determine the scope, then choose the appropriate branch.","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":"Only a few blocks / pages are corrupted","next":"block_level"},{"label":"A single object (table/index) is affected","next":"restore"},{"label":"Corruption is widespread / many objects","next":"restore"},{"label":"Check tools don't work, the DB won't open","next":"last_resort"},{"label":"Corruption keeps recurring (root cause suspected)","next":"hardware"}]},"block_level":{"kind":"solution","title":"Root cause: limited block/page corruption → targeted recovery","detail":"If only a few blocks/pages are corrupted, there is no need to restore the whole database. Minimize data loss by repairing only the corrupted parts from backup/logs with the engine's block/page-level recovery feature.","steps":["Oracle: after reviewing the corrupted block list in v$database_block_corruption (run RMAN> VALIDATE DATABASE; first if the list is stale), recover a single block with RMAN> RECOVER DATAFILE 5 BLOCK 1234; or all listed blocks with RMAN> RECOVER CORRUPTION LIST;.","SQL Server: single-page restore — RESTORE DATABASE [db] PAGE='5:1234' FROM DISK='D:\\backup\\db.bak' WITH NORECOVERY;  then apply the subsequent log backups WITH NORECOVERY and the last one WITH RECOVERY.","PostgreSQL: there is no automatic block-level repair; partially restore the affected table from backup, or enable zero_damaged_pages=on temporarily ONLY to extract data (corrupted rows are lost).","MySQL/InnoDB: there is no single-page repair; restore the affected table from the last good backup and roll forward with PITR if needed.","After the repair, rerun the check command (VALIDATE / DBCC CHECKDB / pg_amcheck) to confirm that no corruption remains.","Verify the business data in the recovered blocks by sampling it on the application side."],"related":["restore-dump","last-backup-status"],"playbook":"corruption"},"restore":{"kind":"solution","title":"Root cause: object-level/widespread corruption → full restore or PITR from backup","detail":"If the corruption has spread to one or more objects, the safest path is to restore from a good backup and, where possible, roll forward to the moment of corruption (PITR). This avoids the data loss repair tools would cause.","steps":["Determine the approximate time the corruption started (alert log / error log) so you can set the PITR target just before it.","Verify the latest GOOD full backup and the transaction logs up to that point (archived logs / 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: restore the base backup, set recovery_target_time='2026-07-04 09:59:00' and apply WAL up to that point.","MySQL/MariaDB: restore the full backup, then apply the binlog up to that point with mysqlbinlog --stop-datetime='2026-07-04 09:59:00'.","MongoDB: restore with mongorestore from a good replica/snapshot; Elasticsearch: restore the corrupted indices with POST _snapshot/my_repo/snap1/_restore.","After opening, rerun the integrity check and validate the data with row counts/application checks."],"related":["restore-dump","logical-dump","last-backup-status"],"playbook":"corruption"},"last_resort":{"kind":"step","title":"No backup / DB won't open — moving to the last resort?","detail":"Repair tools (REPAIR_ALLOW_DATA_LOSS, innodb_force_recovery, --fix) CAN CAUSE DATA LOSS and can break integrity constraints. They are a last resort only when no usable backup exists and the database cannot be opened. First make sure there really is no usable backup.","choices":[{"label":"I found a good backup, let me restore","next":"restore"},{"label":"There is no backup at all, a repair tool is the only option","next":"repair"}]},"repair":{"kind":"solution","title":"Last resort: repair tools with data loss risk","detail":"These tools discard corrupted rows/pages to make the database openable. Use them only if there is no backup and you have first taken a copy of the existing files. Goal: open it just enough to export the data, then move it to a clean copy.","steps":["FIRST take a raw copy of the existing datafiles (attempts can make things worse).","SQL Server: DBCC CHECKDB (N'db', REPAIR_ALLOW_DATA_LOSS) WITH ALL_ERRORMSGS;  (the database must be in SINGLE_USER mode; this command can cause data loss).","MySQL/InnoDB: start with innodb_force_recovery=1 under [mysqld] in my.cnf; if it does not open, increase to 2, 3... (up to 6); once it opens, immediately export the data with mysqldump, then import it into a clean installation and remove the setting.","PostgreSQL: as a last resort, pg_resetwal (high data loss risk) only when the WAL is corrupted; then extract the data with pg_dump and load it into a new cluster.","Oracle: without a backup, DBMS_REPAIR / event-based recovery is evaluated with Oracle Support; blocks can be skipped with 'skip corrupt' (data loss).","MongoDB: open with mongod --repair or WiredTiger salvage, then move the data to a new replica set.","After the repair, ALWAYS move to a clean installation, rerun the integrity check and report the lost data."],"related":["logical-dump","restore-dump"],"playbook":"corruption"},"hardware":{"kind":"solution","title":"Root cause: hardware/infrastructure → fix the source and enable checksums","detail":"If corruption keeps recurring, the real cause is most likely the disk, the controller, memory (RAM) or an unstable storage layer. Recovering the data alone is not enough; if you do not fix the source, the corruption will come back. Also enable checksum/integrity verification for early detection.","steps":["Check disk health: SMART values (smartctl -a /dev/sdX), RAID controller logs and storage alerts.","Run a memory test (memtest86+ or ECC error counters); single-bit errors are a classic cause of corruption.","Review OS/filesystem and hardware error logs (dmesg, Event Viewer, storage array alerts).","PostgreSQL: create a checksummed cluster with initdb --data-checksums (or pg_checksums --enable) so corruption is caught when it is written to disk.","Oracle: enable block verification with DB_BLOCK_CHECKSUM=FULL and DB_BLOCK_CHECKING=FULL; add DB_LOST_WRITE_PROTECT=TYPICAL.","SQL Server: make sure the database uses PAGE_VERIFY CHECKSUM (ALTER DATABASE [db] SET PAGE_VERIFY CHECKSUM).","MySQL/InnoDB: protect against partial writes with innodb_checksum_algorithm=crc32 and, if needed, innodb_doublewrite=ON.","After fixing the source, restore from the latest good backup to a clean copy and tighten monitoring."],"related":["restore-dump","last-backup-status"],"playbook":"corruption"}}},"solver/failover-drill.json":{"id":"failover-drill","title":"Failover drill / planned role transition","icon":"🔀","category":"Replication","intro":"A decision tree that identifies the type of role change (planned switchover, emergency failover, or a drill that does not touch production) and gives the correct command sequence and verification steps for each scenario. Goal: be ready to transition with no data loss when planned, with minimal loss in an emergency, and with no downtime at all in a drill.","start":"root","nodes":{"root":{"kind":"step","title":"What do you want to do?","detail":"Choose the context of the role change. Are you doing a planned transition while the primary is healthy (maintenance/migration), has the primary really died and an emergency transition is needed, or are you running a drill/readiness validation without touching production?","choices":[{"label":"Planned switchover (primary is healthy)","next":"planned"},{"label":"Emergency failover (primary is dead)","next":"emergency_check"},{"label":"Drill / readiness test (do not touch production)","next":"drill"}]},"planned":{"kind":"step","title":"Planned transition — are you ready first?","detail":"The goal of a planned switchover is ZERO data loss. For that, before the transition you must make sure the replica has caught up with the primary (lag ~0) and is healthy. Check the lag with the commands below; if the lag is close to zero, proceed to the transition steps.","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":"Lag is close to zero, ready to transition","next":"sol_planned"},{"label":"Lag is high, wait for sync first","next":"drill"}]},"sol_planned":{"kind":"solution","title":"Planned switchover (controlled transition without data loss)","detail":"While the primary and standby are healthy and in sync, swap the roles in a controlled way. Order matters: stop writes, make sure the remaining changes have reached the standby, then hand over the role and redirect the application to the new primary.","steps":["Announce the maintenance window and hold the application in a controlled way for reads and writes.","Oracle Data Guard (12c+): SQL> ALTER DATABASE SWITCHOVER TO standby_db VERIFY; then SQL> ALTER DATABASE SWITCHOVER TO standby_db;  (with the Broker: DGMGRL> SWITCHOVER TO 'standby_db'; 11g and earlier: COMMIT TO SWITCHOVER TO PHYSICAL STANDBY).","PostgreSQL: shut down the primary cleanly (pg_ctl stop -m fast), promote the standby with SELECT pg_promote();, then reattach the old primary as the new standby with pg_rewind.","SQL Server AG: on a synchronous replica, ALTER AVAILABILITY GROUP [ag] FAILOVER;  (planned failover without data loss; SYNCHRONOUS_COMMIT is required).","MongoDB: use rs.stepDown(60) to force the current primary to step down for 60 s; the most up-to-date secondary is elected automatically and becomes primary.","Redis: on the new master candidate, REPLICAOF NO ONE; then on the other nodes REPLICAOF new_master port; (with Sentinel, SENTINEL FAILOVER master_name).","Point the application connection string / routing to the new primary (VIP, service name or connection string).","Verify: the new primary accepts writes, the old primary is streaming as a standby with lag ~0, and the application has no errors."],"related":["promote-failover","replication-status","list-replicas"],"playbook":"replication-lag"},"emergency_check":{"kind":"step","title":"Emergency transition — is the primary really dead?","detail":"Before forcing a failover, verify that the primary is truly unreachable and assess the split-brain risk. If the primary is actually alive (e.g. a transient network outage), a forced transition leads to two separate writable copies (split-brain). Also compare lag to choose the most up-to-date standby.","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":"Yes, the primary is dead — force the transition","next":"emergency"},{"label":"Not sure / the primary may come back","next":"planned"}]},"emergency":{"kind":"solution","title":"Emergency failover (primary is dead, transition with risk of data loss)","detail":"The primary is unreachable and you cannot wait for it to come back. You need to force-promote the standby; with asynchronous replication some of the last transactions may be lost. Your priority is to restore service, then identify the loss window and safely bring back the old primary.","steps":["Verify that the primary is really dead (isolate the old primary from the network / shut it down to prevent split-brain — fencing/STONITH).","Choose the most up-to-date standby: compare lag/offset before acting so that you promote the one with the least data loss.","Oracle Data Guard: DGMGRL> FAILOVER TO 'standby_db';  (or SQL> ALTER DATABASE ACTIVATE STANDBY DATABASE;).","PostgreSQL: promote the standby immediately with SELECT pg_promote();; when the old primary comes back, do NOT OPEN it as a primary without pg_rewind (split-brain risk).","SQL Server AG: ALTER AVAILABILITY GROUP [ag] FORCE_FAILOVER_ALLOW_DATA_LOSS;  (data loss is possible with an asynchronous replica).","MongoDB: if no majority remains, reconfigure the surviving members with rs.reconfig(cfg, {force:true}).","Redis: without Sentinel, run REPLICAOF NO ONE on the new master candidate; attach the others to it; never let the old master rejoin automatically.","Redirect the application to the new primary; then identify and report the lost data window (lost transactions) and cleanly rebuild the old primary as a new standby."],"related":["promote-failover","replication-status","list-replicas"],"playbook":"db-down"},"drill":{"kind":"solution","title":"Drill / readiness test (validate without touching production)","detail":"Prove that you are ready for failover without performing a real transition. Verify replica health, that lag is acceptable and that the application can be redirected, without affecting production. Optionally rehearse a real switchover on an isolated copy.","steps":["Measure lag and verify it is below the threshold (PG: pg_stat_replication.replay_lag; MySQL: Seconds_Behind_Source; Oracle: v$dataguard_stats; Mongo: rs.printSecondaryReplicationInfo()).","Verify replica health: all standbys are streaming and synchronous mode is active on the expected members (SQL Server: synchronization_state_desc='SYNCHRONIZED').","Read/role test: run a read-only query on the standby and confirm you get a response without delay (PG hot standby, Oracle Active Data Guard, read replica).","Application routing rehearsal: in a test environment, point the connection string/service name to the new primary and verify that the application connects without problems (do not touch production traffic).","Rehearse a real switchover end to end on an isolated copy and measure the transition time (RTO).","Also rehearse the failback steps: rebuilding the old primary with pg_rewind / DG reinstate / AG resume.","Record findings, timings and gaps; update the runbook and schedule the drill periodically."],"related":["replication-status","list-replicas","promote-failover"],"playbook":"replication-lag"}}},"solver/security-incident.json":{"id":"security-incident","title":"Security incident / suspicious access","icon":"🚨","category":"Security","intro":"For suspicious access, an unexpected privilege change or a possible data leak, a decision tree that leads, based on the symptom, to the right response steps (lock the account, revoke excess privileges, cut connections, determine scope) and to permanent post-incident hardening.","start":"root","nodes":{"root":{"kind":"step","title":"What did you notice?","detail":"Choose the incident type. Is the symptom a suspicious login or a burst of failed logins, an unexpected privilege (GRANT/role) change, a possible data leak / abnormal export, or is the incident closed and you are doing permanent hardening? Choose the most appropriate branch based on your first observation. First, note the time of the incident and the affected account/resource.","choices":[{"label":"Suspicious login / burst of failed logins","next":"identity"},{"label":"Unexpected privilege (GRANT/role) change","next":"privilege"},{"label":"Data leak / abnormal export","next":"data"},{"label":"Permanent post-incident measures (hardening)","next":"harden"}]},"identity":{"kind":"step","title":"Identity incident: which symptom?","detail":"Many failed logins point to a brute-force / credential stuffing attack. In contrast, a successful but unexpected login (from an unusual IP/at an unusual time) indicates the account may have been compromised. In both cases, collect evidence first, then respond.","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":"Burst of failed logins (brute force)","next":"id_bruteforce"},{"label":"Successful but unexpected login (compromise)","next":"id_compromised"}]},"id_bruteforce":{"kind":"solution","title":"Brute force → block the source and protect the account","detail":"With a burst of failed logins, access may not have been gained yet; the goal is to block the source, protect the account with lockout/delay, and rule out any successful intrusion.","steps":["Identify the attacking IPs (queries above) and block them at the network level: firewall / pg_hba.conf reject / listener ACL / security group.","Temporarily lock the targeted account: 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.","Apply a login attempt limit / lockout policy: Oracle: FAILED_LOGIN_ATTEMPTS in the profile; PostgreSQL: the auth_delay module (shared_preload_libraries; it delays failed attempts rather than locking out); MySQL: FAILED_LOGIN_ATTEMPTS ... PASSWORD_LOCK_TIME.","Reset the targeted account's password to a strong value anyway (an attempt may have succeeded).","Verify whether an attempt succeeded: check for a successful login from the same IP; if there is one, also follow the compromise branch.","Record the incident with a timeline and follow the hardening step for permanent measures ('Permanent post-incident measures' from the root)."],"related":["change-password","list-users"],"playbook":"harden-sql"},"id_compromised":{"kind":"solution","title":"Compromised account → stop, investigate, rotate credentials","detail":"An unexpected successful login indicates the account has been compromised. First stop the account and its sessions, investigate what it did, then reset the password and narrow the access source.","steps":["Lock the account immediately (LOCK/DISABLE commands above) and terminate its active sessions: 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.","Investigate what the account did in its sessions: the queries it ran, the tables it accessed and any GRANT/CREATE operations in the audit trail/logs.","Reset the password to a strong value and rotate all related service/application credentials.","Look for any new accounts or privilege changes the attacker may have created (also follow the privilege branch).","Narrow the access source: allow the account to connect only from known IPs (pg_hba.conf / listener ACL / firewall).","Follow the hardening step for permanent measures ('Permanent post-incident measures' from the root)."],"related":["change-password","list-users","show-privileges"],"playbook":"harden-sql"},"privilege":{"kind":"solution","title":"Privilege escalation → audit and revoke excess privileges","detail":"An unexpected GRANT, role assignment or a new highly privileged account may be the second stage of a compromise (persistence/privilege escalation). Audit recent privilege changes, revoke those that are not legitimate and return to the principle of least privilege.","steps":["Audit recent privilege changes: Oracle: SELECT * FROM dba_audit_trail WHERE action_name IN ('GRANT','REVOKE','CREATE USER') ORDER BY timestamp DESC;  SQL Server: GRANT events in the default trace / Server Audit and SELECT * FROM sys.server_permissions;  MySQL: GRANT entries in the audit log and SELECT * FROM mysql.user;  MongoDB: db.getRoles({showPrivileges:true}).","List current privileges: 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'@'%';","Revoke illegitimate excess privileges: 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'@'%';","Look for and remove hidden/backup accounts the attacker may have created (unexpected CREATE USER records).","Review and narrow excessive privileges granted to PUBLIC or broad roles.","Verify that the changes match a legitimate change request; treat everything that does not match as part of the incident and follow the hardening step."],"related":["show-privileges","list-users","create-user-grant"],"playbook":"harden-sql"},"data":{"kind":"solution","title":"Data leak → cut access and determine scope","detail":"Abnormally large queries/exports or unexpected external connections point to a data leak. First cut the active dangerous connections, then determine the scope with the audit trail/logs: which data was accessed, by whom and when.","steps":["Review active connection sources: 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.","Immediately cut suspicious/external connections and lock that account (KILL/LOCK commands).","Block the source at the network level: close the suspicious IP block with firewall / pg_hba.conf / listener ACL.","Review the audit log: determine which tables were SELECTed/EXPORTed, the row counts and the time window (Oracle unified_audit_trail; SQL Server sys.fn_get_audit_file; PostgreSQL pgaudit logs; MongoDB auditLog).","Look for export traces: large result sets, COPY / SELECT ... INTO OUTFILE, mongoexport, bulk read patterns.","Document the affected data scope (which tables, which personal/confidential fields) and the possible data volume; assess legal/notification obligations and follow the hardening step."],"related":["show-privileges","list-users"],"playbook":"harden-sql"},"harden":{"kind":"solution","title":"Permanent post-incident hardening","detail":"After the emergency response, apply permanent controls to prevent recurrence: enable auditing, enforce least privilege, restrict network access and record the incident completely.","steps":["Enable auditing: capture critical events (logins, GRANT/REVOKE, sensitive table access) in the audit trail; for details, follow the 'Audit setup' tree.","Least privilege: grant each account only the privileges it needs, separate shared/service accounts, disable unused accounts.","Password/identity policy: strong passwords, regular rotation, multi-factor authentication where possible, and centralized identity (LDAP/Kerberos).","Network restriction: open the database only to the application servers (firewall, security group, pg_hba.conf, listener ACL); block direct internet access.","Encryption: enable TLS in transit and, where required, encryption at rest.","Monitoring/alerting: set alerts for spikes in failed logins, new GRANTs and abnormal data volumes.","Incident record: report the timeline, affected assets, actions taken and lessons learned; update the runbook."],"related":["list-users","show-privileges","change-password"]}}},"solver/audit-setup.json":{"id":"audit-setup","title":"Audit setup","icon":"🧾","category":"Security","intro":"A decision tree that, depending on what you want to audit (logins/sessions, DDL/schema changes, data access or privilege changes), enables auditing for that scope on the right database engine, then gives the steps for reading the records and for retention/rotation.","start":"root","nodes":{"root":{"kind":"step","title":"What do you want to audit?","detail":"Choose the audit scope. Auditing everything creates noise and cost; narrow it to your needs. Do you want to track login/session events, DDL/schema changes, data access (DML/SELECT) or privilege changes (GRANT/REVOKE)?","choices":[{"label":"Logins / sessions (login, connect)","next":"login"},{"label":"DDL / schema changes (CREATE/ALTER/DROP)","next":"ddl"},{"label":"Data access (DML / SELECT)","next":"dml"},{"label":"Privilege changes (GRANT / REVOKE)","next":"grants"},{"label":"Reading audit records and retention / rotation","next":"retention"}]},"login":{"kind":"solution","title":"Login / session auditing","detail":"Records successful and failed logins: who connected, from where and when. It is the foundation for detecting unauthorized access and brute-force attempts. Enable the relevant policy per engine, then read and retain the records.","steps":["Oracle (Unified Auditing): CREATE AUDIT POLICY login_pol ACTIONS LOGON, LOGOFF;  AUDIT POLICY login_pol;  (for failures: AUDIT POLICY login_pol WHENEVER NOT SUCCESSFUL;).","PostgreSQL: Set log_connections=on and log_disconnections=on in postgresql.conf and reload; connections are logged without needing pgaudit.","MySQL/MariaDB: Install the audit_log plugin (INSTALL PLUGIN audit_log ...) and run SET GLOBAL audit_log_policy='LOGINS'; (MariaDB: server_audit_events='CONNECT').","SQL Server: Create a Server Audit (CREATE SERVER AUDIT ...), add FAILED_LOGIN_GROUP and SUCCESSFUL_LOGIN_GROUP to a Server Audit Specification, and ENABLE both.","MongoDB: Start with --auditDestination file --auditFilter '{ atype: { $in: [\"authenticate\", \"logout\"] } }' (Enterprise/Percona).","Redis: Track failed authentication/command attempts with ACL LOG (for details use the ACL log, not 'notify').","Elasticsearch: Set xpack.security.audit.enabled: true in elasticsearch.yml and add authentication_success, authentication_failed to the include list.","To read and retain/rotate the records, follow the 'Reading audit records and retention / rotation' step from the root."],"related":["list-users","change-password"]},"ddl":{"kind":"solution","title":"DDL / schema change auditing","detail":"Records schema changes such as CREATE/ALTER/DROP. Used to detect unauthorized object changes, hidden tables/backdoors and schema drift.","steps":["Oracle: CREATE AUDIT POLICY ddl_pol ACTIONS CREATE TABLE, ALTER TABLE, DROP TABLE, CREATE USER, ALTER USER;  AUDIT POLICY ddl_pol;","PostgreSQL (pgaudit): Load it with shared_preload_libraries='pgaudit', then ALTER SYSTEM SET pgaudit.log='ddl'; SELECT pg_reload_conf();","MySQL/MariaDB: With audit_log, SET GLOBAL audit_log_policy='ALL'; then filter DDL statements in the log (MariaDB: server_audit_events='QUERY_DDL').","SQL Server: Add SCHEMA_OBJECT_CHANGE_GROUP (and DATABASE_OBJECT_CHANGE_GROUP) to a Database Audit Specification and ENABLE it.","MongoDB: --auditFilter '{ atype: { $in: [\"createCollection\",\"dropCollection\",\"createIndex\",\"dropIndex\",\"createDatabase\",\"dropDatabase\"] } }'.","Elasticsearch: Add index_creation/index_deletion-related events to the audit include list; for schema (mapping) changes, log _mapping calls as requests.","To read and retain/rotate the records, follow the 'Reading audit records and retention / rotation' step from the root."],"related":["show-privileges"]},"dml":{"kind":"solution","title":"Data access (DML / SELECT) auditing","detail":"Records read/write access (SELECT/INSERT/UPDATE/DELETE) to sensitive tables. Because it can generate high volume, limit auditing to sensitive objects. It is critical for leak detection and compliance (KVKK/GDPR).","steps":["Narrow the scope: audit only sensitive tables/columns, not all data (to control noise and cost).","Oracle: CREATE AUDIT POLICY dml_pol ACTIONS SELECT, INSERT, UPDATE, DELETE ON HR.SALARIES;  AUDIT POLICY dml_pol;","PostgreSQL (pgaudit): For broad logging use ALTER SYSTEM SET pgaudit.log='read,write'; or, for targeted object-level auditing, grant the audit role privileges only on the relevant tables and set pgaudit.role.","MySQL/MariaDB: Log only the relevant tables/users with an audit_log filter (audit_log_filter / MariaDB server_audit_events='QUERY_DML').","SQL Server: Add SELECT, INSERT, UPDATE, DELETE actions for specific tables and a principal (e.g. public) to a Database Audit Specification and ENABLE it.","MongoDB: --auditFilter '{ atype: \"authCheck\", \"param.command\": { $in: [\"find\",\"insert\",\"update\",\"delete\"] }, \"param.ns\": \"db.sensitive\" }' (watch the volume).","Elasticsearch: Add access_granted/access_denied to the audit include list and use emit_request_body to see which index a query targets (be careful when logging bodies for sensitive data).","To read and retain/rotate the records, follow the 'Reading audit records and retention / rotation' step from the root."],"related":["show-privileges","list-users"]},"grants":{"kind":"solution","title":"Privilege change (GRANT / REVOKE) auditing","detail":"Records privilege and role changes (GRANT/REVOKE, role assignment, user creation). It is the foundation for detecting privilege escalation and backdoor accounts; usually low-volume but high-value auditing.","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();  (captures GRANT/REVOKE/CREATE ROLE events).","MySQL/MariaDB: Log GRANT/REVOKE/CREATE USER statements with audit_log (policy 'ALL' or DDL) and keep ACL statements in the filter.","SQL Server: Add SERVER_PERMISSION_CHANGE_GROUP, SERVER_ROLE_MEMBER_CHANGE_GROUP and DATABASE_PERMISSION_CHANGE_GROUP to a Server Audit Specification and ENABLE it.","MongoDB: --auditFilter '{ atype: { $in: [\"grantRolesToUser\",\"revokeRolesFromUser\",\"createUser\",\"dropUser\",\"createRole\"] } }'.","Elasticsearch: For role/user changes, log security-related request events (realm/role API calls) in the audit trail.","To read and retain/rotate the records, follow the 'Reading audit records and retention / rotation' step from the root."],"related":["show-privileges","create-user-grant","list-users"]},"retention":{"kind":"solution","title":"Reading audit records and retention / rotation","detail":"Auditing is only useful when the records are read and retained. Define here how to query the records, where they are kept, and the retention/rotation policy that keeps their growth under control.","steps":["Read the records: Oracle: SELECT event_timestamp, dbusername, action_name, object_name, sql_text FROM unified_audit_trail ORDER BY event_timestamp DESC;  PostgreSQL: pgaudit output goes to the server log file (log_directory); filter the 'AUDIT:' lines with grep.","SQL Server: SELECT * FROM sys.fn_get_audit_file('D:\\audit\\*.sqlaudit', DEFAULT, DEFAULT);  MySQL: read the audit_log file (JSON/XML) or MariaDB's server_audit.log; MongoDB: parse audit.json; Elasticsearch: query the .security-audit-* indices.","Set the retention period (according to compliance requirements, e.g. 90 days / 1 year) and write it into the policy.","Rotation/cleanup: Oracle: archive/purge old records with DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP and CLEAN_AUDIT_TRAIL; for PostgreSQL/MySQL/MongoDB file logs, rotate by size/time with logrotate.","Elasticsearch: Define an ILM policy (rollover + delete) for the audit indices.","Send audit records to an immutable (WORM) location with access separate from the DBAs (separation of duties); restrict access.","Monitor storage growth: periodically check that auditing does not fill the disk or excessively affect performance."],"related":["list-users","show-privileges"]}}},"solver/decide-ha.json":{"id":"decide-ha","title":"Decision: which HA/DR solution?","icon":"🧭","category":"Decision Guide","intro":"Choosing a high availability and disaster recovery solution starts not with technology but with the balance between business requirements (RTO/RPO), the need for geographic distribution and cost. Starting from your tolerance for downtime and data loss, this tree points you to the right approach among synchronous replicas, asynchronous standbys, automatic failover clusters and backup+PITR.","start":"root","nodes":{"root":{"kind":"step","title":"What is your data loss tolerance (RPO)?","detail":"Start with the hardest constraint: in a disaster, how many seconds of data can you afford to lose? If zero data loss (RPO=0) is required, synchronous replication is mandatory; if a few seconds/minutes are acceptable, asynchronous solutions and cheaper approaches open up. The RPO target usually drives every other decision.","choices":[{"label":"Zero data loss is mandatory (RPO = 0)","next":"rpo_zero"},{"label":"A few seconds/minutes of data loss is acceptable","next":"rpo_soft"}]},"rpo_zero":{"kind":"step","title":"Is automatic, fast takeover (low RTO) also required?","detail":"You want zero data loss; but how quickly must you be back up when the primary goes down? Automatic failover within seconds, or is a short manual intervention window acceptable? This determines the difference between a plain synchronous replica and a fully automated cluster.","choices":[{"label":"Automatic failover within seconds is required","next":"sol_cluster"},{"label":"Manual/planned takeover is enough; zero loss comes first","next":"sol_sync"}]},"rpo_soft":{"kind":"step","title":"Is a different region/geography required (DR)?","detail":"If some data loss is acceptable, the real question is the scope of resilience: must you be protected against losing an entire data center/region, or is protection against a single node failure in the same region enough? If geographic distribution is required, an asynchronous standby comes to the fore; if not, cost-first options are evaluated.","choices":[{"label":"Yes, a separate region against geographic disasters is mandatory","next":"sol_async"},{"label":"No, budget and simplicity come first","next":"budget"}]},"budget":{"kind":"step","title":"Can you accept a long recovery time (high RTO)?","detail":"You are staying in one region and you tolerate some data loss. Are you aiming for the lowest cost, or do you want a standby node that takes over quickly on failure? If recovery taking hours is acceptable, backup+PITR alone is the most economical path; otherwise you need a warm standby.","choices":[{"label":"Yes, recovery taking hours is acceptable (cheapest)","next":"sol_backup"},{"label":"No, a warm standby that takes over within minutes is required","next":"sol_async"}]},"sol_sync":{"kind":"solution","title":"Recommendation: synchronous replica (zero data loss)","detail":"The primary does not complete a commit until at least one standby acknowledges it; so even if the primary is lost, no acknowledged transaction is lost (RPO=0). The ideal solution within one region on a low-latency network.","steps":["Why: a commit does not complete until acknowledged transactions are written to the standby, so data loss is mathematically zero.","When: financial/critical OLTP, the same region (same/adjacent availability zone), with a low-RTT network available.","Example: PostgreSQL synchronous_commit + synchronous_standby_names, MySQL Group Replication / semisync, Oracle Data Guard SYNC (Maximum Protection/Availability).","Caution: synchronous acknowledgement adds network latency to every commit; if the standby slows down/disconnects and there is only one synchronous copy, the primary may stop accepting writes too. Design with at least two standbys or an appropriate fallback.","Caution: do not replicate synchronously to a remote/high-latency region; write performance collapses. Use asynchronous replication for geographic DR."],"related":["replication-status","promote-failover"]},"sol_async":{"kind":"solution","title":"Recommendation: asynchronous standby (geographic DR)","detail":"The standby applies changes as a stream without delaying the primary's commits. It is the standard way to keep a copy in another region without hurting write performance; in exchange, a small data loss window (equal to the replication lag) is accepted.","steps":["Why: the primary does not wait for standby acknowledgement, so replication to a remote region adds no write latency.","When: when disaster recovery at the region/data center level is needed and data loss on the order of seconds is acceptable.","Example: PostgreSQL streaming replication (async), MySQL asynchronous replication, Oracle Data Guard ASYNC (Maximum Performance).","Caution: at failover you may lose as much data as the lag; monitor replication lag continuously and set up threshold alerts.","Caution: takeover is usually done manually or by external orchestration; a clear failover procedure and regular drills are essential."],"related":["replication-status","promote-failover"]},"sol_cluster":{"kind":"solution","title":"Recommendation: automatic failover cluster","detail":"A cluster solution combining health monitoring, leader election and automatic takeover; it targets both low RTO (automatic failover within seconds) and low/zero RPO with a synchronous copy. Operational overhead is higher, but human intervention is minimized.","steps":["Why: the cluster manager detects a primary failure automatically, promotes a healthy copy and redirects traffic; no manual intervention at midnight.","When: if both low RTO and low RPO are mandatory and the operations team can manage the extra complexity.","Example: Patroni/etcd for PostgreSQL, MySQL InnoDB Cluster (Group Replication + MySQL Router), SQL Server Always On Availability Groups, Oracle RAC + Data Guard with Fast-Start Failover (automatic failover requires FSFO and an observer).","Caution: the split-brain risk is real; do not deploy without proper fencing/quorum (an odd number of nodes, a reliable distributed store).","Caution: the cluster itself is a new failure surface; rehearse failover regularly and validate the automation under real conditions."],"related":["promote-failover","replication-status"]},"sol_backup":{"kind":"solution","title":"Recommendation: backup + PITR (most economical)","detail":"Instead of a running standby, only regular backups and point-in-time recovery (PITR) via WAL/archived logs are kept. It is the lowest-cost solution; the price is that recovery can take hours (high RTO).","steps":["Why: there is no cost for a continuously running second node; only backup storage and archived logs are kept.","When: non-critical systems with high downtime tolerance; dev/test environments; when budget and simplicity come first.","Example: PITR with a physical backup + archived logs (PG base backup + WAL, Oracle RMAN + archivelog, MySQL xtrabackup + binlog).","Caution: RTO is high — restore + log apply time grows with data size; measure it to confirm it fits the time you accept.","Caution: an untested backup is not a backup; run restore drills and backup verification regularly."],"related":["last-backup-status","logical-dump"]}}},"solver/decide-isolation.json":{"id":"decide-isolation","title":"Decision: Which isolation level?","icon":"🧭","category":"Decision Guide","intro":"The transaction isolation level determines the balance between concurrency and consistency. Higher isolation means fewer anomalies but more locking/conflicts and retries. This tree helps you choose between Read Committed, Repeatable Read/Snapshot, and Serializable based on which read anomalies (dirty, non-repeatable, phantom) you can tolerate.","start":"root","nodes":{"root":{"kind":"step","title":"Can you tolerate dirty reads?","detail":"Start with the most basic anomaly: should a transaction be able to read data that has not been committed yet (and may be rolled back)? Almost no serious application wants this; in most modern engines the default already prevents dirty reads. It is relaxed only for approximate/analytical rough counts and maximum speed.","choices":[{"label":"No, I must read only committed data","next":"committed"},{"label":"Yes, a rough/approximate read is enough (rare)","next":"sol_read_uncommitted"}]},"committed":{"kind":"step","title":"Must you see the same result when you re-read within the same transaction?","detail":"Having ruled out dirty reads, the next question: if you read the same row twice within a transaction, is it a problem for you to see a different value because someone committed in between (non-repeatable read), or for new rows to appear (phantom)? Logic that requires report/calculation consistency usually needs a consistent snapshot.","choices":[{"label":"No, it is fine for each statement to see the latest committed data","next":"sol_read_committed"},{"label":"Yes, a consistent snapshot is needed for the whole transaction","next":"consistent"}]},"consistent":{"kind":"step","title":"Must concurrent transactions be logically fully ordered?","detail":"You want consistent reads; is that enough, or must concurrent transactions produce a result as if they had run one by one, in sequence (serializable)? If you can tolerate subtle anomalies such as write skew (e.g. two transactions reading different rows and writing in a way that invalidates each other), Snapshot is enough; otherwise full serialization is required.","choices":[{"label":"No, a consistent snapshot is enough","next":"sol_repeatable_read"},{"label":"Yes, no concurrency anomaly is acceptable","next":"sol_serializable"}]},"sol_read_uncommitted":{"kind":"solution","title":"Recommendation: Read Uncommitted (special cases only)","detail":"The lowest isolation; it allows dirty reads. It reads with almost no waiting on locks, but you may see data that can be rolled back. Not recommended for general-purpose applications.","steps":["Why: The reader does not wait for the writer's lock; maximum concurrency but no consistency guarantee.","When: Only in cases such as rough metrics/progress counts where accuracy does not matter.","Caution: In PostgreSQL this level behaves like Read Committed, and Oracle does not offer Read Uncommitted at all; actual dirty reads mostly come up in lock-based SQL Server (NOLOCK).","Caution: 'NOLOCK for speed' should not become a permanent habit; it carries the risk of reading wrong/duplicate/missing rows."]},"sol_read_committed":{"kind":"solution","title":"Recommendation: Read Committed (default OLTP)","detail":"Each statement sees the latest data committed at that moment; dirty reads are prevented. It is the default in most engines and offers the best concurrency/consistency balance for typical OLTP workloads.","steps":["Why: It prevents dirty reads without forcing the transaction to hold locks unnecessarily long; it provides high concurrency.","When: Short, targeted OLTP transactions; the right default for the vast majority of web applications.","Example: Read Committed is the default in PostgreSQL, SQL Server, and Oracle (in Oracle, consistency is provided by a statement-level snapshot).","Caution: Two reads in the same transaction may return different results (non-repeatable/phantom); do not base logic that needs consistent reporting on it.","Caution: The MySQL/InnoDB default is Repeatable Read; be aware of the engine difference when migrating."]},"sol_repeatable_read":{"kind":"solution","title":"Recommendation: Repeatable Read / Snapshot (consistent reads)","detail":"The transaction works on a consistent snapshot fixed at its start (or at its first read); reads do not change during the transaction. With MVCC, readers do not block writers and writers do not block readers.","steps":["Why: It provides a fixed view for the whole transaction; it prevents non-repeatable reads (and in practice phantoms too in MVCC engines), making read-heavy report/calculation logic consistent.","When: Reports, reconciliations, and batch calculations that read the same data multiple times in one transaction and need consistent results.","Example: MySQL/InnoDB default Repeatable Read; SQL Server SNAPSHOT (row versioning); PostgreSQL REPEATABLE READ; Oracle SERIALIZABLE is actually snapshot semantics.","Caution: It does not prevent serialization anomalies such as write skew; move up to Serializable if the business rule requires it.","Caution: Snapshot/row versioning can cause growth of tempdb or the undo/version store; long transactions bloat old versions."]},"sol_serializable":{"kind":"solution","title":"Recommendation: Serializable (full isolation)","detail":"The highest isolation: concurrent transactions produce a result as if they had been run serially (one by one, in sequence). It prevents all read anomalies and write skew; the price is more conflicts and the need for retries.","steps":["Why: It guarantees that the result is always equivalent to a valid serial execution; it eliminates all concurrency anomalies.","When: Logic carrying correctness-critical invariants: balance/stock checks, cases where double booking must never happen.","Example: PostgreSQL SSI (Serializable Snapshot Isolation), SQL Server SERIALIZABLE (range locks); in Oracle, true serialization is handled at the application level.","Caution: Serialization failures (e.g. 40001 in PG) are expected behavior; retry logic that automatically re-runs the transaction in the application is MANDATORY.","Caution: Throughput can drop under heavily conflicting load; apply it not everywhere but only to transactions where correctness is critical."]}}},"solver/decide-sqlnosql.json":{"id":"decide-sqlnosql","title":"Decision: SQL or NoSQL (which database family)?","icon":"🧭","category":"Decision Guide","intro":"Choosing the right database family is determined by the shape of the data model, your consistency and scale priorities, and your query complexity. Generalizations such as 'NoSQL is more modern' are misleading; each family is designed for specific access patterns. Starting from the structure of the data, this tree guides you to the right tool among relational, document, key-value, and search engines, and reminds you of the polyglot approach when needed.","start":"root","nodes":{"root":{"kind":"step","title":"What is the nature of the data and the dominant access pattern?","detail":"Start with the most decisive question: is the data structured records with strong relationships, documents with a flexible/variable schema, simple key-value lookups, or a special need such as text search? The shape of the data and how you query it largely determine the family.","choices":[{"label":"Relational, structured records and complex queries","next":"relational_check"},{"label":"Nested documents with a flexible/variable schema","next":"sol_document"},{"label":"Simple key-value, very low latency/caching","next":"sol_keyvalue"},{"label":"Full-text / faceted search, log analytics","next":"sol_search"}]},"relational_check":{"kind":"step","title":"Is strong consistency (ACID) or horizontal scale the priority?","detail":"The data looks relational; but what is your priority? Are multi-table joins, transactional integrity, and strong consistency (ACID) essential, or is it horizontally scaling a huge volume that will not fit on a single node? The relational model is the right default for most workloads; it is strained only when scale truly exceeds a single node.","choices":[{"label":"ACID, joins, and complex query integrity come first","next":"sol_relational"},{"label":"Scale above all else, weak relationships/joins","next":"sol_document"}]},"sol_relational":{"kind":"solution","title":"Recommendation: Relational database (RDBMS)","detail":"The right default for workloads with structured, strongly related data that require transactional integrity. ACID guarantees, a mature SQL query engine, and joins let it answer complex questions reliably.","steps":["Why: It offers ACID transactions, foreign key integrity, and strong consistency, plus a mature planner that optimizes complex queries joining multiple tables.","When: OLTP and reporting workloads where relationships and consistency are critical, such as finance, orders/inventory, ERP/CRM.","Example: PostgreSQL, MySQL, Oracle, SQL Server; for time series needs, extensions such as PostgreSQL + TimescaleDB.","Caution: Schema changes (migrations) require discipline; if schema changes are frequent and unpredictable, evaluate the document model.","Caution: If you reach the single-node write/data limit, scale with read replicas, partitioning, and sharding if needed."],"related":["create-partition"]},"sol_document":{"kind":"solution","title":"Recommendation: Document database","detail":"Stores flexible, nested data with a frequently changing schema in its natural form (JSON-like documents). Suitable for applications that mostly read/write through a single document, need few joins, and need horizontal scale.","steps":["Why: The schema is flexible; documents can add or remove fields, fetch nested structures in a single read, and lend themselves to horizontal scale (sharding).","When: Document-centric, variably structured, rapidly evolving data models such as catalogs, user profiles, content/CMS, event records.","Example: MongoDB, Couchbase; in the relational world, JSONB (PostgreSQL) offers similar flexibility to a limited extent.","Caution: Complex multi-document joins and reporting are weak; if relationships dominate, the relational model is more suitable.","Caution: Responsibility for consistency and schema validation shifts largely to the application; protect data quality in code."]},"sol_keyvalue":{"kind":"solution","title":"Recommendation: Key-value store / cache","detail":"Designed for workloads that need very fast reads/writes by key and no complex queries. It usually runs in memory and provides microsecond/millisecond latency; ideal for tasks such as caching, sessions, and counters.","steps":["Why: Extremely low latency and high throughput for simple key access; in-memory structures remove load from the relational database.","When: Cache tier, session store, rate-limit counters, leaderboards, temporary queues/pub-sub.","Example: Redis, Memcached; Redis also offers rich structures such as lists/sets/sorted sets and persistence options.","Caution: It is usually not the single source of truth; keep the source data in a durable database and store derived/cached data here.","Caution: Memory is limited and expensive; plan the eviction policy, TTLs, and persistence/durability settings according to business needs."]},"sol_search":{"kind":"solution","title":"Recommendation: Search / analytics engine","detail":"An inverted-index-based engine specialized for full-text search, relevance ranking, faceted filtering, and high-volume log/text analytics. It delivers the search experience that relational LIKE queries cannot.","steps":["Why: With an inverted index and scoring, it performs full-text search, autocomplete, facets, and aggregations with high performance.","When: Product/site search, log and observability analytics, applications that need a relevance-ranked search experience.","Example: Elasticsearch/OpenSearch, Apache Solr; PostgreSQL tsvector may be enough for medium-scale full-text search.","Caution: It is usually not the primary system of record; keep the main data in a relational/document DB and feed the search index from there (CDC/sync).","Caution: Real-time strong consistency guarantees are weak (near-real-time indexing); account for indexing latency in the design."],"related":["setup-cdc-replication"]}}},"solver/decide-shard-replica.json":{"id":"decide-shard-replica","title":"Decision: Sharding or Replication (scaling)?","icon":"🧭","category":"Decision Guide","intro":"The database scaling decision depends on whether the bottleneck is reads or writes and whether the data fits on a single node. The wrong strategy means unnecessary complexity or an unresolved bottleneck. This tree first reminds you of the cheapest and simplest options (vertical scaling, caching) and then helps you choose correctly between read replicas and sharding.","start":"root","nodes":{"root":{"kind":"step","title":"Have you tried the simplest path first: vertical scaling and query tuning?","detail":"Before moving to a distributed architecture, take the cheapest win: growing the hardware (CPU/RAM/IOPS), adding missing indexes and fixing expensive queries solves most bottlenecks. Add complexity only when these are exhausted.","choices":[{"label":"No / I have not hit the limit yet","next":"sol_vertical"},{"label":"Yes, vertical scaling and tuning are not enough","next":"bottleneck"}]},"bottleneck":{"kind":"step","title":"Is the bottleneck reads, or writes/data volume?","detail":"Isolate the source of the scaling problem: are many read queries choking a single node (read-heavy), or does the write rate or total data size exceed a single node's capacity (write/volume-heavy)? This distinction is the fundamental difference between replicas and sharding.","choices":[{"label":"Read traffic dominates, writes fit on a single node","next":"read_path"},{"label":"Write rate or data volume exceeds a single node","next":"sol_sharding"}]},"read_path":{"kind":"step","title":"Are reads repetitive/cacheable, or fresh and varied?","detail":"Split the read bottleneck one step further: are the same results read again and again (a cache is very effective), or do reads request varied, broad and current data (extra read capacity is needed)? This decides between a cache layer and read replicas.","choices":[{"label":"The same data is read repeatedly, slight staleness is acceptable","next":"sol_cache"},{"label":"Varied/fresh reads, raw read capacity is needed","next":"sol_read_replica"}]},"sol_vertical":{"kind":"solution","title":"Recommendation: vertical scaling + query tuning first","detail":"The first and cheapest path before moving to a distributed architecture. Growing the hardware and fixing queries/indexes solves most scaling problems without adding operational complexity.","steps":["Why: lowest complexity and risk; if you stay on a single node your transaction/consistency model does not change and you do not have to split application code.","When: if you have not yet reached the hardware ceiling of a single node, or the bottleneck actually comes from bad queries/missing indexes.","Step: profile slow queries, add missing indexes, fix plans/statistics; then increase CPU/RAM/IOPS.","Caution: vertical scaling is finite and its cost grows exponentially; a single node is still a single point of failure (set up a replica separately for HA).","Caution: plan the next step (replicas/sharding) in advance as the ceiling approaches; rushed migrations are risky."]},"sol_read_replica":{"kind":"solution","title":"Recommendation: read replicas (read scaling)","detail":"Writes stay on a single node (the primary) while read traffic is distributed to one or more replicas. It is the standard way to increase raw read capacity and resilience for read-heavy workloads.","steps":["Why: read queries are spread over multiple copies; the primary handles only writes, and read throughput grows almost linearly.","When: workloads with a high read/write ratio; separating reporting and analytics queries from the primary; gaining an HA standby at the same time.","Example: PostgreSQL streaming replicas, MySQL replicas + proxy routing, read/write splitting in the application (read-write split).","Caution: with asynchronous replicas, replication lag creates a read-your-writes problem (you cannot immediately read what you just wrote); route critical reads to the primary.","Caution: replicas do not solve a write bottleneck; the single writer node is still the limit — if writes are also a bottleneck, sharding is required."],"related":["replication-status"]},"sol_cache":{"kind":"solution","title":"Recommendation: cache layer","detail":"Keeping frequently re-read results in a fast in-memory layer drastically reduces database load. With the right pattern it answers without going to the database, lowering both latency and load.","steps":["Why: repeated reads are answered at memory speed without ever reaching the database; it is the read scaling method with the highest benefit/cost ratio.","When: the same data is read many times and slight staleness is acceptable; e.g. product details, settings, reference data.","Example: the cache-aside pattern with Redis/Memcached; the application goes to the cache first and, on a miss, to the database.","Caution: cache invalidation is hard; design the TTL and the update/purge-on-write strategy explicitly.","Caution: do not rely on a stale cache for reads that require strong consistency (money/stock); serve those reads from the source."]},"sol_sharding":{"kind":"solution","title":"Recommendation: sharding (write/data scaling)","detail":"Data is split by a shard key and distributed across multiple independent nodes; each node owns a slice of the data and its writes. It is the way to scale write rate and total data volume, but it adds significant complexity.","steps":["Why: writes and storage are spread across multiple nodes; the single-writer and single-node data limits disappear and horizontal scaling opens up.","When: when write throughput or data size no longer fits on a single node (and read replicas); when no other option remains.","Example: shard routing in the application layer, Vitess (MySQL), Citus (PostgreSQL), built-in sharding in document DBs.","Caution: choosing the shard key is critical and hard to reverse; a bad key creates hotspots and uneven distribution. Choose a high-cardinality key that matches the access pattern.","Caution: cross-shard joins, transactions and uniqueness become difficult; application complexity, rebalancing (resharding) and operational load increase significantly."],"related":["create-partition"]}}},"solver/decide-backup.json":{"id":"decide-backup","title":"Decision: Which backup strategy?","icon":"🧭","category":"Decision Guide","intro":"A backup strategy is chosen by balancing three factors: how much data loss you can tolerate (RPO), your data size, and how quickly you must recover (RTO). No single method fits every scenario. Starting from the RPO target, this tree guides you between logical dumps, physical backups + PITR, and snapshots, and reminds you that 3-2-1 and verification are non-negotiable.","start":"root","nodes":{"root":{"kind":"step","title":"What is your data loss target (RPO)?","detail":"Start with the most decisive constraint: in a disaster, how many minutes/hours of data can you afford to lose? If near-zero data loss is required, continuous log archiving (PITR) is mandatory; if daily/weekly backup tolerance is acceptable, simpler methods are enough. RPO defines the framework of the strategy.","choices":[{"label":"Near zero — I must be able to return to any point in time","next":"sol_physical_pitr"},{"label":"Daily/periodic backups (a few hours of loss) are acceptable","next":"size"},{"label":"First remind me of general best practice (3-2-1 / verification)","next":"sol_321"}]},"size":{"kind":"step","title":"What are the data size and portability requirements?","detail":"If periodic backups are enough, the next question is scale and purpose: is the database small/medium and do you want the backup to be portable to another environment/version, or is the data large and your main concern getting back within minutes? This decides between a logical dump and a snapshot.","choices":[{"label":"Small/medium, portable and compatible across versions","next":"sol_logical"},{"label":"Large data, recovery must be very fast (low RTO)","next":"sol_snapshot"}]},"sol_logical":{"kind":"solution","title":"Recommendation: Logical dump","detail":"Exports data at the logical level (schema definitions + rows); the output is either SQL/text (pg_dump plain format, mysqldump) or a tool-specific binary dump (pg_dump -Fc, Oracle Data Pump). A flexible and portable method for small and medium databases, migrations, version upgrades, and recovery of selected tables/objects.","steps":["Why: The output is portable (and readable in text form); suitable for moving between versions/engines and for restoring a single table/object.","When: Small/medium data, version upgrades/migrations, development environment copies, the need for logical (row-level) recovery.","Example: PostgreSQL pg_dump/pg_dumpall, MySQL mysqldump, Oracle Data Pump (expdp/impdp; produces a binary .dmp, use impdp SQLFILE= to see the DDL as text).","Caution: Because a restore reprocesses the data, both taking the backup and restoring are very slow for large databases (high RTO).","Caution: On its own it does not provide a low RPO; point-in-time recovery requires a physical backup + log archive. Use the appropriate consistency options for a consistent snapshot."],"related":["logical-dump","last-backup-status"]},"sol_physical_pitr":{"kind":"solution","title":"Recommendation: Physical backup + WAL/archived logs (PITR)","detail":"You take regular physical base backups and continuously archive the transaction logs (WAL/redo/binlog). This lets you perform Point-in-Time Recovery to any moment and reduce data loss to near zero.","steps":["Why: Thanks to continuous log archiving, you can roll forward to any transaction after the last base backup and return to the desired moment (e.g. just before an erroneous command) — the lowest RPO.","When: Large and critical production databases; systems that need a low RPO and a reasonable RTO.","Example: PostgreSQL base backup + WAL archive (pgBackRest/barman), Oracle RMAN + archivelog, MySQL physical backup + binlog (xtrabackup).","Caution: Setup and operations are more complex; continuously monitor that archived logs flow without interruption or gaps — a single gap breaks the PITR chain.","Caution: The log archive store grows continuously; without a retention policy and a storage plan, the disk fills up."],"related":["last-backup-status","logical-dump"]},"sol_snapshot":{"kind":"solution","title":"Recommendation: Snapshot","detail":"Takes a block-level snapshot of the data at the storage or virtualization layer. Backup and restore are very fast even for very large databases; however, it depends on the infrastructure and requires careful use.","steps":["Why: A block-level copy is created almost instantly and restores are very fast; it gives the lowest RTO for large data.","When: Terabytes of data, fast recovery as the priority; if the storage/cloud layer supports snapshots.","Example: SAN/LVM snapshot, cloud disk snapshot (EBS, etc.), ZFS/filesystem snapshot; triggered consistently with the database engine.","Caution: A snapshot depends on the storage system and usually lives on the same infrastructure; on its own it does not satisfy 3-2-1 — copy it offsite.","Caution: To be application-consistent, the database must be in the appropriate mode (fsfreeze/backup mode/hot backup) at snapshot time; otherwise you get a 'crash-consistent' backup."],"related":["last-backup-status"]},"sol_321":{"kind":"solution","title":"Recommendation: 3-2-1 rule + offsite + mandatory verification","detail":"Whatever method you choose, these principles must sit on top of the strategy. The mere existence of a backup is not enough; it must be on different media, in a different location, and tested regularly. This is a framework that complements all of the options above.","steps":["3-2-1: Keep at least 3 copies, on 2 different media/types, with 1 copy offsite.","Why offsite: Fire, flood, ransomware or the loss of a region can destroy every copy in a single location at once; at least one copy must be isolated/remote.","Verification is mandatory: An untested backup is not a backup; run real restore drills regularly and measure the recovery time (RTO).","Immutability: Against ransomware, keep an undeletable/unmodifiable (immutable/WORM) or air-gapped copy.","Caution: Automatically monitor backup integrity (checksum) and recoverability; set alerts for failed/stale backups. Encrypt the backups themselves as well."],"related":["last-backup-status"]}}},"solver/decide-index.json":{"id":"decide-index","title":"Decision: Which index type?","icon":"🧭","category":"Decision Guide","intro":"The right index type depends on the query's access pattern (equality, range, or text/JSON search), the column's cardinality, and the read/write balance. The wrong index both wastes space and slows down writes. This tree starts from the shape of the query and guides you among B-tree, Hash, Bitmap, GIN/GiST, partial, and covering index options, and reminds you that column order is critical in a composite index.","start":"root","nodes":{"root":{"kind":"step","title":"How does your query use this column?","detail":"The access pattern is the most decisive factor in choosing an index. Do you only match the column with '=', do you do range/sorting (>, <, BETWEEN, ORDER BY), or do you search full text, a JSON field, or geometric data/arrays? Start here; do not choose an index type until the need is clear.","choices":[{"label":"Mixed equality and range/sorting (=, >, <, BETWEEN, ORDER BY)","next":"cardinality"},{"label":"Equality only (=), no range or sorting","next":"sol_hash"},{"label":"Full-text, JSON/JSONB field, array, or geometric search","next":"sol_gin_gist"},{"label":"The query always targets the same small subset of rows (e.g. WHERE status='active')","next":"sol_partial"}]},"cardinality":{"kind":"step","title":"What is the column's cardinality (number of distinct values)?","detail":"If range/sorting is needed, the default is B-tree; however, for columns with very few distinct values (low cardinality: gender, status, yes/no) in a read-heavy data warehouse, a Bitmap index may be better. Does the column have many unique values, or a few repeating values?","choices":[{"label":"High cardinality (many unique values) or OLTP workload","next":"coverage"},{"label":"Low cardinality and read-heavy / data warehouse (DWH)","next":"sol_bitmap"}]},"coverage":{"kind":"step","title":"Does the query read only the columns in the index?","detail":"The access pattern suits a B-tree. One last optimization: if all columns the query returns and filters on are in the index, the engine can answer from the index alone without visiting the table (index-only / covering scan). Does your query read a few columns, or does it need most of the row?","choices":[{"label":"Yes, a few columns; it can be answered without visiting the table","next":"sol_covering"},{"label":"No, standard access is enough","next":"sol_btree"}]},"sol_btree":{"kind":"solution","title":"Recommendation: B-tree (default)","detail":"A B-tree is a balanced tree structure that supports a wide range of access patterns: equality, range, sorting (ORDER BY), prefix matching with LIKE 'abc%', and MIN/MAX. It is the default index type in most relational engines and the right starting point when in doubt.","steps":["Why: A single structure efficiently serves both equality and range/sorting; it is the most general-purpose index.","When: OLTP queries, WHERE + range + ORDER BY together, primary/unique keys.","Column ORDER is critical in a composite index: put the most selective column used with equality first and leave the range column last. An (a,b) index helps a filter on 'a' or 'a,b'; it generally does not help a filter on 'b' alone (leftmost-prefix rule).","Example: the default B-tree/B+tree index in PostgreSQL/Oracle/MySQL(InnoDB)/MSSQL alike.","Caution: Every index increases write (INSERT/UPDATE/DELETE) cost and disk space; do not keep unnecessary or duplicate indexes."],"related":["slow-query"]},"sol_hash":{"kind":"solution","title":"Recommendation: Hash index (equality only)","detail":"A hash index stores the hash of the column value and is very fast for exact equality ('=') lookups only. It does not support range, sorting, or prefix searches, so its use case is narrow.","steps":["Why: It provides average constant-time (O(1)) access for equality lookups; it can be advantageous for very large equality workloads that fit in memory.","When: The column is only used with '=' and range/sorting is never needed.","Engine differences: PostgreSQL has hash indexes, but in practice B-tree is sufficient and more flexible in most cases; MySQL(InnoDB) manages hash indexing only internally as the adaptive hash index, while the MEMORY engine uses hash explicitly; in Oracle it appears in the context of clusters/hash clusters.","Caution: Range, BETWEEN, ORDER BY, and LIKE 'abc%' are not supported; choose a B-tree once those needs arise.","Caution: Since the default B-tree is usually sufficient, do not switch to hash without measuring."],"related":["slow-query"]},"sol_bitmap":{"kind":"solution","title":"Recommendation: Bitmap index (low cardinality / DWH)","detail":"A bitmap index keeps a bit array for each distinct value and combines multiple low-cardinality conditions very quickly with bitwise AND/OR. It is strong in read-heavy workloads such as data warehousing and reporting.","steps":["Why: It takes very little space on columns with few distinct values and efficiently combines multiple conditions (WHERE gender=.. AND region=..) with bitmap operations.","When: Low-cardinality columns, star schema queries, data warehouse/reporting, read-heavy workloads.","Engine differences: A classic strength of Oracle; PostgreSQL has no persistent bitmap index, but the planner can produce a 'bitmap heap scan' at runtime. MySQL/MSSQL have no persistent bitmap index as such.","Caution: Do not use on frequently updated (OLTP) tables; bitmap locks severely reduce row-level concurrency.","Caution: The advantage disappears on high-cardinality columns; prefer a B-tree there."],"related":["slow-query"]},"sol_gin_gist":{"kind":"solution","title":"Recommendation: GIN / GiST (text, JSON, array, geometry)","detail":"PostgreSQL has specialized index families for access types a standard B-tree cannot handle. GIN points to many components within a value (words, JSONB keys, array elements); GiST suits queries such as geometric/range/nearest-neighbor.","steps":["Why: For these data types, queries such as 'contains/overlaps/near' cannot be served efficiently by a B-tree; GIN/GiST speed them up.","When GIN: Full-text search (tsvector), JSONB field/key lookups (@>, ?), array containment queries; read-heavy, write-light.","When GiST: Geometric/geospatial (PostGIS), range types, nearest-neighbor (KNN) queries.","Engine differences: GIN/GiST are PostgreSQL-specific. FULLTEXT and SPATIAL (R-tree) indexes in MySQL, Oracle Text and Spatial in Oracle, and Full-Text Index in MSSQL serve a similar purpose.","Caution: GIN is expensive on writes and slow to build (can be mitigated with fastupdate); measure the cost on write-heavy tables."],"related":["slow-query"]},"sol_partial":{"kind":"solution","title":"Recommendation: Partial / filtered index","detail":"A partial index indexes only the rows that satisfy a condition (WHERE ...) instead of the whole table. If queries always target the same small subset, the index becomes much smaller and faster, and write cost drops.","steps":["Why: Because it indexes only the relevant rows, both space and maintenance cost go down; the frequently queried 'hot' subset becomes much faster.","When: Stable, narrow conditions such as WHERE status='active', deleted_at IS NULL, priority='high'.","Engine differences: PostgreSQL supports this directly with a 'partial index' (CREATE INDEX ... WHERE ...), MSSQL with a 'filtered index'. In Oracle, function-based/conditional index patterns are used for an equivalent; MySQL(InnoDB) does not support it directly.","Caution: The index's WHERE condition must match the query's condition; otherwise the planner cannot use the index.","Caution: If the subset grows, the advantage shrinks; make sure the condition is stable."],"related":["slow-query"]},"sol_covering":{"kind":"solution","title":"Recommendation: Covering (INCLUDE) index","detail":"A covering index holds all the columns the query needs, letting the engine answer from the index alone without visiting the table (heap) at all (index-only scan). It gives a significant speedup for frequently run queries that return few columns.","steps":["Why: Table access (heap fetch) is eliminated; disk I/O and latency drop noticeably.","When: Frequently run queries with well-defined WHERE + SELECT columns and a narrow result set.","Engine differences: MSSQL and PostgreSQL add non-key columns to the index leaf with 'INCLUDE'; in MySQL(InnoDB), secondary indexes already contain the primary key, so covering is achieved with a composite index; in Oracle, index-only access happens when all required columns are in the index.","Caution: Extra/wide INCLUDE columns bloat the index and slow down writes; add only the columns that are truly needed.","Caution: In PostgreSQL, an index-only scan requires an up-to-date visibility map; regular VACUUM is essential."],"related":["slow-query"]}}},"solver/decide-partition.json":{"id":"decide-partition","title":"Decision: Which partitioning strategy?","icon":"🧭","category":"Decision Guide","intro":"Partitioning splits a large table into multiple physical pieces under a single logical name, making it easy to drop old data and letting queries look only at the relevant pieces (partition pruning). On small tables, however, it only adds complexity. This tree first questions whether partitioning is really needed, then guides you among Range, List, Hash, and Composite based on access and distribution patterns.","start":"root","nodes":{"root":{"kind":"step","title":"Is the table really large enough to need partitioning?","detail":"Partitioning is not performance magic; it is a scale and maintenance (lifecycle) tool. If the table is not large enough or queries already perform well with indexes, partitioning only brings plan complexity and management overhead. Is your table reaching tens of millions+ rows/hundreds of GB, or is it still small/medium?","choices":[{"label":"Yes, it is very large and/or old data needs to be deleted/archived in bulk","next":"access"},{"label":"No, small/medium table, or I am not sure","next":"sol_no_partition"}]},"access":{"kind":"step","title":"How do you access the data and how should it be split?","detail":"The partition key should be the column most queries filter on; only then does pruning kick in. What is the dominant access pattern? By date/sequential ranges (last 30 days), by specific category/region values, or is there no clear filter and you want to distribute the load evenly?","choices":[{"label":"Sequential ranges such as date/sequence number (last X days, by month)","next":"sol_range"},{"label":"Discrete value sets such as category/region/tenant","next":"sol_list"},{"label":"No clear range/category; the goal is to distribute load evenly","next":"sol_hash"},{"label":"Both date and high volume; need to split on two dimensions","next":"sol_composite"}]},"sol_no_partition":{"kind":"solution","title":"Recommendation: Do not partition (yet)","detail":"Partitioning brings no benefit on small/medium tables; well-chosen indexes and normal maintenance are enough. Premature partitioning complicates query plans, misses pruning with the wrong key, and adds operational overhead.","steps":["Why: On a small table a full scan or index access is already fast; partitioning adds nothing but extra management and plan complexity.","When to stay this way: Under a few million rows, no need to drop old data in bulk, queries perform well with indexes.","Instead: The right indexes, an archive table if needed, regular statistics/VACUUM/ANALYZE.","Caution: Do not set up partitioning early on the assumption that 'it will grow'; switch once growth is proven and the right key is clear.","Engine differences: A general principle valid for all engines; PostgreSQL declarative partitioning, MySQL PARTITION BY, and Oracle and MSSQL partitioning can easily be added later."],"related":[]},"sol_range":{"kind":"solution","title":"Recommendation: Range partitioning","detail":"Range partitioning splits rows by sequential ranges of a column (e.g. date ranges: one partition per month/quarter). It is the most common and most powerful strategy for time series and continuously growing tables.","steps":["Why: When queries filter by date/range, the engine reads only the relevant partitions (partition pruning); old data is dropped instantly with a single command (partition DROP).","When: Time series, logs, order/transaction history; 'last N days' queries and time-based archiving.","Partition key: Choose the date/sequential column present in the WHERE clause of the overwhelming majority of queries; otherwise pruning does not work.","Example: PostgreSQL RANGE partitioning, Oracle range/interval partitioning (automatic new partitions), MySQL PARTITION BY RANGE (usually RANGE COLUMNS for dates), MSSQL partition function/scheme.","Caution: Automate creation of new partitions for future ranges (Oracle interval or a scheduled job); a filled 'default/maxvalue' partition becomes a hotspot."],"related":[]},"sol_list":{"kind":"solution","title":"Recommendation: List partitioning","detail":"List partitioning groups rows by discrete, known values of a column (e.g. region = 'TR','EU','US' or tenant ID). When natural, finite categories exist, it makes access and management clear.","steps":["Why: When queries filter by specific category values, only that partition is read; per-category maintenance/deletion becomes easier.","When: Discrete and relatively stable value sets such as region/country, product type, status, multi-tenant separation.","Partition key: The value set should be finite and predictable; if new values are added frequently, consider Hash instead of List.","Example: PostgreSQL LIST partitioning, Oracle list partitioning, MySQL PARTITION BY LIST, MSSQL partition scheme with suitable boundaries.","Caution: Keep a 'default' partition for undefined/new values; otherwise the row cannot be inserted and you get an error."],"related":[]},"sol_hash":{"kind":"solution","title":"Recommendation: Hash partitioning (even distribution)","detail":"Hash partitioning distributes rows relatively evenly across a predefined number of partitions based on the hash of the key column. It is used to avoid hotspots and balance load when there is no natural range/category.","steps":["Why: Rows are distributed evenly; the write hotspot piling up on a single partition or at the end of the primary key is reduced.","When: High write volume, no natural range/category, the goal is to equalize I/O and size (e.g. a high-cardinality id).","Partition key: Choose a high-cardinality, evenly distributed column (usually the primary/unique key).","Example: PostgreSQL HASH partitioning, Oracle hash partitioning, MySQL PARTITION BY HASH/KEY.","Caution: It provides no pruning for range queries (hashing destroys order); queries like 'last N days' scan all partitions. Changing the number of partitions later requires redistribution."],"related":[]},"sol_composite":{"kind":"solution","title":"Recommendation: Composite (multi-level) partitioning","detail":"Composite partitioning combines two strategies: it splits at the top level first, then divides each partition into subpartitions (e.g. Range-by-Hash or Range-by-List). It is used when both time-based lifecycle and even distribution/category separation are needed at the same time.","steps":["Why: When a single strategy is not enough, it meets both needs together; for example, split by month (archiving) and distribute by hash within the month (hotspot prevention).","When: Very large time series + high concurrent writes, or a dual access pattern of date + region/tenant.","Example: Oracle composite (range-hash, range-list, list-hash, etc.); in PostgreSQL it is built manually by partitioning the partitions again (partition of partition); MySQL subpartitions (RANGE/LIST + HASH/KEY).","Caution: It is the option with the highest management and schema complexity; choose it only when both dimensions are a proven need.","Caution: A wrong top/sub key choice loses pruning; verify that both levels match the query pattern."],"related":[]}}},"solver/decide-pooling.json":{"id":"decide-pooling","title":"Decision: How to set up connection pooling?","icon":"🧭","category":"Decision Guide","intro":"Every database connection carries memory and scheduling cost; an uncontrolled growth in connection count slows the server down or even brings it down. A connection pool solves this by reusing a small number of persistent connections. This tree guides decisions on where the pool sits, which mode (session/transaction/statement) to use, and the right size, and stresses why an external pooler is mandatory in serverless environments.","start":"root","nodes":{"root":{"kind":"step","title":"How does your application architecture connect?","detail":"The pooling decision starts with the architecture. Is it a single long-lived application process (classic server), many independent processes/instances (microservices, many pods), or serverless/Lambda functions that are born and die with every request? Each requires a different pool placement.","choices":[{"label":"One/few long-lived application processes","next":"sol_app_pool"},{"label":"Many processes/instances/pods connect to the same DB","next":"middleware"},{"label":"Serverless / Lambda / short-lived functions","next":"sol_serverless"},{"label":"First show me how to choose the right pool size","next":"sol_size"}]},"middleware":{"kind":"step","title":"How should the middleware pooler mode be chosen?","detail":"With many clients, a middleware pooler (PgBouncer, ProxySQL) keeps the connection count on the DB low. But the pool mode depends on the features the application uses: do you rely on session-bound state (prepared statements, temporary tables, SET, advisory locks, session-level features), or does everything complete within a single transaction?","choices":[{"label":"I depend on session state (SET, temporary tables, session prepared statements)","next":"sol_session_mode"},{"label":"Transactions are short and independent; I keep no session state","next":"sol_txn_mode"}]},"sol_app_pool":{"kind":"solution","title":"Recommendation: In-application connection pool","detail":"For one/few long-lived processes, an in-application pool library (HikariCP, the built-in pool of Go's database/sql, .NET connection pooling) covers most needs. Connections are opened once and reused over and over.","steps":["Why: The cost of opening/closing connections disappears; a limited and predictable connection count protects the DB.","When: A classic monolith or a few application instances; total connection demand is below DB capacity.","Example: Java HikariCP, .NET SqlConnection/Npgsql pooling, Node pg-pool, Python SQLAlchemy pool.","For sizing: Determine the pool size based on cores/disks (next decision); 'the more the better' does not apply.","Caution: Each application instance opens its own pool; instance count × pool size must not exceed DB max_connections. If it does, move to a middleware pooler."],"related":["connection-storm"]},"sol_size":{"kind":"solution","title":"Recommendation: Size the pool correctly","detail":"The pool should be neither too small (requests wait) nor too large. Too many connections create CPU context switching and lock contention and reduce throughput. The size is not a number to grow indefinitely, but a small number based on cores and I/O.","steps":["Starting formula: A common starting point is pool size ≈ (core count × 2) + effective spindle count; on SSD/many cores, around 'cores × 2-4' is a good start.","Why keep it small: The DB's real parallelism is bounded by the core count; extra connections just queue up and slow everything down through context switching/lock contention.","Rule: With hundreds of concurrent clients, the solution is not to enlarge the pool but to turn over a small pool quickly (short transactions + queuing).","Total limit: (application instance count × pool size) < DB max_connections; leave headroom for superuser and maintenance connections.","Caution: Start with the formula but always measure; tune using wait time, active connections, and queue depth metrics."],"related":["connection-storm"]},"sol_session_mode":{"kind":"solution","title":"Recommendation: Session pool mode","detail":"In session mode, a client connection is pinned to a backend server connection until its session ends. Session-level state (SET, temporary tables, advisory locks, session prepared statements) is preserved, but connection sharing is minimal.","steps":["Why: It is the only safe mode for applications that need state preserved throughout the session; behavior is the same as connecting directly to the DB.","When: Applications using SET session variables, temporary tables, LISTEN/NOTIFY, session-level prepared statements, or advisory locks.","Example: PgBouncer pool_mode=session; in ProxySQL, routing session-sensitive traffic separately.","Caution: Connection sharing is low, so the benefit shrinks with many clients; where possible, aim to make the application free of session state and move to transaction mode.","Engine differences: PgBouncer for PostgreSQL; on the MySQL side, ProxySQL handles similar behavior differently, and session variables need care."],"related":["connection-storm"]},"sol_txn_mode":{"kind":"solution","title":"Recommendation: Transaction pool mode","detail":"In transaction mode, a server connection is lent to the client only for the duration of a transaction and returns to the pool when the transaction ends. Thus thousands of clients can work with very few real connections on the DB; it is the most common high-scale mode.","steps":["Why: Because connections are handed over per transaction, sharing is highest; you carry many clients with few DB connections.","When: Short, independent transactions; microservice/multi-pod architectures that keep no session state (or are made not to).","Example: PgBouncer pool_mode=transaction (the most common setup), ProxySQL's default multiplexing behavior.","Caution: Features spanning the session break: session-level prepared statements, temporary tables, SET, and advisory locks are not safe. For protocol-level named prepared statements, verify application/pooler compatibility.","Statement mode: The even more aggressive (statement) mode hands over on every statement; it is considered only for very specific, transactionless read-only scenarios and is very restrictive."],"related":["connection-storm"]},"sol_serverless":{"kind":"solution","title":"Recommendation: An external pooler is mandatory for serverless","detail":"Serverless/Lambda functions scale rapidly and each instance opens its own connection; an in-application pool does not help here because instances are short-lived and share nothing. When hundreds/thousands of instances spring up suddenly, the DB connection limit is blown. The solution is an external connection pool in front of the DB.","steps":["Why: In short-lived, non-shared function instances an in-application pool cannot persist; a connection storm occurs at scale-out.","When: Environments that spawn an instance per request, such as AWS Lambda, Cloud Functions, Azure Functions.","Example: PgBouncer/ProxySQL as a separate tier, or a managed pooler (e.g. AWS RDS Proxy, Data API/external pooler services for PostgreSQL).","Mode: Transaction pool mode is usually preferred; make the function code free of session state.","Caution: Also set a per-function concurrency limit (reserved concurrency); size the pooler and DB max_connections together, otherwise the pooler itself becomes the bottleneck."],"related":["connection-storm"]}}},"solver/decide-cache.json":{"id":"decide-cache","title":"Decision: which caching strategy?","icon":"🧭","category":"Decision Guide","intro":"A cache keeps frequently read data in a fast layer in front of the database (e.g. Redis), lowering latency and DB load. But not all data is suitable for caching, and wrong invalidation creates a risk of stale/incorrect data. This tree first checks whether you really need a cache, then helps you choose between cache-aside, read-through, write-through and write-behind based on the read/write pattern, and covers preventing stampedes.","start":"root","nodes":{"root":{"kind":"step","title":"What does your workload look like: read-heavy or write-heavy?","detail":"A cache pays off most when the same data is read many times (read-heavy) and does not change often. First establish the overall pattern: is the data read a lot and changed rarely, is it updated constantly, or are you unsure whether adding a cache is worth it?","choices":[{"label":"Very read-heavy, data changes relatively rarely","next":"read_pattern"},{"label":"Writes are heavy too; written data is read immediately/often","next":"write_pattern"},{"label":"Not sure; do I really need a cache?","next":"sol_no_cache"}]},"read_pattern":{"kind":"step","title":"Who should be responsible for populating the cache?","detail":"In a read-heavy scenario the key choice is who populates the cache. Should your application manage the cache explicitly and fall back to the DB itself (high flexibility), or should the cache layer fetch the data from the backing DB on a miss and return it transparently?","choices":[{"label":"Let the application manage it explicitly; I want flexibility and control","next":"sol_cache_aside"},{"label":"Let the cache layer fetch from the DB on a miss (transparent)","next":"sol_read_through"}]},"write_pattern":{"kind":"step","title":"On writes, is consistency or write speed the priority?","detail":"If writes are heavy, you must decide how consistent the written data will be between the cache and the DB. Should the cache and DB be updated together on every write to guarantee consistency (somewhat slower writes), or should writes go to the cache first and be applied to the DB asynchronously in the background (very fast writes but a risk of loss)?","choices":[{"label":"Consistency matters; reads must always be current","next":"sol_write_through"},{"label":"Write speed is critical; slightly delayed consistency and risk of loss are acceptable","next":"sol_write_behind"}]},"sol_no_cache":{"kind":"solution","title":"Recommendation: do not add a cache for now","detail":"A cache is not free: it introduces a consistency problem (stale data), extra infrastructure and invalidation complexity. If the query is already fast or the data changes on every request, a cache adds no benefit, only failure surface.","steps":["Why: a cache adds consistency, invalidation and operational overhead. Without a payoff it is a net loss.","When it is not needed: the query is already fast thanks to an index, the data differs on every read/is user-specific and not reused, or writes far outnumber reads.","Instead: first index/query optimization, a materialized view or the DB's own buffer/plan cache.","Caution: do not add a cache 'just in case'; do not add one without a measured read bottleneck and reused data.","Next: once a real read hot spot is proven, start with cache-aside."],"related":["slow-query","high-cpu"]},"sol_cache_aside":{"kind":"solution","title":"Recommendation: cache-aside (lazy loading)","detail":"With cache-aside the application checks the cache first; if the data is absent (miss) it reads from the DB, writes it to the cache and returns it. It is the most common and flexible pattern; the cache fills only with data that is actually requested.","steps":["Why: simple, flexible and resilient; even if the cache fails, the application keeps working by falling back to the DB. Only requested data is cached.","When: general-purpose read acceleration; many reads, moderate writes; the classic use with Redis/Memcached.","Invalidation: on write, update or delete the related key (write-invalidate); bound staleness by giving every key a reasonable TTL.","Example: Redis GET → if absent, DB query → SET key value EX <ttl>.","Caution: the first request is always a miss (cold start). If the same key is requested by many clients at once, a cache stampede can occur — see stampede prevention (next item).","Stampede: when a popular key expires, thousands of requests hit the DB at once; prevent it with a lock/single-flight, early refresh or TTLs with jitter."],"related":["high-cpu","slow-query"]},"sol_read_through":{"kind":"solution","title":"Recommendation: read-through cache","detail":"With read-through the application talks only to the cache; on a miss, the cache layer itself fetches the data from the DB and populates it (through a provider/loader). Application code becomes simpler and the population logic lives in one place.","steps":["Why: population logic is centralized in the cache layer; application code is freed from repeating 'handle the miss everywhere'.","When: read-heavy workloads where you can use a consistent cache access layer (library/service).","Example: a cache library/client (e.g. a local cache with a defined loader or an access layer in front of Redis) loads from the DB on a miss.","Invalidation: TTLs and invalidation on write are still needed; read-through alone does not guarantee freshness.","Caution: very similar to cache-aside; the difference is 'who populates it'. The stampede risk exists here too; apply the same countermeasures.","Engine note: Redis does not do read-through by itself; the application/library layer provides this pattern."],"related":["slow-query"]},"sol_write_through":{"kind":"solution","title":"Recommendation: write-through cache","detail":"With write-through every write goes to both the cache and the DB (synchronously); both are updated together. The cache therefore never falls behind the DB and reads always see current data.","steps":["Why: the cache and DB stay consistent; if written data is read right away, there is no risk of stale reads.","When: scenarios where written data is read immediately and often, and strong read consistency is required.","Example: on write, the application updates the DB first and then immediately sets the cache key to the new value (or deletes it).","Caution: every write goes to two targets, so write latency increases; data that will never be read may also be cached needlessly. The two writes are not atomic (one succeeds, the other fails), which can cause inconsistency — on error, delete the cache entry.","Dual-write trap: instead of writing the DB and the cache separately, the 'write the DB, delete (invalidate) the cache' approach is usually safer."],"related":[]},"sol_write_behind":{"kind":"solution","title":"Recommendation: write-behind (write-back) cache","detail":"With write-behind the write goes to the cache first and returns to the user immediately; the DB write happens in the background, asynchronously (batched/delayed). Write speed is the highest, but it carries durability and consistency risks.","steps":["Why: write latency drops sharply and writes can be batched and transferred to the DB efficiently; the DB is relieved under write-heavy load.","When: very high write volumes such as counters, metrics or activity feeds where slightly delayed consistency is acceptable.","Example: write to Redis → a queue/background worker flushes to the DB in batches at intervals.","Caution: if the cache node crashes before writing to the DB, the data is LOST; do not use it for critical data that requires durability, or protect it with a durable queue/AOF.","Caution: the DB stays stale for a while; other systems reading the DB directly see outdated data. Accept the consistency delay and the loss window deliberately."],"related":[]}}},"solver/decide-datatype.json":{"id":"decide-datatype","title":"Decision: choose the right data type","icon":"🧭","category":"Decision Guide","intro":"Choosing a data type is one of the most expensive decisions to fix later: the wrong type produces wrong results (e.g. float for money), wasted space and slow queries. This tree guides you to the right type for numeric, text, date/time, key (PK), semi-structured (JSON) and category (ENUM) fields, and highlights classic pitfalls such as money and character sets.","start":"root","nodes":{"root":{"kind":"step","title":"What kind of data are you storing?","detail":"The right type is determined by the nature of the data. Which category does the field fall into: a number, text, a date/time, the table's primary key, flexible/semi-structured (JSON) data, or a limited set of categories?","choices":[{"label":"Numeric value (quantity, money, measurement, counter)","next":"numeric"},{"label":"Text (name, description, code)","next":"sol_text"},{"label":"Date and/or time","next":"sol_datetime"},{"label":"I am designing a primary key (PK)","next":"sol_pk"},{"label":"Flexible/nested/variable fields (semi-structured data)","next":"sol_json"},{"label":"A limited, known set of categories (status, type, priority)","next":"sol_enum"}]},"numeric":{"kind":"step","title":"Is the number an integer or a decimal — and is it money?","detail":"For numerics, the most critical distinction is exactness. Is the value an integer (quantity, ID, counter) or a decimal? If decimal, is it MONEY/a financial amount? Binary floating point (FLOAT/DOUBLE) is never used for money or anywhere exactness is required, because it produces rounding errors.","choices":[{"label":"Integer (quantity, counter, id)","next":"sol_integer"},{"label":"Money or exact decimal (price, balance, rate)","next":"sol_decimal"}]},"sol_integer":{"kind":"solution","title":"Recommendation: integer (SMALLINT / INT / BIGINT)","detail":"For integers, pick the smallest type that covers the required range. An unnecessarily large type wastes space and index size; but for fields that can grow, such as counters/PKs, sufficient width is essential to prevent overflow.","steps":["Why: the right width optimizes both storage and the index/memory footprint; arithmetic is fast.","When SMALLINT/INT: for a limited range (age, quantity, small counter), SMALLINT/INT is enough.","When BIGINT: choose BIGINT for ever-increasing IDs/counters and PKs whose row count may reach billions — INT overflow is a serious cause of production outages.","Engine differences: in MySQL INT UNSIGNED doubles the range; PostgreSQL has no UNSIGNED, move to BIGINT. Oracle NUMBER(p), MSSQL INT/BIGINT.","Caution: do not put BIGINT everywhere 'in case it grows'; but for an increasing PK, BIGINT from the start is cheap insurance."],"related":[]},"sol_decimal":{"kind":"solution","title":"Recommendation: NUMERIC / DECIMAL — NEVER FLOAT for money","detail":"For money and exact decimals use fixed-point (exact) NUMERIC/DECIMAL. FLOAT/DOUBLE are binary floating point and cannot store values like 0.1 exactly; addition/multiplication produces cent drift, wrong balances and audit problems.","steps":["Why: NUMERIC/DECIMAL store values exactly in decimal; money arithmetic gives correct results without rounding errors.","When: every field that requires exactness, such as price, balance, tax, rate or interest. Specify an appropriate scale: DECIMAL(precision, scale), e.g. DECIMAL(19,4) for an amount.","Alternative: some systems store money in the smallest unit (cents) as BIGINT; this also gives full exactness.","Engine differences: PostgreSQL NUMERIC, MySQL DECIMAL, Oracle NUMBER(p,s), MSSQL DECIMAL/MONEY (mind MONEY's limitations).","Caution: FLOAT/DOUBLE are only for scientific/measured approximate values and cases where performance outweighs exactness — never for money."],"related":[]},"sol_text":{"kind":"solution","title":"Recommendation: text — VARCHAR vs TEXT vs CHAR","detail":"For text, VARCHAR/TEXT is used for variable length and CHAR for truly fixed length. In modern engines the storage difference between VARCHAR and TEXT is mostly negligible; the real decision is whether to impose a semantic limit (a length constraint).","steps":["VARCHAR(n): if the length is variable and there is a sensible upper limit (e.g. email, name), use VARCHAR(n) — the limit is a data validation tool.","TEXT: for long free text of unpredictable length (description, body), use TEXT.","CHAR(n): use only for truly fixed lengths (e.g. country code 'TR', always 2 characters); for short text CHAR pads with trailing spaces and is usually unnecessary.","Engine differences: in PostgreSQL VARCHAR and TEXT perform the same, the limit is only a constraint. In MySQL VARCHAR is stored in-row, TEXT may be stored separately, and indexing has a prefix limit. In Oracle use VARCHAR2.","Charset/collation: for emoji and full Unicode, always use utf8mb4 in MySQL (the old 'utf8' is 3 bytes and breaks emoji); choose the collation (e.g. utf8mb4_0900_ai_ci) according to sorting/comparison needs. In PostgreSQL, typically a UTF-8 database + an appropriate collation."],"related":[]},"sol_datetime":{"kind":"solution","title":"Recommendation: date/time — TIMESTAMP with time zone","detail":"If you store an instant (an event time), choose a time-zone-aware type. Storing without a time zone leads to wrong calculations and comparisons across regions/daylight saving changes.","steps":["Why: a time-zone-aware type normalizes the instant to UTC and provides consistent comparison and conversion; essential for global systems.","When TIMESTAMPTZ: for event/transaction instants in PostgreSQL, use TIMESTAMP WITH TIME ZONE (timestamptz); store UTC and convert to the user's time zone for display.","Date or time only: DATE for a calendar day, TIME for a time unrelated to time zones; INTERVAL for durations (in engines that support it).","Engine differences: PostgreSQL timestamptz (actually stores UTC). In MySQL, TIMESTAMP is converted according to the time zone but its range is limited (2038); DATETIME is not converted — enforce a store-in-UTC discipline in the application. Oracle TIMESTAMP WITH TIME ZONE, MSSQL DATETIMEOFFSET.","Caution: storing time as text (VARCHAR) or as an epoch INT breaks sorting, range and time zone operations; use a real date/time type."],"related":[]},"sol_pk":{"kind":"solution","title":"Recommendation: primary key — BIGINT vs UUID","detail":"There are two main options for a PK: an increasing number (BIGINT identity/sequence) or a UUID. A numeric PK is small and index-friendly but predictable and requires central generation; a UUID can be generated in a distributed way but is larger and its random order can hurt the index.","steps":["BIGINT (increasing): the smallest, fastest index; sequential inserts are ideal for a B-tree. The default choice for a single DB/central generation; BIGINT instead of INT from the start is safe against overflow.","UUID: can be generated on clients/distributed systems without collisions and does not reveal a sequence number when the ID is exposed. But it takes 16 bytes (2×BIGINT).","UUID order matters: fully random UUIDs (v4) are not sequential, so they cause scattered inserts into the index (page splits, low cache hit ratio); time-ordered UUIDs (v7/ULID) largely eliminate this.","Engine differences: PostgreSQL has a native uuid type; in MySQL store the UUID as BINARY(16) (instead of text CHAR(36)) and generate it in order where possible. MSSQL uniqueidentifier + NEWSEQUENTIALID, Oracle RAW(16).","Caution: storing a UUID as VARCHAR/CHAR(36) text roughly doubles or triples space and index size; store it as binary."],"related":[]},"sol_json":{"kind":"solution","title":"Recommendation: JSON / JSONB — use in moderation","detail":"The JSON type is powerful for fields with a flexible schema, nested or sparse fields; but relational discipline (constraints, indexes, type safety) can be lost. Rule: move fixed and frequently queried fields into normal columns, keep only the truly variable part in JSON.","steps":["Why: stores variable/optional fields flexibly in a single column; fields can be added without a schema migration.","When: unpredictable structures such as external API payloads, user-defined fields or product attributes.","Engine differences: in PostgreSQL use JSONB (binary, indexable, @>/? queries with GIN) — prefer it over plain JSON. MySQL's JSON type is also binary and is queried with a generated column + index. Oracle 21c+ has a JSON type/OSON; MSSQL before 2025 keeps JSON in NVARCHAR and processes it with functions; SQL Server 2025 and Azure SQL have a native (binary) json type.","Caution: index frequently filtered JSON fields (PostgreSQL GIN, MySQL generated column index); otherwise every query scans the whole document.","Caution: do not stuff everything into JSON — relational integrity, foreign keys and typing are lost. Fields that need queries/constraints should be real columns."],"related":["decide-sqlnosql"]},"sol_enum":{"kind":"solution","title":"Recommendation: ENUM vs lookup (reference) table","detail":"There are two options for limited category sets: the engine's ENUM type, or a lookup table + foreign key. ENUM is compact and simple, but adding a value requires a schema change; a lookup table is flexible and can carry extra attributes (description, active flag, order).","steps":["When ENUM: if the values are very stable and rarely change (e.g. gender, weekday) and no extra attributes are needed; compact and readable.","When a lookup table: if the values may change over time, extra fields such as translations/description/order/active flag are needed, or reports join on it — it is more flexible.","Engine differences: PostgreSQL and MySQL offer a native ENUM type (in PostgreSQL adding a value to an ENUM requires ALTER TYPE, and ordering follows the definition order). Oracle and MSSQL have no built-in ENUM; use a CHECK constraint or a lookup table.","Caution: adding/removing a value in an ENUM is a schema migration and can cause locking/cost on a large table; prefer a lookup table for frequently changing sets.","Alternative: free text + a CHECK constraint is also a practical middle ground for small, stable sets."],"related":[]}}},"solver/decide-capacity.json":{"id":"decide-capacity","title":"Decision: Server sizing","icon":"🧭","category":"Decision Guide","intro":"Correct sizing starts from the workload profile (OLTP, data warehouse, mixed) and requires finding the real bottleneck (CPU, I/O or RAM). Golden rule: the frequently accessed data set (working set) must fit in RAM; measure first, then scale, and leave headroom for sudden load. This tree helps you identify the workload and choose the resource and scaling direction (vertical/horizontal) based on the bottleneck.","start":"root","nodes":{"root":{"kind":"step","title":"What is your workload profile?","detail":"Sizing starts with the workload, because OLTP and analytical loads require completely different resource profiles. What is the system's predominant work: many small, fast transactions (OLTP), reporting/analytics with large scans and aggregations (DWH), or a mix of both? If you are not sure, you need to measure first.","choices":[{"label":"OLTP — many small, short transactions (web/application)","next":"bottleneck"},{"label":"Analytics / data warehouse (DWH) — large scans and aggregations","next":"sol_dwh"},{"label":"Mixed workload, or I am not sure","next":"sol_measure"},{"label":"I know the bottleneck; should I scale vertically or horizontally?","next":"sol_scale"}]},"bottleneck":{"kind":"step","title":"Where is the bottleneck: RAM, I/O or CPU?","detail":"In OLTP, find the real bottleneck instead of blindly adding resources. Look at your metrics: does the frequently accessed data not fit in RAM with high disk reads (RAM/I/O), is disk latency/IOPS insufficient (I/O), or is the data already in memory but the CPU saturated (CPU)? Scaling the wrong resource wastes money and time.","choices":[{"label":"Frequently accessed data does not fit in RAM; disk reads are high","next":"sol_ram"},{"label":"Data is in memory, but disk latency/IOPS is insufficient (write-heavy)","next":"sol_io"},{"label":"Data is in memory and the disk is fine, but CPU is constantly saturated","next":"sol_cpu"}]},"sol_measure":{"kind":"solution","title":"Recommendation: Measure first, then size","detail":"Buying hardware based on guesses almost always produces the wrong size. The workload profile and the bottleneck emerge from real metrics. Sizing is not a guess but a measurement-driven iteration.","steps":["Why: Whether it is OLTP or DWH, and whether the bottleneck is CPU, I/O or RAM, only becomes clear when measured under real load; guessing is an expensive mistake.","Measure: CPU usage and run queue, memory/cache hit ratio (buffer cache hit), disk IOPS and latency, active connections and wait events.","Engine tools: PostgreSQL pg_stat_* and wait events, Oracle AWR/ASH, MySQL Performance Schema, MSSQL DMVs and wait stats.","Approach: Test with a representative load, find the tightest resource, scale only that, and measure again. Change one resource at a time so you can isolate the effect.","Next: Once the profile is clear, move on to the relevant decision (OLTP bottleneck, DWH or horizontal/vertical scaling)."],"related":["high-cpu","memory-pressure","slow-query"]},"sol_ram":{"kind":"solution","title":"Recommendation: Add RAM — the working set must fit in memory","detail":"The most critical OLTP rule: frequently accessed data and indexes (the working set) must fit in memory (buffer/cache). If they do not, every query goes to disk, latency explodes and I/O becomes the bottleneck. RAM is usually the highest-return investment.","steps":["Why: Reading from memory is hundreds to thousands of times faster than from disk; when the working set fits in RAM, the cache hit ratio rises and disk I/O collapses.","When: A low cache hit ratio, frequent 'disk read' wait events, an active data set larger than the available RAM.","How: Size the DB memory area correctly — PostgreSQL shared_buffers + OS page cache, MySQL innodb_buffer_pool_size (usually most of the RAM), Oracle SGA/buffer cache, MSSQL max server memory.","Target: Ideally the working set + indexes fit in memory; if not all of it fits, at least the most frequently accessed 'hot' part must.","Caution: Do not give all physical RAM to the buffer pool; leave room for the OS, per-connection memory and other processes, otherwise swapping slows everything down."],"related":["memory-pressure","slow-query"]},"sol_io":{"kind":"solution","title":"Recommendation: Strengthen the I/O layer (IOPS/latency)","detail":"Even when data is in memory, disk speed is decisive under a write-heavy load (commit, WAL/redo/binlog, checkpoint). The issue here is not capacity but IOPS and latency. The wrong disk class brings the system to its knees despite fast CPU/RAM.","steps":["Why: For durability, every commit writes the log to disk; a low-IOPS/high-latency disk directly limits transaction throughput.","When: High disk latency, long 'log/WAL write' waits, a bottleneck during checkpoints, write-intensive OLTP.","How: Use NVMe/SSD; move the transaction log to a fast disk separate from the data files; in the cloud, choose a disk class with sufficient provisioned IOPS/throughput (e.g. provisioned IOPS).","Target: Set both IOPS and latency targets — not only IOPS, p99 latency matters too. RAID/write cache (BBU) can reduce write latency.","Caution: In the cloud, disk throughput and IOPS often also depend on the instance size; latency on network-attached disks is higher than on local NVMe."],"related":["disk-full","slow-query"]},"sol_cpu":{"kind":"solution","title":"Recommendation: Resolve the CPU bottleneck (queries first, then cores)","detail":"If the CPU is saturated while data is in memory and the disk is fine, the root cause is usually inefficient queries (bad plans, missing indexes, many small queries) or excessive concurrency. Reducing the load before adding cores is usually cheaper and more effective.","steps":["Why: CPU is consumed most by full scans, sorts/hashes, plan compilation and the context switching of too many connections. Fixing these is cheaper than cores.","Optimize first: Find the most expensive queries (pg_stat_statements / AWR / Performance Schema), add missing indexes, reduce N+1 queries, shrink the connection pool.","Then scale: If the queries are healthy and CPU is still saturated, increase the core count (vertical) — especially for highly concurrent OLTP.","Engine note: More connections = more context switching; a small pool with fast turnover often beats more cores (see the connection pool decision).","Caution: If licensing is per core (Oracle/MSSQL), adding cores is expensive; query optimization first lowers the cost."],"related":["high-cpu","slow-query"]},"sol_dwh":{"kind":"solution","title":"Recommendation: Analytics/DWH — throughput, parallelism and horizontal scale","detail":"A data warehouse workload consists of a small number of heavy queries that scan and aggregate large tables. The priority here is not low latency but high scan throughput, parallelism and usually a horizontally scalable architecture.","steps":["Why: Queries scan terabytes; disk/memory bandwidth and cores running in parallel are decisive, not the latency of individual transactions.","How: Storage with high I/O throughput, plenty of RAM (for aggregation/hash joins), many cores and parallel query; a columnar store or MPP architecture is very efficient at large scale.","Horizontal scale: For a large DWH, distributed/MPP (e.g. sharded analytical engines) usually scales better than growing a single machine; partitioning + partition pruning shrinks scans.","Engine note: Do not carry OLTP settings over — for DWH, larger work_mem/sort areas, a higher degree of parallelism and large blocks/readahead are preferred.","Caution: Do not mix OLTP and DWH on the same server; heavy reports choke OLTP. If possible, move analytics to a separate replica/data warehouse."],"related":["decide-shard-replica","decide-partition"]},"sol_scale":{"kind":"solution","title":"General principle: Vertical vs horizontal + headroom + measure first","detail":"How you scale matters as much as which resource you scale. Vertical (a bigger machine) is simple but has a ceiling and a single point of failure; horizontal (more machines) scales but adds complexity. In every case, leave headroom for sudden load and validate the decision with measurements.","steps":["Vertical (scale-up): Increase resources (CPU/RAM/IO) on a single machine — the simplest option and the first step for most OLTP; but there is a physical/cost ceiling and a single point of failure.","Horizontal (scale-out): Add read replicas (for read load) or sharding (for write/data volume) — near-unlimited scale, but it brings consistency and operational complexity.","Headroom: Do not plan resources for 100%; leave ~30% headroom at normal peak load so sudden spikes, maintenance and growth fit.","Measure first: Before scaling, confirm the bottleneck by measurement, target the tightest resource, and measure again after the change.","Caution: Horizontal scaling does not solve every problem — distributed transactions and joins are expensive; switch only once vertical scaling and optimization are exhausted."],"related":["decide-shard-replica","decide-ha"]}}},"solver/slow-write.json":{"id":"slow-write","title":"Slow writes (INSERT/UPDATE latency)","icon":"🐢","category":"Performance","intro":"INSERT/UPDATE/DELETE operations are slower than expected. First we determine what the write is waiting on (commit/redo, locks, disk or per-row overhead), then drill down to the root cause step by step.","start":"root","nodes":{"root":{"kind":"step","title":"What is the write waiting on?","detail":"Classify the bottleneck by looking at wait events: is it dominated by commit/redo (WAL) synchronization, locks, disk write latency, or per-row index/trigger overhead?","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":"Waiting on commit / redo (WAL sync)","next":"commit"},{"label":"Waiting on a lock","next":"lock"},{"label":"Suspected per-row overhead (indexes/triggers)","next":"overhead"},{"label":"Disk write latency is high","next":"sol_disk"}]},"commit":{"kind":"step","title":"Commit pattern or fsync?","detail":"A separate commit per row (autocommit) triggers redo/log synchronization as many times as there are rows. Conversely, if the commit count is reasonable but each commit waits on fsync, the problem is log disk latency.","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 / a commit on every row","next":"sol_batch"},{"label":"Commit waits on log synchronization (fsync)","next":"sol_disk"}]},"lock":{"kind":"step","title":"Who is holding the lock?","detail":"If the write is waiting on a lock: another session may be holding the same row/index, or many sessions may be trying to write to the same 'hot' row.","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":"Another session is holding the lock","next":"sol_lock"},{"label":"Many sessions write to the same hot row","next":"sol_hotrow"}]},"overhead":{"kind":"step","title":"Where does the per-row overhead come from?","detail":"Every INSERT/UPDATE also updates all indexes on the table and fires any triggers/constraints. An excessive number of indexes or a heavy trigger slows writes significantly.","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":"The table has too many / unnecessary indexes","next":"sol_index"},{"label":"There is a heavy trigger or constraint cost","next":"sol_trigger"}]},"sol_batch":{"kind":"solution","title":"Root cause: autocommit / commit per row","detail":"A separate commit for each row repeats redo/log synchronization as many times as there are rows. By grouping operations you can dramatically reduce the number of commits (and therefore fsyncs).","steps":["Disable autocommit in the application and apply batch commits, committing every N rows (e.g. 500-5000).","Use multi-row INSERT / bulk-load paths instead of single-row INSERTs (Postgres COPY, Oracle bulk insert, MySQL LOAD DATA, MSSQL BULK INSERT).","Use prepared statements to reduce the cost of repeated parsing (hard parse).","After the change, verify that the 'log file sync' / WRITELOG wait has dropped."],"related":["active-sessions"]},"sol_disk":{"kind":"solution","title":"Root cause: slow redo/log disk (fsync latency)","detail":"Every commit waits until the redo/WAL record is written to durable storage (fsync). If the log disk is slow (high write latency, insufficient IOPS), every commit pays that latency.","steps":["Move the log/redo files to a dedicated low-latency disk (preferably NVMe SSD); separate them physically from the datafiles.","Measure storage latency (file I/O statistics); if ms per write is high, review the hardware/RAID write cache.","In virtual/cloud environments, raise the provisioned IOPS and throughput limits.","If durability requirements allow, consider group commit / commit delay settings (e.g. Postgres commit_delay); but weigh the risk of data loss."],"related":[]},"sol_lock":{"kind":"solution","title":"Root cause: another session is holding the lock","detail":"The write is waiting for a row/index lock held by another session. The blocker is usually a long-running transaction or one whose commit was forgotten.","steps":["Identify the blocker session and examine what it is doing (a long batch, or idle in transaction?).","If the blocker stays open/long for no reason, fix the commit/rollback pattern in the application.","In an emergency, terminate the blocker in a controlled way (kill session / pg_terminate_backend / KILL).","Keep transactions short; do not wait for user interaction inside a transaction."],"related":["blocking-sessions","current-locks"]},"sol_hotrow":{"kind":"solution","title":"Root cause: hot row contention","detail":"Many sessions try to write to the same row (e.g. a counter, sequence number, balance); they all queue for the same lock and writes are serialized.","steps":["For the hot counter pattern, split the value into parallel buckets (sharded counter) and sum them on read.","Move sequence/ID generation from a central single row to a sequence/identity mechanism.","Reduce update frequency: buffer running totals in the application and write them periodically.","Reduce contention by standardizing operation order and shortening lock duration."],"related":["current-locks"]},"sol_index":{"kind":"solution","title":"Root cause: excessive index overhead","detail":"Every index on the table is updated on every write. Many or unused indexes slow INSERT/UPDATE directly.","steps":["Identify unused/overlapping indexes (usage statistics) and drop them safely.","Simplify wide composite indexes; merge indexes that repeat the same leading columns.","For large bulk loads, consider temporarily disabling indexes and rebuilding them after the load.","After the change, measure write latency again and confirm the improvement."],"related":["slow-queries"]},"sol_trigger":{"kind":"solution","title":"Root cause: heavy trigger / constraint","detail":"A trigger that fires per write or a constraint being validated (especially a foreign key check without an index) adds extra cost to every row.","steps":["Make heavy work in the trigger body (e.g. synchronous log writes) asynchronous/queue-based.","Index the referencing (child) foreign key columns; otherwise every DELETE/UPDATE on the parent scans the child table in full.","Where possible, move row-level triggers to statement-level.","Move unnecessary trigger logic, or logic that has become business rules, to the application layer."],"related":[]}}},"solver/io-bottleneck.json":{"id":"io-bottleneck","title":"High IO wait / disk latency","icon":"💽","category":"Performance","intro":"The system is waiting on disk IO. First we separate whether the load is read- or write-heavy, then drill down to the causes: insufficient buffer cache, full table scans, checkpoint storms, IOPS limits or spills to temporary space.","start":"root","nodes":{"root":{"kind":"step","title":"Is the IO read-heavy or write-heavy?","detail":"Use file IO statistics to determine whether the wait is on the read side (data being read from disk) or on the write side (redo/checkpoint/data writes). This splits the tree in two.","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":"Mostly read IO","next":"read"},{"label":"Mostly write IO","next":"write"}]},"read":{"kind":"step","title":"Why is read IO high?","detail":"Excessive physical reads come from three main causes: the buffer cache is too small (the working set does not fit in RAM), queries perform full table scans, or large sort/hash operations spill to temporary space.","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":"Buffer cache hit is low, the working set does not fit","next":"sol_cache"},{"label":"Queries perform full table scans","next":"sol_scan"},{"label":"Large sorts/hashes spill to temporary space","next":"sol_spill"}]},"write":{"kind":"step","title":"Why is write IO high?","detail":"A write-side bottleneck comes either from periodic checkpoints/flushes briefly saturating the disk (checkpoint storm) or from the storage lacking the IOPS/throughput to sustain the load.","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":"Sudden checkpoint/flush waves (checkpoint storm)","next":"sol_checkpoint"},{"label":"Storage cannot keep up with sustained load (IOPS limit)","next":"sol_iops"}]},"sol_cache":{"kind":"solution","title":"Root cause: insufficient buffer cache","detail":"The frequently accessed data set (working set) does not fit in the buffer cache, so blocks are constantly re-read from disk. The hit ratio is low and physical reads are high.","steps":["Increase the buffer/cache size: Oracle db_cache_size/SGA, Postgres shared_buffers, MySQL innodb_buffer_pool_size, MSSQL max server memory.","Compare the hit ratio and physical reads before and after the change.","If RAM is physically insufficient, add server memory or reduce the working set with archiving/partitioning.","If the real bottleneck comes from a single scan pattern, switch to the scan branch of the cache-hit tree."],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"Root cause: full table scans","detail":"Queries scan large tables end to end because there is no suitable index; this both causes many physical reads and pollutes the cache with useless blocks.","steps":["Get the plans (EXPLAIN) of the queries with the most physical reads and find the full scans.","Add suitable indexes on filter and join columns; verify that the plan switches to index access.","For very large tables, reduce the data scanned with date/range-based partitioning.","Select only the required columns instead of an unnecessary 'SELECT *' to reduce the number of blocks read."],"related":["slow-queries"]},"sol_spill":{"kind":"solution","title":"Root cause: sorts/hashes spilling to temporary space","detail":"Large sort, hash join or group by operations do not fit in working memory and write to temporary space (temp/tempdb), generating heavy read/write IO.","steps":["Raise the working memory setting: Oracle pga_aggregate_target, Postgres work_mem, MySQL sort_buffer_size/join_buffer_size, MSSQL memory grant.","Reduce the operation volume: apply filters early, remove unnecessary sorts, improve the join order.","Prevent spills to disk by adding suitable indexes for frequently used sorting/grouping.","Move temporary space to a fast disk; if the problem persists, dig deeper with the undo/temp pressure tree."],"related":["slow-queries"]},"sol_checkpoint":{"kind":"solution","title":"Root cause: checkpoint storm","detail":"Dirty pages accumulate for a long time and are written to disk in bulk at short intervals; these sudden write waves periodically saturate IO.","steps":["Spread checkpoints over time: in Postgres increase checkpoint_completion_target and balance checkpoint_timeout.","In MySQL, set innodb_io_capacity / innodb_io_capacity_max according to the storage and tune adaptive flushing.","In MSSQL, flatten sudden waves with indirect checkpoints (target_recovery_time).","In Oracle, balance checkpoint frequency with fast_start_mttr_target and enlarge the redo log files."],"related":[]},"sol_iops":{"kind":"solution","title":"Root cause: insufficient IOPS / throughput","detail":"The storage lacks the IOPS or bandwidth to serve the read+write requests the workload requires; queue depth and latency are persistently high.","steps":["Measure disk latency (ms per request) and compare it with the hardware/cloud IOPS limit.","Move to faster storage (HDD→SSD/NVMe) or raise the provisioned IOPS/throughput tier in the cloud.","Reduce contention by spreading data, redo/log and temporary space across different disks.","As a lasting fix, reduce total IO demand (indexing, archiving, query optimization)."],"related":[]}}},"solver/undo-temp-pressure.json":{"id":"undo-temp-pressure","title":"Undo / TempDB / temporary space pressure","icon":"🧯","category":"Capacity","intro":"Undo/rollback space, temporary (temp) space, or TempDB is under pressure. We determine where the pressure is and drill down to the root cause, such as a large sort/hash, a long query/transaction, or a space leak.","start":"root","nodes":{"root":{"kind":"step","title":"Where is the pressure: undo, temp, or tempdb contention?","detail":"Classify the source of pressure by looking at space usage: is the undo/rollback tablespace filling up, the general temporary (temp) space, or, in MSSQL, is it TempDB page allocation latch contention?","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":"Undo/rollback space is filling up (ORA-01555 / ORA-30036)","next":"undo"},{"label":"Temporary (temp) space is filling up (sort/hash spill)","next":"temp"},{"label":"TempDB allocation latch contention (PFS/GAM/SGAM)","next":"tempdb"}]},"undo":{"kind":"step","title":"What is consuming undo?","detail":"Undo pressure is typically one of two kinds: a long QUERY needs old undo for the consistent read as of its start time (ORA-01555), or a long/open TRANSACTION generates so much undo that it fills the space and leads to 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":"A long query needs old undo (ORA-01555)","next":"sol_undo_query"},{"label":"A long/open transaction is accumulating undo (ORA-30036)","next":"sol_undo_tx"}]},"temp":{"kind":"step","title":"What is filling the temporary space?","detail":"Temp pressure comes either from a large sort/hash operation spilling to disk (normal but huge) or from a single session holding temp continuously (open cursor, leak, never-ending query).","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":"A large sort/hash operation is spilling to disk","next":"sol_temp_sort"},{"label":"A single session is holding temp (leak/open cursor)","next":"sol_temp_leak"}]},"tempdb":{"kind":"step","title":"Is the TempDB contention about allocation or volume?","detail":"Two different problems occur in MSSQL TempDB: PAGELATCH contention on PFS/GAM/SGAM pages (many small temporary objects), or a large sort/version store filling TempDB by 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":"PFS/GAM/SGAM latch contention","next":"sol_tempdb_latch"},{"label":"A large sort/version store is filling TempDB","next":"sol_temp_sort"}]},"sol_undo_query":{"kind":"solution","title":"Root cause: Long query + insufficient undo (ORA-01555)","detail":"The old undo that the long query needs for a consistent read was overwritten because retention expired or space was insufficient.","steps":["Raise undo_retention to a value that exceeds the longest query duration (v$undostat.maxquerylen).","Enlarge the UNDO tablespace or enable autoextend so that the retention period is backed by space.","For critical reports, protect old undo with ALTER TABLESPACE undotbs RETENTION GUARANTEE (DML may fail if the space fills up; be careful).","For a permanent fix, optimize the long query or move it to a standby/replica."],"related":["slow-queries"]},"sol_undo_tx":{"kind":"solution","title":"Root cause: A long/open transaction filled undo (ORA-30036)","detail":"A single huge DML or an open transaction with a delayed commit generated too much undo; the UNDO tablespace could not extend and ORA-30036 was raised.","steps":["Identify the active transaction holding the most undo (v$transaction.used_ublk) and find its owner.","Run the single huge DML in date/range chunks with intermediate commits.","Fix sessions with a forgotten commit / idle-in-transaction on the application side; terminate them in a controlled way if necessary.","Temporarily add space to the UNDO tablespace; permanently, plan retention and space size according to the workload."],"related":["active-sessions"]},"sol_temp_sort":{"kind":"solution","title":"Root cause: A large sort/hash is filling temporary space","detail":"Large sort/hash/group by operations that do not fit in memory spill to temporary space and can fill it.","steps":["Increase working memory: Oracle pga_aggregate_target, Postgres work_mem, MySQL sort_buffer_size, MSSQL memory grant.","Improve the query: remove unnecessary sorts, apply filters early, add indexes on sort/group columns.","Split very large jobs into chunks; limit concurrency so they do not all hold temp at the same time.","Enlarge the temporary space and move it to a fast disk; configure MSSQL TempDB with multiple files based on core count."],"related":["slow-queries"]},"sol_temp_leak":{"kind":"solution","title":"Root cause: A session is holding temp space (leak)","detail":"A single session does not release temporary space because of unclosed cursors, a never-ending query, or open temporary tables.","steps":["Identify the session holding the most temp and the SQL it is running.","Make sure cursors/result sets and temporary tables are closed properly in the application.","Terminate the stuck/excessively long session in a controlled way and verify that the space is returned.","Define global temporary tables so that they are cleaned up automatically at session/transaction end."],"related":["active-sessions"]},"sol_tempdb_latch":{"kind":"solution","title":"Root cause: TempDB PFS/GAM/SGAM latch contention","detail":"When many sessions create small temporary objects at the same time, PAGELATCH contention arises on TempDB's allocation map pages (PFS/GAM/SGAM).","steps":["Configure the number of TempDB data files proportional to the CPU core count, with equal sizes (e.g. 4-8 files, all with the same size/autogrowth).","On modern SQL Server, enable options that reduce allocation contention (memory-optimized tempdb metadata).","Reduce temporary table creation: replace unnecessary #temp tables with table variables / a single query.","After the change, verify that PAGELATCH wait times have dropped."],"related":["current-locks"]}}},"solver/long-transaction.json":{"id":"long-transaction","title":"Long-running / open transaction","icon":"⏱️","category":"Locks","intro":"A long-running or open transaction is accumulating locks, undo and bloat. We find the offending transaction, determine its type (idle in transaction, long batch, impact) and resolve it safely.","start":"root","nodes":{"root":{"kind":"step","title":"Which is the oldest open transaction?","detail":"List open transactions by start time. The oldest one is usually the source of the problem. Check its state: is it actively running, or is it 'idle in transaction' waiting for a 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 forgotten, sitting idle)","next":"idle"},{"label":"Active but very long-running batch/DML","next":"batch"},{"label":"Accumulating locks/bloat/undo while the transaction continues","next":"impact"}]},"idle":{"kind":"step","title":"Why does idle in transaction occur?","detail":"The session opened a transaction but sits idle without running a query; that is, the commit/rollback was forgotten. This holds locks and undo unnecessarily. Is an application fix enough, or does it need to be terminated urgently?","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":"The application/connection pool does not commit (permanent fix)","next":"sol_idle"},{"label":"I need to terminate it right now","next":"sol_kill"}]},"batch":{"kind":"step","title":"What is the active long transaction doing?","detail":"The session is actually running (active). Is it a single huge DML/report, or a blocker that is making other sessions wait and must be cut off urgently?","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":"A single huge DML/report; it needs to be split up","next":"sol_batch"},{"label":"It is making others wait; terminate it urgently","next":"sol_kill"}]},"impact":{"kind":"step","title":"What is the long transaction accumulating?","detail":"Determine the side effect of the long transaction: is it mainly a lock backlog making other sessions wait, or bloat from holding old row versions and blocking cleanup (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":"A lock backlog is making other sessions wait","next":"sol_lock_backlog"},{"label":"Old version buildup is blocking cleanup (undo/bloat)","next":"sol_bloat"}]},"sol_idle":{"kind":"solution","title":"Root cause: the application does not commit (idle in transaction)","detail":"The connection pool or application opens a transaction and then keeps the connection idle without committing/rolling back. This holds locks and undo unnecessarily.","steps":["In the application code, guarantee a commit/rollback on every path where a transaction is opened; do not wait for user input or long IO inside a transaction.","Automatically cut off idle transactions: Postgres idle_in_transaction_session_timeout, MySQL connection/timeout settings, application pool maxLifetime.","Configure the connection pool around the 'autocommit on, short transactions' principle.","Set up monitoring: raise an alert for long idle-in-transaction sessions."],"related":["active-sessions","blocking-sessions"]},"sol_kill":{"kind":"solution","title":"Solution: terminate the offending session safely","detail":"If a blocking/long session is making others wait, it must be terminated in a controlled way. Termination triggers a rollback, which may need space/time; be careful.","steps":["First verify what the session is (which user/application, which job) so you do not cut off a critical job.","Terminate: Oracle ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE; Postgres SELECT pg_terminate_backend(pid); MySQL KILL thread_id; MSSQL KILL session_id;","If you kill a large write transaction, wait for the rollback (undo/log reversal) to complete.","After termination, verify that the locks and waiting sessions have cleared; fix the root cause (idle/batch) permanently."],"related":["blocking-sessions"]},"sol_batch":{"kind":"solution","title":"Root cause: a single huge DML/report","detail":"A DML that processes millions of rows in a single transaction, or a very long report, accumulates locks and undo/bloat for its entire duration.","steps":["Split the large DML into chunks by date/range/key and commit after each chunk.","Move the heavy report outside peak hours or offload it to a standby/read replica.","Optimize the long query to shorten its total duration (and therefore the time it holds locks/undo).","Make the batch job observable: log progress and make it resumable from where it left off."],"related":["slow-queries"]},"sol_lock_backlog":{"kind":"solution","title":"Root cause: lock backlog queue","detail":"Because of the locks it holds, the long transaction has created a wait queue behind it; many sessions are waiting for the same resource.","steps":["Identify the session at the head of the lock chain (head blocker); it is the only node that will clear the queue.","Shorten the head blocker (commit/rollback) or terminate it in a controlled way; watch the queue drain.","To shorten lock duration, make transactions smaller and define a consistent access order.","Where appropriate, switch to approaches that need weaker locks (row-level, a suitable isolation level)."],"related":["blocking-sessions","current-locks"]},"sol_bloat":{"kind":"solution","title":"Root cause: old version buildup (undo/bloat)","detail":"The long-running transaction prevents cleanup of the old row versions that predate it. In Postgres this shows up as table/index bloat, in Oracle as undo pressure.","steps":["Find the session holding the oldest xmin/transaction (Postgres backend_xmin, Oracle the oldest active transaction).","Shorten/finish this transaction so cleanup (VACUUM / undo recycling) can progress again.","In Postgres, after the transaction ends, run VACUUM on the bloated tables (VACUUM FULL/pg_repack if needed).","Permanently adopt an application pattern that keeps transactions short, and make autovacuum more aggressive."],"related":[]}}},"solver/plan-regression.json":{"id":"plan-regression","title":"Query plan regressed (suddenly slow)","icon":"📉","category":"Performance","intro":"A query that was fast yesterday is suddenly slow today. Usually the optimizer has switched to a worse plan. We find the root cause among stale statistics, parameter sensitivity, data volume growth or a sudden plan change, and stabilize the plan.","start":"root","nodes":{"root":{"kind":"step","title":"Did the plan really change, and when?","detail":"Are there multiple plans (plan_hash/plan_generation) for the same query, and when did the slowdown start? Does it coincide with a recent deploy, upgrade, parameter change or large data load?","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":"Statistics may be stale","next":"stats"},{"label":"The same query behaves differently depending on the parameter (sniffing/peeking)","next":"sniff"},{"label":"Data volume has grown significantly","next":"sol_datagrowth"},{"label":"The plan changed suddenly after an upgrade/flush","next":"sol_planflip"}]},"stats":{"kind":"step","title":"Are the statistics current?","detail":"The optimizer bases its decisions on statistics. If the last analyze date is old or the modification counter is high, the estimates may really have drifted, which leads to a bad 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":"Statistics are stale/missing","next":"sol_stats"},{"label":"Statistics are current but the plan is still parameter-sensitive","next":"sniff"}]},"sniff":{"kind":"step","title":"Is there parameter sensitivity?","detail":"The query chooses and caches a plan based on the parameter value of its first execution (MSSQL parameter sniffing / Oracle bind peeking). That plan can be bad for other values with a skewed distribution.","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":"Yes, the plan flips between good and bad depending on the parameter","next":"sol_sniff"},{"label":"The right plan is known, I want to pin it permanently","next":"sol_forceplan"}]},"sol_stats":{"kind":"solution","title":"Root cause: stale / missing statistics","detail":"Based on old statistics that do not match reality, the optimizer misestimated the row count and chose a bad plan. This is the most common cause of plan regression.","steps":["Gather fresh statistics for the affected tables and indexes: Oracle DBMS_STATS.GATHER_TABLE_STATS, Postgres ANALYZE, MySQL ANALYZE TABLE, MSSQL UPDATE STATISTICS ... WITH FULLSCAN.","Create histograms/extended statistics on columns with a skewed distribution.","After gathering statistics, verify the plan with EXPLAIN and confirm it has returned to the good plan.","To prevent recurrence, keep automatic statistics collection (autovacuum/auto update stats) enabled with suitable thresholds."],"related":["slow-queries"]},"sol_sniff":{"kind":"solution","title":"Root cause: parameter sensitivity (sniffing / bind peeking)","detail":"The cached plan was chosen for the first parameter value that arrived and is bad for other values with a skewed distribution. The goal is to make sure a plan suited to the value is chosen.","steps":["In MSSQL, define targeted behavior on the critical query with OPTION (RECOMPILE) or OPTIMIZE FOR; prefer recompile for frequently changing values.","In Oracle, use adaptive cursor sharing / bind-aware behavior; if needed, split the query by value range.","In Postgres, observe the generic vs custom plan choice; steer it with plan_cache_mode if needed.","If a single permanent, accurate plan is needed, move to the plan-pinning solution."],"related":["slow-queries"]},"sol_forceplan":{"kind":"solution","title":"Solution: pin the right plan (baseline / force plan)","detail":"If a known good plan exists, pinning it so the optimizer does not deviate from it stops the regression permanently.","steps":["Oracle: create a SQL Plan Baseline for the good plan (DBMS_SPM) or accept it through SQL Plan Management.","MSSQL: select the good plan in Query Store and force it with sp_query_store_force_plan.","MySQL/Postgres: steer the plan with optimizer hints (e.g. index hints) or pg_hint_plan.","Monitor performance after pinning; if the data pattern changes fundamentally, re-evaluate the baseline."],"related":[]},"sol_datagrowth":{"kind":"solution","title":"Root cause: data volume grew, the plan no longer fits","detail":"As the table grows, a plan that used to be ideal (e.g. nested loop) now scales poorly; the optimizer needs statistics and access paths that correctly reflect the growth.","steps":["Gather current statistics so the optimizer sees the new row count.","Add suitable indexes for the growing tables; fix the access path that now scales poorly (e.g. full scan/nested loop).","On very large tables, limit the scanned data with date/range-based partitioning.","Rewrite the query to suit the growth (early filtering, pagination, summary/materialized views)."],"related":["slow-queries"]},"sol_planflip":{"kind":"solution","title":"Root cause: sudden plan change (upgrade / cache flush)","detail":"The plan changed suddenly after a version upgrade, a parameter change or the plan cache being emptied. The optimizer behavior or environmental conditions have shifted.","steps":["Identify the trigger of the change: upgrade/patch, optimizer parameters, a data load or a restart/cache flush.","If a parameter is the cause, revert the change and re-evaluate it in a controlled way.","Pin the known good plan (baseline / force plan) so the optimizer does not deviate again.","For post-upgrade regressions, review the optimizer compatibility/readiness settings."],"related":[]}}},"solver/replica-resync.json":{"id":"replica-resync","title":"Replica broken / resync needed","icon":"🔗","category":"Replication","intro":"Synchronization between the replica/standby and the primary is broken. Determine whether replication has stopped, how far behind it is and whether the position/GTID chain is broken; then take the right path among skipping an error, waiting, or rebuilding (reseed). Includes a split-brain warning.","start":"root","nodes":{"root":{"kind":"step","title":"What is the overall state of replication?","detail":"First read the replication health: are the apply threads running, how large is the lag, is there an error? Also verify that the two nodes are not both accepting writes (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":"Replication has stopped / is failing","next":"error"},{"label":"Running but far behind (large lag)","next":"lag"},{"label":"GTID/position broken, inconsistent — rebuild","next":"sol_reseed"},{"label":"Both nodes are writing as primary (suspected split-brain)","next":"sol_splitbrain"}]},"error":{"kind":"step","title":"Can the error be repaired, or is a rebuild needed?","detail":"Examine the apply error. Some errors (duplicate key, missing row) can be resolved by safely skipping a single event; but if there is data inconsistency/corruption, the only correct path is to rebuild the replica.","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":"A single/few apply errors (skippable)","next":"sol_skiperror"},{"label":"Irreparable inconsistency / corruption","next":"sol_reseed"}]},"lag":{"kind":"step","title":"Is the lag temporary or persistent?","detail":"Look at the lag trend over time. Does it close (catch up) after a temporary load spike, or does it keep growing so that the replica never catches up with the primary?","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":"Temporary load spike; lag is closing","next":"sol_lag_wait"},{"label":"Keeps growing; cannot catch up","next":"sol_lag_perm"}]},"sol_skiperror":{"kind":"solution","title":"Solution: skip the apply error carefully","detail":"If replication stopped on a single apply error (e.g. a duplicate key) and you are sure the data is genuinely consistent, you can skip that event and continue the stream. This is a last resort; blind skipping creates silent data drift.","steps":["First understand why the error occurred and verify data consistency by comparing the affected row on both sides.","On MySQL without GTID, skip a single event: STOP REPLICA; SET GLOBAL sql_replica_skip_counter=1; START REPLICA; (before 8.0.26: sql_slave_skip_counter; carefully).","With GTID, to skip the failing transaction, mark that GTID with an empty transaction, then start the replica.","After skipping, run a row-level consistency check (checksum) between the two sides; if there is drift, move to a reseed."],"related":["replication-status"]},"sol_reseed":{"kind":"solution","title":"Solution: rebuild the replica (reseed / rebuild)","detail":"If the position/GTID chain is broken, data has drifted or there is an irreparable error, the only reliable path is to rebuild the replica from a fresh copy taken from the primary.","steps":["Take a consistent base backup from the primary: MySQL xtrabackup/mysqldump or CLONE, Postgres pg_basebackup, Oracle RMAN duplicate, MSSQL full+log backup.","Clean the old state on the replica and restore the backup (Postgres pg_basebackup -R, MSSQL RESTORE ... WITH NORECOVERY).","Start replication from the correct position/GTID: MySQL CHANGE REPLICATION SOURCE TO ...; START REPLICA; for MSSQL, re-add the database to the AG (automatic seeding).","Monitor the catch-up; once lag reaches zero, verify consistency of both sides with a checksum."],"related":["replication-status"]},"sol_lag_wait":{"kind":"solution","title":"Root cause: temporary load spike (lag is closing)","detail":"Replication is healthy; lag opened up because of a temporary write burst on the primary or network fluctuation, but the replica is catching up.","steps":["Confirm with several measurements that the lag trend is really decreasing.","Until the load window ends, route only lag-sensitive reads to the primary if needed.","If it recurs, spread write bursts (batch jobs, ETL) over time or move them off peak hours.","Add network/apply latency to monitoring so that temporary lag is spotted before it becomes persistent."],"related":["replication-status"]},"sol_lag_perm":{"kind":"solution","title":"Root cause: the replica cannot keep up with the primary","detail":"The apply (apply/redo) side cannot keep pace with the rate of change produced by the primary; lag keeps growing. Usually single-threaded apply, slow replica hardware or a network bottleneck.","steps":["Find the apply bottleneck: is it single-threaded apply, the replica's disk/CPU, or network bandwidth that limits it?","Enable/increase parallel apply: multiple replica workers in MySQL (replica_parallel_workers); review apply resources in Postgres/Oracle.","Bring the replica hardware at least to the primary's level (especially disk IOPS).","Reduce unnecessary heavy read queries on the replica to leave resources for apply; if it still cannot keep up, reassess after a reseed."],"related":["replication-status"]},"sol_splitbrain":{"kind":"solution","title":"WARNING: split-brain (two primaries) — risk of data loss","detail":"If both nodes accept writes in the primary role, conflicting changes accumulate on both sides; this is the most dangerous situation and can lead to data loss. Reducing writes to a single node comes first.","steps":["Immediately consolidate writes on a single node: route application traffic strictly to one primary, make the other read-only/isolate it.","Identify the authoritative (correct) node: decide whose data is considered valid from a business standpoint.","Compare the conflicting (divergent) changes on both sides; manually reconcile the critical changes from the losing side.","Rebuild the other node with a reseed using the authoritative node as the source; then set up a fencing/quorum mechanism that prevents dual primaries."],"related":["replication-status"]}}},"solver/cache-hit-low.json":{"id":"cache-hit-low","title":"Low cache hit ratio (buffer/cache hit)","icon":"🎯","category":"Performance","intro":"The cache hit ratio is low; data is constantly read from disk (or from the source). Determine whether the problem is in the DB buffer/shared cache or in the Redis/application cache, then measure and fix it.","start":"root","nodes":{"root":{"kind":"step","title":"Which cache has a low hit ratio?","detail":"Measure the hit ratio and isolate the problem: is the database buffer/shared cache low (high physical reads), or is the Redis/application cache seeing many misses and evictions?","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":"DB buffer/shared cache hit ratio is low","next":"dbcache"},{"label":"Many misses / evictions in the Redis/application cache","next":"rediscache"}]},"dbcache":{"kind":"step","title":"Why is it reading from disk: size or scan pattern?","detail":"There are two typical causes of a low buffer hit ratio: the frequently accessed data (working set) does not fit in memory (cache too small), or large full table scans pollute the cache with single-use blocks and push useful pages out.","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":"Working set does not fit in RAM (cache too small)","next":"sol_sizing"},{"label":"Full table scans pollute the cache","next":"sol_scan"}]},"rediscache":{"kind":"step","title":"Why the Redis misses: maxmemory eviction or TTL/key management?","detail":"If the Redis hit ratio is low, either memory has hit maxmemory and keys are being evicted, or TTL management/the caching pattern for the keys is weak (cold keys, low reuse).","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 full, high eviction","next":"sol_maxmem"},{"label":"Weak TTL/key pattern (low reuse)","next":"sol_ttl"}]},"sol_sizing":{"kind":"solution","title":"Root cause: the cache is too small for the working set","detail":"The frequently accessed data is larger than the allocated buffer/cache, so it is constantly evicted and re-read from disk. Hit ratio is low, physical reads are high.","steps":["Grow the cache to cover the working set: Oracle db_cache_size/SGA, Postgres shared_buffers, MySQL innodb_buffer_pool_size, MSSQL max server memory.","If physical RAM is insufficient, add server memory; this is the main resource the DB scales with.","Shrink the working set: archive/partition old data, trim unnecessary columns/indexes.","After the change, compare the hit ratio and physical reads to confirm the improvement."],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"Root cause: the scan pattern pollutes the cache","detail":"Large full table scans fill the cache with blocks that are used once and discarded, pushing hot pages out; this lowers the hit ratio.","steps":["Get the execution plans of the queries with the most physical reads and turn full scans into index access (add appropriate indexes).","Select only the needed columns instead of 'SELECT *'; reduce the volume of blocks scanned.","On large tables, narrow scans to the relevant range with partitioning.","Separate heavy analytical/scan jobs from the OLTP cache (a read replica or a separate time window)."],"related":["slow-queries","cache-hit-ratio"]},"sol_maxmem":{"kind":"solution","title":"Root cause: Redis maxmemory eviction","detail":"Redis memory has hit the maxmemory limit; old keys are evicted to make room for new ones (evicted_keys keeps rising). This loses entries that are still needed and lowers the hit ratio.","steps":["Raise maxmemory to match the real working set, or shrink the data set.","Choose the eviction policy to match the access pattern (e.g. LRU/LFU: allkeys-lfu is often effective at separating hot from cold).","Weed out large/rarely used structures; shrink values, remove unnecessary fields, use appropriate data types.","If the need is permanent, scale Redis (more RAM or cluster/sharding) and start monitoring evicted_keys."],"related":[]},"sol_ttl":{"kind":"solution","title":"Root cause: weak TTL / caching pattern","detail":"Either keys get no TTL at all, so memory fills with cold data, or the caching pattern has low reuse, so most requests end up as misses.","steps":["Assign appropriate TTLs to cached keys so cooling data expires on its own.","Cache frequently read (hot) data; do not cache rarely used data (avoid filling the cache with entries that never hit).","In the cache-aside pattern, implement the fill-after-miss correctly and use locking/request deduplication against the 'thundering herd'.","Monitor the keyspace_hits/misses ratio and refine TTL and key design based on the data."],"related":[]}}},"solver/arch-oltp.json":{"id":"arch-oltp","title":"Reference Architecture: 3-tier OLTP application","icon":"🏛️","category":"Reference Architecture","intro":"Classic business applications (orders, payments, inventory) run a large number of short, single-row, consistency-critical transactions (OLTP). This tree shows step by step, starting from identifying the load profile, when to introduce the primary relational database, a connection pooler, read replicas, a cache layer, backup + PITR and monitoring. The goal is not to 'add everything up front' but to add the right component as each bottleneck is proven.","start":"root","nodes":{"root":{"kind":"step","title":"What is your load profile: transaction mix and consistency requirements?","detail":"The first decision in an OLTP architecture is whether the workload is truly transactional (a short, ACID-requiring mix of writes and reads). Is the data naturally relational and is consistency critical (money, stock, orders), is the workload mostly reads, or is the load still very low?","choices":[{"label":"Short transactions, strong consistency (money/stock/orders)","next":"primary_db"},{"label":"Reads heavily dominate, few writes, and traffic is growing","next":"scale_reads"},{"label":"Load is very low; is the simplest setup enough?","next":"sol_single_node"}]},"primary_db":{"kind":"step","title":"Have you chosen the primary database engine, and are you worried about scale?","detail":"A relational engine is the right foundation for the transactional core (PostgreSQL, MySQL/InnoDB, Oracle, SQL Server). Once the engine choice is clear, the next question is scaling: is a single instance enough for now, or are connection counts and read load increasing?","choices":[{"label":"Engine is clear; a single instance is enough for now","next":"sol_single_node"},{"label":"Connection count has spiked, resources are running out","next":"sol_pooler"},{"label":"Read load is growing and straining the primary write instance","next":"scale_reads"}]},"scale_reads":{"kind":"step","title":"How will you scale reads: replica or cache?","detail":"If read load is straining the primary instance, there are two complementary options. Reads that can tolerate slightly delayed data, such as reports/lists, can be routed to a read replica; if the same 'hot' data is requested over and over, a cache with microsecond latency (Redis) relieves the DB.","choices":[{"label":"Many report/list reads; slight lag is acceptable","next":"sol_read_replica"},{"label":"The same popular data is read again and again","next":"sol_cache"}]},"sol_single_node":{"kind":"solution","title":"Recommendation: Start with a single primary + pool + backup","detail":"Keep the architecture simple early on: a single relational primary instance, a connection pool in front of it and regular backups are enough. Add complexity only when a measured bottleneck forces it.","steps":["Primary DB: an ACID-compliant relational engine such as PostgreSQL or MySQL/InnoDB; set up the schema, constraints and indexes correctly.","Connection pool: put an application-side pool (e.g., HikariCP) or a server-side PgBouncer in place from the start; it prevents connection storms.","Backup + PITR: daily full backups + WAL/binlog archiving for point-in-time recovery; store backups in separate storage.","Caution: do not add replicas/sharding/cache early on; trying to solve an unmeasured problem only enlarges the failure surface.","Monitoring: collect latency, error rate and connection count metrics from the start; do not miss the growth signal."],"related":["decide-pooling","decide-backup"]},"sol_pooler":{"kind":"solution","title":"Recommendation: Add a connection pooler","detail":"In OLTP, when every application instance opens its own connections, the primary DB chokes on memory and scheduler overhead under thousands of connections. Putting a pooler in between shares a small number of real DB connections across many application requests.","steps":["PostgreSQL: PgBouncer (transaction pooling mode) reduces many clients to a small number of backend connections.","MySQL: ProxySQL similarly provides connection multiplexing and routing.","Pool size: generally proportional to the CPU core count; a few dozen backend connections are enough. 'More is better' does not apply.","Caution: in transaction pooling, session-level features (prepared statements, temporary tables, session variables) are restricted; write the application accordingly.","Next: if the pool also saturates, the problem is not connections but query/transaction duration; hunt down slow queries and long transactions."],"related":["decide-pooling","connection-storm"]},"sol_read_replica":{"kind":"solution","title":"Recommendation: Add a read replica","detail":"Writes stay on the primary while read-only traffic (reports, lists, search) is routed to one or more asynchronous replicas fed from the primary. This frees the primary for writes and consistency-critical reads.","steps":["Topology: primary -> one or more asynchronous replicas; the application splits reads/writes and sends reads to the replica.","Engine: PostgreSQL streaming replication, MySQL replicas or managed read replicas.","When: for reads that tolerate a little lag, such as reports/analytics/lists; when you want to offload the primary's CPU/IO.","Caution: replication lag produces stale reads; serve flows that need 'read your own write immediately' from the primary (read-your-writes).","Next: if replicas are not enough either, consider a cache for hot data or sharding on the write side."],"related":["replication-lag","decide-shard-replica"]},"sol_cache":{"kind":"solution","title":"Recommendation: Add a cache layer (Redis)","detail":"If the same popular data (sessions, catalog, profiles, page fragments) is read repeatedly, put an in-memory cache in front of the DB and serve most reads without ever reaching the DB. Latency drops and the primary DB is relieved.","steps":["Engine: the cache-aside pattern with Redis (or Memcached); the application checks the cache first and, on a miss, fetches from the DB and writes to the cache.","TTL + invalidation: give every key a reasonable TTL; on write, delete/update the related key so no stale data remains.","When: many reads, data changes relatively rarely, and the same keys are requested repeatedly.","Caution: cache stampede; when a popular key expires, thousands of requests can hit the DB at once. Use locking/single-flight and TTLs with jitter.","Caution: do not treat the cache as the 'source of truth'; durable truth always lives in the DB, and the application must keep working by falling back to the DB even if the cache fails."],"related":["decide-cache","cache-hit-low"]}}},"solver/arch-dwh.json":{"id":"arch-dwh","title":"Reference Architecture: Data warehouse / analytics","icon":"🏛️","category":"Reference Architecture","intro":"Analytical queries (reporting, aggregation, wide scans) are fundamentally different from OLTP transactions: few but very wide, long-running queries. Starting from the decision to separate analytics from OLTP, this tree walks step by step through components such as the load method (ETL vs ELT), columnar/columnstore storage, star schema modeling, partitioning + compression and precomputation with materialized views, while weighing the trade-off between concurrency and throughput.","start":"root","nodes":{"root":{"kind":"step","title":"Does your analytical workload run on the same database as OLTP?","detail":"The most common mistake is running heavy reports on the live transactional DB and degrading OLTP latency. The first decision is separation: is analytics still running on the OLTP instance, are you building a separate analytical environment, or are you deciding how to move the data there?","choices":[{"label":"Reports still run on live OLTP; they slow OLTP down","next":"sol_separate"},{"label":"A separate warehouse exists; how should I load data?","next":"load_method"},{"label":"A warehouse exists; queries are still slow, how should I model?","next":"model"}]},"load_method":{"kind":"step","title":"Where will you transform: before or after loading?","detail":"When moving data from sources to the warehouse, will you do cleansing/transformation in a separate processing layer before loading (ETL), or load the raw data into the warehouse first and transform it with SQL inside the powerful warehouse engine (ELT)? The decision depends on the warehouse's processing power and the need for schema flexibility.","choices":[{"label":"Transformation is complex/external; do it before loading","next":"sol_etl"},{"label":"The warehouse is powerful; load raw, transform inside with SQL","next":"sol_elt"}]},"model":{"kind":"step","title":"Is the query bottleneck in the storage layout or the data model?","detail":"If analytics is slow there are two main levers. Wide scans read the entire row in row-based storage; columnar storage reads only the needed columns and compresses well. On the model side, excessive normalization means many joins slow queries down; a star schema (fact + dimension) simplifies things for analytics.","choices":[{"label":"Wide aggregation scans are slow; few columns are read","next":"sol_columnar"},{"label":"Too many joins, complex normalized schema","next":"sol_star"},{"label":"The same heavy report produces the same result repeatedly","next":"sol_matview"}]},"sol_separate":{"kind":"solution","title":"Recommendation: Separate analytics from OLTP","detail":"Transactional and analytical workloads choke each other on the same engine: long reports hold locks/IO and delay short transactions. Moving analytics to a separate target (a read replica or a separate analytical warehouse) relieves both sides.","steps":["First step: Route light reports to an OLTP read replica and heavy analytics to a separate analytical engine.","Analytical engine: A columnar/analytical engine (e.g. SQL Server with columnstore, ClickHouse, or a columnar warehouse) is many times faster than row-based storage on wide scans.","Concurrency vs throughput: OLTP is tuned for many small transactions (high concurrency), the warehouse for few huge queries (high throughput); you cannot optimize both on the same box.","Caution: The warehouse is not a 'real-time copy of production'; it is data loaded at intervals, delayed but organized for analysis.","Next: After separating, decide on the load method (ETL/ELT) and the model (star schema)."],"related":["decide-sqlnosql","slow-query"]},"sol_etl":{"kind":"solution","title":"Recommendation: ETL (transform before load)","detail":"In ETL, data is pulled from sources (Extract), cleansed and transformed in a separate processing layer (Transform), and only then loaded into the warehouse (Load). Only clean, model-conformant data enters the warehouse.","steps":["When: The transformation is complex, you need to combine multiple sources, or you want raw/sensitive data never to enter the warehouse.","Flow: Source (OLTP/file/API) → transformation layer → batch load into the warehouse.","Pro: Keeps warehouse storage clean; sensitive data can be masked before loading.","Con: Transformation depends on the power of a separate system; it cannot benefit from the warehouse's SQL flexibility.","Caution: Load in a batch window (e.g. overnight); plan to avoid clashes with heavy queries during loading."],"related":["decide-capacity"]},"sol_elt":{"kind":"solution","title":"Recommendation: ELT (transform inside the warehouse)","detail":"In ELT, raw data is loaded into the warehouse first and transformation happens inside the warehouse's own powerful SQL engine. Because modern columnar warehouses have high processing power, you move the data once and process it inside instead of taking the transformation to the data.","steps":["When: The warehouse engine is powerful and scalable; you also want to keep raw data (audit/reprocessing).","Flow: Source → raw layer (staging) → SQL/transformation views → modeled fact/dimension layer.","Pro: One engine, flexible; because raw data is retained you can change the transformation later and regenerate.","Con: Raw (possibly sensitive) data enters the warehouse; access control and masking are mandatory.","Caution: Model in layers (raw → staging → mart); write transformations idempotently so re-runs are safe."],"related":["decide-capacity"]},"sol_columnar":{"kind":"solution","title":"Recommendation: Columnar / columnstore storage + compression","detail":"Analytical queries typically scan a few columns across all rows. Columnar storage keeps data column by column; it reads only the needed columns, compresses very well because similar values sit next to each other, and IO drops dramatically.","steps":["Engine/feature: SQL Server columnstore index, ClickHouse, or columnar warehouses; columnar extensions on the PostgreSQL side.","Compression: The columnar layout gives high compression; less disk, fewer bytes read, faster scans.","Partitioning: Partition large fact tables by date/range so partition pruning skips irrelevant partitions entirely.","Caution: Columnar is bad for many single-row updates/point reads; do not move OLTP onto it.","Concurrency vs throughput: Columnar shines on a few huge scan queries, not on thousands of small concurrent queries."],"related":["decide-partition","io-bottleneck"]},"sol_star":{"kind":"solution","title":"Recommendation: Star schema (fact + dimension)","detail":"In an analytical model, a fact table holding the measures sits in the center, surrounded by descriptive dimension tables. Compared with an over-normalized schema, the number of joins drops, queries become simpler and the engine produces better plans.","steps":["Structure: The fact (measures + dimension keys) in the center; dimensions (time, product, customer) form a star around it.","When: Reports join many tables and the normalized schema drowns in joins.","Denormalization: Deliberate denormalization is normal in analytics; consistency is ensured in the load process (ETL/ELT).","Next: Precompute frequently run heavy aggregations with materialized views.","Caution: Do not convert OLTP to a star schema; this model is for analytics, not transactional writes."],"related":["decide-index"]},"sol_matview":{"kind":"solution","title":"Recommendation: Precompute with materialized views","detail":"If the same heavy aggregation (daily revenue, monthly summary) runs repeatedly and produces the same result, physically store the result in a materialized view and have reports read the precomputed result. The heavy computation runs once and queries return instantly.","steps":["When: Certain summary queries frequently and repeatedly scan the same large table.","Refresh: Refresh the materialized view on a schedule (e.g. nightly batch or incremental); set the frequency according to freshness needs.","Engine: PostgreSQL materialized view (REFRESH), Oracle materialized view (even query rewrite), SQL Server indexed view.","Caution: The view comes with a freshness window; where real-time data is required, a live query is mandatory.","Next: If refresh cost is high, consider incremental refresh or partition-based refresh."],"related":["decide-partition"]}}},"solver/arch-microservice.json":{"id":"arch-microservice","title":"Reference Architecture: Microservice data layer","icon":"🏛️","category":"Reference Architecture","intro":"In microservices the most critical decision is data ownership: each service must own its data (database-per-service) and must not reach directly into another service's tables. This tree walks step by step through the shared-DB anti-pattern, achieving cross-service consistency without distributed transactions (saga + outbox), synchronizing data with CDC, and cross-service queries (API composition vs a CQRS read model). The goal is to keep services truly independent and separately deployable.","start":"root","nodes":{"root":{"kind":"step","title":"How do services own data: a shared DB or one per service?","detail":"Data isolation is the backbone of a microservice architecture. Do services use a single shared database (a hidden dependency), does each service have its own database (the right pattern), or is the real challenge cross-service consistency/queries? Establish the current state first.","choices":[{"label":"All services use a single shared DB","next":"sol_db_per_service"},{"label":"Each service has its own DB, but operations need cross-service consistency","next":"consistency"},{"label":"Each service has its own DB, but combined queries/reports are needed","next":"query"}]},"consistency":{"kind":"step","title":"How will you keep a cross-service workflow consistent?","detail":"For an operation spanning multiple services (create order → take payment → decrement stock), a classic distributed transaction (2PC) is fragile and does not scale. Instead, split the flow into local transaction + event steps (saga) and publish events reliably (outbox). Which problem are you dealing with?","choices":[{"label":"I am trying to keep a multi-service workflow atomic (2PC?)","next":"sol_saga"},{"label":"I cannot make the local write and event publish consistent (dual write)","next":"sol_outbox"}]},"query":{"kind":"step","title":"How will you serve a query that shows data from multiple services?","detail":"Once data is split across services, you cannot build a combined screen with a single join. There are two main approaches: call the relevant services' APIs at request time and merge the results (API composition), or maintain a pre-joined read model fed by events (CQRS). Query frequency and complexity determine the choice.","choices":[{"label":"Simple, infrequent join; calling a few services and merging is enough","next":"sol_api_composition"},{"label":"Complex/frequent combined query; a ready read model is needed","next":"sol_cqrs"}]},"sol_db_per_service":{"kind":"solution","title":"Recommendation: Database-per-service (split the shared DB)","detail":"Services sharing one database is a hidden and dangerous dependency: one service's schema change breaks the others, ownership of tables becomes unclear and independent deployment becomes impossible. Each service must own its data.","steps":["Principle: Each service reads/writes only its own database; it cannot access another service's tables directly, only request data through that service's API.","Engine independence: Each service can choose an engine for its needs (e.g. PostgreSQL for orders, MongoDB for the catalog, Elasticsearch for search, Redis for the cart).","Anti-pattern: The 'shared database': common tables, cross joins and a single schema; this is not microservices but a distributed monolith.","Decomposition: First logical separation (separate schema/user), then physical separation; remove cross-table joins.","Caution: After splitting, consistency (saga/outbox) and combined query (composition/CQRS) problems arise; these are the natural cost of this transition."],"related":["decide-isolation","decide-sqlnosql"]},"sol_saga":{"kind":"solution","title":"Recommendation: Saga pattern (avoid distributed transactions)","detail":"Do not try to make an operation spanning multiple services atomic with a single distributed transaction (2PC); 2PC holds locks and brings single-point fragility and low availability. Instead, split the flow into steps that are each a local transaction in their own service, and undo them with compensation steps on failure.","steps":["Principle: Model the workflow as a chain of local transactions + events; each step triggers the next.","Compensation: If a step fails, run compensating actions that undo the previous steps (e.g. cancel the order if payment fails).","Choreography vs orchestration: In simple flows services trigger each other with events (choreography); in complex flows a central orchestrator manages the steps.","Caution: A saga is not atomic; it provides eventual consistency. Intermediate states may be visible externally, so design business rules accordingly.","Caution: Do not use 2PC/XA in high-scale microservices; blocking and lock duration will stall the system."],"related":["decide-isolation","long-transaction"]},"sol_outbox":{"kind":"solution","title":"Recommendation: Transactional outbox (the dual-write trap)","detail":"When a service wants to both write to its own DB and publish an event, doing the two separately is the 'dual write' trap: if one succeeds and the other fails, the system is left inconsistent. The solution is to write the event into an outbox table in the SAME local transaction as the business data, then publish it with a separate process.","steps":["Principle: The business record + event record are written to the outbox table in one local transaction; either both happen or neither does (atomic).","Publishing: A separate relay/CDC process reads outbox rows, publishes them to a message broker (e.g. Kafka) and marks them.","With CDC: A CDC tool such as Debezium reads the DB log (WAL/binlog) and reliably moves outbox changes to Kafka.","Idempotency: Consumers may receive the same event more than once; process idempotently using the event id (assume at-least-once delivery).","Caution: Do not use the 'write to the DB first, then publish to Kafka' approach directly; a crash at publish time causes event loss."],"related":["replication-lag"]},"sol_api_composition":{"kind":"solution","title":"Recommendation: API composition (merge at request time)","detail":"Where combined data is needed, the caller (e.g. a gateway/BFF) calls the relevant services' APIs and merges the results in memory. It is the simplest solution for simple joins spanning few services; no extra data copy is kept.","steps":["Principle: A composer (API gateway/BFF) calls service-A and service-B, merges the result and returns it.","When: The join is simple, spans few services and query frequency is moderate; you do not want the maintenance burden of an extra read model.","Pro: No extra data store or synchronization; each service returns its own current data.","Caution: For joins across many services or large datasets, latency accumulates and in-memory joins are expensive.","Next: If the join becomes complex or very frequent, move to a CQRS read model."],"related":[]},"sol_cqrs":{"kind":"solution","title":"Recommendation: CQRS read model (fed by events)","detail":"For complex and frequent combined queries, keep a pre-joined, query-optimized read model (materialized view/denormalized copy) fed by events emitted by the services. The write side stays in the services; the read side serves ready data quickly from one place.","steps":["Principle: Services publish state changes as events; a projection listens to these events and updates the read model.","Store choice: Choose the read model store by query type: Elasticsearch for search, MongoDB for flexible documents, Redis for fast key access, PostgreSQL for relational reports.","When: The combined query is complex/frequent, low latency is required and the cost of API composition has grown.","Caution: The read model is eventually consistent; it lags slightly behind until events are processed.","Idempotency: The projection may see the same event twice; update idempotently with the event id and track order/version if needed."],"related":["decide-cache"]}}},"solver/arch-eventdriven.json":{"id":"arch-eventdriven","title":"Reference Architecture: Event-driven / CQRS","icon":"🏛️","category":"Reference Architecture","intro":"In an event-driven architecture, services publish events to an event log (e.g. Kafka) instead of calling each other directly, and consumers process them asynchronously. Starting from building the event backbone, this tree walks step by step through event sourcing (deriving state from events), command/query separation with CQRS, read model projection, idempotency, tolerance for eventual consistency and the 'exactly-once delivery' fallacy. The goal is a loosely coupled, scalable system whose consistency trade-offs are well understood.","start":"root","nodes":{"root":{"kind":"step","title":"Which decision are you facing in your event-driven architecture?","detail":"The foundation of an event-driven system is a durable event log that connects services through asynchronous events. Are you still building the backbone, do you want to derive state from events (event sourcing), or are you separating the read and write paths (CQRS)?","choices":[{"label":"I am building the event backbone that connects services","next":"sol_event_log"},{"label":"I want to derive state from the event history","next":"sol_event_sourcing"},{"label":"I want to separate the read and write paths (CQRS)","next":"cqrs_split"}]},"cqrs_split":{"kind":"step","title":"How will you build the read side in CQRS, and what is your consistency tolerance?","detail":"In CQRS, the command (write) and query (read) sides are separated; write events update the projection that feeds the read model. The key question: can you tolerate the read model lagging behind writes (eventual consistency), or do you expect every event to be processed exactly once?","choices":[{"label":"I will feed the read model from events via a projection","next":"sol_read_model"},{"label":"Reads must be current; lag is unacceptable","next":"sol_consistency"},{"label":"I want events processed 'exactly once'","next":"sol_idempotency"}]},"sol_event_log":{"kind":"solution","title":"Recommendation: A durable event log backbone","detail":"At the center of an event-driven architecture is a durable, ordered event log that producers write to and consumers read at their own pace. This backbone decouples services in time and scale.","steps":["Engine: Apache Kafka (or a similar log-based platform): a partitioned, persistent, replayable event log.","Partitions & ordering: Ordering is guaranteed only within a partition; preserve order by routing events for the same entity to the same key.","Consumer independence: Each consumer group keeps its own offset; it can re-read/rewind without affecting the producer.","Retention: Retain the log long enough for new consumers and reprocessing to read the history.","Caution: Kafka is not a queue but a replayable log; assume at-least-once delivery and write idempotent consumers."],"related":[]},"sol_event_sourcing":{"kind":"solution","title":"Recommendation: Event sourcing (derive state from events)","detail":"In event sourcing you store not the current state of an entity but all (immutable) change events applied to it; the current state is derived by applying these events in order. It provides a full audit history and the ability to return to any point in the past.","steps":["Principle: The event stream is the source of truth; state is rebuilt by replaying the events from the start (or a snapshot + subsequent events).","Snapshots: For long streams, take periodic snapshots to avoid replaying from the start every time, and apply later events on top.","Pro: A full audit trail, an answer to 'how did we get to this state' over time, and the ability to derive different read models.","Caution: Event schemas evolve; reading old events requires versioning/upcasting, because events live forever.","Caution: Not needed for every scenario; if current state is enough, classic CRUD is simpler. Usually used together with CQRS."],"related":[]},"sol_read_model":{"kind":"solution","title":"Recommendation: Read model projection","detail":"In CQRS the write side produces events; a projection listens to these events and updates a query-optimized read model (denormalized view). Reads are served from this ready model, completely separated from the write path, and both sides scale independently.","steps":["Principle: Events → projection → read store; read queries hit only this denormalized model.","Store choice: By query type: Elasticsearch for search, MongoDB for flexible documents, Redis for fast access, PostgreSQL for relational.","Rebuildability: Because the read model is derived from events, it can be dropped and rebuilt from scratch by re-reading the log.","Idempotency: The projection may see the same event more than once; update idempotently using the event id/version.","Caution: The read model is eventually consistent; it lags behind writes until the event is processed, so design the UI accordingly."],"related":["decide-cache"]},"sol_consistency":{"kind":"solution","title":"Recommendation: Design for eventual consistency tolerance","detail":"In event-driven/CQRS systems the read model lags slightly behind writes (eventual consistency). Rather than trying to 'eliminate' this, design to tolerate it; keep the few flows that require strict immediate consistency separate.","steps":["Principle: Most business flows tolerate consistency with a short delay; give the user 'request received, processing' feedback.","Read-your-writes: If users must see their own writes immediately, serve that read from the write side/synchronously.","Core requiring strict consistency: Keep work needing absolute immediate consistency, such as a monetary balance, inside a single service/DB with an ACID transaction; do not distribute it through events.","Caution: Do not hide eventual consistency; handle intermediate/transient states explicitly in screens and business rules.","Caution: A goal of 'making the whole system strongly consistent' destroys the benefits of event-driven architecture (scale, loose coupling)."],"related":["decide-ha","replication-lag"]},"sol_idempotency":{"kind":"solution","title":"Recommendation: Idempotency (the exactly-once fallacy)","detail":"In distributed messaging, end-to-end 'exactly-once delivery' is a fallacy in practice; message duplicates are unavoidable because of the network and retries. The right goal is idempotent processing: processing the same event twice must give the same result as processing it once.","steps":["Principle: Accept at-least-once delivery; make duplicates harmless in the consumer.","Method: Give every event a unique id; record processed ids and skip them if they arrive again (dedup), or use naturally idempotent operations (upsert, set).","Exactly-once semantics: Kafka's 'exactly-once' feature applies only within Kafka's own transaction boundary; side effects on an external DB are not guaranteed without idempotency.","Ordering: Avoid reordering by routing events for the same entity to the same partition key.","Caution: The assumption 'I configured exactly-once delivery, no dedup needed' produces inconsistencies in production; always process idempotently."],"related":[]}}},"solver/arch-multiregion.json":{"id":"arch-multiregion","title":"Reference Architecture: multi-region / global","icon":"🏛️","category":"Reference Architecture","intro":"When users are geographically distributed or resilience to a regional disaster is required, data spreads across multiple regions. This tree walks step by step through read locality (a read replica in each region), write topology (single-leader vs multi-leader vs quorum), data residency/compliance (GDPR), conflict resolution, and the trade-offs between RPO/RTO and latency. The basic truth: because of the laws of physics, low latency, strong consistency and multi-region writes cannot all be fully achieved at the same time; a deliberate trade-off is mandatory.","start":"root","nodes":{"root":{"kind":"step","title":"Why are you going multi-region: read locality, writes/resilience or compliance?","detail":"A multi-region architecture is costly and complex; clarify the motivation. Do you want low read latency for distant users, cross-region writes/disaster resilience, or is the real requirement that data stays in a specific country (legal residency)?","choices":[{"label":"Fast reads for distant users; writes can stay in one region","next":"sol_read_locality"},{"label":"Cross-region writes / resilience to losing a region are required","next":"write_topology"},{"label":"Data must legally stay in a specific country/region","next":"sol_data_residency"}]},"write_topology":{"kind":"step","title":"What should the write topology be: single-leader, multi-leader or quorum?","detail":"For cross-region writes, the key decision is the leadership model. Do all writes go to a single region (simple, consistent, but remote writes are slow), can every region write (low write latency but conflicts), or is a write accepted with the acknowledgment of a majority of regions (quorum: consistent but with added latency)?","choices":[{"label":"One region writes, the others read (simple and consistent)","next":"sol_single_leader"},{"label":"Every region can write locally (low write latency)","next":"sol_multi_leader"},{"label":"Write with majority acknowledgment; consistency comes first","next":"sol_quorum"}]},"sol_read_locality":{"kind":"solution","title":"Recommendation: a read replica per region (read locality)","detail":"Writes stay in a single primary region while you place in each user region a read replica fed from it. Users read from the nearest replica and read latency drops; since writes still go to a single center, the consistency model stays simple.","steps":["Topology: One primary region (writes) → an asynchronous read replica in each user region.","Engines: PostgreSQL streaming replication, MySQL cross-region replicas or managed global read replicas.","Routing: The application routes reads to the nearest replica and writes to the primary region (geo DNS / smart router).","Caution: Cross-region replication lag causes stale reads; have flows that need read-your-writes read from the primary.","RPO/RTO: If the primary region goes down, an asynchronous replica can be promoted with some data loss (RPO > 0); prepare the failover plan in advance."],"related":["replication-lag","decide-shard-replica"]},"sol_single_leader":{"kind":"solution","title":"Recommendation: single-leader writes","detail":"All writes go to a single leader region; the other regions are read replicas fed asynchronously from that leader. It is the simplest and most strongly consistent model; there are no conflicts because there is a single write point. The price is high write latency from remote regions.","steps":["Principle: A single write authority → no conflicts, simple consistency model.","When: Write volume can be handled in one region and remote write latency is tolerable.","Failover: If the leader region goes down, a replica is promoted to leader; automatic/controlled failover and split-brain protection are mandatory.","RPO/RTO: With asynchronous replication, a small amount of data can be lost on failover (RPO); synchronous replication gives RPO=0 but increases write latency.","Caution: Writes from remote users are slow; if write locality is critical, look at multi-leader or quorum."],"related":["decide-ha","failover-drill"]},"sol_multi_leader":{"kind":"solution","title":"Recommendation: multi-leader writes + conflict resolution","detail":"Because every region can write locally, write latency is low, and even if one region is cut off, the others keep writing. The price is heavy: two regions can update the same record differently, and you must design conflict resolution.","steps":["Principle: Every region is a leader; writes are propagated asynchronously to the other regions and merged.","Conflict resolution: Last-write-wins (simple but risks data loss), application-specific merging, or conflict-free data types such as CRDTs.","Engine examples: Multi-master/multi-leader configurations (e.g. EDB Postgres Distributed (BDR)/pgEdge for PostgreSQL, Redis Enterprise Active-Active (CRDT), CouchDB). Cassandra is leaderless rather than multi-leader; with LOCAL_ONE/LOCAL_QUORUM it allows local writes in every region for a similar outcome and resolves conflicts with last-write-wins (timestamps).","When: Local write latency and regional independence matter more than strong consistency.","Caution: Design conflict resolution from the start; the assumption that 'conflicts will not happen' produces silent data loss. Watch for cross-region collisions in unique ID generation."],"related":["decide-ha"]},"sol_quorum":{"kind":"solution","title":"Recommendation: quorum-based writes (majority acknowledgment)","detail":"A write is accepted when a majority (quorum) of regions/replicas acknowledges it; when reads also go to a majority, the latest value is guaranteed. It is resilient to region loss and strongly consistent; the price is the latency of every write reaching a majority.","steps":["Principle: Majority acknowledgment with the W + R > N rule; even if a minority region goes down, the system keeps writing consistently.","Engine examples: Consensus-based distributed DBs (e.g. systems using Raft/Paxos), the QUORUM consistency level in Cassandra.","When: Both regional disaster resilience and strong consistency are required, and higher write latency is acceptable.","RPO/RTO: Since a majority writes durably, RPO≈0; with automatic re-formation of the majority after a region loss, RTO can be low.","Caution: Quorum between geographically distant regions adds tens of ms to every write due to speed-of-light latency; choose the number and placement of regions carefully."],"related":["decide-ha","decide-shard-replica"]},"sol_data_residency":{"kind":"solution","title":"Recommendation: data residency / compliance (GDPR)","detail":"Some data (personal, health, financial) must legally remain within the borders of a specific country/region (e.g. GDPR). In this case you design the architecture not around 'copy everywhere' but so that data is pinned geographically.","steps":["Principle: Keep personal/regulated data in the region where it originates; restrict or prohibit replication outside the region.","Regional partitioning: Route users to the region where they are registered (geo-partitioning); each region holds its own residents' data.","Global vs local data: Global data such as a catalog can be replicated everywhere; personal data stays in the region.","Caution: Backups and read replicas also move data; the backup storage region and the DR region are also within compliance scope.","Caution: A decision like 'let us copy it everywhere just for low latency' can be a legal violation for regulated data; build residency constraints into the architecture from the start."],"related":["security-incident","audit-setup"]}}},"solver/arch-highscale.json":{"id":"arch-highscale","title":"Reference Architecture: high scale / high traffic","icon":"🏛️","category":"Reference Architecture","intro":"As traffic grows, there is a right order for protecting the database: first the simple, cheap levers, then the complex ones. This tree walks step by step through the vertical scaling → read replica → sharding order, caching patterns (cache-aside, write-back), buffering with a queue + backpressure, rate limiting, isolating the database with bulkheads/circuit breakers, and preventing hot spots. Core principle: leave hard-to-reverse decisions such as sharding for last, and exhaust the cheap wins first.","start":"root","nodes":{"root":{"kind":"step","title":"Where is the bottleneck: is a single instance saturated, are reads or writes heavy, or is traffic bursty?","detail":"To solve a scaling problem in the right place, first identify the bottleneck. Is a single instance saturated on CPU/memory, are reads dominant, are writes hitting the limit, or are sudden traffic spikes crushing the database? Each points to a different component.","choices":[{"label":"A single instance is saturated; I have not tried the simple levers yet","next":"scale_path"},{"label":"Reads are very heavy and growing","next":"sol_cache"},{"label":"Sudden traffic spikes / bursty load are crushing the DB","next":"protect"}]},"scale_path":{"kind":"step","title":"Which scaling step are you at: vertical, read replicas or sharding?","detail":"Scaling has a right order. Vertical scaling (more powerful hardware) is the simplest first step; then you spread reads across replicas; only when a single primary can no longer handle the writes do you move to horizontal partitioning (sharding). Where are you?","choices":[{"label":"I have not scaled up the hardware / exhausted the basic tuning yet","next":"sol_vertical"},{"label":"Writes fit on one instance, but I need to spread the read load","next":"sol_read_replica"},{"label":"A single primary can no longer handle the write load","next":"sol_sharding"}]},"protect":{"kind":"step","title":"How will you protect the database from load: buffering, limiting or isolation?","detail":"Scaling is not always about making the DB bigger; often it is about protecting the DB from overload. Will you smooth sudden write spikes with a queue, limit the incoming request rate (rate limiting), or prevent a single dependency from bringing down the whole system (bulkhead/circuit breaker)?","choices":[{"label":"I want to smooth out sudden write spikes","next":"sol_queue"},{"label":"I want to limit/fairly share the incoming request rate","next":"sol_ratelimit"},{"label":"I do not want one failing dependency to take down the whole system","next":"sol_bulkhead"}]},"sol_vertical":{"kind":"solution","title":"Recommendation: vertical scaling and basic tuning first","detail":"The cheapest and least risky step is vertical scaling: adding more CPU/RAM/faster disks and fixing the basics (indexes, connection pooling, memory parameters). Exhaust these wins before complex decisions such as sharding.","steps":["First step: Scale up the hardware (CPU/RAM/NVMe) and tune the buffer/cache memory parameters to the workload.","Cheap wins: Add missing indexes, fix heavy queries, put a connection pooler (PgBouncer/ProxySQL) in front; most 'scale' problems are actually tuning problems.","When it is enough: If the scaled-up single instance meets the target latency/throughput, stop; do not add more complexity.","Caution: Vertical scaling has a physical and cost ceiling; as you approach it, move to read replicas and caching.","Next: If reads dominate, replicas + cache; if writes dominate and the ceiling is exceeded, sharding."],"related":["decide-capacity","high-cpu"]},"sol_read_replica":{"kind":"solution","title":"Recommendation: spread reads with read replicas","detail":"Writes stay on a single primary while read-only traffic is spread across one or more replicas. It is much simpler than sharding and solves most read-heavy workloads; the primary remains only for writes and consistency-critical reads.","steps":["Topology: Primary (writes) → multiple asynchronous read replicas; reads are distributed to the replicas through a load balancer.","Engines: PostgreSQL streaming replication, MySQL replicas, managed read replicas.","When: The read/write ratio is heavily skewed toward reads and writes can still be handled by a single instance.","Caution: Replication lag causes stale reads; serve reads that need read-your-writes from the primary.","Next: If even replicas are not enough, or the real bottleneck is writes, add a cache, then look at sharding."],"related":["replication-lag","decide-shard-replica"]},"sol_sharding":{"kind":"solution","title":"Recommendation: sharding (horizontal partitioning) as a last resort","detail":"When a single primary cannot handle the write load, you split the data by a key into multiple independent pieces (shards); each shard takes on part of the writes. It is powerful but hard to reverse; it complicates cross-shard queries and transactions, so it is left for last.","steps":["Shard key: Choose a key that distributes load and data evenly and lets most queries land on a single shard (e.g. customer_id).","Method: Hash-based (even distribution) or range-based (easy range queries but hot spot risk) partitioning.","Engine examples: MongoDB sharding, Citus (PostgreSQL), Vitess (MySQL) or application-level sharding.","Caution: Cross-shard joins/transactions are expensive and complex; design the schema so that most transactions complete within a single shard.","Caution: A wrong key creates hot spots (load piling onto one shard); avoid monotonic keys such as increasing ids/dates, and remember that resharding is hard."],"related":["decide-shard-replica","decide-partition"]},"sol_cache":{"kind":"solution","title":"Recommendation: caching patterns (cache-aside + write-back)","detail":"With read-heavy high traffic, you put an in-memory cache (Redis) in front of the DB and serve most reads without them ever reaching the DB. For high-volume writes, write-back buffers writes and flushes them to the DB in batches, relieving the primary.","steps":["Cache-aside: The application checks Redis first; on a miss it fetches from the DB and writes to the cache. This takes most of the read load off the DB.","Write-back (write-behind): For very high write volumes such as counters/metrics, write to the cache first and flush to the DB in batches in the background.","TTL + invalidation: Give keys a reasonable TTL; on write, update/delete the related key so no stale data remains.","Caution: Cache stampede: when a popular key expires, thousands of requests hit the DB at once; use locking/single-flight, early refresh and jittered TTLs.","Caution: With write-back, if the cache node crashes before writing to the DB, data is lost; protect critical data with a durable queue/AOF."],"related":["decide-cache","cache-hit-low"]},"sol_queue":{"kind":"solution","title":"Recommendation: buffering with a queue (backpressure)","detail":"To keep sudden write spikes from hitting the DB directly, you put a durable queue (e.g. Kafka/a message queue) in between and consume writes at the rate the DB can sustain. The queue absorbs the spike; instead of failing synchronously, the system runs with controlled latency (backpressure).","steps":["Principle: The producer writes the event to the queue; the consumer writes to the DB at its sustainable rate (possibly in batches).","Engine: Kafka or a message queue; it acts as a buffer that isolates the DB from peak traffic.","Backpressure: If the queue keeps growing, slow down/signal the producer; do not allow unbounded accumulation, otherwise latency explodes.","When: Write traffic is very bursty and immediate synchronous processing is not mandatory (asynchronous processing is acceptable).","Caution: A queue introduces asynchrony; the result is eventually consistent. Make the consumer idempotent (messages may be redelivered)."],"related":["connection-storm"]},"sol_ratelimit":{"kind":"solution","title":"Recommendation: rate limiting (limit the request rate)","detail":"By limiting incoming requests per source (rate limiting), you prevent both abuse and a single client consuming all resources. Capping the load that reaches the DB keeps the system predictable and fair.","steps":["Placement: Apply limiting before the DB (API gateway / application edge); only accepted requests should reach the DB.","Method: Per-client or per-user rate with token bucket / sliding window; return 429 when exceeded.","Fair sharing: Use quotas to prevent a 'noisy neighbor' from swallowing all capacity.","When: Traffic can spike suddenly, or there is a risk of abuse/automated clients.","Caution: Rate limiting is a protective measure, not scaling; if there is a real capacity need, also resolve the actual bottleneck (vertical/replica/shard)."],"related":["connection-storm"]},"sol_bulkhead":{"kind":"solution","title":"Recommendation: bulkhead + circuit breaker (isolation)","detail":"To prevent a dependency (e.g. a slowing DB or an external service) from bringing down the whole system, you partition resources into compartments (bulkheads) and temporarily cut calls to a dependency that keeps failing (circuit breaker). This way a partial failure does not spread to the whole system.","steps":["Bulkhead: Allocate separate connection pools/threads for different kinds of work; one slow path must not exhaust the whole pool and starve the others.","Circuit breaker: When a dependency exceeds an error/latency threshold, 'open' the circuit and fail fast for a while; then probe recovery with trial calls (half-open).","Timeouts: Put a reasonable timeout on every DB/service call; hanging calls must not lock up the pool.","When: In a system with many dependencies, where one component slowing down carries a risk of cascading failure.","Caution: Design graceful degradation for when the circuit is open: return stale data from the cache or a partial result instead of rejecting the entire request."],"related":["connection-storm","high-cpu"]}}},"types/types-functions.json":{"schemaVersion":1,"sections":[{"id":"datatypes","title":"Data Types","icon":"🔤","rows":[{"id":"var-text","concept":"Variable-length text","engines":{"oracle":"VARCHAR2(n)","postgres":"VARCHAR(n) / TEXT","mysql":"VARCHAR(n)","mssql":"NVARCHAR(n)","mongodb":"string","redis":"string (value)","elasticsearch":"text / keyword"},"note":"In ES, text is used for full-text search and keyword for exact match/aggregations."},{"id":"fixed-text","concept":"Fixed-length text","engines":{"oracle":"CHAR(n)","postgres":"CHAR(n)","mysql":"CHAR(n)","mssql":"NCHAR(n)","mongodb":"string","redis":"string","elasticsearch":"keyword"},"note":"Document/key-value systems have no concept of fixed length; values are stored as strings."},{"id":"large-text","concept":"Large text (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":"Integer (32-bit)","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 has no separate INT type; NUMBER is used."},{"id":"int64","concept":"Big integer (64-bit)","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":"Decimal/exact number (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":"For exact values such as money, these types are preferred over float."},{"id":"float","concept":"Floating point (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":"Logical (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":"A BOOLEAN column type arrived in Oracle SQL only with 23c; before that it is simulated with 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":"The Oracle DATE type holds both date and time; it is not date-only."},{"id":"datetime","concept":"Date-time (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-time with time zone","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 and ES store dates as UTC; the time zone is applied on display."},{"id":"binary","concept":"Binary data (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":"Redis strings are binary-safe and can hold any byte sequence."},{"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":"Mongo's native _id is ObjectId; a UUID can also be stored as 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":"In Postgres, JSONB is preferred for querying/indexing; MongoDB is document-based to begin with."},{"id":"auto-increment","concept":"Auto-increment (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/fixed list","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":"Array","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":"In ES, every field can implicitly hold an array; there is no separate array type."},{"id":"ip-address","concept":"IP address","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":"The native IP types in Postgres and ES support range/subnet queries."},{"id":"money","concept":"Money","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":"To avoid rounding errors, money is stored with DECIMAL/Decimal128 rather than float."},{"id":"spatial","concept":"Geographic/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":"Common Functions","icon":"🧩","rows":[{"id":"coalesce","concept":"Return a default if 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":"Replace if 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":"Current date-time (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":"Current date","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":"Concatenate text (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":"In MySQL, || means OR by default; use CONCAT for concatenation."},{"id":"substring","concept":"Substring","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":"String length","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":"Upper/lower case","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":"Trim whitespace (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":"Replace text (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":"Type conversion (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":"Format a date as text (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":"Convert text to a 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":"Add to a 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":"Difference between two 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":"Extract a date part (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":"Round (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 (remainder)","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":"Conditional (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":"Greatest/least (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":"Concatenate within a group (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":"Row number (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":"First N rows (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":"Current user","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":"Generate a 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":"Extract a JSON field","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":"Regular expression match (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 (and Azure SQL) added native regex functions such as REGEXP_LIKE, REGEXP_REPLACE and REGEXP_SUBSTR (REGEXP_LIKE requires compatibility level 170); earlier versions require LIKE or a CLR function."}]}]},"reference/params-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"params":[{"name":"sga_target","default":"0","recommended":"~60% of RAM on a dedicated server","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Sets the total SGA size for Automatic Shared Memory Management (ASMM); distributes the sub-pools automatically.","tags":["memory","memory","sga"]},{"name":"sga_max_size","default":"Same as sga_target","recommended":"Equal to or slightly above sga_target","unit":"bytes (K/M/G)","dynamic":"restart","desc":"Sets the upper limit to which the SGA can grow at runtime; increasing it requires an instance restart.","tags":["memory","memory","sga"]},{"name":"pga_aggregate_target","default":"10M or 20% of the SGA","recommended":"~20% of RAM","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Sets the total target PGA memory used by all server processes; affects sort and hash operations.","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":"The default is usually sufficient","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Hard upper limit for instance-wide PGA memory; when exceeded, calls of the sessions consuming the most are aborted (SYS and background processes excluded). Introduced in Oracle 12c.","tags":["memory","memory","pga","limit"]},{"name":"memory_target","default":"0","recommended":"0 (separate SGA/PGA management is preferred)","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Combines SGA and PGA under a single target with Automatic Memory Management (AMM); not recommended on large systems.","tags":["memory","memory","amm"]},{"name":"db_cache_size","default":"0 (automatic)","recommended":"Leave at 0 if sga_target is used","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Sets the size of the buffer cache for the default block size; keeps data blocks in memory.","tags":["memory","memory","cache","buffer"]},{"name":"shared_pool_size","default":"0 (automatic)","recommended":"Leave at 0 if sga_target is used","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Size of the shared pool, which holds SQL/PLSQL parse results, the dictionary cache and the library cache.","tags":["memory","memory","shared","pool"]},{"name":"processes","default":"100 / 300 (depending on version)","recommended":"Increase based on expected concurrent connections","unit":"integer","dynamic":"restart","desc":"Maximum number of operating system processes that can connect to the instance; changing it requires a restart.","tags":["connection","connection","process","process"]},{"name":"sessions","default":"Derived from processes (1.5x+22)","recommended":"The default derivation is usually sufficient","unit":"integer","dynamic":"restart","desc":"Maximum number of sessions that can exist at the same time; derived automatically from the processes value.","tags":["connection","connection","session","session"]},{"name":"open_cursors","default":"50","recommended":"300-1000 depending on application needs","unit":"integer","dynamic":"dynamic","desc":"Sets the maximum number of cursors a session can keep open at the same time.","tags":["cursor","cursor","session","session"]},{"name":"cursor_sharing","default":"EXACT","recommended":"EXACT (use bind variables)","unit":"EXACT | FORCE","dynamic":"dynamic","desc":"Determines whether SQL statements containing literals are shared via bind variables; FORCE replaces literals.","tags":["cursor","cursor","sql","parse"]},{"name":"optimizer_mode","default":"ALL_ROWS","recommended":"ALL_ROWS","unit":"ALL_ROWS | FIRST_ROWS_n","dynamic":"dynamic","desc":"Sets the goal of the cost-based optimizer; whether total throughput or first-row response time is prioritized.","tags":["optimizer","optimize","plan","sql"]},{"name":"undo_retention","default":"900","recommended":"Based on the longest query/flashback duration","unit":"seconds","dynamic":"dynamic","desc":"Sets how long committed undo data is retained if possible; affects the risk of ORA-01555.","tags":["undo","rollback","retention","flashback"]},{"name":"undo_tablespace","default":"UNDOTBS1","recommended":"An adequately sized, dedicated undo tablespace","unit":"string (tablespace name)","dynamic":"dynamic","desc":"Sets the active undo tablespace used by automatic undo management; can be changed online.","tags":["undo","rollback","tablespace"]},{"name":"db_recovery_file_dest_size","default":"0","recommended":"Large enough to hold archivelogs + backups + flashback logs","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Sets the maximum disk size of the Fast Recovery Area (FRA); holds archived logs and backups.","tags":["recovery","recovery","fra","archive"]},{"name":"db_writer_processes","default":"1 or based on CPU count","recommended":"About CPU/8 (increase under heavy write load)","unit":"integer","dynamic":"restart","desc":"Sets the number of database writer (DBWn) processes that write dirty blocks to disk; parallelizes the write load.","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":"Set based on workload and CPU count","unit":"integer","dynamic":"dynamic","desc":"Limits the maximum number of parallel execution servers (PX) that can run at the same time in the instance.","tags":["parallel","parallel","px","query"]},{"name":"statistics_level","default":"TYPICAL","recommended":"TYPICAL","unit":"BASIC | TYPICAL | ALL","dynamic":"dynamic","desc":"Sets the level of AWR/ADDM and automatic statistics collection; BASIC disables many diagnostics.","tags":["statistics","statistics","awr","monitoring"]},{"name":"deferred_segment_creation","default":"TRUE","recommended":"TRUE (saves space on empty tables)","unit":"TRUE | FALSE","dynamic":"dynamic","desc":"Defers creation of the first segment of an empty table/index until the first row is inserted; saves space.","tags":["segment","deferred-segment","space","storage"]},{"name":"audit_trail","default":"NONE (DB in databases created with DBCA)","recommended":"Use DB or unified auditing","unit":"NONE | DB | DB,EXTENDED | OS | XML","dynamic":"restart","desc":"Determines where and how traditional audit records are written; changing it requires a restart.","tags":["audit","audit","security","security"]},{"name":"compatible","default":"Installed version (e.g., 19.0.0)","recommended":"Raise once the new version's features are stable","unit":"version string","dynamic":"restart","desc":"Determines which version features are enabled; cannot be lowered once raised, and requires a restart.","tags":["compatible","compatibility","version","version"]},{"name":"db_block_size","default":"8192","recommended":"8K (OLTP); set only at creation time","unit":"bytes","dynamic":"restart","desc":"Sets the standard block size of the database; it is set only when the database is created and cannot be changed afterwards.","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":"Controls the use of asynchronous and direct I/O for datafiles on file systems.","tags":["io","io","async","directio"]},{"name":"log_archive_max_processes","default":"4","recommended":"Increase based on the number of archive destinations","unit":"integer","dynamic":"dynamic","desc":"Sets the maximum number of ARCn processes that archive redo logs; increased to reduce archiving lag.","tags":["archive","archive","redo","log"]}]},"reference/params-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"params":[{"name":"shared_buffers","default":"128MB","recommended":"~25% of RAM","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"Amount of shared memory PostgreSQL allocates for caching data blocks; changing it requires a restart.","tags":["memory","memory","cache","buffer"]},{"name":"effective_cache_size","default":"4GB","recommended":"~50-75% of RAM","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Tells the planner the estimated total size of the OS + PostgreSQL cache; influences index usage.","tags":["memory","memory","planner","cache"]},{"name":"work_mem","default":"4MB","recommended":"16-64MB (mind the connection count)","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Memory that sort and hash operations can use before spilling to disk; allocated per query plan node.","tags":["memory","memory","sort","query"]},{"name":"maintenance_work_mem","default":"64MB","recommended":"256MB-1GB","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Sets the maximum memory maintenance operations such as VACUUM, CREATE INDEX and ALTER TABLE can use.","tags":["memory","memory","vacuum","maintenance"]},{"name":"max_connections","default":"100","recommended":"100-200 (use a pooler beyond that)","unit":"integer","dynamic":"restart","desc":"Maximum number of concurrent client connections accepted; changing it requires a restart.","tags":["connection","connection","limit"]},{"name":"wal_buffers","default":"-1 (1/32 of shared_buffers)","recommended":"16MB","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"Sets the size of the shared memory buffer that holds WAL records before they are written to disk. The -1 (automatic) value is bounded to at least 64kB and at most one WAL segment (usually 16MB).","tags":["wal","memory","memory","buffer"]},{"name":"max_wal_size","default":"1GB","recommended":"4-16GB depending on workload","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Sets the soft upper limit WAL can grow to before an automatic checkpoint is triggered.","tags":["wal","checkpoint","disk"]},{"name":"min_wal_size","default":"80MB","recommended":"1-2GB","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Lower limit below which WAL files are kept for recycling; reduces frequent reallocation.","tags":["wal","checkpoint","disk"]},{"name":"checkpoint_completion_target","default":"0.9","recommended":"0.9","unit":"ratio (0-1)","dynamic":"dynamic","desc":"Sets the fraction of the checkpoint interval over which checkpoint writes are spread; smooths I/O peaks. The default became 0.9 in PG14; it was 0.5 before.","tags":["checkpoint","io","wal"]},{"name":"checkpoint_timeout","default":"5min","recommended":"15-30min","unit":"time (s/min)","dynamic":"dynamic","desc":"Sets the maximum time between automatic checkpoints; increasing it reduces write load but lengthens recovery time.","tags":["checkpoint","wal","recovery"]},{"name":"random_page_cost","default":"4.0","recommended":"1.1 for SSD","unit":"decimal","dynamic":"dynamic","desc":"Sets the planner's relative cost of random disk access; lowering it on SSD encourages index scans.","tags":["planner","optimize","io","index"]},{"name":"effective_io_concurrency","default":"1","recommended":"200 for SSD","unit":"integer","dynamic":"dynamic","desc":"Estimated number of concurrent I/O operations the disk can serve at once; affects prefetching in bitmap scans. The default is 1 in PG17 and earlier, and 16 in PG18.","tags":["io","gc","ssd","prefetch"]},{"name":"default_statistics_target","default":"100","recommended":"100 (raise per column on large tables)","unit":"integer","dynamic":"dynamic","desc":"Sets the level of statistics detail ANALYZE collects; raising it improves plan accuracy and increases analyze time.","tags":["statistics","statistics","analyze","planner"]},{"name":"autovacuum","default":"on","recommended":"on","unit":"on | off","dynamic":"dynamic","desc":"Enables the automatic vacuum process that removes dead tuples and collects statistics; disabling it leads to bloat.","tags":["autovacuum","vacuum","maintenance","bloat"]},{"name":"autovacuum_vacuum_scale_factor","default":"0.2","recommended":"0.05-0.1 on large tables","unit":"ratio (0-1)","dynamic":"dynamic","desc":"Sets the fraction of a table's rows that must change before it is vacuumed; lowering it cleans large tables more often.","tags":["autovacuum","vacuum","bloat","maintenance"]},{"name":"autovacuum_max_workers","default":"3","recommended":"3-6 (raise on systems with many tables)","unit":"integer","dynamic":"restart","desc":"Sets the maximum number of autovacuum worker processes that can run at once; in PG17 and earlier changing it requires a restart. In PG18 it can be changed with a reload up to the autovacuum_worker_slots limit.","tags":["autovacuum","vacuum","worker","maintenance"]},{"name":"synchronous_commit","default":"on","recommended":"on (critical data); off (performance first)","unit":"on | off | local | remote_write | remote_apply","dynamic":"dynamic","desc":"Determines whether a commit waits for WAL to be written to disk; off carries a risk of small data loss on a crash.","tags":["wal","commit","durability","performance"]},{"name":"wal_level","default":"replica","recommended":"replica (for replication/PITR)","unit":"minimal | replica | logical","dynamic":"restart","desc":"Determines how much information is written to WAL; required for replication and logical replication, and changing it requires a restart.","tags":["wal","replication","replication","pitr"]},{"name":"max_wal_senders","default":"10","recommended":"Based on the number of standbys + backups","unit":"integer","dynamic":"restart","desc":"Sets the maximum number of processes that can stream WAL concurrently (for standbys and backups).","tags":["wal","replication","replication","standby"]},{"name":"wal_keep_size","default":"0","recommended":"Enough to cover standby lag","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Minimum amount of WAL kept in pg_wal for standbys; protects a lagging standby when there is no replication slot. Introduced in PG13; before that wal_keep_segments (number of segments) was used.","tags":["wal","replication","replication","standby"]},{"name":"log_min_duration_statement","default":"-1 (disabled)","recommended":"1000ms to catch slow queries","unit":"milliseconds","dynamic":"dynamic","desc":"Logs statements that run longer than the specified time; used for slow query detection, -1 disables it.","tags":["logging","log","slow-query","slow-query"]},{"name":"idle_in_transaction_session_timeout","default":"0 (disabled)","recommended":"60000ms to prevent held locks","unit":"milliseconds","dynamic":"dynamic","desc":"Terminates sessions that stay idle inside an open transaction for the specified time; reduces lock and bloat risk.","tags":["timeout","transaction","transaction","lock"]},{"name":"statement_timeout","default":"0 (disabled)","recommended":"Application-dependent (low for OLTP)","unit":"milliseconds","dynamic":"dynamic","desc":"Automatically cancels statements that exceed the specified time; prevents runaway queries from consuming resources.","tags":["timeout","query","query","limit"]},{"name":"shared_preload_libraries","default":"'' (empty)","recommended":"pg_stat_statements (for monitoring)","unit":"string (comma-separated list)","dynamic":"restart","desc":"Specifies the shared libraries loaded at server start; extensions such as pg_stat_statements are configured here.","tags":["extension","extension","preload","monitoring"]},{"name":"max_parallel_workers_per_gather","default":"2","recommended":"2-4 (depending on CPU count)","unit":"integer","dynamic":"dynamic","desc":"Sets the maximum number of parallel workers a single Gather/GatherMerge node can start; 0 disables parallelism.","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% of RAM on a dedicated server","unit":"bytes (M/G)","dynamic":"dynamic","desc":"The main memory pool where InnoDB caches data and indexes; in 8.0 it can be changed online, but large changes need care.","tags":["memory","memory sizing","innodb","buffer"]},{"name":"innodb_buffer_pool_instances","default":"1 (or 8 if size>1GB)","recommended":"8 for large pools","unit":"integer","dynamic":"restart","desc":"Sets the number of instances the buffer pool is divided into; reduces mutex contention under concurrency, changing it requires a restart.","tags":["memory","memory sizing","innodb","concurrency"]},{"name":"innodb_redo_log_capacity","default":"100MB (8.0.30+)","recommended":"1-8GB (depending on write load; ~1 hour of redo at peak)","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Sets the total disk space occupied by the InnoDB redo log; increasing it smooths checkpoint pressure and peak write load. Introduced in MySQL 8.0.30 and changeable without a restart via SET GLOBAL/SET PERSIST. The old innodb_log_file_size (default 48MB) and innodb_log_files_in_group are deprecated since 8.0.30; before 8.0.30 the redo size is set with these two and requires a restart.","tags":["innodb","redo","log","io"]},{"name":"innodb_flush_log_at_trx_commit","default":"1","recommended":"1 (full ACID); 2 (performance)","unit":"0 | 1 | 2","dynamic":"dynamic","desc":"Controls whether the redo log is written and synced to disk at each commit; 1 gives full durability, 2/0 carry a risk of loss.","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":"Defines how InnoDB flushes data and log files to disk; O_DIRECT avoids double buffering, changing it requires a restart.","tags":["innodb","io","flush","gc"]},{"name":"innodb_io_capacity","default":"8.0: 200 / 8.4: 10000","recommended":"Based on the storage's real IOPS (1000-2000 for SSD on 8.0)","unit":"integer (IOPS)","dynamic":"dynamic","desc":"Sets the estimated disk IOPS capacity used by InnoDB background tasks (flush, merge). In MySQL 8.4 the defaults were raised to innodb_io_capacity=10000 and innodb_io_capacity_max=20000 (200/2000 on 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 (depending on read load)","unit":"integer","dynamic":"restart","desc":"Sets the number of InnoDB background threads that handle read I/O requests; changing it requires a restart.","tags":["innodb","io","thread","read"]},{"name":"innodb_write_io_threads","default":"4","recommended":"4-8 (depending on write load)","unit":"integer","dynamic":"restart","desc":"Sets the number of InnoDB background threads that handle write I/O requests; changing it requires a restart.","tags":["innodb","io","thread","write"]},{"name":"max_connections","default":"151","recommended":"200-500 depending on workload","unit":"integer","dynamic":"dynamic","desc":"Sets the maximum number of simultaneous client connections allowed; can be changed online.","tags":["connection","connections","limit"]},{"name":"thread_cache_size","default":"-1 (autosized)","recommended":"Automatic; increase with many short-lived connections","unit":"integer","dynamic":"dynamic","desc":"Sets how many threads are kept in the cache for reuse; reduces thread creation for new connections.","tags":["connection","connections","thread","cache"]},{"name":"table_open_cache","default":"4000","recommended":"4000-8000 depending on the number of open tables","unit":"integer","dynamic":"dynamic","desc":"Sets the cache size for open table handles across all threads; reduces the overhead of reopening tables.","tags":["cache","table","table cache","open"]},{"name":"tmp_table_size","default":"16MB","recommended":"64-256MB","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Sets the maximum size of an in-memory temporary table before it is converted to an on-disk table; works together with max_heap_table_size.","tags":["memory","memory sizing","temp","temporary table"]},{"name":"max_heap_table_size","default":"16MB","recommended":"Same as tmp_table_size","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Sets the maximum size to which MEMORY engine tables and in-memory temporary tables can grow.","tags":["memory","memory sizing","temp","heap"]},{"name":"sort_buffer_size","default":"256KB","recommended":"256KB-2MB (mind the per-connection cost)","unit":"bytes (K/M)","dynamic":"dynamic","desc":"Buffer allocated per session for ORDER BY and GROUP BY sorts; making it too large consumes memory.","tags":["memory","memory sizing","sort","buffer"]},{"name":"join_buffer_size","default":"256KB","recommended":"256KB-1MB (prefer solving it with an index)","unit":"bytes (K/M)","dynamic":"dynamic","desc":"Buffer allocated per session for joins that cannot use an index; the lasting fix is adding a proper index.","tags":["memory","memory sizing","join","buffer"]},{"name":"sync_binlog","default":"1","recommended":"1 (full safety); 100-1000 under heavy load","unit":"integer","dynamic":"dynamic","desc":"Sets after how many commits the binary log is synced to disk; 1 is safest, higher values trade durability for performance.","tags":["binlog","replication","durability","commit"]},{"name":"binlog_expire_logs_seconds","default":"2592000 (30 days)","recommended":"According to the backup/replication window","unit":"seconds","dynamic":"dynamic","desc":"Sets how long binary log files are kept before automatic purging; controls disk usage.","tags":["binlog","log","retention","replication"]},{"name":"long_query_time","default":"10","recommended":"1 (catch slow queries)","unit":"seconds","dynamic":"dynamic","desc":"Sets the duration a query must exceed to be recorded in the slow query log; accepts fractional values.","tags":["slow-query","slow-query-log","log","performance"]},{"name":"slow_query_log","default":"OFF","recommended":"ON (for diagnostics)","unit":"ON | OFF","dynamic":"dynamic","desc":"Controls whether queries exceeding long_query_time are written to the slow query log; enable it for performance analysis.","tags":["slow-query","slow-query-log","log","monitoring"]},{"name":"wait_timeout","default":"28800 (8 hours)","recommended":"According to the application pool (e.g. 600)","unit":"seconds","dynamic":"dynamic","desc":"Sets the maximum time a non-interactive connection may stay idle before it is closed; cleans up idle connections.","tags":["timeout","connection","connections","idle"]},{"name":"max_allowed_packet","default":"64MB","recommended":"64-256MB for large BLOBs/rows","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Sets the maximum size of a single packet or any generated/intermediate row; increase it for large BLOB transfers.","tags":["packet","packet size","blob","network"]},{"name":"innodb_lock_wait_timeout","default":"50","recommended":"10-50 for OLTP","unit":"seconds","dynamic":"dynamic","desc":"Sets how long an InnoDB transaction waits for a row lock before timing out.","tags":["innodb","lock","lock wait","timeout"]},{"name":"character_set_server","default":"utf8mb4","recommended":"utf8mb4","unit":"text (character set)","dynamic":"dynamic","desc":"Sets the server's default character set; utf8mb4 provides full Unicode support (including emoji).","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":"Total RAM minus the share reserved for the OS and other services (~10-25%)","unit":"MB","dynamic":"dynamic","desc":"Sets the maximum amount of memory the SQL Server buffer pool and other memory consumers can use. The default is effectively unlimited; if other services run on the server, it must be lowered to leave enough memory for the operating system. Set with sp_configure; takes effect immediately with RECONFIGURE.","tags":["memory","memory limit","buffer pool"]},{"name":"min server memory (MB)","default":"0","recommended":"On critical instances, a reasonable lower bound relative to max","unit":"MB","dynamic":"dynamic","desc":"The minimum amount of memory SQL Server will not release back to the operating system once allocated. Used to prevent SQL Server from releasing too much memory under memory pressure. Set with sp_configure; takes effect with RECONFIGURE.","tags":["memory","memory limit"]},{"name":"max degree of parallelism","default":"0","recommended":"Usually 8 or the number of physical cores per NUMA node","unit":"count","dynamic":"dynamic","desc":"Limits the maximum number of processors (cores) a single query can use in parallel execution. 0 means all cores may be used; it is limited to avoid excessive parallelism in OLTP workloads. Takes effect immediately with sp_configure + RECONFIGURE.","tags":["parallelism","parallel query","maxdop","cpu"]},{"name":"cost threshold for parallelism","default":"5","recommended":"25-50 on modern hardware","unit":"cost","dynamic":"dynamic","desc":"The minimum estimated cost threshold a query plan must reach to be executed in parallel. The default of 5 is very low and causes small queries to be parallelized unnecessarily; raising it improves server-wide parallelism behavior. Takes effect immediately with sp_configure + RECONFIGURE.","tags":["parallelism","parallel query","cpu"]},{"name":"max worker threads","default":"0","recommended":"0 (let SQL Server calculate it)","unit":"count","dynamic":"dynamic","desc":"Sets the maximum number of worker threads SQL Server can create to process client requests. 0 makes it calculated automatically based on processor count and architecture. It is an advanced option; applied with sp_configure + RECONFIGURE without a restart (sys.configurations.is_dynamic = 1).","tags":["threads","worker","cpu","concurrency"]},{"name":"optimize for ad hoc workloads","default":"0","recommended":"1 (on)","unit":"0/1","dynamic":"dynamic","desc":"For ad hoc queries that run only once, caches a small compiled plan stub on first execution instead of the full plan. Prevents the plan cache from filling with single-use plans and improves memory usage. Takes effect immediately with sp_configure + RECONFIGURE.","tags":["plan cache","memory","memory usage"]},{"name":"backup compression default","default":"0","recommended":"1 (on)","unit":"0/1","dynamic":"dynamic","desc":"Makes new backups compressed by default. Compression significantly reduces backup size and backup duration at the cost of some CPU. Takes effect immediately with sp_configure + RECONFIGURE.","tags":["backup","backup size","compression","backup compression"]},{"name":"fill factor (%)","default":"0","recommended":"0 or 90-100 (depending on workload)","unit":"%","dynamic":"restart","desc":"Sets, as a percentage, how full leaf-level pages are filled when an index is created or rebuilt (REBUILD). 0 and 100 fill pages completely; lower values reduce page splits but increase disk and memory usage. The server-level default is changed with sp_configure but does not take effect until the service is restarted (is_dynamic = 0); the per-index FILLFACTOR option can be used immediately.","tags":["index","page split","storage","disk usage"]},{"name":"recovery interval (min)","default":"0","recommended":"0 (automatic)","unit":"minutes","dynamic":"dynamic","desc":"Sets the frequency of automatic checkpoints in terms of the targeted maximum recovery time. 0 lets SQL Server manage the target recovery time automatically per database. Takes effect immediately with sp_configure + RECONFIGURE.","tags":["checkpoint","recovery","crash recovery"]},{"name":"remote query timeout (s)","default":"600","recommended":"600 or 0 (unlimited), depending on workload","unit":"seconds","dynamic":"dynamic","desc":"The time the server waits for a response to remote queries (over a linked server) before timing out. 0 means wait indefinitely. Takes effect immediately with sp_configure + RECONFIGURE.","tags":["timeout","remote","linked server"]},{"name":"network packet size (B)","default":"4096","recommended":"4096 (do not change except in special cases)","unit":"bytes","dynamic":"dynamic","desc":"The packet size used for data transfer over the network. The default is recommended except for special bulk operations transferring very large data; a wrong setting can degrade performance. Takes effect with sp_configure + RECONFIGURE.","tags":["network","network packet","packet"]},{"name":"database compatibility level (ALTER DATABASE)","default":"Depends on server version","recommended":"The latest tested level","unit":"level","dynamic":"dynamic","desc":"Determines which SQL Server version's behaviors and query optimizer model a database runs with. Set per database with ALTER DATABASE ... SET COMPATIBILITY_LEVEL and takes effect immediately; affects optimizer features such as cardinality estimation.","tags":["database","compatibility level","optimizer","compatibility"]},{"name":"MAXDOP (database scoped configuration)","default":"0 (inherits the server setting)","recommended":"A value suited to the database workload","unit":"count","dynamic":"dynamic","desc":"Overrides the server-wide max degree of parallelism setting at the database level. Set with ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP and takes effect immediately; allows different parallelism behavior for different databases.","tags":["parallelism","parallel query","maxdop","database scoped"]},{"name":"tempdb file count (recommendation)","default":"SQL Server 2016+ setup: equal to the number of logical cores, at most 8 (1 before)","recommended":"Equal to the number of logical cores, at most 8 (increase in steps of 4 if needed)","unit":"count","dynamic":"dynamic","desc":"The number of tempdb data files is important for reducing allocation page contention. The general recommendation is to use equally sized files up to the number of cores (max 8). A file added with ALTER DATABASE tempdb ADD FILE is used immediately without a restart; however, if the existing files are full, bring all files to equal size for proportional fill balance. Removing a tempdb file (REMOVE FILE) is often only possible after a restart because the file does not empty.","tags":["tempdb","storage","data files","contention"]},{"name":"Trace Flag 1117 / 1118 (note)","default":"Default behavior for tempdb in SQL 2016+","recommended":"Generally unnecessary in SQL 2016+; on in older versions","unit":"flag","dynamic":"restart","desc":"TF 1117 forces all files in a filegroup to grow together; TF 1118 forces uniform extent allocation instead of mixed extents. Since SQL Server 2016 these behaviors are the default for tempdb, so the flags are generally unnecessary. Requires a restart when added as a startup flag.","tags":["trace flag","tempdb","note"]},{"name":"Trace Flag 3226","default":"Off","recommended":"On (suppresses successful backup messages)","unit":"flag","dynamic":"dynamic","desc":"Prevents successful backup operations from being written to the SQL Server error log and the Windows event log. On systems with frequent log backups, it prevents the logs from filling with unnecessary messages (log spam). Can be enabled immediately at runtime with DBCC TRACEON(3226, -1); to persist across restarts, also add -T3226 as a startup parameter.","tags":["trace flag","backup","backup messages","log"]},{"name":"lock pages in memory (Windows policy note)","default":"Off","recommended":"Granted to the service account in specific scenarios","unit":"policy","dynamic":"restart","desc":"A Windows user rights policy that allows the SQL Server service account to prevent its memory pages from being paged to disk by the operating system. Can reduce performance fluctuations under memory pressure but must be configured carefully. Changing the policy requires a service restart.","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":"Keep the default or ~50% of available RAM","unit":"GB","dynamic":"restart","desc":"Sets, in gigabytes, the amount of memory allocated to the internal cache of the WiredTiger storage engine. By default half of (total RAM - 1GB) is used. Setting the value manually is needed only in special cases and requires a mongod restart.","tags":["memory","memory","cache","wiredtiger"]},{"name":"net.maxIncomingConnections","default":"1000000 (effectively limited by OS limits)","recommended":"According to the workload and the operating system ulimit","unit":"count","dynamic":"restart","desc":"Limits the maximum number of simultaneous incoming connections a mongod or mongos instance accepts. If the value is higher than the operating system's file descriptor/connection limit (ulimit), it has no effect; the OS determines the actual ceiling (visible as current + available in db.serverStatus().connections). The change is made in the configuration file and requires a restart.","tags":["network","network","connections","connection"]},{"name":"operationProfiling.slowOpThresholdMs","default":"100","recommended":"100 or lower depending on the workload","unit":"ms","dynamic":"dynamic","desc":"Sets, in milliseconds, the duration threshold for an operation to be considered slow and recorded by the profiler and in the log. Operations exceeding this threshold are recorded to make diagnosis easier. It can be changed immediately at runtime with db.setProfilingLevel().","tags":["profiling","slow query","slow query","monitoring"]},{"name":"replication.oplogSizeMB","default":"~5% of disk size (max 50GB)","recommended":"Large enough to cover the maintenance window","unit":"MB","dynamic":"dynamic","desc":"Sets, in megabytes, the maximum size of the oplog collection that stores operations on replica set members. A sufficiently large oplog lets lagging members catch up without needing a full resync. It can be changed immediately on a running replica set with the replSetResizeOplog command.","tags":["replication","oplog","replica set"]},{"name":"security.authorization","default":"disabled","recommended":"enabled (role-based access control)","unit":"enabled/disabled","dynamic":"restart","desc":"Enables role-based access control (RBAC); when enabled, clients must authenticate and be authorized. It must always be enabled in production environments. It is set in the configuration file and requires a restart.","tags":["security","security","authorization","authorization","rbac"]},{"name":"net.compression.compressors","default":"snappy,zstd,zlib","recommended":"snappy,zstd (the default is fine)","unit":"list","dynamic":"restart","desc":"Sets the compression algorithms, and their order of preference, that can be used for client and intra-cluster network traffic. It reduces network bandwidth usage at some CPU cost. It is set in the configuration file and requires a restart.","tags":["network","network","compression","compression"]},{"name":"storage.wiredTiger.collectionConfig.blockCompressor","default":"snappy","recommended":"snappy (speed) or zstd (high compression)","unit":"algorithm","dynamic":"restart","desc":"Sets the block compression algorithm WiredTiger uses when writing collection data to disk. snappy favors speed, while zlib and zstd provide higher compression ratios. It applies only to collections created after the setting and requires a restart.","tags":["storage","storage","compression","compression","wiredtiger"]},{"name":"storage.directoryPerDB","default":"false","recommended":"true if you want to separate disks","unit":"true/false","dynamic":"restart","desc":"Stores each database's data in a separate subdirectory, so different databases can be placed on different disk volumes. It can only be enabled on an empty data directory. The change is made in the configuration file and requires a restart.","tags":["storage","storage","directory"]},{"name":"setParameter.cursorTimeoutMillis","default":"600000","recommended":"The default (10 minutes), depending on the workload","unit":"ms","dynamic":"dynamic","desc":"Sets, in milliseconds, how long idle cursors wait before the server closes them automatically. It prevents abandoned cursors from consuming resources. It can be changed immediately at runtime with db.adminCommand({setParameter:1}).","tags":["cursor","timeout","setparameter"]},{"name":"setParameter.transactionLifetimeLimitSeconds","default":"60","recommended":"The default unless long transactions are needed","unit":"seconds","dynamic":"dynamic","desc":"Sets, in seconds, the maximum time a multi-document transaction can stay open before it is aborted automatically. It prevents long-running transactions from pinning resources and the WiredTiger cache. It can be changed immediately with setParameter.","tags":["transaction","transaction","setparameter"]},{"name":"setParameter.maxTransactionLockRequestTimeoutMillis","default":"5","recommended":"The default, depending on lock contention","unit":"ms","dynamic":"dynamic","desc":"Sets, in milliseconds, the maximum time a transaction waits to acquire a lock it needs before timing out. Tuning lock wait behavior prevents transactions from being blocked for too long. It can be changed immediately with setParameter.","tags":["transaction","lock","lock","setparameter"]},{"name":"net.tls.mode","default":"disabled","recommended":"requireTLS (in production)","unit":"mode","dynamic":"restart","desc":"Determines the server's use of TLS/SSL encrypted connections. requireTLS forces all connections to be encrypted; preferTLS and allowTLS provide intermediate modes for transition periods. It is used together with certificate configuration and requires a restart.","tags":["security","security","tls","ssl","encryption"]},{"name":"storage.syncPeriodSecs","default":"60","recommended":"60 (do not change the default)","unit":"seconds","dynamic":"dynamic","desc":"Sets, in seconds, how often WiredTiger takes checkpoints, i.e. the interval between flushes of in-memory data to disk. The default suits most workloads and changing it is generally not recommended. It is set in the configuration file; at runtime it can also be changed without a restart with db.adminCommand({ setParameter: 1, syncdelay: <seconds> }).","tags":["storage","storage","checkpoint","sync"]},{"name":"replication.enableMajorityReadConcern","default":"true","recommended":"true (cannot be changed in 5.0+)","unit":"true/false","dynamic":"restart","desc":"Allows the majority read concern level to be used; this level guarantees reading data that has been written to a majority of members and will not be rolled back. Since MongoDB 5.0 this setting cannot be changed and is always true; in older versions (e.g. in a PSA architecture) it could be set to false. It is set in the configuration file and requires a restart.","tags":["replication","read concern","replica set"]},{"name":"processManagement.fork","default":"false","recommended":"false with systemd, true for a manual daemon","unit":"true/false","dynamic":"restart","desc":"Makes the mongod process fork and run in the background as a daemon. With systemd or a similar service manager this is usually left false, because the service manager handles process management. It is set in the configuration file and affects startup behavior.","tags":["process","process","daemon","fork"]}]},"reference/params-redis.json":{"engineId":"redis","engine":"Redis","order":6,"params":[{"name":"maxmemory","default":"0 (unlimited)","recommended":"~70-75% of physical RAM","unit":"bytes","dynamic":"dynamic","desc":"Sets the maximum amount of memory Redis can use for data. A value of 0 means unlimited usage and can lead to out-of-memory conditions; setting a limit is strongly recommended. Can be changed immediately with CONFIG SET maxmemory.","tags":["memory","memory"]},{"name":"maxmemory-policy","default":"noeviction","recommended":"allkeys-lru for a cache, otherwise noeviction","unit":"policy","dynamic":"dynamic","desc":"The policy that determines which keys Redis evicts, and how, when the maxmemory limit is reached. noeviction returns write errors, while allkeys-lru removes the least recently used keys. Can be changed immediately with CONFIG SET.","tags":["memory","memory","eviction","cache"]},{"name":"maxclients","default":"10000","recommended":"Based on workload and the OS ulimit","unit":"count","dynamic":"dynamic","desc":"Sets the maximum number of concurrent client connections accepted. The operating system's file descriptor limit can cap this value. Can be changed immediately with CONFIG SET maxclients.","tags":["connections","connection","clients"]},{"name":"appendonly","default":"no","recommended":"yes if persistence is required","unit":"yes/no","dynamic":"dynamic","desc":"Enables the AOF (Append Only File) persistence mechanism; when enabled, every write operation is appended to a log file. Offers less data-loss risk than RDB snapshots. Can be enabled immediately with CONFIG SET appendonly yes.","tags":["persistence","persistence","aof"]},{"name":"appendfsync","default":"everysec","recommended":"everysec (balanced)","unit":"policy","dynamic":"dynamic","desc":"Sets how often the AOF file is synced to disk (fsync). always syncs on every write and is the safest but slow; everysec syncs once per second, balancing performance and durability; no leaves syncing to the operating system. Can be changed immediately with CONFIG SET.","tags":["persistence","persistence","aof","fsync"]},{"name":"save","default":"3600 1 300 100 60 10000","recommended":"Workload-dependent, or disabled (AOF only)","unit":"rule","dynamic":"dynamic","desc":"Defines the conditions (a given number of changes within a given time) under which an RDB snapshot is saved to disk. Setting an empty value disables RDB snapshots completely. Can be changed immediately with CONFIG SET save.","tags":["persistence","persistence","rdb","snapshot"]},{"name":"tcp-keepalive","default":"300","recommended":"300 (the default is fine)","unit":"seconds","dynamic":"dynamic","desc":"Sets, in seconds, the interval at which Redis sends TCP keepalive ACKs to client connections. Ensures dead connections are detected and closed and half-open connections are cleaned up. Can be changed immediately with CONFIG SET.","tags":["network","network","tcp","keepalive"]},{"name":"timeout","default":"0","recommended":"0 (never close connections) or a reasonable idle time","unit":"seconds","dynamic":"dynamic","desc":"Sets, in seconds, the maximum time a client connection can stay idle; the connection is closed when it is exceeded. A value of 0 means clients can stay idle indefinitely. Can be changed immediately with CONFIG SET timeout.","tags":["connections","connection","timeout","idle"]},{"name":"hz","default":"10","recommended":"10 (up to 100 on busy systems)","unit":"Hz","dynamic":"dynamic","desc":"Sets how many times per second Redis runs its background tasks (expiring keys, checking client timeouts, etc.). Higher values give more precise control but increase CPU usage. Can be changed immediately with CONFIG SET hz.","tags":["performance","cpu","background"]},{"name":"activedefrag","default":"no","recommended":"yes if memory fragmentation is high","unit":"yes/no","dynamic":"dynamic","desc":"Enables active memory defragmentation; reorganizes data while running to reduce memory fragmentation. Useful on long-running instances with high fragmentation. Can be enabled immediately with CONFIG SET activedefrag yes.","tags":["memory","memory","defragmentation"]},{"name":"maxmemory-samples","default":"5","recommended":"5 (can be raised for more accurate eviction)","unit":"count","dynamic":"dynamic","desc":"Sets the number of keys the LRU and LFU eviction algorithms sample when choosing a key. Higher values come closer to true LRU/LFU behavior but consume slightly more CPU. Can be changed immediately with CONFIG SET.","tags":["memory","memory","eviction","lru"]},{"name":"io-threads","default":"1","recommended":"2-4 on multi-core servers (depending on core count)","unit":"count","dynamic":"restart","desc":"Sets the number of I/O threads used for writing to and reading from client sockets. Can increase throughput on multi-core systems with heavy network traffic. This setting can only be changed in the configuration file and requires a restart.","tags":["performance","threads","io","cpu"]},{"name":"repl-backlog-size","default":"1mb","recommended":"Larger, based on write rate and outage duration","unit":"bytes","dynamic":"dynamic","desc":"Sets the size of the replication buffer kept on the primary for partial resynchronization when a replica connection drops. A large enough buffer lets the replica catch up after short outages without a full sync. Can be changed immediately with CONFIG SET.","tags":["replication","replication","backlog"]},{"name":"repl-diskless-sync","default":"yes","recommended":"yes (on fast networks)","unit":"yes/no","dynamic":"dynamic","desc":"During a full sync, sends RDB data to replicas directly over the socket without writing it to disk. Speeds up synchronization on systems with a disk I/O bottleneck. Can be changed immediately with CONFIG SET repl-diskless-sync.","tags":["replication","replication","diskless","sync"]},{"name":"requirepass","default":"(empty)","recommended":"Set a strong password","unit":"password","dynamic":"dynamic","desc":"Sets the password clients must authenticate with via AUTH before running commands. A strong password must be set to prevent unauthorized access to Redis. Can be changed immediately with CONFIG SET requirepass.","tags":["security","security","authentication","password"]},{"name":"cluster-enabled","default":"no","recommended":"yes if horizontal scaling is required","unit":"yes/no","dynamic":"restart","desc":"Enables Redis Cluster mode; when enabled, data is distributed across multiple nodes via hash slots. Used for horizontal scaling and automatic data partitioning. This setting can only be changed in the configuration file and requires a restart.","tags":["cluster","cluster","scaling","scaling"]},{"name":"cluster-node-timeout","default":"15000","recommended":"15000 or a value tuned to network latency","unit":"ms","dynamic":"dynamic","desc":"In cluster mode, sets in milliseconds the maximum time a node can go without responding before it is considered unreachable (fail). Directly affects failover behavior and cluster stability. Can be changed immediately with CONFIG SET.","tags":["cluster","cluster","timeout","failover"]},{"name":"aof-use-rdb-preamble","default":"yes","recommended":"yes (fast reload)","unit":"yes/no","dynamic":"dynamic","desc":"Enables the hybrid persistence format in which an AOF rewrite writes a compact RDB snapshot at the start of the file followed by incremental AOF commands. This shortens AOF load time on restart. Can be changed immediately with CONFIG SET.","tags":["persistence","persistence","aof","rdb"]}]},"reference/params-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"params":[{"name":"-Xms / -Xmx (JVM heap)","default":"Automatic depending on version (half of RAM)","recommended":"50% of physical RAM, at most ~30-31GB (Xms=Xmx)","unit":"GB","dynamic":"restart","desc":"Sets the initial (-Xms) and maximum (-Xmx) size of the JVM heap; both should be set equal. It is recommended not to exceed half of total RAM or the ~32GB limit where compressed object pointers stop applying. Set in jvm.options and requires a node restart.","tags":["memory","memory","jvm","heap"]},{"name":"indices.memory.index_buffer_size","default":"10%","recommended":"10% (can be increased under heavy indexing load)","unit":"% / bytes","dynamic":"restart","desc":"Sets the share of heap allocated to the memory buffer holding new documents during indexing. Increasing this buffer can improve indexing throughput for write-heavy workloads. Set per node in elasticsearch.yml and requires a restart.","tags":["memory","memory","indexing","buffer"]},{"name":"thread_pool.write.queue_size","default":"10000","recommended":"10000 (increase carefully if queue rejections occur)","unit":"count","dynamic":"restart","desc":"Sets the queue length of the thread pool for index, delete and bulk write operations. When the queue is full, requests are rejected. Raising the value too much can cause memory pressure. Set in elasticsearch.yml and requires a restart.","tags":["thread pool","write","queue","queue"]},{"name":"thread_pool.search.queue_size","default":"1000","recommended":"1000 (increase carefully if search rejections occur)","unit":"count","dynamic":"restart","desc":"Sets the queue length of the thread pool for search operations. When concurrent searches exceed the pool capacity, requests wait in this queue, and are rejected if it fills up. Set in elasticsearch.yml and requires a restart.","tags":["thread pool","search","search","queue"]},{"name":"indices.fielddata.cache.size","default":"unbounded","recommended":"A limit such as 20-40% (for memory protection)","unit":"% / bytes","dynamic":"restart","desc":"Sets the maximum size of the fielddata cache used for sorting and aggregations on text fields. Since the default is unbounded, setting a limit is recommended to prevent memory exhaustion. Set in elasticsearch.yml and requires a restart.","tags":["memory","memory","fielddata","cache"]},{"name":"index.number_of_shards","default":"1","recommended":"Based on data volume; target 10-50GB per shard","unit":"count","dynamic":"restart","desc":"Sets the number of primary shards an index is split into. It is a static index setting (not a restart, but only given at index creation time): it cannot be changed directly on an existing index; change it by producing a new index with a new index + _reindex, or with the _split (multiply) / _shrink (reduce to a divisor) APIs. Too many shards increase cluster overhead, while too few limit scaling.","tags":["index","index","shard","scaling"]},{"name":"index.number_of_replicas","default":"1","recommended":"At least 1 in production (for high availability)","unit":"count","dynamic":"dynamic","desc":"Sets the number of replica shards kept for each primary shard. Replicas provide both high availability and read performance. This setting can be changed instantly on a live index with the _settings API.","tags":["index","index","replica","availability"]},{"name":"index.refresh_interval","default":"1s","recommended":"30s or -1 (temporarily) under heavy indexing","unit":"duration","dynamic":"dynamic","desc":"Sets how often the refresh operation runs to make newly indexed documents visible to search. Increasing the value, or setting it to -1 during bulk loads, significantly improves indexing throughput. Can be changed instantly with the _settings API.","tags":["index","index","refresh","indexing"]},{"name":"indices.breaker.total.limit","default":"95%","recommended":"95% (default; change carefully)","unit":"%","dynamic":"dynamic","desc":"Prevents the node from crashing with OutOfMemory by setting the maximum share of heap that all circuit breakers combined can use. Can be changed cluster-wide instantly with the _cluster/settings API.","tags":["memory","memory","circuit breaker","stability"]},{"name":"cluster.routing.allocation.disk.watermark.low","default":"85%","recommended":"85% (the default is fine)","unit":"% / bytes","dynamic":"dynamic","desc":"When a node's disk usage exceeds this threshold, Elasticsearch stops allocating new shards to that node (existing shards remain). It is the first warning level for preventing a full disk. Can be changed instantly with the _cluster/settings API.","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.high","default":"90%","recommended":"90% (the default is fine)","unit":"% / bytes","dynamic":"dynamic","desc":"When a node's disk usage exceeds this threshold, Elasticsearch starts relocating shards from this node to other nodes. Relieves nodes whose disks are filling up. Can be changed instantly with the _cluster/settings API.","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.flood_stage","default":"95%","recommended":"95% (the default is fine)","unit":"% / bytes","dynamic":"dynamic","desc":"When disk usage exceeds this critical threshold, indices with a shard on the affected node are put into read-only (read-only-allow-delete) mode and writes are blocked. It prevents data corruption before the disk is completely full; in 7.4+ the block is released automatically once disk usage drops below the high watermark. In 8.x, on large disks max_headroom (default 100GB free) is decisive rather than the percentage. Can be changed instantly with the _cluster/settings API.","tags":["disk","allocation","watermark","read-only"]},{"name":"index.translog.durability","default":"request","recommended":"request (durability) or async (performance)","unit":"mode","dynamic":"dynamic","desc":"Sets when the transaction log (translog) is synced (fsync) to disk. request syncs after every request and minimizes data loss; async syncs periodically and improves performance, but carries a risk of data loss on a crash. Can be changed instantly with the _settings API.","tags":["translog","durability","durability","indexing"]},{"name":"indices.recovery.max_bytes_per_sec","default":"40mb","recommended":"Can be increased based on network and disk capacity","unit":"bytes/s","dynamic":"dynamic","desc":"Sets the maximum data transfer rate per node during shard recovery. Increasing the value speeds up recovery but can consume the network and disk resources of the normal workload. Can be changed instantly with the _cluster/settings API.","tags":["recovery","recovery","throttle","network"]},{"name":"action.destructive_requires_name","default":"true","recommended":"true (blocks destructive wildcard operations)","unit":"true/false","dynamic":"dynamic","desc":"Blocks destructive operations such as deleting indices with a wildcard (*) or _all, and requires the index name to be given explicitly. An important safety setting to prevent accidentally deleting all indices. Can be changed instantly with the _cluster/settings API.","tags":["security","security","destructive","safety"]},{"name":"discovery.seed_hosts","default":"(empty / 127.0.0.1)","recommended":"Addresses of the master-eligible nodes in the cluster","unit":"list","dynamic":"restart","desc":"Sets the list of seed host (master-eligible node) addresses a node contacts to join the cluster. Required for cluster discovery and for nodes to find each other. Set in elasticsearch.yml and requires a restart.","tags":["discovery","cluster","cluster","network"]}]},"reference/waits-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"waits":[{"name":"log file sync","kind":"wait","meaning":"The session issued a COMMIT (or ROLLBACK) and is waiting for the LGWR process to write the redo records to the online redo log files and acknowledge.","causes":"Slow redo log I/O, very frequent small commits, an overloaded LGWR process, LGWR delayed by CPU starvation, redo logs on slow disks.","action":"Reduce commit frequency (batch commits), move the redo log files to fast/low-latency disks (SSD), remove unnecessary commits, consider COMMIT NOWAIT/BATCH only for work where loss is tolerable (WARNING: it reduces durability; transactions that appear committed can be lost in a crash), and measure log writer I/O latency (log file parallel write).","tags":["commit","redo","io","lgwr"]},{"name":"db file sequential read","kind":"wait","meaning":"The session is performing a single-block physical read; it is waiting for the block to be brought from disk into the buffer cache, usually during index access or a table block read by ROWID.","causes":"Index scans, inefficient access plans, a small buffer cache, a slow disk subsystem, blocks not found in the cache because of high physical reads.","action":"Tune SQL to reduce unnecessary reads, add suitable indexes, increase the buffer cache size, improve the I/O subsystem, and identify the top reading queries with AWR/ASH.","tags":["io","index","single-block","read"]},{"name":"db file scattered read","kind":"wait","meaning":"The session is performing a multi-block physical read; it is waiting for several blocks to be read from disk, usually during a full table scan or a fast full index scan.","causes":"Full table scans, missing or unused indexes, inefficient query plans, large table scans, incorrect optimizer statistics.","action":"Prevent unnecessary full scans by adding indexes, review query plans, refresh statistics, examine the db_file_multiblock_read_count setting, and consider parallelizing full scans that are really needed.","tags":["io","full-scan","multi-block","read"]},{"name":"direct path read","kind":"wait","meaning":"The session reads blocks directly into the PGA, bypassing the buffer cache; usually seen during parallel queries, large table scans or LOB reads.","causes":"Parallel full table scans, a serial direct path read decision (adaptive direct read), scans of large tables, temporary tablespace (temp) reads, LOB access.","action":"Reduce the need for large scans through query tuning, evaluate the PGA size, optimize sort/hash operations if temp usage is high, and increase I/O bandwidth.","tags":["io","direct-path","parallel","read"]},{"name":"direct path write","kind":"wait","meaning":"The session writes blocks directly to disk without going through the buffer cache; usually seen during direct-path inserts, sort spills or LOB writes.","causes":"Direct-path INSERT (APPEND) operations, sort/hash spills to the temporary tablespace, parallel DML, LOB writes, slow temp or data disks.","action":"Increase the PGA for sort/hash operations to reduce temp spills, put the temp tablespace on fast disks, review unnecessary direct-path loads, and improve I/O performance.","tags":["io","direct-path","temp","write"]},{"name":"buffer busy waits","kind":"wait","meaning":"The session wants to access a block in the buffer cache, but the block is being used by another session in an incompatible mode, so it waits for it to be released.","causes":"Heavy concurrent access to the same block (hot block), insufficient freelists/ASSM, right-hand block contention on a sequential index (right-hand index growth), segments with high DML intensity.","action":"Spread hot blocks (hash partitioning, reverse key index), use ASSM, increase freelists/freelist groups, and improve sequence caching and distribution on tables that generate sequential keys.","tags":["concurrency","buffer","hot-block","contention"]},{"name":"read by other session","kind":"wait","meaning":"Another session is already reading from disk the block this session needs; this session waits for that read of the same block to complete.","causes":"Concurrent physical read requests for the same hot blocks, a small buffer cache, slow I/O, many sessions accessing popular index or table blocks at the same time.","action":"Increase the buffer cache to reduce physical reads, identify hot segments and improve their access plans, lower I/O latency, and eliminate unnecessary full scans.","tags":["io","concurrency","read","hot-block"]},{"name":"latch: cache buffers chains","kind":"wait","meaning":"The session is waiting to acquire the latch that protects a buffer cache hash chain; serialization occurs while locating blocks in the buffer cache.","causes":"Inefficient SQL generating excessive logical I/O, heavy concurrent access to hot blocks, repeated access to the same blocks via nested loops, very popular index root/branch blocks.","action":"Tune SQL to reduce logical reads, eliminate unnecessary nested loop access, spread hot blocks, and find and fix queries producing excessive buffer gets with AWR/ASH.","tags":["latch","concurrency","logical-io","buffer"]},{"name":"cursor: pin S wait on X","kind":"wait","meaning":"The session wants to pin a cursor with a mutex in shared (S) mode, but waits because another session holds the same cursor in exclusive (X) mode.","causes":"A high hard/soft parse rate, popular SQL parsed very frequently, literal queries that do not use bind variables, child cursor proliferation, many sessions parsing the same SQL concurrently.","action":"Reduce parse load by using bind variables, cache cursors in the application (session_cached_cursors), reduce unnecessary re-parsing, and fix SQL with a high version count.","tags":["mutex","parse","cursor","concurrency"]},{"name":"library cache lock","kind":"wait","meaning":"The session is waiting to acquire a lock on the handle of an object in the library cache (package, view, table definition); usually seen during concurrent access to/changes of the object definition.","causes":"DDL on objects in use, packages/views being recompiled constantly, invalid objects, heavy concurrent connections and parsing, privilege (grant) changes.","action":"Run DDL at off-peak times, prevent unnecessary compilations, fix invalid objects, and reduce excessive connect/parse loops on the application side.","tags":["library-cache","concurrency","ddl","parse"]},{"name":"library cache: mutex X","kind":"wait","meaning":"The session is waiting to acquire a mutex to protect an object in the library cache in exclusive (X) mode; usually seen under high parsing and object handle contention.","causes":"Excessive parsing (hard/soft), a wide variety of literal SQL without binds, a high child cursor count, frequent object invalidation, many sessions accessing the same object concurrently.","action":"Use bind variables and cursor caching to reduce parsing, standardize SQL literals, fix high version count problems, and improve application connection behavior.","tags":["mutex","library-cache","parse","concurrency"]},{"name":"enq: TX - row lock contention","kind":"wait","meaning":"The session wants to modify a row locked by another transaction and is waiting for that transaction to commit/roll back and release the row lock.","causes":"Concurrent UPDATE/DELETE of the same rows, long-running transactions that do not commit, a poorly designed application workflow, indirect row locks via unique/foreign keys, hung sessions.","action":"Keep transactions short and commit promptly, redesign the contended access pattern, enforce ordered access with SELECT FOR UPDATE if needed, and find and resolve the blocking session with ASH.","tags":["lock","transaction","row-lock","contention"]},{"name":"enq: TX - allocate ITL entry","kind":"wait","meaning":"The session needs an ITL (Interested Transaction List) slot in a block to work on it, but has to wait because no free ITL slot is left in the block.","causes":"A low INITRANS setting, a small block size, many concurrent transactions in the same block, densely packed blocks receiving heavy DML.","action":"Increase INITRANS for the table/index, raise PCTFREE to put fewer rows per block, spread hot blocks, and rebuild the object if necessary.","tags":["lock","transaction","itl","block"]},{"name":"gc buffer busy acquire","kind":"wait","meaning":"In a RAC environment, the session wants to obtain a block through the global cache but waits because another session on the same instance has already started a global request for the same block.","causes":"Sharing of hot blocks between instances in RAC, insufficient interconnect performance, right-hand contention on a sequential index, a poorly partitioned workload, heavy global cache traffic.","action":"Partition the workload sensibly across instances (application affinity/services), spread hot segments (reverse key/partition), improve interconnect latency, and analyze gc contention with the AWR RAC section and GV$ views.","tags":["rac","global-cache","concurrency","interconnect"]},{"name":"gc cr block busy","kind":"wait","meaning":"In a RAC environment, the session requested a block for consistent read (CR) from another instance and is waiting for that instance to prepare the block and ship it over the interconnect.","causes":"Hot block sharing between instances, a slow or saturated interconnect, heavy redo/commit traffic, a poorly partitioned workload, excessive global cache CR requests.","action":"Improve workload affinity, optimize interconnect bandwidth and latency, reduce hot blocks, review commit and DML patterns, and tune RAC.","tags":["rac","global-cache","cr","interconnect"]},{"name":"log file switch (checkpoint incomplete)","kind":"wait","meaning":"The session wants to switch to a new redo log, but the checkpoint for the next log file has not completed yet, so LGWR cannot reuse that log and all redo generation stops.","causes":"Redo log files that are too small or too few, slow DBWR/checkpoint I/O, a high redo generation rate, an insufficient number of log groups.","action":"Enlarge the redo log files and add groups, improve DBWR/checkpoint I/O performance, make application optimizations that reduce redo generation, and monitor log switch frequency.","tags":["redo","checkpoint","log-switch","io"]},{"name":"free buffer waits","kind":"wait","meaning":"The session is looking for a clean/free buffer in the buffer cache to read a new block, but waits for a free buffer because DBWR cannot write dirty buffers to disk fast enough.","causes":"Slow DBWR write I/O, a small buffer cache, rapid dirty buffer generation from heavy DML, insufficient checkpoint speed, a saturated disk subsystem.","action":"Speed up datafile write I/O, increase the buffer cache size, evaluate the number of DBWR processes (db_writer_processes), and review DML intensity and checkpoint settings.","tags":["buffer","dbwr","io","checkpoint"]},{"name":"Buffer Cache Hit Ratio","kind":"metric","meaning":"The ratio of requested blocks served from the buffer cache without going to disk; shows what percentage of logical reads did not require a physical read.","causes":"A low ratio is usually due to a small buffer cache, inefficient full scans, a cold cache or high physical reads. On its own it can be misleading; a high ratio can also hide bad SQL.","action":"Do not target the ratio by itself; evaluate it together with wait events and physical read trends. Tune inefficient SQL, reduce unnecessary logical reads, and size the cache for the workload.","tags":["metric","buffer-cache","ratio","memory"]},{"name":"Average Active Sessions","kind":"metric","meaning":"The average number of sessions active at the same time (running on CPU or waiting on an event) over an interval; summarizes the overall load and bottleneck intensity of the database.","causes":"A high AAS can be due to CPU saturation, I/O waits, lock/latch contention or increased concurrency. Values approaching or exceeding the CPU core count point to a resource bottleneck.","action":"Compare AAS with the CPU core count, find the dominant wait class (CPU/IO/concurrency) with ASH, tune the SQL and sessions generating the most load, and resolve the resource or concurrency problem.","tags":["metric","load","ash","sessions"]}]},"reference/waits-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"waits":[{"name":"LWLock:WALWrite","kind":"wait","meaning":"The session is waiting for the lightweight lock required to write WAL buffers to disk; usually caused by serialization of WAL writes during commit.","causes":"Slow WAL disk I/O, very frequent/small commits, high write load, insufficient WAL write bandwidth, many backends writing WAL at the same time.","action":"Move WAL to a fast/low-latency disk, group commits (evaluate synchronous_commit, commit_delay), increase wal_buffers, reduce unnecessary write operations.","tags":["wal","lwlock","commit","io"]},{"name":"LWLock:BufferContent","kind":"wait","meaning":"The session is waiting for the lightweight lock of a buffer in the shared buffer pool that protects the page content; indicates contention from concurrent access to the page.","causes":"Heavy concurrent reads/writes on the same page (hot page), right-edge contention on sequential indexes, small tables receiving heavy updates, high concurrency.","action":"Spread hot pages (fillfactor setting, partitioning), reduce sequential key contention, restructure heavily updated rows, review the concurrent access pattern.","tags":["lwlock","buffer","concurrency","contention"]},{"name":"Lock:transactionid","kind":"wait","meaning":"The session is waiting for another transaction, which has locked the row it wants to modify, to finish (commit/rollback); the lock wait is on the target transaction's ID.","causes":"Concurrent UPDATE/DELETE on the same rows, long-running transactions that do not commit, idle-in-transaction sessions, a poorly designed contended workflow.","action":"Keep transactions short and commit promptly, limit idle-in-transaction sessions (idle_in_transaction_session_timeout), redesign the contended access pattern, find the blocking query with pg_stat_activity.","tags":["lock","transaction","row-lock","contention"]},{"name":"Lock:tuple","kind":"wait","meaning":"The session is waiting to acquire a lock on a specific tuple (row); seen when multiple sessions queue up to update/lock the same row.","causes":"Concurrent SELECT FOR UPDATE/UPDATE on the same row, hot row contention (e.g. counter rows), row locks held for a long time, high concurrency.","action":"Spread or queue hot row updates, shorten transaction duration, redesign contended patterns such as counters (e.g. batched updates), consider FOR UPDATE SKIP LOCKED.","tags":["lock","tuple","row-lock","contention"]},{"name":"Lock:relation","kind":"wait","meaning":"The session is waiting to acquire a table-level lock on a table/index (relation); usually seen with contention between incompatible lock modes of DDL and DML.","causes":"DDL such as ALTER/DROP/TRUNCATE while queries are running, VACUUM FULL requiring an aggressive lock, concurrent access to the same table in incompatible modes, long-running queries.","action":"Run DDL during quiet periods with a short lock_timeout, use regular autovacuum instead of VACUUM FULL, monitor long queries, review operations that serialize access to the table.","tags":["lock","relation","ddl","table-lock"]},{"name":"IO:DataFileRead","kind":"wait","meaning":"The session is waiting for a data file page to be read from disk into shared buffers; this is the physical read wait that occurs when the requested page is not in cache.","causes":"Small shared_buffers, inefficient query plans and large scans, a cold cache, a slow disk subsystem, insufficient index usage.","action":"Tune shared_buffers and the OS page cache for the workload, fix inefficient queries and missing indexes, improve I/O performance, set effective_cache_size correctly.","tags":["io","read","buffer","disk"]},{"name":"IO:WALSync","kind":"wait","meaning":"The session is waiting for WAL to be durably written to disk (fsync/synchronization); for commit durability it waits for WAL to be physically flushed.","causes":"Slow storage fsync latency, very frequent commits, a low-performance WAL disk, a disk without write-back cache, high write load.","action":"Move the WAL disk to fast storage with low fsync latency, group commits (commit_delay/commit_siblings), evaluate synchronous_commit against business requirements, improve the storage layer.","tags":["wal","io","fsync","commit"]},{"name":"IO:DataFileWrite","kind":"wait","meaning":"The session is waiting for a dirty data page to be written to disk; usually seen when the backend itself writes to find a clean page in the buffer pool.","causes":"Slow data disk write I/O, checkpoint congestion, an insufficient background writer, a small buffer pool, the backend writing itself due to high write load.","action":"Smooth out checkpoints (checkpoint_completion_target, max_wal_size), tune the background writer (bgwriter_lru_maxpages), improve disk write performance, review shared_buffers.","tags":["io","write","checkpoint","bgwriter"]},{"name":"Client:ClientRead","kind":"wait","meaning":"The backend is waiting for the next command/data from the client; seen when the server is idle, waiting for the client to send data.","causes":"A slow network, idle-in-transaction sessions, the client application sending/receiving data slowly, large result sets consumed slowly, connection pool problems.","action":"Examine application-side and network latency, limit idle-in-transaction sessions, consume results efficiently (e.g. cursor/batch), improve the connection pool. This wait is usually not a database bottleneck.","tags":["client","network","idle","application"]},{"name":"IPC:ProcArrayGroupUpdate","kind":"wait","meaning":"The session is waiting for the group leader backend to mark its transaction's end in the ProcArray as a group; this is serialization of ProcArray updates during commit.","causes":"Very high commit/transaction rate, excessive concurrent short transactions, high connection count, heavy snapshot/ProcArray access.","action":"Manage transaction rate and concurrent connection count (connection pool), reduce unnecessary short transactions, limit the connection count under very high concurrency, balance the workload.","tags":["ipc","commit","procarray","concurrency"]},{"name":"LWLock:LockManager","kind":"wait","meaning":"The session is waiting for the lock manager lightweight lock that protects the heavyweight lock table; caused by serialization of lock acquire/release operations.","causes":"Queries touching many tables/partitions (exceeding the fast-path lock limit), high concurrency, over-partitioned tables, a workload acquiring/releasing many locks.","action":"Reduce the number of partitions/tables touched per query (partition pruning), review over-partitioning, manage concurrency with a connection pool, simplify operations that take unnecessary locks.","tags":["lwlock","lock-manager","concurrency","partitioning"]},{"name":"Timeout:VacuumDelay","kind":"wait","meaning":"The vacuum/autovacuum process is deliberately paused due to cost-based vacuum delay; an intentional wait to limit I/O load.","causes":"Throttling due to vacuum_cost_delay/autovacuum_vacuum_cost_delay settings, a low cost_limit, vacuum cleaning many tables, non-aggressive autovacuum settings.","action":"If vacuum is too slow and bloat/dead tuples are accumulating, increase cost_limit and lower cost_delay, tune the number of autovacuum workers and thresholds, balance against I/O capacity.","tags":["vacuum","timeout","throttle","maintenance"]},{"name":"cache hit ratio","kind":"metric","meaning":"The ratio of block requests served from shared buffers without going to disk; calculated from blks_hit and blks_read in pg_stat_database.","causes":"A low ratio usually indicates small shared_buffers, inefficient large scans, a cold cache or insufficient memory. Because the OS page cache also serves reads, the ratio alone can be misleading.","action":"Interpret the ratio together with wait events and I/O trends, tune shared_buffers and effective_cache_size, fix inefficient queries; if persistently low, examine memory and query plans.","tags":["metric","cache","ratio","memory"]},{"name":"replication lag","kind":"metric","meaning":"How far a standby/replica server lags behind in applying the WAL produced by the primary; measured in bytes (LSN difference) or seconds of delay.","causes":"A slow network, heavy query load on the standby (especially replay conflicts), slow standby disk I/O, a high primary write rate, long-running read queries delaying recovery.","action":"Monitor write/flush/replay lag with pg_stat_replication, improve network and standby I/O performance, manage conflicting long queries (hot_standby_feedback/max_standby_streaming_delay), review the need for synchronous replication.","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":"The thread is waiting for the mutex protecting the InnoDB buffer pool LRU list; it indicates serialization during page reads/eviction. In 8.0 the old single buf_pool_mutex was split; the other related instruments are buf_pool_free_list_mutex, buf_pool_flush_state_mutex and buf_pool_zip_mutex.","causes":"Heavy buffer pool access under high concurrency, a single buffer pool instance, frequent page reads/eviction, an undersized buffer pool.","action":"Split the mutex by increasing innodb_buffer_pool_instances, size innodb_buffer_pool_size to the working set, tune queries to reduce physical reads, and manage concurrency.","tags":["innodb","mutex","buffer-pool","concurrency"]},{"name":"wait/io/file/innodb/innodb_data_file","kind":"wait","meaning":"The thread is waiting for physical file I/O (read/write) against InnoDB data files (ibd/ibdata) to complete.","causes":"A slow disk subsystem, many physical reads due to a small buffer pool, large table scans, checkpoint/flush pressure, insufficient I/O capacity.","action":"Grow the buffer pool, fix inefficient queries and missing indexes, move to fast storage (SSD/NVMe), set innodb_io_capacity to match the storage, and review flush behavior.","tags":["innodb","io","datafile","disk"]},{"name":"wait/lock/table/sql/handler","kind":"wait","meaning":"The thread is waiting to acquire the table-level handler lock (table lock) in the SQL layer; seen with concurrent access to a table in incompatible modes.","causes":"Operations requiring a table lock, storage engines with table-level locking such as MyISAM, LOCK TABLES statements, DDL vs. DML contention, long-running queries.","action":"Use InnoDB with row-level locking, reduce LOCK TABLES usage, run DDL during low activity, optimize long queries, and review patterns that serialize access to the table.","tags":["lock","table-lock","sql-layer","contention"]},{"name":"wait/synch/mutex/sql/LOCK_open","kind":"wait","meaning":"The thread is waiting for the LOCK_open mutex, which protects open table definitions/the object dictionary; it is the serialization of table open/close operations.","causes":"Constant access to many different tables, a small table_open_cache/table_definition_cache, frequent table opening and closing, high concurrency, too many tables/partitions.","action":"Increase table_open_cache and table_open_cache_instances, tune table_definition_cache, review an excessive number of tables/partitions, and reduce unnecessarily frequent table opens.","tags":["mutex","table-cache","sql-layer","concurrency"]},{"name":"row lock waits","kind":"wait","meaning":"A transaction is waiting for an InnoDB row lock held by another transaction to be released; it indicates row-level lock contention.","causes":"Concurrent UPDATE/DELETE on the same rows, long-running transactions that do not commit, wide lock ranges (gap locks) due to missing indexes, hot row contention, deadlock cycles.","action":"Keep transactions short and commit promptly, narrow the locked row range by adding a suitable index, redesign contending patterns, monitor deadlocks (SHOW ENGINE INNODB STATUS), and review the isolation level.","tags":["innodb","row-lock","transaction","contention"]},{"name":"log file waits","kind":"wait","meaning":"The thread is waiting for space to be freed in the InnoDB redo log buffer or for the redo log to be written to disk; usually seen under log buffer flush/checkpoint pressure.","causes":"A small redo log (8.0.30+ innodb_redo_log_capacity; before that innodb_log_file_size × innodb_log_files_in_group), a slow log disk, a high write rate, frequent flushes (innodb_flush_log_at_trx_commit=1 with frequent commits), an insufficient log buffer.","action":"Increase the redo capacity (8.0.30+ innodb_redo_log_capacity, dynamic; before that innodb_log_file_size, requires a restart) and innodb_log_buffer_size, move the redo log to a fast disk, review the commit pattern, and evaluate the flush setting against durability requirements.","tags":["innodb","redo","log","io"]},{"name":"wait/io/file/innodb/innodb_log_file","kind":"wait","meaning":"The thread is waiting for physical I/O (write and fsync) against the InnoDB redo log files to complete; it waits for redo to reach disk for commit durability.","causes":"Slow storage fsync latency, very frequent commits with innodb_flush_log_at_trx_commit=1, a low-performance log disk, a high write load.","action":"Move the redo log disk to low-latency fast storage, group commits where possible, evaluate innodb_flush_log_at_trx_commit against business requirements, and improve the storage layer.","tags":["innodb","redo","fsync","commit"]},{"name":"wait/synch/mutex/innodb/log_writer_mutex","kind":"wait","meaning":"The thread is waiting for the mutex protecting the structures of the InnoDB redo log writer thread. The redo subsystem was redesigned in 8.0; instead of the old single log_sys_mutex there are separate instruments such as log_writer_mutex, log_flusher_mutex and log_checkpointer_mutex.","causes":"A very high write/commit rate, heavy redo generation, contention on the log writer/flusher threads, a slow log disk, a small log buffer.","action":"Balance write load and commit frequency, consider batch operations that reduce redo generation, tune the log buffer, and take advantage of the redo log scalability improvements of your MySQL version.","tags":["innodb","mutex","redo","concurrency"]},{"name":"Innodb_buffer_pool_wait_free","kind":"metric","meaning":"The number of times a thread found no free page in the buffer pool and had to wait for dirty pages to be flushed to get a free page; a steady increase indicates buffer pool pressure.","causes":"A small buffer pool, slow flush/write I/O, a low innodb_io_capacity, fast dirty page generation under a high write load, checkpoint lag.","action":"Increase innodb_buffer_pool_size, set innodb_io_capacity/io_capacity_max to match the storage, improve write I/O performance, and review the dirty page ratio (innodb_max_dirty_pages_pct).","tags":["metric","innodb","buffer-pool","flush"]},{"name":"Threads_running","kind":"metric","meaning":"The number of threads that are not idle and are actively running queries at that moment; it shows the instantaneous concurrent execution on the server and the likely intensity of contention.","causes":"A sudden spike in queries, slow queries piling up, threads stalling due to lock/mutex contention, CPU saturation, a thundering herd effect.","action":"When Threads_running spikes, find slow/blocked queries (SHOW PROCESSLIST), tune queries, limit concurrency with a connection pool and thread pool, and remove the source of contention.","tags":["metric","concurrency","threads","load"]},{"name":"Innodb_row_lock_time_avg","kind":"metric","meaning":"The average time (milliseconds) spent waiting to acquire a row lock; it shows the severity of row-level lock contention.","causes":"Heavy concurrent access to the same rows, long transactions, wide lock ranges due to missing indexes, waits approaching deadlock/lock wait timeout.","action":"Shorten transaction duration, narrow the lock scope with suitable indexes, redesign the contending access pattern, and review innodb_lock_wait_timeout and the isolation level.","tags":["metric","innodb","row-lock","contention"]},{"name":"history list length","kind":"metric","meaning":"The number of transactions in the InnoDB undo log that have not yet been purged (holding old row versions); it shows the accumulation of old versions kept for MVCC.","causes":"Long-running open transactions (especially long read transactions), a slow purge thread, a high DML rate, uncommitted/idle transactions.","action":"Identify and terminate long-running and idle transactions, tune the number of purge threads (innodb_purge_threads), and keep transactions short in the application; a high HLL leads to performance and bloat problems.","tags":["metric","innodb","undo","mvcc"]},{"name":"Innodb_data_reads","kind":"metric","meaning":"The total number of physical read requests InnoDB performed from disk; it shows the read load that the buffer pool could not serve and that went to disk.","causes":"A small buffer pool, a cold cache, inefficient queries and large scans, missing indexes, a working set larger than memory.","action":"Increase the buffer pool size, fix inefficient queries and missing indexes, monitor the physical read trend together with the buffer pool hit ratio, and improve I/O performance.","tags":["metric","innodb","io","read"]},{"name":"Innodb_buffer_pool_read_requests","kind":"metric","meaning":"The total number of logical read requests served from the InnoDB buffer pool; used together with Innodb_buffer_pool_reads (the number of pages not found in the buffer pool and read from disk) to calculate the buffer pool hit ratio.","causes":"A high value indicates intense query activity. If the ratio of Innodb_buffer_pool_reads to this value is high (low hit ratio), there is a memory shortage or inefficient access.","action":"Monitor the buffer pool hit ratio (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests); if it is low, grow the buffer pool and optimize queries, and tune inefficient queries that generate excessive logical reads.","tags":["metric","innodb","buffer-pool","logical-io"]}]},"reference/waits-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"waits":[{"name":"CXPACKET","kind":"wait","meaning":"Wait that occurs while threads synchronize during parallel query execution; seen while work distribution/matching between parallel plan operators is pending.","causes":"A high degree of parallelism, uneven work distribution (skew), unlimited/high MAXDOP (0) and a low cost threshold for parallelism (default 5), large parallel scans due to missing indexes, inefficient plans.","action":"Tune cost threshold for parallelism and MAXDOP to the workload, fix missing indexes and statistics, and do not treat CXPACKET as a problem on its own; separate out the real wait using CXCONSUMER and target inefficient parallel plans.","tags":["parallelism","cxpacket","query","cpu"]},{"name":"CXCONSUMER","kind":"wait","meaning":"In a parallel plan, the consumer thread waiting for data to arrive from producer threads; usually a harmless parallelism wait.","causes":"The consumer thread waiting during normal parallel execution, uneven data flow, delays on the producer side, inefficient parallel plans.","action":"CXCONSUMER is mostly benign and needs no action on its own; to find the real bottleneck, focus on the underlying wait types on the producer side (IO, LCK, etc.) and on plan efficiency.","tags":["parallelism","cxconsumer","query","benign"]},{"name":"PAGEIOLATCH_SH","kind":"wait","meaning":"The thread is waiting on a latch to protect a data page in shared (SH) mode while it is read from disk into the buffer pool; a physical I/O wait for reads.","causes":"Slow disk I/O, insufficient memory (small buffer pool), large scans and missing indexes, a cold cache, heavy physical read load.","action":"Evaluate memory (max server memory) and the buffer pool, fix inefficient queries and missing indexes, improve storage I/O latency, and identify and tune the queries that read the most.","tags":["io","pageiolatch","read","buffer-pool"]},{"name":"PAGEIOLATCH_EX","kind":"wait","meaning":"The thread is waiting on a latch to protect a data page being read from disk in exclusive (EX) mode; it usually waits for the physical I/O of a page that will be read for modification.","causes":"Slow disk I/O, a heavy write workload, insufficient memory, checkpoint/lazy writer pressure, operations making large data changes.","action":"Improve storage write/read performance, increase buffer pool memory, make heavy-writing operations batched and efficient, and separate and measure tempdb and data disk I/O.","tags":["io","pageiolatch","write","buffer-pool"]},{"name":"PAGELATCH_UP","kind":"wait","meaning":"The thread is waiting on a latch to protect a page already in memory in update (UP) mode; this is not physical I/O but contention for concurrent access to an in-memory page.","causes":"Contention on hot in-memory pages, especially tempdb system page (PFS/GAM/SGAM) contention, heavy concurrent tempdb object creation, last-page (hot page) insert contention.","action":"Split tempdb into equally sized data files matching the core count, reduce tempdb contention, spread hot-page patterns, and eliminate unnecessary tempdb usage (spills, etc.).","tags":["pagelatch","tempdb","concurrency","contention"]},{"name":"PAGELATCH_EX","kind":"wait","meaning":"The thread is waiting on a latch to protect an in-memory page in exclusive (EX) mode; usually seen with last-page insert contention on tables with ascending (sequential) keys.","causes":"Last-page insert contention on indexes with sequential/ascending keys, tempdb system page contention, heavy concurrent writes to hot pages, a high insert rate.","action":"For last-page contention, use OPTIMIZE_FOR_SEQUENTIAL_KEY = ON (SQL Server 2019+) or key distribution (hash/GUID/reverse index), split tempdb into multiple files, and redesign the heavy insert pattern.","tags":["pagelatch","last-page-insert","concurrency","contention"]},{"name":"LCK_M_X","kind":"wait","meaning":"The thread is waiting to acquire an exclusive lock on a resource (row/page/table); it usually waits for other locks to be released to modify data (UPDATE/DELETE/INSERT).","causes":"Long-running transactions, concurrent changes to the same resources, lock escalation, wide lock scope due to missing indexes, blocking sessions.","action":"Keep transactions short and commit promptly, narrow the lock scope with appropriate indexes, find the blocking query (sys.dm_exec_requests/blocking), review lock escalation and the isolation level, and consider RCSI if needed.","tags":["lock","exclusive","blocking","transaction"]},{"name":"LCK_M_U","kind":"wait","meaning":"The thread is waiting to acquire an update lock on a resource; while an UPDATE scans, reading a row and preparing to modify it, it waits for other locks to be released.","causes":"Concurrent updates to the same rows, long transactions, many rows scanned and locked due to missing indexes, blocking sessions, lock contention.","action":"Add appropriate indexes for UPDATE statements to reduce the number of rows scanned/locked, shorten transaction duration, analyze the blocking chain, and evaluate isolation level and RCSI options.","tags":["lock","update-lock","blocking","transaction"]},{"name":"WRITELOG","kind":"wait","meaning":"The thread is waiting for transaction log records to be written to disk (log flush); it usually waits at commit time for the log to become durable.","causes":"Slow log disk I/O, very frequent/small commits, high write load, contention between the log disk and the data disk, the effect of synchronous Always On replication.","action":"Move the transaction log to a fast, low-latency disk, batch commits, reduce unnecessarily frequent commits, consider delayed durability (if appropriate), and measure log I/O latency.","tags":["log","writelog","commit","io"]},{"name":"ASYNC_NETWORK_IO","kind":"wait","meaning":"SQL Server is ready to send results but is waiting on the network/client side because the client is not consuming the data fast enough; usually an application-side bottleneck.","causes":"The client application processing results slowly (row-by-row consumption), large result sets, a slow network, RBAR processing in the application, insufficient client resources.","action":"Consume results quickly and in bulk in the application, reduce unnecessarily large result sets (only the columns/rows needed), and check network latency. This is usually a client/network problem, not a SQL Server one.","tags":["network","client","application","async"]},{"name":"SOS_SCHEDULER_YIELD","kind":"wait","meaning":"Wait that occurs when a thread uses up its CPU time slice (quantum) and voluntarily yields the scheduler; mostly indicates CPU pressure or CPU-intensive processing.","causes":"Intensive CPU-bound queries, high concurrency and CPU saturation, spinlock contention, inefficient queries performing many logical reads.","action":"Find and tune CPU-consuming inefficient queries (missing index, bad plan), reduce logical reads, evaluate CPU capacity and concurrency, and if it stays high, investigate the hardware/plan bottleneck.","tags":["cpu","scheduler","cpu-bound","yield"]},{"name":"RESOURCE_SEMAPHORE","kind":"wait","meaning":"The query is waiting for the memory grant it needs to run; execution cannot start until enough query memory is free.","causes":"Queries requesting excessive memory (large sorts/hashes), wrong (inflated) cardinality estimates, insufficient server memory, many concurrent memory-intensive queries, stale statistics.","action":"Update statistics to correct excessive memory grant estimates, optimize queries that need large sorts/hashes (index, filter), review Resource Governor and max server memory settings, and limit concurrent memory-intensive load.","tags":["memory","memory-grant","query","resource"]},{"name":"THREADPOOL","kind":"wait","meaning":"Tasks waiting for a free thread from the thread pool because no worker thread is available to run a new task; a critical wait type indicating serious worker thread exhaustion.","causes":"Threads held by long blocking, very high concurrent connections, insufficient max worker threads, heavy parallelism, deadlocks/long lock chains.","action":"Resolve the blocking chain urgently, manage concurrent connections with a connection pool, eliminate long-held locks, review the max worker threads setting; connect via the DAC to find the root cause.","tags":["threadpool","worker-thread","blocking","critical"]},{"name":"HADR_SYNC_COMMIT","kind":"wait","meaning":"On an Always On synchronous replica, the primary waiting for the log record to be hardened on the secondary replica before committing the transaction; synchronous commit latency.","causes":"A slow/high-latency network to the secondary replica, slow log disk I/O on the secondary, an overloaded secondary, a geographically remote replica, a high write rate.","action":"Improve network latency and bandwidth between replicas, increase secondary log disk performance, balance the secondary load, and if latency is critical, review the synchronous/asynchronous replica topology.","tags":["alwayson","replication","sync-commit","hadr"]},{"name":"Page Life Expectancy","kind":"metric","meaning":"Metric showing the average number of seconds a data page stays in the buffer pool (without being evicted from memory); an important indicator of memory pressure.","causes":"A low or rapidly falling PLE means memory pressure, inefficient large scans, a working set larger than memory, or insufficient memory. NUMA nodes should also be evaluated individually.","action":"Look at the PLE trend and sudden drops instead of a fixed threshold, evaluate buffer pool memory (max server memory), tune inefficient queries that consume a lot of memory, and monitor PLE per NUMA node.","tags":["metric","memory","buffer-pool","ple"]},{"name":"Buffer cache hit ratio","kind":"metric","meaning":"The ratio of data page requests satisfied from the buffer cache without going to disk; shows the percentage of pages read from memory.","causes":"On modern systems this ratio is usually very high (thanks to read-ahead), and a low value indicates significant memory pressure. A high ratio alone does not guarantee good performance.","action":"Interpret this metric not on its own but together with PLE, PAGEIOLATCH waits and physical read trends; if there are signs of memory pressure, increase buffer pool memory and fix inefficient queries.","tags":["metric","memory","buffer-pool","ratio"]}]},"reference/waits-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"waits":[{"name":"WiredTiger cache used %","kind":"metric","meaning":"How much of the WiredTiger storage engine's allocated internal cache is in use; shows the pressure of keeping data in memory (the default target threshold is around 80%).","causes":"A working set larger than the cache, an undersized cache, large scans, heavy reads/writes, many open transactions.","action":"Evaluate the WiredTiger cache size (storage.wiredTiger.engineConfig.cacheSizeGB) and RAM, add indexes that shrink the working set, and monitor eviction pressure and latency as usage approaches the 80% target and the 95% eviction threshold.","tags":["metric","wiredtiger","cache","memory"]},{"name":"WiredTiger cache dirty %","kind":"metric","meaning":"The percentage of data in the WiredTiger cache not yet written to disk (dirty); shows write pressure and checkpoint/eviction load (the dirty eviction threshold is usually around 20%).","causes":"High write load, slow disk I/O, large/long-running writes, checkpoint pressure, the cache unable to keep up with the write rate.","action":"If the dirty percentage approaches the eviction threshold (20%), improve disk write performance, balance the write load, evaluate the cache size, and review long writes and the checkpoint interval.","tags":["metric","wiredtiger","cache","write"]},{"name":"tickets available (read/write)","kind":"metric","meaning":"The remaining number of concurrency tickets WiredTiger grants for concurrent read and write operations; if it drops to zero, new operations queue for a ticket.","causes":"Slow operations holding tickets for a long time, a disk I/O bottleneck, locking/contention, sudden load spikes, tickets exhausted by long-running queries.","action":"If available tickets approach zero and a queue forms, optimize slow queries (add indexes), improve disk I/O, balance the load; ticket exhaustion is usually a symptom of a deeper I/O/contention bottleneck.","tags":["metric","wiredtiger","concurrency","tickets"]},{"name":"global lock queue","kind":"metric","meaning":"The queue length of read (readers) and write (writers) operations waiting for a global/database/collection lock; shows the number of operations waiting under lock contention.","causes":"Long-held locks, heavy write contention, long-running operations, DDL operations and the brief exclusive collection lock taken at the start/end of an index build (4.2+ hybrid builds; foreground builds no longer exist), locks held long due to slow I/O.","action":"If the queue is persistently full, find and optimize long-running operations (currentOp), schedule index builds and other DDL for off-peak hours, improve I/O, and review contending write patterns.","tags":["metric","lock","queue","concurrency"]},{"name":"replication lag (secondary)","kind":"metric","meaning":"How far (in seconds) a secondary member lags behind in applying the primary's oplog; shows the data delay between replica set members.","causes":"Slow network, slow disk I/O on the secondary, heavy read load on the secondary, a high primary write rate, insufficient secondary hardware, long-running operations.","action":"Monitor lag with rs.printSecondaryReplicationInfo, improve network and secondary I/O performance, balance secondary load, keep the oplog size sufficient, and review the write concern requirement.","tags":["metric","replication","lag","secondary"]},{"name":"oplog window","kind":"metric","meaning":"The time span (hours) between the oldest and newest operations held in the oplog; shows how long a secondary can fall behind without losing sync.","causes":"A small oplog size, a sudden high write rate, secondaries offline for a long time, increased replication load.","action":"If the oplog window has shrunk, increase the oplog size, keep secondary lag below the window, and review write load and backup/sync requirements; if the window is missed, a full resync is required.","tags":["metric","replication","oplog","window"]},{"name":"page faults","kind":"metric","meaning":"The number of page faults (especially hard faults) where the operating system must fetch data pages not in memory from disk; shows data spilling out of memory to disk.","causes":"A working set larger than RAM, insufficient memory, large scans, cache pressure, memory consumption by other processes.","action":"Evaluate server memory/working set, reduce scanned data by adding indexes, balance the WiredTiger cache and OS memory, and monitor increases in hard page faults together with I/O latency.","tags":["metric","memory","page-fault","io"]},{"name":"connections current","kind":"metric","meaning":"The number of client connections currently open to the server; shows connection pool usage and how close it is to the maximum.","causes":"Misconfigured connection pools, connection leaks, a sudden increase in clients, long-held connections, excessive connections from microservices.","action":"Compare the connection count with the server limit (maxIncomingConnections), size the connection pool correctly on the application side, fix connection leaks and monitor sudden spikes.","tags":["metric","connections","pool","capacity"]},{"name":"scanAndOrder / COLLSCAN ratio","kind":"metric","meaning":"The ratio of queries that fully scan a collection without an index (COLLSCAN) or sort in memory (scanAndOrder); an indicator of index inefficiency.","causes":"Missing or unsuitable indexes, sort keys that do not match the query, broad unfiltered queries, poor query patterns.","action":"Review slow queries and their plans (explain), add compound indexes matching query and sort patterns, fix queries producing COLLSCAN and scanAndOrder, and monitor the examined/returned document ratio.","tags":["metric","index","collscan","query"]},{"name":"queued operations","kind":"metric","meaning":"The number of read and write operations queued (not yet able to run actively) because they are waiting for a lock or resource; shows that the system cannot meet demand.","causes":"Lock contention, ticket exhaustion, an I/O bottleneck, long-running queries, sudden load spikes, slow disks.","action":"If the queue grows, inspect and optimize active and long-running operations (currentOp), improve indexes and I/O, balance the load, and find the root cause together with ticket and lock contention metrics.","tags":["metric","queue","concurrency","load"]}]},"reference/waits-redis.json":{"engineId":"redis","engine":"Redis","order":6,"waits":[{"name":"keyspace hit ratio","kind":"metric","meaning":"The ratio of key lookups found in memory, calculated from keyspace_hits and keyspace_misses; shows how effectively Redis works as a cache.","causes":"A low ratio indicates eviction due to insufficient memory, short TTLs, a cold cache, many lookups of unique keys that were never cached, or poor key design.","action":"Monitor the hit ratio as hits/(hits+misses); if it is low, review memory/eviction policy and TTLs, pre-warm frequently accessed data, and improve key design and caching strategy.","tags":["metric","cache","hit-ratio","keyspace"]},{"name":"evicted_keys","kind":"metric","meaning":"The number of keys removed (deleted) from memory by the eviction policy when the maxmemory limit is reached; a direct indicator of memory pressure.","causes":"Exceeding the maxmemory limit, insufficient memory, accumulation of keys without TTL, a data volume larger than memory, an unsuitable eviction policy.","action":"With steadily increasing eviction, add memory or reduce data volume, choose maxmemory-policy (e.g. allkeys-lru/volatile-lru) to suit the workload, and review TTL usage and big keys.","tags":["metric","memory","eviction","maxmemory"]},{"name":"blocked_clients","kind":"metric","meaning":"The number of clients waiting for an event/data with blocking commands such as BLPOP, BRPOP, WAIT and XREAD BLOCK; normal in queue consumer patterns, but an abnormal increase is a warning sign.","causes":"Consumers using blocking list/stream commands, no data arriving from the producer side, waiting for replica acknowledgment with WAIT, consumer-producer imbalance.","action":"Compare the number of blocked clients with the expected consumer model; on an abnormal increase, investigate the producer side and replication acknowledgments, and balance the queue consumption rate.","tags":["metric","clients","blocking","queue"]},{"name":"mem_fragmentation_ratio","kind":"metric","meaning":"The ratio of physical memory the operating system has allocated to Redis (RSS) to the memory Redis actually uses; shows memory fragmentation or swapping.","causes":"Values clearly above 1 indicate memory fragmentation, while values below 1 indicate Redis has been swapped out (to disk). Variable-sized keys and delete/insert cycles increase fragmentation.","action":"With high fragmentation, consider enabling activedefrag or restarting; if it is below 1, investigate swapping and add memory; review allocator (jemalloc) behavior and big-key patterns.","tags":["metric","memory","fragmentation","swap"]},{"name":"instantaneous_ops_per_sec","kind":"metric","meaning":"The number of commands Redis is processing per second right now (instantaneous throughput); shows workload intensity and sudden traffic changes.","causes":"Sudden traffic spikes, batch job load, inefficient client access patterns, not using pipelining instead of individual commands, heavy read/write bursts.","action":"Monitor throughput together with latency and CPU, correlate sudden drops/spikes with traffic and slow commands, improve efficiency with pipelining/batching, and plan capacity.","tags":["metric","throughput","ops","load"]},{"name":"rdb_last_bgsave_status","kind":"metric","meaning":"The success status of the last RDB background save (BGSAVE); shows whether the persistence snapshot was taken successfully.","causes":"A failed (err) status can be caused by a full disk/insufficient space, insufficient memory for the fork, a disk I/O error, permission problems or excessive load.","action":"If the status is 'err', check disk space, the memory required for the fork (overcommit setting) and disk health, and review the logs; a failed BGSAVE creates a data-loss risk, so review the persistence configuration.","tags":["metric","persistence","rdb","bgsave"]},{"name":"master_link_status","kind":"metric","meaning":"The status (up/down) of a replica's replication link to the master; shows whether the connection between replica and master is healthy.","causes":"A 'down' status can result from a network outage, the master being unreachable, a replication sync error, an authentication problem or master overload.","action":"If the status is 'down', check network connectivity, master reachability and auth settings, review the replica logs, monitor master_repl_offset lag, and verify that synchronization has completed.","tags":["metric","replication","master-link","availability"]},{"name":"latency (slowlog)","kind":"metric","meaning":"The latency of slow commands exceeding a set threshold, detected via slowlog and latency monitoring; in single-threaded Redis it shows the risk of one command blocking the others.","causes":"Commands with high time complexity (KEYS, large SORT, SMEMBERS/HGETALL on large collections), big keys, slow Lua scripts, fork/persistence pauses, swapping.","action":"Find slow commands with the SLOWLOG and LATENCY commands, eliminate O(N) commands (SCAN instead of KEYS) and big keys, optimize scripts, and monitor pauses caused by fork/AOF/RDB.","tags":["metric","latency","slowlog","performance"]},{"name":"expired_keys","kind":"metric","meaning":"The total number of keys deleted because their TTL expired; an indicator of TTL-based expiration behavior and memory reclamation.","causes":"Keys with a TTL expiring is normal. Sudden changes may indicate a TTL policy change, mass key expiration or unexpected deletion patterns.","action":"Distinguish expired from evicted (expired is healthy TTL behavior, evicted is memory pressure), monitor the impact of sudden expiration waves on cache misses and load, and spread the TTL distribution.","tags":["metric","ttl","expiration","keyspace"]},{"name":"connected_clients vs maxclients","kind":"metric","meaning":"How close the current number of connected clients is to the configured maxclients limit; shows the risk of exhausting connection capacity.","causes":"Connection leaks, a misconfigured connection pool, a sudden increase in clients, long-held idle connections, many microservice connections.","action":"Compare connected clients with maxclients; when approaching the limit, size the connection pool correctly, fix connection leaks, set an idle connection timeout, and increase maxclients if necessary.","tags":["metric","connections","maxclients","capacity"]}]},"reference/waits-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"waits":[{"name":"JVM heap used %","kind":"metric","meaning":"The percentage of a node's JVM heap in use; a high, persistently full heap means memory pressure and a risk of heavy GC.","causes":"Excessive fielddata/aggregation memory, too many shards, heavy bulk/query load, large mappings, an undersized heap, memory leak patterns.","action":"Monitor heap usage (intervene especially if it stays above 75% after GC), reduce the shard count, limit heavy aggregations and fielddata usage, and keep the heap at no more than half of RAM and below 32GB.","tags":["metric","jvm","heap","memory"]},{"name":"GC duration/frequency","kind":"metric","meaning":"The duration and frequency of JVM garbage collection; long/frequent GC pauses make the node unresponsive and degrade performance.","causes":"High heap pressure, large old generation (old gen) accumulation, heavy aggregations/fielddata, an undersized heap, too many shards, long-running large queries.","action":"Eliminate the heap pressure that is the root cause of long pauses (especially old GC), optimize memory-hungry queries, reduce the shard count; frequent and long GC is an early warning of node instability.","tags":["metric","jvm","gc","pause"]},{"name":"thread_pool write rejected","kind":"metric","meaning":"The number of indexing requests rejected because the write (indexing/bulk) thread pool queue was full; shows that the node cannot handle the write load.","causes":"Excessive bulk/indexing rate, slow disk I/O, many small requests instead of bulk, insufficient node capacity, refresh pressure.","action":"Send indexing requests as appropriately sized bulks, throttle the write rate to node capacity (backoff), improve disk I/O, increase refresh_interval, scale capacity.","tags":["metric","thread-pool","indexing","rejection"]},{"name":"thread_pool search rejected","kind":"metric","meaning":"The number of search requests rejected because the search thread pool queue was full; shows that the node cannot handle the query load.","causes":"Excessive concurrent queries, heavy/slow aggregations, touching too many shards, inefficient queries, insufficient node/CPU capacity.","action":"Limit query concurrency on the client side, optimize heavy queries and aggregations, reduce unnecessary shard fan-out (routing/filtering), add nodes and increase the number of replica shards (searches are also served by replica shards), scale capacity.","tags":["metric","thread-pool","search","rejection"]},{"name":"indexing/search latency","kind":"metric","meaning":"The average latency of indexing and search operations; derived from the index_time_in_millis and query_time_in_millis metrics, it reflects the response time seen by users.","causes":"Slow disk I/O, heavy queries/aggregations, GC pauses, a high shard count, refresh/merge pressure, insufficient resources.","action":"Find and optimize slow queries with the slow log, tune bulk and refresh_interval for indexing, improve disk and heap performance, and interpret latency together with thread pool rejections and GC.","tags":["metric","latency","indexing","search"]},{"name":"refresh/flush time","kind":"metric","meaning":"Time spent on refresh, which makes new documents searchable, and flush, which persists the translog into Lucene; shows indexing overhead.","causes":"Too frequent refreshes (low refresh_interval), slow disk I/O, heavy indexing, a large translog, many small segments.","action":"During heavy indexing, increase refresh_interval (e.g. 30s) or disable it temporarily, improve disk I/O, tune bulk size, review segment merge behavior.","tags":["metric","refresh","flush","indexing"]},{"name":"unassigned shards","kind":"metric","meaning":"The number of shards not assigned to any node; an unassigned primary means data is inaccessible (red health), an unassigned replica means a durability/capacity risk.","causes":"Node failure/departure, disk watermark exceeded, a replica setting too high for the number of nodes, allocation rules/filters, delays during shard recovery.","action":"Find the reason with the cluster allocation explain API, recover failed nodes, fix disk space/watermark problems, review the replica count and allocation rules; prioritize red status.","tags":["metric","shards","allocation","cluster-health"]},{"name":"disk watermark %","kind":"metric","meaning":"Node disk usage relative to the low/high/flood-stage thresholds (default 85%/90%/95%) that affect Elasticsearch shard placement.","causes":"Disk filling up, data growth, insufficient storage, unbalanced shard distribution, old indices not being cleaned up.","action":"At the low watermark, no new shards are assigned to the node; at the high watermark, shards are relocated to other nodes; at flood-stage, indices become read-only. Free disk space (delete old indices/use ILM), grow storage, balance shard distribution and monitor the watermark thresholds.","tags":["metric","disk","watermark","storage"]},{"name":"fielddata/query cache evictions","kind":"metric","meaning":"The number of entries evicted from the fielddata cache and the query (filter) cache because of memory limits; indicates cache inefficiency and memory pressure.","causes":"High fielddata usage (sorting/aggregations on text fields), small cache limits, highly varied query filters, memory pressure, filters that are never reused.","action":"Use keyword/doc_values instead of fielddata on text fields, limit heavy fielddata aggregations, review cache sizes; frequent evictions should be evaluated together with heap pressure.","tags":["metric","cache","fielddata","eviction"]},{"name":"circuit breaker tripped","kind":"metric","meaning":"The number of times circuit breakers (parent/fielddata/request breaker) predicted that a request would exceed memory and rejected it; a safety mechanism that protects the node from OOM.","causes":"Aggregations/queries requesting excessive memory, high fielddata usage, large bulk requests, general heap pressure, very large result sets.","action":"Treat circuit breaker trips as a sign of heap pressure, optimize memory-hungry queries, reduce fielddata/aggregation usage, lower the bulk size, and review the heap and shard count.","tags":["metric","circuit-breaker","memory","oom"]},{"name":"pending tasks","kind":"metric","meaning":"The queue of cluster state update tasks waiting to be processed by the master node; shows that the master cannot keep up with the cluster management load.","causes":"A large number of indices/shards, frequent mapping/settings changes, insufficient master node resources, a large cluster state, network/master election problems.","action":"If the queue is persistently full, reduce the number of shards/indices, limit frequent dynamic mapping updates, use separate dedicated master nodes, and review the cluster state size and master resources.","tags":["metric","master","cluster-state","queue"]}]},"reference/alerts-oracle.json":{"engineId":"oracle","engine":"Oracle Database","order":1,"alerts":[{"metric":"Buffer cache hit ratio","warn":"< 95%","crit":"< 90%","tool":"V$SYSSTAT / V$BUFFER_POOL_STATISTICS","note":"The buffer cache hit ratio shows the percentage of data blocks read from memory without going to disk. Low values indicate increasing physical reads and an undersized SGA. On its own it can be misleading; always evaluate it together with the physical reads in the AWR report.","tags":["memory","cache","sga"]},{"metric":"Tablespace usage (%used)","warn":"> 85%","crit":"> 95%","tool":"DBA_TABLESPACE_USAGE_METRICS","note":"If tablespace usage reaches 100%, no new extents can be allocated and operations stop with errors such as ORA-01653. Even with autoextend enabled, watch the disk limit. The DBA_TABLESPACE_USAGE_METRICS view gives the correct percentage for both autoextensible and fixed-size files.","tags":["disk","tablespace","capacity"]},{"metric":"PGA memory usage","warn":"> 90% pga_aggregate_target","crit":"> 100% (over-alloc)","tool":"V$PGASTAT / V$PGA_TARGET_ADVICE","note":"When the PGA target is exceeded, sort and hash operations spill to disk (temp) and queries slow down. A steadily increasing over allocation count shows that the target is insufficient. Review the pga_aggregate_target setting against server memory.","tags":["memory","pga","temp"]},{"metric":"TEMP tablespace usage","warn":"> 80%","crit":"> 95%","tool":"V$TEMPSEG_USAGE / V$SORT_SEGMENT / DBA_TEMP_FREE_SPACE","note":"TEMP space is used for large sorts, hash joins and global temporary table operations. When it fills up, ORA-01652 is raised and queries fail. Sudden fills usually stem from a single query with a bad plan or one sorting too much data.","tags":["temp","disk","sort"]},{"metric":"Average Active Sessions (AAS)","warn":"> CPU core count","crit":"> 2x CPU core count","tool":"V$ACTIVE_SESSION_HISTORY / OEM","note":"Average Active Sessions summarizes the database's current load in a single number. If the value exceeds the CPU core count, sessions are queuing for CPU or wait events. If it stays at a multiple of the core count, a bottleneck analysis is needed.","tags":["cpu","load","session"]},{"metric":"Data Guard apply lag","warn":"> 60 s","crit":"> 300 s","tool":"V$DATAGUARD_STATS (apply lag)","note":"Apply lag is how far the standby database is behind the primary. High apply lag increases the amount of redo to apply during failover and thus lengthens recovery time (RTO); if the redo has reached the standby, no data is lost. Potential data loss (RPO) is determined by transport lag. Review it together with transport lag to determine whether the network or redo apply is slow.","tags":["replication","dataguard","lag"]},{"metric":"Fast Recovery Area (FRA) usage","warn":"> 85%","crit":"> 95%","tool":"V$RECOVERY_FILE_DEST / V$RECOVERY_AREA_USAGE","note":"When the FRA fills up, archivelog generation stops and the whole database hangs. This is one of the most common causes of downtime in production systems. Regularly check RMAN deletion policies and the db_recovery_file_dest_size value.","tags":["disk","fra","archivelog"]},{"metric":"Blocking session count","warn":">= 1 (> 30 s)","crit":">= 1 (> 120 s)","tool":"V$LOCK / V$SESSION (blocking_session)","note":"When a lock held by one session makes other sessions wait, a cascading slowdown occurs across the application. Long-lasting blocking usually stems from uncommitted transactions or application bugs. Find the root session of the blocking chain and terminate it if necessary.","tags":["lock","blocking","concurrency"]},{"metric":"Invalid object count","warn":"> 0","crit":"> 50","tool":"DBA_OBJECTS (status='INVALID')","note":"Invalid packages, views or triggers can cause unexpected errors at run time. They usually appear after a DDL change. Resolve them by recompiling with UTL_RECOMP.RECOMP_PARALLEL or utlrp.sql, and investigate the root cause of persistently invalid objects.","tags":["object","compile","maintenance"]},{"metric":"Longest-running query","warn":"> 5 min","crit":"> 30 min","tool":"V$SQL / V$SESSION_LONGOPS","note":"A single long-running query can monopolize resources and slow down other operations too. Except for batch jobs, queries running for minutes in OLTP systems usually stem from a missing index or a bad plan. Examine the plan and actual row counts with a SQL Monitor report.","tags":["query","performance","sql"]},{"metric":"ORA errors in the alert log","warn":">= 1 (non-critical)","crit":"ORA-00600 / ORA-07445 / ORA-04031","tool":"alert log / V$DIAG_ALERT_EXT","note":"The alert log is the database's health journal. ORA-00600 and ORA-07445 are internal errors that may indicate data corruption, while ORA-04031 indicates shared pool memory exhaustion. If these errors appear, investigate them immediately and share them with Oracle Support if needed.","tags":["error","alertlog","monitoring"]},{"metric":"Redo log switch frequency","warn":"> 6 / hour","crit":"> 12 / hour","tool":"V$LOG_HISTORY","note":"Very frequent redo log switches indicate undersized log files and checkpoint pressure. Ideally, a switch every 15-20 minutes is expected during peak hours. Excessively frequent switches increase IO and archiving load; consider enlarging the redo log groups.","tags":["redo","io","checkpoint"]},{"metric":"Age of last successful RMAN backup","warn":"> 24 hours","crit":"> 48 hours","tool":"V$RMAN_BACKUP_JOB_DETAILS","note":"Backup age determines the most recent point you can recover to and directly reflects the risk of data loss. If failed or skipped backups go unnoticed, there is no way back in a disaster. Also monitor archivelog backups separately to guarantee point-in-time recovery capability.","tags":["backup","rman","recovery"]},{"metric":"Disk IO latency (db file sequential read)","warn":"> 10 ms","crit":"> 20 ms","tool":"V$SYSTEM_EVENT / AWR","note":"The average wait time of single-block reads shows the performance of the storage layer. On modern flash/SSD systems this value should be a few milliseconds. Persistently high latency indicates a storage bottleneck or excessive physical reads; evaluate the SGA and the storage layer together.","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":"The cache hit ratio is the percentage of blocks served from shared_buffers alone; reads served by the operating system cache are also counted as blks_read, so the ratio can make actual disk reads look worse than they are. In healthy PostgreSQL OLTP systems this ratio should be above 99%. Low values indicate insufficient memory or a cold cache; it is calculated from blks_hit and blks_read.","tags":["memory","cache"]},{"metric":"Connection count (connections vs max_connections)","warn":"> 80%","crit":"> 90%","tool":"pg_stat_activity / max_connections","note":"Active connections approaching the max_connections limit lead to new connection failures. Because each connection consumes memory, a high count also creates memory pressure. Keeping the backend count low with a connection pooler such as PgBouncer is recommended.","tags":["connection","limit","pool"]},{"metric":"Replication lag","warn":"> 10 MB","crit":"> 100 MB","tool":"pg_stat_replication (replay_lag / lsn diff)","note":"Replication lag is the amount of WAL, in bytes, by which the standby is behind the primary. High lag means stale data on read replicas and a risk of data loss on failover. Monitor the LSN difference in bytes together with the replay_lag duration.","tags":["replication","lag","standby"]},{"metric":"Oldest transaction age","warn":"> 5 min","crit":"> 30 min","tool":"pg_stat_activity (xact_start)","note":"Long-running open transactions prevent VACUUM from removing dead tuples and lead to table bloat. Sessions in the 'idle in transaction' state also hold locks unnecessarily. Identify such sessions and limit them with idle_in_transaction_session_timeout.","tags":["query","vacuum","transaction"]},{"metric":"Deadlocks","warn":"> 1 / min","crit":"> 5 / min","tool":"pg_stat_database (deadlocks)","note":"A deadlock occurs when two transactions wait for each other's locks, and PostgreSQL resolves it by canceling one. Occasional deadlocks can happen, but frequent ones indicate inconsistent lock ordering in the application. Enable log_lock_waits to see which queries conflict.","tags":["lock","deadlock","concurrency"]},{"metric":"Temp file usage","warn":"> 1 GB / hour","crit":"> 10 GB / hour","tool":"pg_stat_database (temp_bytes)","note":"Temporary files are created when sort and hash operations that do not fit in work_mem spill to disk. High temp usage causes both slowness and disk pressure. For queries that spill frequently, consider increasing work_mem or optimizing the query.","tags":["temp","disk","work_mem"]},{"metric":"Autovacuum lag (dead tuples)","warn":"> 10% dead tuples","crit":"> 20% dead tuples","tool":"pg_stat_user_tables (n_dead_tup)","note":"A rising dead tuple ratio shows that autovacuum cannot keep up and table bloat is growing. Bloated tables both take up space and slow down scans. On busy tables, lower autovacuum_vacuum_scale_factor so cleanup runs more often.","tags":["vacuum","bloat","maintenance"]},{"metric":"Disk usage (data directory)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"PostgreSQL offers no self-protection mechanism before the disk fills up. When the data disk fills, writes fail with 'could not extend file' / 53100; if the WAL (pg_wal) disk fills, the server shuts down with PANIC. WAL and temporary files can cause sudden growth; early intervention is critical.","tags":["disk","capacity"]},{"metric":"WAL retention (replication slot lag)","warn":"> 1 GB","crit":"> 10 GB","tool":"pg_replication_slots (restart_lsn)","note":"An unused or lagging replication slot prevents WAL files from being removed and bloats the pg_wal directory. This can end with a full disk. Identify and drop dead slots, and set an upper bound with max_slot_wal_keep_size.","tags":["wal","slot","disk"]},{"metric":"Checkpoint frequency (requested checkpoints)","warn":"> 10% requested ratio","crit":"> 30% requested ratio","tool":"PG16 and earlier: pg_stat_bgwriter (checkpoints_req) / PG17+: pg_stat_checkpointer (num_requested)","note":"When the ratio of requested checkpoints to timed ones rises, max_wal_size is too small. Frequent checkpoints cause IO spikes and performance degradation. Review the max_wal_size and checkpoint_completion_target settings to spread the load.","tags":["checkpoint","wal","io"]},{"metric":"Age of last successful backup","warn":"> 24 hours","crit":"> 48 hours","tool":"pgBackRest / barman info","note":"Backup age determines the latest point you can return to and directly expresses your RPO. An unbroken backup chain and WAL archive are required for point-in-time recovery. Backups must not only be taken; their validity must also be verified with regular restore tests.","tags":["backup","recovery","rpo"]},{"metric":"Disk IO latency","warn":"> 10 ms","crit":"> 20 ms","tool":"node_exporter / iostat","note":"Storage read/write latency directly affects query times. On modern SSD systems this value should be a few milliseconds. Sustained high latency indicates a disk bottleneck or excessive physical reads; evaluate shared_buffers and the storage layer together.","tags":["io","disk","latency"]},{"metric":"Transaction ID wraparound (age)","warn":"> 1 billion","crit":"> 1.5 billion","tool":"pg_database (age(datfrozenxid))","note":"If the transaction ID age approaches 2 billion, PostgreSQL stops all writes to prevent data loss (wraparound protection). This is one of the most dangerous PostgreSQL failures. Monitor the datfrozenxid age and make sure autovacuum is not falling behind on freeze work.","tags":["wraparound","vacuum","critical"]}]},"reference/alerts-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"alerts":[{"metric":"InnoDB buffer pool hit ratio","warn":"< 99%","crit":"< 95%","tool":"SHOW ENGINE INNODB STATUS / mysqld_exporter","note":"The buffer pool hit ratio is the percentage of data and index pages read from memory without going to disk. Low values indicate that innodb_buffer_pool_size is too small for the working set. On a dedicated server, typically ~70-80% of memory is allocated to the buffer pool (less on a shared server).","tags":["memory","cache","innodb"]},{"metric":"Connection count (Threads_connected vs max_connections)","warn":"> 80%","crit":"> 90%","tool":"SHOW STATUS / max_connections","note":"As the connection count approaches the max_connections limit, new connections are rejected with 'Too many connections'. Max_used_connections shows the historical peak. Keep concurrent connections under control with a connection pool on the application side.","tags":["connections","limit","thread"]},{"metric":"Replica lag (Seconds_Behind_Source)","warn":"> 30 s","crit":"> 300 s","tool":"SHOW REPLICA STATUS","note":"Seconds_Behind_Source gives, in seconds, how far the replica is behind the source. High lag means stale data on read replicas and failover risk. With single-threaded apply, the value can be misleading during large transactions; confirm it together with the GTID gap.","tags":["replication","lag","replica"]},{"metric":"InnoDB row lock wait time","warn":"avg > 1 s","crit":"avg > 5 s","tool":"SHOW STATUS (Innodb_row_lock_time_avg)","note":"The average row lock wait time shows how much transactions make each other wait. High values point to heavily conflicting updates or long-running transactions. Review it together with Innodb_row_lock_waits and find the conflicting queries in performance_schema.","tags":["lock","blocking","innodb"]},{"metric":"History list length (undo)","warn":"> 100000","crit":"> 1000000","tool":"SHOW ENGINE INNODB STATUS","note":"The history list length is the number of unpurged undo records and grows because of long-running open transactions. A high value shows that purge is falling behind and that read performance is degrading due to MVCC. Find and terminate long-running transactions so the list can shrink.","tags":["undo","transaction","mvcc"]},{"metric":"Failed connections (Aborted_connects)","warn":"> 5 / min","crit":"> 20 / min","tool":"SHOW STATUS (Aborted_connects)","note":"Aborted_connects counts connections that fail during authentication or the handshake. Sudden spikes can indicate wrong passwords, network problems or a possible attack attempt. Review it together with Aborted_clients to determine where the connection drops.","tags":["connections","error","security"]},{"metric":"Temporary tables written to disk","warn":"> 25% ratio","crit":"> 50% ratio","tool":"SHOW STATUS (Created_tmp_disk_tables)","note":"The ratio of in-memory temporary tables spilling to disk shows that queries exceed the tmp_table_size limit. On-disk temporary tables slow queries down noticeably. Optimize queries with GROUP BY and BLOB/TEXT columns and consider increasing tmp_table_size and max_heap_table_size.","tags":["temp","disk","query"]},{"metric":"Slow queries","warn":"> 5 / min","crit":"> 20 / min","tool":"slow query log / Slow_queries status","note":"Queries exceeding the long_query_time threshold are written to the slow query log and are the main source of performance problems. Frequent slow queries point to missing indexes or poorly written queries. Analyze the log with pt-query-digest and prioritize the most expensive queries.","tags":["query","performance","slowlog"]},{"metric":"CPU usage","warn":"> 80%","crit":"> 90%","tool":"node_exporter / top","note":"Sustained high CPU usage can stem from heavy queries, missing indexes or too many connections. Short peaks are normal, but sustained saturation causes query queuing. Identify the queries consuming the most CPU with performance_schema or the slow log.","tags":["cpu","load"]},{"metric":"Disk usage (data directory)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"A full data directory prevents MySQL from writing and causes table errors. Growth of binlog files, undo tablespaces and the temporary tablespace (ibtmp1) can fill the disk suddenly; the redo log has a fixed size (8.0.30+ #innodb_redo, innodb_redo_log_capacity). Make sure old binlogs are purged via binlog_expire_logs_seconds.","tags":["disk","capacity","binlog"]},{"metric":"Longest-running query","warn":"> 60 s","crit":"> 300 s","tool":"SHOW PROCESSLIST / information_schema.PROCESSLIST","note":"A single long-running query can affect other transactions by holding locks and consuming resources. In OLTP systems, queries running for minutes usually stem from a bad plan. Examine the plan with EXPLAIN and, if needed, kill the query and optimize it.","tags":["query","performance"]},{"metric":"Age of last successful backup","warn":"> 24 hours","crit":"> 48 hours","tool":"xtrabackup / mysqldump job log","note":"Backup age determines the latest point you can return to, and therefore the RPO. Together with binlog backups, point-in-time recovery becomes possible. Merely taking backups is not enough; verify their validity with regular restore tests.","tags":["backup","recovery","rpo"]},{"metric":"Disk IO latency","warn":"> 10 ms","crit":"> 20 ms","tool":"iostat / node_exporter","note":"Storage latency translates directly into query times and is critical especially for workloads that do not fit in the buffer pool. A few milliseconds is expected on modern SSD systems. High latency indicates a disk bottleneck or excessive physical reads; also review the buffer pool size.","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 shows, in seconds, the estimated time a data page can stay in memory before being evicted from the buffer pool. Low values indicate memory pressure and pages being flushed from the cache quickly. Monitor it separately per NUMA node and treat sudden drops as memory shortage.","tags":["memory","cache","ple"]},{"metric":"Buffer cache hit ratio","warn":"< 98%","crit":"< 95%","tool":"sys.dm_os_performance_counters","note":"The buffer cache hit ratio is the percentage of pages read from memory without going to disk. In SQL Server this value is usually very high; evaluate it together with PLE. Because it can be misleading on its own, use PLE as the primary metric when analyzing a memory bottleneck.","tags":["memory","cache"]},{"metric":"Blocked processes","warn":">= 1 (> 5 s)","crit":">= 1 (> 30 s)","tool":"sys.dm_exec_requests (blocking_session_id)","note":"When a lock held by one session makes others wait, a blocking chain forms and application-wide slowdown follows. Long-lasting blocking usually stems from uncommitted transactions or a poor isolation level. Identify the head blocker with the blocked process report.","tags":["lock","blocking","concurrency"]},{"metric":"Transaction log usage (%used)","warn":"> 75%","crit":"> 90%","tool":"sys.dm_db_log_space_usage / DBCC SQLPERF(LOGSPACE)","note":"When the transaction log fills up, the database cannot write and stops with error 9002. In the FULL recovery model, the log keeps growing if log backups are not taken. Monitor log backup frequency and the VLF count to make sure the log is being truncated and reused.","tags":["log","disk","recovery"]},{"metric":"tempdb usage (%used)","warn":"> 80%","crit":"> 95%","tool":"sys.dm_db_file_space_usage","note":"tempdb is shared for temporary tables, sorts, the version store and hash operations. When it fills up, queries across the whole server fail. Sudden growth usually comes from a large query or a long-running snapshot transaction; monitor the version store size separately.","tags":["tempdb","disk","sort"]},{"metric":"Longest-running query","warn":"> 60 s","crit":"> 300 s","tool":"sys.dm_exec_requests / Query Store","note":"A single long-running query can affect other work by holding locks and consuming resources. In OLTP systems, queries running for minutes usually stem from a missing index, parameter sniffing or a bad plan. Investigate plan regressions with Query Store.","tags":["query","performance"]},{"metric":"Availability Group redo queue","warn":"> 1 MB","crit":"> 100 MB","tool":"sys.dm_hadr_database_replica_states","note":"The redo queue shows the amount of log on the secondary replica still waiting to be applied. A high redo queue lengthens recovery time after failover (RTO) and means stale data on readable replicas. Examine it together with redo_rate to predict when the lag will close.","tags":["replication","ag","lag"]},{"metric":"Deadlock rate","warn":"> 1 / min","crit":"> 5 / min","tool":"system_health XEvent / Performance counters","note":"A deadlock occurs when two transactions wait for each other's locks, and SQL Server resolves it by choosing one as the victim. Occasional deadlocks can happen, but frequent ones indicate inconsistent lock ordering. Extract the deadlock graph from the system_health extended event session and fix the conflicting queries.","tags":["lock","deadlock","concurrency"]},{"metric":"Worker thread usage","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_schedulers / max worker threads","note":"When the worker thread pool is exhausted, new requests queue on the THREADPOOL wait and the server can become unresponsive. Long-running blocking or excessive concurrent connections usually cause this. Reduce thread pressure by resolving blocking chains.","tags":["cpu","thread","load"]},{"metric":"CPU usage","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_ring_buffers / Performance Monitor","note":"Sustained high CPU can come from heavy queries, missing indexes or bad plans. Short peaks are normal, but sustained saturation causes query queuing. Identify the top CPU-consuming queries with sys.dm_exec_query_stats.","tags":["cpu","load"]},{"metric":"Age of last successful backup","warn":"> 24 hours (full)","crit":"> 48 hours (full)","tool":"msdb.dbo.backupset","note":"Backup age determines the latest point you can return to and your RPO. In the FULL recovery model, point-in-time recovery is possible with log backups. Monitor the chain of full, differential and log backups to guarantee recoverability, and run regular restore tests.","tags":["backup","recovery","rpo"]},{"metric":"Disk IO latency (avg read/write)","warn":"> 15 ms","crit":"> 30 ms","tool":"sys.dm_io_virtual_file_stats","note":"Average read/write latency per file shows storage performance. Around 15 ms is expected for data files, and lower values for log files. Sustained high latency indicates a storage bottleneck; identify the slowest files with sys.dm_io_virtual_file_stats.","tags":["io","disk","latency"]}]},"reference/alerts-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"alerts":[{"metric":"WiredTiger cache dirty ratio","warn":"> 5%","crit":"> 20%","tool":"db.serverStatus().wiredTiger.cache","note":"The dirty cache ratio is the percentage of modified pages in the WiredTiger cache that have not yet been written to disk. Exceeding 20% means eviction pressure and a write bottleneck; MongoDB forces application threads to perform eviction. Evaluate cache size and disk write throughput together.","tags":["memory","cache","wiredtiger"]},{"metric":"WiredTiger cache usage (used %)","warn":"> 80%","crit":"> 95%","tool":"db.serverStatus().wiredTiger.cache","note":"When cache usage exceeds the 80% eviction target, WiredTiger starts evicting pages aggressively and performance drops. If the working set does not fit in the cache, physical reads increase. Review the wiredTigerCacheSizeGB setting and server memory.","tags":["memory","cache","wiredtiger"]},{"metric":"Available tickets (read/write tickets)","warn":"available/totalTickets < 20% or queueLength > 0 (sustained)","crit":"available = 0 and queueLength keeps growing","tool":"db.serverStatus().wiredTiger.concurrentTransactions","note":"WiredTiger limits concurrent read and write operations with a ticket system. Before 7.0 the default was a fixed 128 tickets; in 7.0+ the ticket count is adjusted dynamically (storageEngineConcurrencyAdjustmentAlgorithm: throughputProbing), so monitor the available/totalTickets ratio and queueLength instead of the absolute number. Tickets running out and operations queuing is a sign of a serious bottleneck; it is usually caused by slow queries or disk I/O saturation.","tags":["concurrency","ticket","bottleneck"]},{"metric":"Replication lag","warn":"> 10 s","crit":"> 60 s","tool":"rs.printSecondaryReplicationInfo()","note":"Replication lag gives, in seconds, how far a secondary member is behind the primary. High lag means stale data depending on the read preference, and a risk of data loss on failover. It is usually caused by disk I/O or network bottlenecks on the secondary, or by long-running operations.","tags":["replication","lag","replicaset"]},{"metric":"Connection usage (connections %)","warn":"> 80%","crit":"> 90%","tool":"db.serverStatus().connections","note":"Active connections approaching the configured upper limit cause new connections to be rejected. Because each connection consumes memory and a thread, a high count also creates resource pressure. Prevent connection storms by sizing the connection pool correctly on the application side.","tags":["connection","limit","pool"]},{"metric":"Page fault rate","warn":"> 100 / s","crit":"> 1000 / s","tool":"db.serverStatus() / extra_info page_faults","note":"A page fault occurs when data is not in memory and must be fetched from disk. A high fault rate shows that the working set does not fit in memory and the system is running disk-bound. Sustained high values require a memory upgrade or query/index optimization.","tags":["memory","disk","pagefault"]},{"metric":"Queued operations","warn":"> 10","crit":"> 50","tool":"db.serverStatus().globalLock.currentQueue","note":"globalLock currentQueue shows the number of reads/writes queued while waiting for a lock or resource. A persistently full queue indicates that the server cannot keep up with the incoming load. Examine it together with ticket exhaustion and disk I/O to determine the source of the bottleneck.","tags":["concurrency","queue","lock"]},{"metric":"Oplog window","warn":"< 24 hours","crit":"< 6 hours","tool":"rs.printReplicationInfo()","note":"The oplog window is the time span covered by the oplog and determines how long a secondary can stay disconnected and still resynchronize. If the window narrows, a member that falls behind requires a full resync. The window shortens as write load increases; grow the oplog size accordingly.","tags":["oplog","replication","recovery"]},{"metric":"Longest-running query","warn":"> 30 s","crit":"> 120 s","tool":"db.currentOp() / slow query log","note":"Long-running queries affect other operations by consuming resources and holding locks. They usually come from queries doing a collection scan (COLLSCAN) because of a missing index. Find slow operations with the profiler and currentOp and add suitable indexes.","tags":["query","performance","index"]},{"metric":"CPU usage","warn":"> 80%","crit":"> 90%","tool":"node_exporter / mongostat","note":"Sustained high CPU can come from unindexed queries, heavy aggregation operations or excessive connections. Short peaks are normal, but sustained saturation causes operations to queue. Use the profiler to identify the queries consuming the most CPU and optimize them.","tags":["cpu","load"]},{"metric":"Disk usage (dbPath)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"A full data directory leaves MongoDB unable to write and can lead to corruption. Although WiredTiger compression helps, the journal and growing collections can fill the disk quickly. Do capacity planning and purge unneeded data automatically with TTL indexes.","tags":["disk","capacity"]},{"metric":"Age of the last successful backup","warn":"> 24 hours","crit":"> 48 hours","tool":"mongodump / Ops Manager / snapshot record","note":"Backup age determines the latest point you can return to and your RPO. Combined with the oplog, point-in-time recovery becomes possible. Taking backups is not enough; validate them with regular restore tests and make sure consistent snapshots are taken in sharded clusters.","tags":["backup","recovery","rpo"]}]},"reference/alerts-redis.json":{"engineId":"redis","engine":"Redis","order":6,"alerts":[{"metric":"Memory usage (used_memory vs maxmemory)","warn":"> 80%","crit":"> 95%","tool":"INFO memory / redis_exporter","note":"The ratio of used_memory to maxmemory shows how much of Redis's memory is in use. When the limit is reached, keys are evicted or writes are rejected according to maxmemory-policy. Track memory growth to prevent unexpected OOM and eviction waves.","tags":["memory","maxmemory","capacity"]},{"metric":"Keyspace hit ratio","warn":"< 90%","crit":"< 80%","tool":"INFO stats (keyspace_hits/misses)","note":"The hit ratio is the percentage of requested keys found in the cache and is calculated from keyspace_hits and keyspace_misses. A low ratio points to ineffective cache use, wrong TTLs or a cold cache. For cache workloads this ratio should be as high as possible.","tags":["cache","hit","performance"]},{"metric":"Evicted keys (evicted_keys)","warn":"> 1 / s","crit":"> 100 / s","tool":"INFO stats (evicted_keys)","note":"evicted_keys is the number of keys removed under maxmemory-policy because memory is full. Continuous eviction shows that memory is insufficient and important data may be lost. Reduce eviction by upgrading memory or revisiting the TTL strategy or data model.","tags":["memory","eviction","maxmemory"]},{"metric":"Blocked clients (blocked_clients)","warn":"> 50","crit":"> 200","tool":"INFO clients (blocked_clients)","note":"blocked_clients is the number of clients waiting on blocking commands such as BLPOP/BRPOP/WAIT. Higher-than-expected values point to consumers not keeping up or a growing queue backlog. In queue-based workloads, check the producer/consumer balance.","tags":["client","blocking","queue"]},{"metric":"Memory fragmentation ratio (mem_fragmentation_ratio)","warn":"> 1.5","crit":"> 2.0","tool":"INFO memory","note":"The fragmentation ratio is the memory obtained from the operating system divided by the memory used by Redis. Above 1.5 indicates excessive fragmentation; below 1.0 indicates memory spilling to swap, which is very dangerous. You can reduce fragmentation by enabling activedefrag or by restarting.","tags":["memory","fragmentation"]},{"metric":"Master link status (master_link_status)","warn":"down (< 10 s)","crit":"down (> 30 s)","tool":"INFO replication (master_link_status)","note":"master_link_status shows the state (up/down) of the replica's connection to the master. 'down' means replication is broken and the replica is serving stale data. Long disconnections require a full resync; check the network and master health immediately.","tags":["replication","master","connection"]},{"metric":"Replication lag (master_repl_offset difference)","warn":"> 1 MB","crit":"> 10 MB","tool":"INFO replication (offset diff)","note":"The difference between the master and replica offsets shows, in bytes, how far behind the replica is. A large difference means risk of data loss on failover and inconsistency on read replicas. Examine network latency and replica load to make sure the gap closes.","tags":["replication","lag","offset"]},{"metric":"Last RDB bgsave status (rdb_last_bgsave_status)","warn":"err (last attempt)","crit":"err (> 24 hours)","tool":"INFO persistence","note":"rdb_last_bgsave_status shows whether the last background snapshot succeeded (ok). A failed bgsave means persistence is not working and there is a risk of data loss on restart. It usually comes from a full disk or insufficient memory for fork.","tags":["backup","persistence","rdb"]},{"metric":"Connection usage (connected_clients)","warn":"> 80% maxclients","crit":"> 90% maxclients","tool":"INFO clients / maxclients","note":"connected_clients approaching the maxclients limit causes new connections to be rejected. Connection leaks or a misconfigured pool can cause sudden spikes. Configure the connection pool correctly on the application side and close idle connections.","tags":["connection","limit","client"]},{"metric":"Instantaneous command latency","warn":"> 1 ms","crit":"> 10 ms","tool":"redis-cli --latency / LATENCY DOCTOR","note":"Because Redis is single-threaded, command latency is usually in the microsecond range. Rising latency can come from slow commands (KEYS, large SORT), blocking during fork or swap usage. Find the source with slowlog and the LATENCY commands.","tags":["latency","performance","slowlog"]},{"metric":"CPU usage","warn":"> 80% (single core)","crit":"> 90% (single core)","tool":"top / redis_exporter","note":"Because the Redis main thread uses a single core, saturating one core slows down the whole server. High CPU can come from expensive commands, too many connections or Lua scripts. Find expensive commands with SLOWLOG and, if needed, split the workload across multiple instances.","tags":["cpu","load"]},{"metric":"Rejected connections (rejected_connections)","warn":"> 0","crit":"> 10 / min","tool":"INFO stats (rejected_connections)","note":"rejected_connections is the number of connections rejected because the maxclients limit was reached. A value above zero means clients are not being served and errors are occurring in the application. Investigate the maxclients value and connection leaks immediately.","tags":["connection","error","limit"]}]},"reference/alerts-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"alerts":[{"metric":"JVM heap usage","warn":"> 75%","crit":"> 85%","tool":"_nodes/stats/jvm / elasticsearch_exporter","note":"JVM heap usage shows the node's memory pressure; sustained values above 75% mean frequent GC and possible OutOfMemory. If heap exceeds 85% the node may become unresponsive or drop out of the cluster. Size the heap at roughly half of server memory and no more than 30-31 GB.","tags":["memory","jvm","heap"]},{"metric":"Unassigned shard count (unassigned shards)","warn":">= 1 (replica)","crit":">= 1 (primary)","tool":"_cluster/health / _cat/shards","note":"Unassigned shards mean part of the data is inaccessible or not replicated. An unassigned primary shard means a risk of data loss; an unassigned replica means loss of redundancy, and the cluster turns yellow/red. Find the reason with _cluster/allocation/explain.","tags":["shard","cluster","health"]},{"metric":"Disk watermark usage","warn":"> 85% (low)","crit":"> 90% (high) / > 95% (flood_stage)","tool":"_nodes/stats/fs / _cat/allocation","note":"Elasticsearch takes graduated action based on disk usage: at the 85% low watermark it stops allocating new shards to the node, at the 90% high watermark it relocates existing shards to other nodes, and at 95% flood_stage it puts a read_only_allow_delete block on indices. In 8.x, on large disks the max_headroom values (low 200GB / high 150GB / flood 100GB free) take precedence over percentages. On 7.4+ the block is released automatically once disk usage falls below the high watermark; still, free up disk before the watermarks are exceeded.","tags":["disk","watermark","capacity"]},{"metric":"Thread pool rejected operations (rejected)","warn":"> 0 (write/search)","crit":"> 100 / min","tool":"_cat/thread_pool / _nodes/stats/thread_pool","note":"When a thread pool queue is full, incoming requests are rejected; a rejection in the write pool means lost indexing, in the search pool a failed query. Sustained rejections indicate the node cannot keep up with the incoming load. Balance the load by reducing bulk size and concurrency.","tags":["threadpool","rejected","load"]},{"metric":"GC pause time (old gen GC pause)","warn":"> 1 s","crit":"> 5 s","tool":"GC logs / _nodes/stats/jvm","note":"During long GC pauses the node cannot respond and may lose its connection to the master, which triggers shard reallocation. Frequent and long old generation GC is a symptom of heap pressure. Review the heap size and memory-hungry aggregation and fielddata usage.","tags":["jvm","gc","pause"]},{"metric":"Indexing latency","warn":"> 50 ms","crit":"> 200 ms","tool":"_nodes/stats/indices (index_time/index_total)","note":"Average indexing time shows document write performance and is calculated by dividing index_time_in_millis by index_total. Rising values indicate a disk IO bottleneck, a heavy analysis chain or refresh pressure. Optimize the bulk size and the refresh_interval setting.","tags":["indexing","latency","performance"]},{"metric":"Search latency","warn":"> 100 ms","crit":"> 500 ms","tool":"_nodes/stats/indices (query_time/query_total)","note":"Average query time directly reflects the search performance users experience and is calculated by dividing query_time_in_millis by query_total. Rising values may come from heavy aggregations, too many shards or a cold cache. Enable the slow log to find expensive queries.","tags":["search","latency","performance"]},{"metric":"Pending tasks","warn":"> 10","crit":"> 100","tool":"_cluster/pending_tasks","note":"Pending tasks are cluster state changes the master node has not yet applied (mapping, shard allocation, etc.). Constant accumulation indicates the master is overloaded or the cluster state has grown too large. Relieve pressure on the master by reducing an excessive shard count.","tags":["cluster","master","queue"]},{"metric":"Circuit breaker trips","warn":"> 0 (tripped)","crit":"> 10 / min","tool":"_nodes/stats/breakers","note":"Circuit breakers cancel a request to prevent an operation from consuming excessive memory and driving the node into OutOfMemory. Frequent trips indicate memory pressure from heavy aggregations or large requests. Monitor the tripped count and lighten the problematic queries.","tags":["memory","breaker","jvm"]},{"metric":"Cluster health status (cluster status)","warn":"yellow","crit":"red","tool":"_cluster/health","note":"Cluster status can be green (all shards assigned), yellow (replicas missing) or red (primaries missing, data inaccessible). Red requires immediate action because some data is lost or inaccessible. Yellow indicates loss of redundancy and must not be ignored.","tags":["cluster","health","shard"]},{"metric":"CPU usage","warn":"> 80%","crit":"> 90%","tool":"_nodes/stats/os / node_exporter","note":"Sustained high CPU can come from heavy queries, excessive indexing or merge operations. Short peaks are normal, but sustained saturation causes query and indexing queuing. Identify the operations consuming the most CPU with the hot threads API (_nodes/hot_threads).","tags":["cpu","load"]},{"metric":"Age of last successful snapshot","warn":"> 24 hours","crit":"> 48 hours","tool":"_snapshot/_status / SLM","note":"Snapshot age determines the latest point you can return to and your RPO. Automatic, regular backups should be ensured with Snapshot Lifecycle Management (SLM). If failed or skipped snapshots go unnoticed, data cannot be recovered in a disaster; verify snapshot status regularly.","tags":["backup","snapshot","rpo"]}]},"cloud/cloud-ops-1.json":{"category":"Connection & Backup & Replica","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":"List instances / databases","aliases":["list","instances","list all"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'","note":"Lists the identifiers of all RDS instances. Add --region to target a region.","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --query 'DBClusters[].DBClusterIdentifier'","note":"Lists Aurora cluster identifiers; check the DBClusterMembers field to see cluster members.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances list","note":"Lists all Cloud SQL instances in the project. Add --project to target a project.","danger":"safe"},"azure-sql":{"command":"az sql db list -g <rg> -s <server>","note":"Lists the databases in the given resource group (-g) and logical server (-s). Use 'az sql server list' to list servers.","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters list","note":"Lists all clusters in the Atlas project. Add --projectId to target a project.","danger":"safe"}}},{"id":"connect","title":"Connect to an instance (find endpoint + connect)","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":"Returns the instance connection address (host). Get the port from Endpoint.Port and connect with the psql/mysql client.","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --db-cluster-identifier <id> --query 'DBClusters[0].Endpoint'","note":"Returns the cluster writer endpoint. Use the ReaderEndpoint field for read-only access.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances describe <instance> --format='value(ipAddresses[].ipAddress)'","note":"Returns the instance IP address(es). Cloud SQL Auth Proxy is recommended for secure connections.","danger":"safe"},"azure-sql":{"command":"az sql db show-connection-string -s <server> -n <db> -c ado.net","note":"Generates the connection string for the given client type (-c: ado.net, jdbc, odbc, php, etc.); you fill in the user/password.","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters connectionStrings describe <clusterName>","note":"Returns the cluster's standard and SRV connection strings. An IP access list entry and a database user are required.","danger":"safe"}}},{"id":"take-snapshot","title":"Take a manual backup / snapshot","aliases":["snapshot","backup","backup"],"providers":{"aws-rds":{"command":"aws rds create-db-snapshot --db-instance-identifier <id> --db-snapshot-identifier <snap-id>","note":"Takes a manual snapshot of the instance (retained until deleted manually). A brief I/O suspension may occur.","danger":"caution"},"aws-aurora":{"command":"aws rds create-db-cluster-snapshot --db-cluster-identifier <id> --db-cluster-snapshot-identifier <snap-id>","note":"Takes a manual snapshot of the Aurora cluster. Since Aurora backups are continuous, this is a fixed copy.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql backups create --instance=<instance>","note":"Creates an on-demand backup of the instance. A brief performance impact is possible during the operation.","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 is backed up automatically; for a portable manual backup, this exports the database as a BACPAC. Point-in-time restore is a separate feature.","danger":"caution"},"mongodb-atlas":{"command":"atlas backups snapshots create <clusterName>","note":"Starts an on-demand backup snapshot of the cluster. Cloud Backup must be enabled.","danger":"caution"}}},{"id":"restore-pitr","title":"Restore to a point in time (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":"Creates a NEW instance from the source instance at the given time; it does not overwrite the existing instance. Use --use-latest-restorable-time to restore to the latest point.","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":"Restores the Aurora cluster to the given time as a new cluster. You must then add instances with create-db-instance.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances clone <src> <new> --point-in-time '2026-07-03T10:00:00Z'","note":"Creates a clone of the source instance at the given time as a new instance. Binary/WAL logging must be enabled on the 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":"Restores the database to the given time as a NEW database; the source database is preserved. Time is in UTC.","danger":"caution"},"mongodb-atlas":{"command":"atlas backups restores start pointInTime --clusterName <cluster> --pointInTimeUTCMillis <ms> --targetClusterName <target> --targetProjectId <projId>","note":"Starts a restore to the given UTC time using Continuous Cloud Backup. The target cluster is overwritten; proceed with care.","danger":"danger"}}},{"id":"read-replica","title":"Create a read replica","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":"Creates an asynchronous read replica from the source instance. Use --source-region for a cross-region copy.","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":"In Aurora, a reader is an instance added to the cluster; it shares the cluster storage. Set the engine value to match the cluster (aurora-mysql / aurora-postgresql).","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <replica> --master-instance-name=<src> --region=<region>","note":"Creates a read replica from the primary (master) instance. Binary logging must be enabled on the source.","danger":"caution"},"azure-sql":{"command":"az sql db replica create -g <rg> -s <server> -n <db> --partner-server <target-server>","note":"Creates an active geo-replicated readable secondary of the database on another server. The target server must exist.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <clusterName> --file cluster.json","note":"Atlas clusters are already replicated replica sets. For extra read capacity, add readOnly or analytics nodes under replicationSpecs and update.","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":"On a Multi-AZ instance, forces a reboot that triggers failover to the standby. Expect brief downtime.","danger":"danger"},"aws-aurora":{"command":"aws rds failover-db-cluster --db-cluster-identifier <cluster> --target-db-instance-identifier <reader>","note":"Promotes a reader in the Aurora cluster to the new writer. If no target is given, a suitable replica is chosen; expect brief downtime.","danger":"danger"},"gcp-cloudsql":{"command":"gcloud sql instances failover <instance>","note":"Fails over an HA (high availability) instance to the standby zone. Works only on HA-enabled instances; downtime occurs.","danger":"danger"},"azure-sql":{"command":"az sql failover-group set-primary -g <rg> -s <secondary-server> -n <fog-name>","note":"Moves the primary role to the secondary server in an auto-failover group (planned failover). Can be forced with --allow-data-loss, which risks data loss.","danger":"danger"},"mongodb-atlas":{"command":"atlas clusters failover <clusterName>","note":"Atlas fails over automatically; this command triggers a manual failover for testing. The primary node changes and the application must reconnect.","danger":"danger"}}}]},"cloud/cloud-ops-2.json":{"category":"Configuration & Scaling & Monitoring","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":"Change instance size (scale vertically)","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 applies the change now and restarts the instance (brief downtime). Without this flag the change is applied in the next maintenance window. On Multi-AZ instances downtime is reduced to roughly the failover time.","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":"In Aurora each instance (writer and readers) is scaled separately; the instance size changes, not the cluster. Scale the readers first, then fail over to replace the writer to minimize downtime. With Serverless v2 you scale via min/max ACU.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --tier db-custom-4-16384","note":"A custom machine type is specified as db-custom-CPU-MEMORY (example: 4 vCPU, 16384 MB RAM). The change restarts the instance, causing brief downtime. Predefined tiers (e.g. db-n1-standard-4) can also be used.","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --service-objective S3","note":"In the DTU model use a service objective such as S3/P2; in the vCore model use --edition GeneralPurpose --family Gen5 --capacity 4. Scaling is performed online; a brief connection drop of a few seconds may occur near the end of the operation.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --tier M30","note":"Atlas resizes the replica set nodes in a rolling fashion, so there is no downtime. Requires the --projectId flag or a default project. When downgrading, watch disk and memory limits.","danger":"caution"}}},{"id":"parameter-group","title":"Change parameters (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":"Use ApplyMethod=immediate for dynamic parameters and pending-reboot for static parameters. The parameter group must be attached to the instance; static parameter changes take effect after a reboot. Use create-db-parameter-group to create a group.","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":"In Aurora, cluster-wide parameters are managed with the DB cluster parameter group and instance-specific parameters with the DB parameter group (modify-db-parameter-group). Static parameters require a reboot across the cluster.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --database-flags max_connections=500,log_min_duration_statement=1000","note":"--database-flags sets ALL flags at once; previously set flags not in the list are reset, so specify them all together. Some flags require an instance restart. View current flags with '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":"In Azure SQL, instead of classic server config, settings are mostly applied with T-SQL: ALTER DATABASE SCOPED CONFIGURATION ...; sp_configure at server level is limited. For elastic pool/database options use the 'az sql db update' flags.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters advancedSettings update <name> --oplogMinRetentionHours 24 --oplogSizeMB 20480","note":"In Atlas, low-level MongoDB process arguments can be set only to a limited extent via 'advancedSettings' (oplog, full-text search, index depth, etc.). Most server parameters are managed by Atlas and cannot be changed directly.","danger":"caution"}}},{"id":"monitoring","title":"View metrics and performance","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":"Basic metrics (CPU, connections, IOPS, latency) are in the CloudWatch AWS/RDS namespace. For query-level analysis enable Performance Insights: 'aws pi get-resource-metrics --service-type RDS ...' or use the Performance Insights dashboard in the 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":"Aurora-specific metrics (AuroraReplicaLag, VolumeReadIOPs, CommitLatency, buffer cache hit) are also in the AWS/RDS namespace. Performance Insights is recommended for Aurora and provides top SQL and wait event analysis.","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":"Cloud SQL metrics are in Cloud Monitoring under cloudsql.googleapis.com/... For query-level analysis enable Cloud SQL Query Insights (console or API); it shows the slowest queries and wait events.","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":"Metrics such as cpu_percent, dtu_consumption_percent, storage_percent and connection_successful come from Azure Monitor. For query performance use the Query Performance Insight and Intelligent Insights (Azure SQL Analytics) dashboards.","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":"Process-level measurements are retrieved with 'atlas metrics processes', disk measurements with 'atlas metrics disks'. For slow query recommendations use Performance Advisor: 'atlas performanceAdvisor slowQueryLogs list' and 'atlas performanceAdvisor suggestedIndexes list'.","danger":"safe"}}},{"id":"maintenance-window","title":"Maintenance window and update management","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":"The window is in UTC, in ddd:hh24:mi-ddd:hh24:mi format, and must be at least 30 minutes. List pending maintenance with 'aws rds describe-pending-maintenance-actions' and apply it immediately or in the next window with 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":"In Aurora the cluster-level maintenance window is set with modify-db-cluster and the instance-level window with modify-db-instance. Pending engine/OS updates are managed with apply-pending-maintenance-action; engine version upgrades may require downtime.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --maintenance-window-day SUN --maintenance-window-hour 3 --maintenance-release-channel production","note":"Sets the day (MON..SUN), hour (0-23, instance local/UTC time) and release channel (production or preview). With the --deny-maintenance-period-* flags you can also define a period during which no maintenance is performed.","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":"The Azure SQL maintenance window is managed by assigning a predefined Maintenance Configuration (weekday/weekend evening windows instead of the default). List configurations with '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=Sunday ... 7=Saturday, hourOfDay 0-23 (UTC). Use 'atlas maintenanceWindows defer' to postpone pending maintenance by one week, and 'atlas maintenanceWindows start' to start it immediately.","danger":"caution"}}},{"id":"encryption","title":"View/configure encryption (at-rest) status","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":"RDS at-rest encryption can be enabled only WHEN the instance is CREATED; to enable it on an existing unencrypted instance, make an encrypted copy of a snapshot and restore from it. The key is managed by 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":"In Aurora, encryption is set at the cluster level and only at creation time. To encrypt an existing unencrypted cluster, copy a snapshot as encrypted and restore it. All instances and backups use the same KMS key.","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 is encrypted by default with a Google-managed key; a customer-managed key (CMEK) can be assigned only at instance creation with --disk-encryption-key. The Cloud SQL service account must be granted encrypt/decrypt permission on the key.","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":"In Azure SQL, at-rest encryption is provided by Transparent Data Encryption (TDE) and is enabled by default on new databases. To use your own key, configure 'az sql server tde-key set' (Azure Key Vault / BYOK) at the server level.","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":"In Atlas, data is encrypted at rest by default. Additionally, envelope encryption with a customer key (BYOK) via AWS KMS / Azure Key Vault / GCP KMS is enabled project-wide with 'atlas encryptionAtRest' and applied to the cluster.","danger":"caution"}}},{"id":"storage-autoscale","title":"Grow storage / enable 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 sets the current size; --max-allocated-storage sets the upper limit for storage autoscaling. Growing storage is online; however, you must wait at least 6 hours between storage modifications and storage cannot be shrunk.","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":"Aurora storage scales fully automatically (in 10 GB increments, up to 128 TB depending on the engine) and grows as it is used; --allocated-storage / --max-allocated-storage are not set. You only monitor usage; no size management is needed.","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 enables automatic growth when the disk approaches full; --storage-auto-increase-limit sets the upper limit (0 = unlimited). Storage can only be increased, it CANNOT BE DECREASED; choose the initial size and limit carefully.","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --max-size 500GB","note":"In Azure SQL the maximum database size is set with --max-size; the database grows automatically within the allocated space. The maximum size depends on the selected service tier/vCore limit. In the Hyperscale tier storage scales automatically and almost without limit.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --diskSizeGB 200   # for autoscaling, diskGBEnabled=true via extended configuration (advancedSettings / API)","note":"Disk size is increased with --diskSizeGB. In Atlas, storage autoscaling (Auto-scale storage) is enabled by default on M10 and larger clusters; the disk grows automatically when it is 90% full. Enabling/disabling is managed in the cluster configuration (autoScaling.diskGBEnabled).","danger":"caution"}}}]},"guide/best-practices.json":{"topics":[{"id":"indexing-strategy","title":"Indexing strategy","category":"Performance","summary":"Indexes speed up read queries but add write and storage cost; add them based on need and measurement.","points":["Target WHERE, JOIN and ORDER BY columns; put the most selective column first in a composite index (leftmost-prefix rule).","Use covering indexes: include all columns the query needs in the index to eliminate table access (lookup).","Identify and drop unused and redundant indexes (duplicates or prefixes of another index); every index slows down INSERT/UPDATE/DELETE.","Avoid standalone B-tree indexes on low-cardinality columns (e.g. gender, boolean); use a partial/filtered index if needed.","Verify with EXPLAIN/EXPLAIN ANALYZE that the index is actually used; a function or type conversion disables the index (as in WHERE UPPER(name))."],"level":"junior"},{"id":"normalization","title":"Normalization vs denormalization","category":"Design","summary":"Normalization ensures data integrity and no redundancy; denormalization is a deliberate trade-off for read performance.","points":["Target 3NF by default in OLTP systems; prevent data redundancy and update anomalies.","Denormalize only when there is a measured performance bottleneck, and justify it.","Guarantee consistency of denormalized data with triggers, materialized views or the application layer.","For reporting/analytics workloads, consider a separate denormalized schema (star schema) or a read replica.","Premature denormalization is technical debt; first try to solve the problem with the right index and query."],"level":"senior"},{"id":"data-types","title":"Choosing the right data type","category":"Design","summary":"The correct and smallest possible data type directly improves storage, index size and performance.","points":["Choose the smallest type that meets the need: SMALLINT for age, DECIMAL/NUMERIC for money (never FLOAT).","Always store time data with a time zone (TIMESTAMPTZ / UTC); avoid storing local time.","Use an ENUM or lookup table for fixed, limited value sets; constrain instead of allowing free text.","For variable-length text, size to the actual need or use TEXT instead of an unnecessarily large VARCHAR(255).","If you use UUID as a PK, watch for index fragmentation; consider sequential UUIDs (v7) or BIGINT identity.","Choose NULL semantics deliberately; make the difference between 'unknown' and 'empty' explicit in the design."],"level":"junior"},{"id":"naming-conventions","title":"Naming conventions","category":"Design","summary":"Consistent naming makes readability, maintenance and team communication easier; set the standard from the start.","points":["Pick one convention and apply it everywhere: snake_case is widespread and robust against case-sensitivity issues.","Settle singular vs plural for table names; prefer meaningful, unabbreviated column names.","Name foreign keys so the source is obvious (e.g. customer_id) and give indexes/constraints standard prefixes (fk_, ix_, uq_).","Avoid using reserved words (order, user, group) as table/column names.","Make boolean columns explicit with is_/has_ prefixes; add consistent suffixes such as _at, _date to date columns."],"level":"junior"},{"id":"partitioning-strategy","title":"Partitioning strategy","category":"Performance","summary":"Partitioning splits large tables into manageable pieces; it improves maintenance, purge and query performance.","points":["Choose the partition key based on query and archiving patterns; date-based RANGE partitioning is the most common for time series.","Verify that partition pruning works: queries must include the partition key in the WHERE clause, otherwise all partitions are scanned.","Instead of deleting old data, purge it instantly and nearly lock-free with partition DROP/DETACH.","An excessive number of partitions adds planning and metadata cost; choose a reasonable granularity (monthly/weekly).","Set up a maintenance job that creates future partitions automatically; prevent errors from writes with no matching partition."],"level":"senior"},{"id":"connection-pooling","title":"Connection pooling","category":"Operations","summary":"A connection pool eliminates the expensive cost of establishing connections and protects the database from connection floods.","points":["Do not open a new connection per request; use a pool in the application or a middle tier (PgBouncer, HikariCP).","Size the pool by CPU cores and workload; an oversized pool chokes the database (a small pool is usually better).","Set connection lifetime, idle and acquire timeout values deliberately; enable leak detection.","Plan the pool against the database max_connections limit; account for the total across multiple application instances.","Use short transactions so connections return to the pool quickly; do not wait on external I/O inside a transaction."],"level":"senior"},{"id":"transaction-design","title":"Transaction design (short and atomic)","category":"Performance","summary":"Transactions should be short, atomic and cover a single logical unit of work; this minimizes lock duration.","points":["Keep transactions as short as possible; do not wrap user interaction, external API calls or long computations in them.","Group in one transaction only the operations that must succeed or fail together.","Always update rows in the same order to reduce lock conflicts (the basis of deadlock prevention).","Avoid unnecessarily broad locks; do not put read-only operations into unnecessary transaction blocks.","Define a clear rollback path on error; do not leave partial commits or open transactions behind.","Commit long bulk operations in batches; a single giant transaction causes WAL/undo bloat."],"level":"senior"},{"id":"isolation-levels","title":"Choosing an isolation level","category":"Performance","summary":"The isolation level sets the balance between consistency and concurrency; choose it deliberately based on business rules.","points":["Know the anomaly types: dirty read, non-repeatable read, phantom read; understand which level prevents which.","Read Committed is sufficient for most OLTP; go higher only when truly required.","Use Serializable or explicit locking (SELECT ... FOR UPDATE) for critical money/inventory operations.","Under Serializable, add automatic retry logic for serialization failures.","Know the behavioral difference between MVCC and lock-based engines; the same level can give different guarantees on different engines."],"level":"expert"},{"id":"deadlock-prevention","title":"Deadlock prevention","category":"Performance","summary":"A deadlock occurs when transactions wait on each other's resources; it can largely be prevented by design.","points":["Always access resources/rows in a consistent, identical order; ordering differences are the most common source of deadlocks.","Keep transactions short and reduce the number of locks; avoid unnecessarily broad updates.","When needed, make conflicts predictable with early locking (SELECT ... FOR UPDATE).","Catch deadlock errors in the application and retry the transaction automatically; eliminating deadlocks entirely is often impossible.","Review deadlock logs/graphs regularly; eliminate recurring patterns by changing indexes or access order."],"level":"expert"},{"id":"sharding-strategy","title":"Sharding / horizontal scaling strategy","category":"Design","summary":"Sharding overcomes single-server limits by distributing data across multiple nodes; because it adds serious complexity it should be a last resort.","points":["First exhaust vertical scaling, indexing, caching and read replicas; shard only when truly necessary.","Choose the shard key carefully: it must give few cross-shard queries and an even distribution, without creating hotspots.","Use consistent hashing or virtual shards for rebalancing (resharding); plan for growth from the start.","Cross-shard JOINs and distributed transactions are expensive; design the schema to minimize them.","Solve globally unique ID generation (snowflake-style sequential IDs, etc.) and the shard routing layer."],"level":"expert"},{"id":"backup-strategy","title":"Backup strategy (3-2-1)","category":"Backup & HA","summary":"A solid backup strategy is the last line of defense against disasters; a backup does not count until it has been tested.","points":["Apply the 3-2-1 rule: 3 copies, 2 different media, 1 copy off-site (offsite/different region).","Combine full + incremental backups with WAL/binlog archiving to gain point-in-time recovery (PITR).","Test backups regularly by ACTUALLY restoring them; an untested backup is not a backup.","Encrypt backups both at rest and in transit; restrict access and set the retention period by policy.","Keep an immutable or air-gapped copy against ransomware."],"level":"junior"},{"id":"hadr-design","title":"HA/DR design and RPO/RTO","category":"Backup & HA","summary":"High availability and disaster recovery design starts with defining business objectives as RPO and RTO metrics.","points":["Define the RPO (acceptable data loss) and RTO (acceptable downtime) targets together with the business.","Synchronous replication gives near-zero RPO but adds latency; asynchronous performs well but carries a risk of data loss.","Set up automatic failover and verify it actually works with regular failover drills.","Use a quorum/witness mechanism to prevent split-brain; never allow both nodes to become master.","Keep the DR environment in a different physical region; be resilient to a single region/data center failure."],"level":"expert"},{"id":"capacity-planning","title":"Capacity planning and growth","category":"Operations","summary":"Capacity planning prevents surprise outages by provisioning resources ahead of the growth trend.","points":["Measure data growth rate, IOPS, CPU, memory and connection usage over time and derive trends.","Set early threshold alerts for disk usage (e.g. 75%/85%); act before space runs out.","Plan growth based on forecasts; account for peak load and seasonal fluctuation.","Regularly review connection counts, table/index bloat and archiving needs.","Decide the scaling path (vertical, replicas, partitioning/sharding) before reaching the bottleneck."],"level":"senior"},{"id":"migration-strategy","title":"Migration strategy (minimal downtime)","category":"Operations","summary":"Schema and data migrations should follow a reversible plan that minimizes downtime, online where possible.","points":["Design every migration to be reversible and idempotent; never go to production without a rollback plan.","Avoid blocking DDL on large tables; use online schema change tools (PG: pg-osc/pgroll; MySQL: gh-ost, pt-osc).","Apply the expand-contract pattern for backward/forward compatible changes: add first, deploy, then remove the old.","Move large data volumes in small batches; do not create lock and WAL bloat with a single giant UPDATE.","Rehearse the migration first in a production-like environment with realistic data volume and measure its duration.","Commit the cutover moment, validation steps and rollback decision to a written plan in advance."],"level":"senior"},{"id":"schema-versioning","title":"Schema versioning (Flyway/Liquibase)","category":"Operations","summary":"Schema changes should be managed with migrations that live with the code, are versioned and applied automatically; manual intervention is error-prone.","points":["Version all schema changes with tools such as Flyway/Liquibase and keep them in source control.","Do not run DDL manually in production; every change must go through a reviewed migration file.","Keep migrations ordered and unique; never modify an applied migration file, fix it with a new migration.","Apply and validate migrations automatically in the CI/CD pipeline; guarantee schema consistency across environments.","Advance application code and schema version in step; adopt the expand-contract pattern for backward-compatible transitions."],"level":"senior"},{"id":"least-privilege","title":"Principle of least privilege","category":"Security","summary":"Every user and application should have only the privileges needed to do its job; excess privilege enlarges the attack surface.","points":["Use separate, limited-privilege accounts for the application; never connect as superuser/admin.","Grant privileges to roles rather than directly to users; manage access through role membership.","Allow only the required tables and operations (SELECT/INSERT/UPDATE); avoid broad GRANT ALL.","Review privileges periodically and revoke unused ones; remove access immediately when an employee/service leaves.","Do not embed secrets (passwords, connection strings) in code; use a secrets manager (vault) and rotate them regularly."],"level":"junior"},{"id":"encryption-basics","title":"Encryption basics (at-rest/in-transit)","category":"Security","summary":"Data should be encrypted both when stored and when transmitted; encryption is basic protection for compliance and against data leaks.","points":["Enforce TLS in transit; reject unencrypted connections and enable certificate verification.","Apply encryption at rest (TDE or disk/volume encryption) so a stolen disk/backup cannot be read.","Take key management seriously: keep keys in a separate KMS/HSM and rotate them regularly.","Additionally encrypt highly sensitive fields such as credit card numbers and national IDs at the application layer (field-level).","Encrypt backups too; extend production protection to backups and replicas.","Mask or anonymize sensitive data in test/development environments (data masking)."],"level":"senior"},{"id":"monitoring-basics","title":"Monitoring basics (golden signals)","category":"Operations","summary":"Effective monitoring means collecting the right metrics and alerting on meaningful thresholds; it catches problems before users notice.","points":["Monitor the golden signals: latency, traffic, error rate and saturation.","Add database-specific metrics: slow queries, lock/wait times, connection count, cache hit ratio, replication lag.","Tie alerts to impact and actionable thresholds rather than symptoms; noisy alerts cause alert fatigue.","Keep the slow query log and query statistics (such as pg_stat_statements) permanently enabled and review them regularly.","Retain metrics historically; build a baseline for trends and capacity planning.","Always alert on critical conditions such as remaining disk space and backup success."],"level":"junior"},{"id":"query-design","title":"Query design and N+1 prevention","category":"Performance","summary":"An efficient query fetches only the data needed with minimal round trips; N+1 is the most common and most insidious performance bug.","points":["Select only the needed columns instead of SELECT *; this improves network, memory and covering-index opportunities.","Prevent the N+1 problem: use a JOIN or a batch (IN) query instead of single queries in a loop; enable eager loading in the ORM.","Paginate large result sets; prefer keyset (seek) pagination over OFFSET on large data.","Leave business logic to the query as much as possible; do not fetch the whole table and filter/aggregate in the application.","Examine the actual plan and row estimates with EXPLAIN ANALYZE; eliminate full table scans and misestimates.","Back frequent, expensive aggregation queries with a materialized view or a precomputed summary table."],"level":"junior"},{"id":"large-table-management","title":"Large table management (archiving/partitioning)","category":"Performance","summary":"Continuously growing tables slow down queries, maintenance and backups over time; keep them under control with archiving and partitioning.","points":["Separate hot (active) and cold (historical) data; move old data to an archive table or cheap storage.","Partition the table by date/range; purge old data instantly with partition DROP instead of DELETE.","Perform large deletes/updates in small batches; a single giant DELETE creates locks, WAL bloat and table bloat.","Monitor bloat and table/index size regularly; apply online reorganization (VACUUM/reorg) when needed.","Keep indexes that suit frequent queries; maintaining unnecessary indexes on a large table is very expensive."],"level":"senior"},{"id":"caching-strategy","title":"Caching strategy","category":"Performance","summary":"A cache lowers latency and load by moving repeated reads away from the database; the real challenge is invalidation.","points":["Use the cache for expensive, frequently read, rarely changing data; do not try to cache everything.","Choose an invalidation strategy: TTL, update on write (write-through) or delete on write (write-invalidate).","Define stale data tolerance based on business rules; do not rely on the cache where strict consistency is required.","Prevent cache stampedes (many requests filling an empty cache at once) with locking or request coalescing.","Monitor the cache hit ratio; a low hit ratio shows the cache is not providing value.","A cache is an acceleration layer, not durable state; the system must keep working correctly when the cache is lost."],"level":"senior"},{"id":"statistics-maintenance","title":"Statistics and maintenance discipline","category":"Operations","summary":"Up-to-date statistics and regular maintenance ensure the query planner makes the right decisions and performance does not degrade over time.","points":["Keep statistics current (ANALYZE / auto-analyze); stale statistics push the planner toward a wrong plan.","Run VACUUM/cleanup regularly to remove dead rows and bloat; this is critical on MVCC engines.","Monitor index fragmentation and rebuild online when necessary (REINDEX/rebuild).","Update statistics manually after a bulk load; prevent the planner from working with the old distribution.","Schedule maintenance jobs in low-traffic windows, monitor their duration/impact and automate them.","Watch for plan regressions (a query that suddenly slows down); statistics freshness is the first thing to check."],"level":"senior"}]},"guide/best-practices-2.json":{"topics":[{"id":"iac","title":"Manage infrastructure as code (IaC)","category":"Operations","summary":"Database servers, networking and configuration should be defined in versioned code, not by hand, so environments are reproducible, auditable and reversible.","points":["Keep server, parameter group, network and user definitions as code with tools such as Terraform/Pulumi/CloudFormation, under source control.","Forbid manual console changes (click-ops); run drift detection and fix resources that deviate from the code.","Manage differences between environments (dev/stage/prod) with variables; build production-like environments from the same module.","Keep the state file remote and locked (e.g., S3 + DynamoDB lock); do not leak secrets into state as plain text.","Review changes through a plan/apply flow; put approval gates in front of production."],"level":"senior"},{"id":"automation-idempotency","title":"Automation and idempotency","category":"Operations","summary":"Operational tasks should be turned from manually repeated commands into idempotent automation that produces the same result no matter how many times it runs.","points":["Write every script to be idempotent so that re-running it neither fails nor corrupts state (e.g., CREATE IF NOT EXISTS instead of CREATE).","Tie recurring maintenance tasks (backup, cleanup, statistics) to scheduled, logged automation; do not leave them to manual runs.","Build explicit precondition checks and safe exits into scripts; re-running a half-finished operation should complete it from where it stopped.","Run automation from code (Ansible/scripts + CI); do not depend on someone typing commands in a terminal in production.","Provide dry-run support for critical operations; show the impact before applying it."],"level":"senior"},{"id":"observability","title":"Observability (metrics/logs/traces)","category":"Operations","summary":"Observability combines metrics, logs and distributed traces so you can move from 'what happened' to 'why it happened'.","points":["Collect all three signals together: metrics (trends), logs (context), traces (request path); none of them alone reveals the root cause.","Trace queries end to end: correlate application traces with database latency (using standards such as OpenTelemetry).","Emit structured (JSON) logs with a correlation/trace id; free-text logs make searching hard.","Avoid high-cardinality labels; keep cardinality explosion and cost under control.","Define SLOs and track the error budget; tie observability to business goals."],"level":"senior"},{"id":"alerting-strategy","title":"Alerting strategy (low-noise, actionable)","category":"Operations","summary":"A good alerting strategy wakes a human only for real, actionable conditions; noise causes alert fatigue and burnout.","points":["Alert on impact and user experience (SLO violations) rather than symptoms; do not page on every metric fluctuation.","Every alert needs a clear owner, condition and runbook; an alert without a defined action is worthless.","Use hysteresis and duration (for: 5m) on thresholds; keep momentary spikes from firing alerts.","Separate paging from notification (ticket/email); page only for what needs immediate intervention.","Review alerts regularly; fix or remove the ones that fire constantly and get ignored.","Always cover silent disasters such as backup failures, full disks and stopped replication."],"level":"senior"},{"id":"dr-testing","title":"DR/failover drill discipline","category":"Backup & HA","summary":"A disaster recovery plan is reliable only when it is validated with regular, realistic drills; an untested plan is just an assumption.","points":["Schedule failover and restore drills and run them periodically; a paper plan reviewed once a year is not enough.","Measure the actual RTO and RPO in every drill; report the deviation from target and improve.","Rehearse planned failover in a production-like environment with real traffic; do not just start and stop the standby.","Simulate scenarios such as region loss, disk failure and data corruption with game days.","Record lessons learned in the runbook after each drill; turn steps remembered by feel into automation."],"level":"expert"},{"id":"blue-green-deploy","title":"Blue-green / zero-downtime deployment","category":"Operations","summary":"Blue-green and canary deployments run the new version alongside the old one and shift traffic gradually, minimizing downtime and risk.","points":["Maintain two production environments (blue/green); deploy the new version to the idle one, validate it, then switch traffic in a single step.","If something goes wrong, switch traffic back to the old environment immediately; rollback should not require a redeploy.","With canary, send a small percentage of traffic to the new version, watch the metrics and increase gradually.","Decouple the database schema from the deployment: both code versions must work with the same schema (expand-contract).","Define health checks and automatic rollback thresholds in advance; shorten the time spent waiting on manual human decisions."],"level":"senior"},{"id":"expand-contract","title":"Backward-compatible schema changes (expand-contract)","category":"Design","summary":"The expand-contract pattern splits a schema change into adding (expand), then migrating, and finally removing the old (contract), enabling a zero-downtime transition.","points":["Expand first: add the new column/table in a backward-compatible way (nullable or with a default) without breaking the old one.","Deploy the application so that it works with both the old and new schema, using dual-write or dual-read.","Migrate data to the new structure in the background in small batches and validate it; do not lock with one giant migration.","Contract only after all instances have moved to the new version and validation is complete: remove the old column/code.","Do each step in a separate deployment; never 'add and drop the old one at the same time', or rollback becomes impossible.","Add NOT NULL and other constraints last, once the data is consistent."],"level":"expert"},{"id":"secrets-management","title":"Secrets management (passwords/keys)","category":"Security","summary":"Database passwords, API keys and connection strings must not be embedded in code or configuration; they should be stored securely and rotated with a central secrets manager.","points":["Do not embed secrets in code, repositories, env files or images; use a secret store such as Vault, AWS Secrets Manager or KMS.","Inject secrets into applications at runtime; do not leave them on disk as plain text.","Rotate passwords and keys regularly; where possible, use short-lived, automatically generated credentials.","Grant access with least privilege and an audit log; it must be traceable who accessed which secret and when.","Scan the codebase for leaked secrets (git-secrets, trufflehog); revoke and rotate a leaked secret immediately.","Make human access temporary and approval-based (just-in-time) rather than personal and permanent."],"level":"senior"},{"id":"cost-optimization","title":"Cloud cost optimization","category":"Operations","summary":"Cloud database cost can be reduced significantly without sacrificing performance through right-sizing, storage tiering and cleaning up unused resources.","points":["Size instances based on actual usage metrics; downsize servers with consistently low utilization (right-sizing).","Use reserved capacity/savings plans for predictable baseline load; leave variable load on-demand.","Move cold data to a cheaper storage tier; separate hot data from rarely accessed data.","Clean up unused resources: orphaned snapshots, idle replicas and abandoned test databases are cost leaks.","Assign costs to owners with tags and set up budget alerts; see surprises immediately, not at month end.","Review backup retention and replica count against business needs; excess is both risk and cost."],"level":"senior"},{"id":"multi-tenancy","title":"Multi-tenant design","category":"Design","summary":"In multi-tenant systems, the isolation model (shared table, separate schema, separate database) is a deliberate trade-off between security, cost and scalability.","points":["Choose the isolation model: shared table (tenant_id), schema per tenant or database per tenant; each has a different cost/isolation balance.","In the shared model, apply a tenant_id filter to every query; guarantee no leakage at the engine level with row-level security (RLS).","Put tenant_id first in the relevant indexes; prevent cross-tenant scans and hot spots.","Apply resource limits and quotas against the noisy neighbor effect; one tenant must not starve the others.","Design a path (tenant migration) from the start for moving large or premium tenants to a separate schema/database.","Think about backup, deletion and data portability per tenant; you must be able to honor KVKK/GDPR erasure requests."],"level":"expert"},{"id":"time-series-design","title":"Time-series data design","category":"Design","summary":"Time-series data is special due to high write volume and time-based query patterns; it should be designed with date-based partitioning, rollups and a retention policy.","points":["Split data with timestamp-based RANGE partitioning; purge old data cheaply with partition DROP.","Instead of accumulating raw data as is, produce regular-interval summaries (rollup/continuous aggregate); let queries run fast on the summaries.","Put the time and tag (tag/source) components first in the index; the typical query pattern is 'specific source, specific range'.","Choose the right tool: at high volume, time-series engines such as TimescaleDB and ClickHouse can be far more efficient than relational ones.","Define the retention policy from the start; keep raw data short, aggregated data long.","Always store time in UTC; handle local time and daylight saving shifts in the query layer."],"level":"senior"},{"id":"event-sourcing","title":"Event sourcing / CQRS fundamentals","category":"Design","summary":"Event sourcing records not the state but the events that change it; CQRS separates read and write models so each scales independently.","points":["Instead of overwriting the current state, accumulate an immutable event stream; derive the current state by replaying events.","With CQRS, separate the write model (commands) from the read model (queries); speed up the read side with denormalized projections.","Version the event schema and evolve it backward-compatibly; never delete old events, convert them to the new version with upcasting.","Accept that read projections will be eventually consistent and account for the lag in the design.","Take snapshots to avoid the cost of replaying a long event stream from the beginning every time.","Use this pattern not everywhere but in domains that need an audit trail, complex business rules and history over time; do not underestimate the complexity it brings."],"level":"expert"},{"id":"data-retention","title":"Data retention and archiving policy","category":"Operations","summary":"Not all data is kept forever; a defined retention and archiving policy based on legal, business and cost requirements delivers both compliance and performance.","points":["Define the retention period for each data type based on business and legal (KVKK/GDPR) requirements; a vague 'keep it just in case' is not a policy.","Archive or delete expired data automatically; do not leave it to manual cleanup, which gets forgotten while tables bloat.","Keep hot data on fast storage and archives on cheap/cold storage; tier by access frequency.","Design the mechanism to handle personal data deletion/anonymization requests from the start (right to erasure).","Keep archived data retrievable and queryable when needed; archived must not mean inaccessible.","Record deletions in the audit log; what was deleted, when and why must be traceable."],"level":"senior"},{"id":"change-management","title":"Change management and production safety","category":"Operations","summary":"Every change to a production database must be reviewed, reversible and traceable; uncontrolled manual intervention is the most common cause of downtime.","points":["Do not run ad-hoc DDL/DML by hand in production; every change should go through a peer-reviewed migration or script.","Apply changes in a low-traffic window with a rollback plan ready; do not touch production unplanned.","Rehearse risky commands first in a production-like environment with real data volumes; measure their duration and locking impact.","Restrict and audit production access: who ran which command and when must be logged (just-in-time access).","Before a delete/update, verify the WHERE clause and the number of affected rows; run inside a transaction and confirm with a SELECT first.","Tie changes to a calendar and communication; do not surprise other teams with unannounced production changes."],"level":"senior"}]},"guide/best-practices-3.json":{"topics":[{"id":"plan-reading-expert","title":"How to read an execution plan","category":"Performance","summary":"An execution plan shows the optimizer's estimates and the physical operators it chose; the bottleneck hides in the deviation between estimated and actual row counts.","points":["Distinguish access methods: Full/Table Scan is for wide reads on large tables, Index Seek/Range Scan for selective predicates, Index Full Scan for ordered reads; Rows Removed by Filter or the predicate/access split shows where selectivity is lost.","Match the join method to the workload: Nested Loop is ideal for a small outer set + indexed inner set, Hash Join for large equi-joins, Merge Join for two pre-sorted sets; a wrong choice is usually the result of a bad cardinality estimate.","Hunt for cardinality estimation errors: compare estimated vs actual rows with EXPLAIN ANALYZE (PG), DBMS_XPLAN ALLSTATS LAST (Oracle) or SET STATISTICS PROFILE (MSSQL); a deviation above 10x suggests the plan choice may be fundamentally wrong.","Read the plan by real cost: find the operator consuming the most time/logical reads (logical reads, buffers); the optimizer 'cost' unit is an estimate, not the actual bottleneck.","Catch spill/temp space warnings: a Hash/Sort operator that does not fit in memory and spills to disk (PG 'Sort Method: external merge', MSSQL Sort/Hash Warning with SpillToTempDb in the plan) indicates insufficient work_mem/sort area.","Read the operator tree from the inside out and bottom up: inner nodes run first and rows flow upward; compute the total cost per operator by multiplying loops by actual time."],"level":"expert"},{"id":"parameter-sniffing-deep","title":"Parameter sniffing and bind peeking","category":"Performance","summary":"The optimizer builds and caches a plan based on the parameter value of the first call; when later values with a different distribution inherit that plan, performance can change dramatically.","points":["Recognize the symptom: the same query is fast with some parameters and very slow with others; it shows the cached plan was compiled for an atypical 'first' value.","Risk is high on columns with skewed distribution; histograms make the optimizer value-sensitive while a single cached plan keeps it value-insensitive, and this tension is the source of the problem.","In MSSQL, OPTION (RECOMPILE) builds a plan on every execution, OPTIMIZE FOR UNKNOWN picks a balanced plan based on the average density, and OPTIMIZE FOR (@p = value) pins a typical value.","In Oracle, adaptive cursor sharing and bind-aware cursors produce different plans for skewed values; in PG, the custom vs generic plan choice is controlled with plan_cache_mode.","For a permanent fix, separate plans by value: design statement splitting, filtered/partial indexes or a dedicated access path for highly atypical values.","In OLTP, excessive RECOMPILE adds CPU and compilation (hard parse) cost; measure and keep the balance on frequently executed queries."],"level":"expert"},{"id":"plan-stability","title":"Plan stability: baselines, hints and plan guides","category":"Performance","summary":"Engine-specific persistence mechanisms are used to freeze verified plans of critical queries and prevent unexpected plan regressions on statistics/version changes.","points":["In Oracle, pin approved plans with SQL Plan Baselines (SPM); new plans are only collected as candidates until they are 'accepted', and are verified in a controlled way with evolve.","In MSSQL, detect plan regression with Query Store and force the known-good plan with 'Force Plan'; Automatic Plan Correction can automate this.","PostgreSQL has no built-in plan freezing; stability comes from the pg_hint_plan extension or from query rewrites, extended statistics and index design.","Use hints as a last resort, with justification and comments; a hint assumes the physical schema and data distribution, and becomes harmful when they change.","Audit the baseline/forced plan inventory periodically; pinned plans can lose optimality over time as data grows, so do not fall into the 'stable but slow' trap.","Capture critical query plans before a version upgrade; prepare baselines as a safety net against optimizer behavior changes."],"level":"expert"},{"id":"latch-mutex-contention","title":"Latch, mutex and spinlock contention","category":"Performance","summary":"Latches/mutexes are lightweight locks protecting in-memory structures; under high concurrency, contention on them burns CPU in spinlock loops and stops scaling.","points":["Separate locks from latches: locks protect logical data (row/table) for the duration of a transaction, latches protect physical memory structures (buffers, hash chains) for microseconds; their diagnostic methods differ.","In Oracle, 'cache buffers chains' latch contention usually points to a hot block; a small, frequently read table or the leading edge of a monotonically increasing index (right-hand index) is a common cause.","Inserts with sequentially increasing keys (identity/sequence) cause last-page/block contention (hot page, PAGELATCH); spread them with hash partitioning, a reverse-key index or a randomized key.","In MSSQL, monitor spinlock statistics and PAGELATCH_* waits; for tempdb allocation contention, use multiple data files and a proper tempdb configuration.","Spinlock contention shows up as high CPU with low throughput; the fix is to reduce contention at the code/architecture level, and adding hardware often makes it worse.","NUMA awareness and limiting the number of connections (pooling) reduce latch contention; excessive parallelism can increase latch pressure."],"level":"expert"},{"id":"wait-event-methodology","title":"Wait event analysis methodology","category":"Performance","summary":"Manage performance by measuring where the system spends its time waiting, not by guessing; top-down wait analysis is the scientific method that systematically narrows down the bottleneck.","points":["Break DB time into its components: separate time spent on CPU from wait time; start with the largest component and do not drift into micro-optimization.","Find the top wait event with ASH/AWR and 'Top Wait Events' in Oracle, pg_stat_activity + pg_wait_sampling in PG, and sys.dm_os_wait_stats and Query Store in MSSQL.","Interpret the wait class: I/O waits point to storage/plans, lock waits to concurrency design, latch waits to hot spots, and network/commit waits to configuration problems.","Drill down to the session/query level: start from total waits, go to the SQL contributing the most and then to that SQL's plan to isolate the cause (drill-down).","Tie measurement to the workload window: take the difference (delta) of cumulative counters between two points; totals are misleading, rates and time intervals matter.","Filter idle/benign waits out of the noise; not every wait is a problem, what matters are the foreground waits contributing to user response time."],"level":"expert"},{"id":"plan-regression-detection","title":"Detecting execution plan regressions","category":"Performance","summary":"A query that suddenly slows down is usually caused by a plan change, not a code change; detecting regressions proactively is cheaper than reactive firefighting.","points":["Keep and compare plan history: track plan changes and duration/resource trends for the same sql_id/query_hash with MSSQL Query Store and Oracle AWR/SQL Monitor.","Identify the regression trigger: statistics updates, data volume growth, version upgrades, parameter changes or dropped indexes are the most common causes.","Set up automatic alerting: raise an alert when the plan hash changes and average duration/logical reads exceed a threshold; track per-execution metrics, not cumulative ones.","Prepare a fast rollback path: temporarily pin the known-good plan with Force Plan (MSSQL) or a SQL Plan Baseline (Oracle) and fix the root cause later.","For statistics-driven regressions, examine locked/current statistics and histogram freshness; automatic statistics gathering may be scheduled during critical hours.","Run regression tests before an upgrade: replay a representative workload against the old and new optimizer and report plan differences (an Oracle SPA-like approach)."],"level":"expert"},{"id":"mvcc-vacuum-internals","title":"MVCC and vacuum/undo internals","category":"Internals","summary":"MVCC prevents reader-writer blocking by keeping old row versions; how and where these versions are kept (undo vs inside the table) determines the maintenance strategy.","points":["In PostgreSQL, old versions remain inside the table itself as dead tuples; VACUUM cleans them up, otherwise bloat and index growth occur.","Oracle and MySQL/InnoDB keep old versions in undo/rollback segments; if a long-running read exhausts undo, errors such as 'ORA-01555 snapshot too old' appear.","Tune autovacuum to the workload: lower the scale factor and reduce the cost delay on high update/delete tables; give aggressive tables per-table settings.","Keep the visibility map healthy; index-only scans and frozen tuple tracking depend on it, and performance degrades if it is neglected.","Monitor transaction ID wraparound risk: track the datfrozenxid/age metric in PG and prevent aggressive/anti-wraparound vacuum from kicking in during business hours.","Encourage HOT (Heap-Only Tuple) updates: updates that do not change an indexed column are done in place without growing the index; an appropriate fillfactor supports this."],"level":"expert"},{"id":"wal-redo-log-mechanics","title":"WAL/redo/transaction log mechanics","category":"Internals","summary":"Durability is achieved by writing the change to the log before data pages are written to disk; the rate and size of the log stream determine both durability and write performance.","points":["Understand the write-ahead principle: for a commit to return, it is enough that the related log records are on durable storage; dirty data pages are written lazily later.","Tune group commit and log flush behavior: balance fsync frequency against the workload's durability requirements with commit_delay/synchronous_commit in PG and log writer behavior in Oracle.","Monitor the log generation rate (WAL/redo generation); a sudden increase points to bulk updates, full-page images after a checkpoint (full_page_writes/FPI) or index maintenance, and increases DR lag.","The log is a source not only for recovery but also for replication and CDC; logical replication and WAL shipping feed from this stream, so design the archive/cleanup policy accordingly.","Separate the log disk from the data disk and put it on low-latency storage; log writes are sequential and latency-sensitive, and commit throughput depends directly on them.","Guard against uncontrolled log growth: in MSSQL use the correct recovery model and regular log backups; in PG make sure archiving/replication slots do not fall behind."],"level":"expert"},{"id":"buffer-pool-management","title":"Buffer/shared pool management and eviction","category":"Internals","summary":"The buffer pool reduces disk I/O by keeping hot data in memory; the algorithm that decides which page is evicted and when governs the hit ratio and stability.","points":["Do not target the buffer cache hit ratio alone; even a high ratio can hide the latency of physical reads, so also look at absolute I/O and wait time.","Understand the eviction algorithm: against pure LRU being polluted by sequential scans, PG uses clock-sweep, Oracle a touch-count/LRU variant, and InnoDB midpoint-insertion LRU.","Prevent large one-off scans from flushing hot data out of the pool (cache pollution); protect it with separate buffer pools or scan-friendly access strategies.","Manage the dirty page ratio and write pressure: too many dirty pages cause I/O spikes at checkpoint, overly aggressive writing generates unnecessary I/O; measure the balance.","Size memory to the working set; the goal is to cover the actively accessed set, not the total data, and oversizing yields diminishing returns.","In the Oracle shared pool, monitor library/dictionary cache contention and frequent hard parses; using bind variables instead of literal SQL reduces shared pool pressure."],"level":"expert"},{"id":"checkpoint-tuning","title":"Checkpoint tuning","category":"Internals","summary":"A checkpoint shortens recovery time by writing dirty pages to disk; too-frequent checkpoints create I/O peaks and too-infrequent ones create long recovery, and this trade-off must be tuned.","points":["Balance recovery time against runtime I/O: infrequent checkpoints mean less write load but longer crash recovery, frequent checkpoints the opposite.","In PG, spread checkpoints over time with checkpoint_timeout and max_wal_size; smooth the I/O peak by distributing writes across the interval with checkpoint_completion_target.","MSSQL indirect checkpoint (target recovery time) provides continuous background writing; it makes latency predictable by preventing sudden large checkpoint waves.","Correlate checkpoint frequency with the log/WAL generation rate; during heavy write periods, monitor whether the size trigger rather than the time trigger kicks in.","full_page_writes (PG) logs a full page on the first change after a checkpoint; if checkpoints are too frequent WAL volume balloons, increasing DR and disk cost.","Catch 'checkpoint sink' symptoms: rising write latency and fsync waits during checkpoints; review the storage queue and distribution."],"level":"expert"},{"id":"statistics-strategy","title":"Statistics gathering strategy","category":"Internals","summary":"The quality of optimizer decisions depends on the freshness and resolution of statistics; sampling rate, histograms and extended statistics determine cardinality estimates.","points":["Choose the sampling rate based on data size and skew: too small a sample produces wrong estimates on skewed distributions, too large a sample creates unnecessary gathering cost.","Model skew with histograms: PG default_statistics_target, Oracle FREQUENCY/HYBRID histograms and MSSQL statistics histograms expose frequent and unbalanced values to the optimizer.","Capture cross-column correlation with extended statistics: PG CREATE STATISTICS (ndistinct/dependencies) and Oracle extended stats fix estimation errors on interdependent columns.","Decouple gathering schedules from the workload: prevent the automatic statistics/gather job from running during critical hours and causing I/O and plan instability.","Manage the risk of stale statistics on fast-growing tables; threshold-based automatic updates can lag, so add manual/scheduled gathering for critical tables.","Know that statistics changes can cause plan regressions; in critical environments, lock statistics (locked stats) or publish them in a controlled way with pending publish."],"level":"expert"},{"id":"partition-pruning-join","title":"Partition pruning and partition-wise joins","category":"Scale/Architecture","summary":"Partitioning does not just split a table; the real gain is that queries never read irrelevant partitions (pruning) and joins run in parallel per partition.","points":["Choose the partition key based on the most frequent and selective filter; pruning works only when the query predicate includes the partition key, otherwise all partitions are scanned.","Distinguish static and dynamic pruning: constant predicates eliminate partitions at compile time, bind/join values at run time (runtime pruning); verify the plan accordingly.","For partition-wise joins, partition both tables on the same key with a compatible scheme; the optimizer reduces a large join to small partition-by-partition joins and parallelizes them.","Balance the number and granularity of partitions: too many partitions means metadata and planning cost, too few means weak pruning; size them to the access pattern.","Combine lifecycle management with partitioning: DETACH/DROP or archive old partitions; this gives instant, low-logging cleanup instead of DELETE.","Remember that functions/type conversions or expressions that hide the key break pruning; verify the number of scanned partitions with EXPLAIN."],"level":"expert"},{"id":"shard-key-selection","title":"Shard key selection and hot spots","category":"Scale/Architecture","summary":"The shard key determines the fate of a distributed system; a bad choice piles load onto a single node (hot shard), stops scaling and makes cross-shard queries expensive.","points":["Choose a high-cardinality key that distributes load evenly; a low-cardinality or temporal/monotonically increasing key piles load onto a single shard and creates a hot spot.","Design the key so the most frequent queries are resolved on a single shard; cross-shard (scatter-gather) queries multiply latency and coordination cost.","Hash sharding evens out load but scatters range queries; range sharding is good for range queries but increases hot spot risk; choose based on the access pattern.","Minimize redistribution when adding/removing nodes with consistent hashing; a fixed modulo hash moves almost all data when the node count changes.","Design the resharding plan from the start; changing the key later is expensive and risky, so model the growth scenario in advance.","When splitting by tenant/customer, prepare a dedicated shard or a sub-partitioning (split) strategy so a giant tenant (whale) does not fill a single shard."],"level":"expert"},{"id":"read-write-splitting","title":"Read-write splitting and replica routing","category":"Scale/Architecture","summary":"Routing reads to replicas relieves the write primary; but because replication lag can break consistency guarantees, routing must be done deliberately.","points":["When distributing read load to replicas, monitor replication lag continuously; a stale replica can show users old data.","Route flows that require read-your-writes (the writing user reading immediately) to the primary, or guarantee a fresh replica with causal reads/tokens.","Do the routing in the infrastructure layer, not the application: distribute transparently with a proxy/router (ProxySQL for MySQL; for PG, Pgpool-II or HAProxy with separate read/write endpoints, as PgBouncer does not split reads and writes; MSSQL read-only routing via application intent).","Remove a replica from the pool when the lag threshold is exceeded; sending reads to a high-lag replica produces data inconsistency under the illusion of 'scaling'.","Isolate heavy, lag-tolerant reads such as analytics/reporting on a dedicated replica; shield OLTP replicas from that load.","Test that read routing updates automatically in a failover scenario; a router unaware of the new primary causes split-brain or downtime."],"level":"expert"},{"id":"connection-pool-sizing","title":"Connection pool sizing math","category":"Scale/Architecture","summary":"Pool size is determined by calculation based on Little's law and core count, not intuition; too many connections lower throughput through contention and context switching.","points":["Apply Little's law: required concurrency ≈ request rate (throughput) × average processing time; derive target concurrency from measured values, not guesses.","A small pool is often faster: when active connections exceed physical core/disk parallelism, CPU context switching and latch contention lower throughput.","As a rough starting point, use a core pool of connections close to the core count (e.g., a small number of real connections behind PgBouncer) and tune by measuring under load.","Plan the application-side pool together with the database max_connections; N application instances × pool size must not exceed the database limit.","On engines with a high per-connection process cost such as PG, use PgBouncer/transaction pooling; every connection costs memory and scheduling.","Monitor pool saturation, queue wait time and timeouts; a constantly growing queue signals either a bottleneck or undersizing, so do not grow the pool blindly."],"level":"expert"},{"id":"lock-escalation-deadlock","title":"Lock escalation and deadlock prevention patterns","category":"Reliability","summary":"Many row locks can escalate to a table lock and kill concurrency; a deadlock is the avoidable result of inconsistent lock ordering.","points":["Break up large bulk operations that trigger lock escalation (batch/chunk); locking millions of rows in a single transaction causes escalation and blocking.","The basic pattern for preventing deadlocks is consistent lock ordering: all code paths must lock resources in the same order (e.g., by primary key order).","Keep transactions short and do a single atomic unit of work; transactions containing user interaction, remote calls or long computations hold locks unnecessarily long.","Choose the isolation level deliberately: unnecessarily high isolation causes excess locking, unnecessarily low causes anomalies; in MVCC engines reads already avoid most locks.","Collect deadlock graphs/traces (MSSQL deadlock graph, PG log_lock_waits, Oracle trace); log the victim and trace recurring patterns to the root cause.","Implement retry logic: retry the deadlock victim transaction idempotently; it is a safety net for contention that is unavoidable by design."],"level":"expert"},{"id":"long-transaction-bloat","title":"Long transactions and bloat/undo management","category":"Reliability","summary":"A long transaction left open blocks cleanup of old versions in MVCC engines, starting a chain of bloat, undo pressure and performance collapse.","points":["Monitor and alert on long-running/open transactions: idle in transaction in PG and long queries holding active undo in Oracle stop cleanup (vacuum/undo reclamation).","In PG, a single long transaction prevents VACUUM from removing dead tuples; tables and indexes bloat, and one 'zombie' session can slow down the whole database.","Terminate stuck transactions automatically with idle_in_transaction_session_timeout and statement_timeout; build a line of defense against application leaks.","In Oracle/InnoDB, a long read exhausts undo and produces 'snapshot too old'; size the undo space/retention to the longest query in the workload.","Split batch jobs with short commit intervals; a single giant transaction accumulates undo/WAL and dangerously prolongs rollback time.","Measure and reclaim existing bloat: measure bloat with pgstattuple and give back space and index efficiency with VACUUM FULL/pg_repack or an online reorg."],"level":"expert"},{"id":"dr-game-day","title":"DR drills (game day) and RPO/RTO validation","category":"Reliability","summary":"An untested backup and failover plan is an assumption; DR capability is proven only by measuring RPO/RTO in realistic drills.","points":["Derive RPO and RTO from business requirements and measure them in practice: backup/replication lag determines RPO, recovery + validation time determines RTO; compare target against reality.","Restore backups regularly and automatically: a backup that has never been restored is a Schrodinger backup; without a restore test, the claim that a backup exists is unverified.","Plan game day drills in a near-production environment with realistic failure scenarios (primary loss, region outage, data corruption).","Run the failover and failback procedures end to end; include DNS/connection routing, application reconnection and replica promotion.","Prove point-in-time recovery (PITR) capability: test that you can return to a specific moment with log archives + full backups in a logical corruption scenario.","Produce the measured RTO/RPO, surprises and action items from every drill; update the runbook and verify the improvement in the next drill."],"level":"expert"},{"id":"observability-slo","title":"Observability and SLOs/golden signals","category":"Reliability","summary":"To manage the database by business impact, tie the golden signals (latency, traffic, errors, saturation) to SLOs and prioritize with an error budget.","points":["Monitor the four golden signals: latency, traffic (throughput), error rate (errors) and saturation; they summarize health without drilling into root cause.","Measure latency with percentiles (p95/p99), not averages; the average hides tail latency and the worst user experience.","Define SLOs and an error budget; alert on symptoms (SLO violations), not on every low-level metric fluctuation, to prevent alert fatigue.","Apply the USE (Utilization, Saturation, Errors) method to resources: observe CPU, memory, disk I/O and connection pool saturation separately.","Correlate monitoring across layers: see the query -> wait event -> resource saturation -> business metric chain on a single time axis.","Set up metrics + logs + traces together; metrics alone answer 'what', logs/traces answer 'why', and without them MTTR grows."],"level":"expert"},{"id":"least-privilege-roles","title":"Least privilege and role design","category":"Security","summary":"Having each account hold only the minimum privileges needed for its job narrows the blast radius of a breach; roles provide a manageable abstraction instead of granting privileges directly.","points":["Grant privileges to roles, not users; design roles by function (reader, writer, DDL, operations) and assign users to roles, avoiding direct grants.","Give application accounts only the required object and operation privileges; DDL, SUPERUSER/DBA and broad schema privileges must not exist on a production application account.","Personalize and audit human administrator access: use individual identities + elevated-on-demand (JIT/break-glass) access instead of a shared admin account.","Revoke PUBLIC/default broad privileges; configure default privileges and schema-level controls to prevent default rights leaking onto new objects.","Review privileged accounts regularly (access recertification); periodically clean up unused accounts, accounts of departed employees and over-privileged accounts.","Enforce separation of duties; the same person must not be able to change production and also erase the audit trail, so protect audit writing with a separate privilege."],"level":"expert"},{"id":"encryption-decision","title":"Encryption decisions: at-rest, in-transit, TDE","category":"Security","summary":"Encryption is chosen based on the threat model; each layer protects against a different attack, and key management is often more critical than the encryption algorithm.","points":["Clarify the threat: at-rest (stolen disks/backups), in-transit (network eavesdropping) and application-level (not exposed to anyone, DBAs included) are different protections; decide which you need.","For in-transit, enforce TLS on client-server connections; do not disable certificate validation, as it leaves you open to man-in-the-middle (MITM) attacks.","For at-rest, compare TDE with file system/disk encryption options; TDE protects datafiles and backups but not in-memory data or authorized DBA access.","For the most sensitive fields, use application/column-level encryption or a client-side approach like Always Encrypted so that even the DBA cannot see plain text.","Take key management seriously: store keys in a KMS/HSM separate from the data and plan rotation and access control; if the key leaks, encryption is worthless.","Measure the performance and functional impact: indexing/searching is restricted on encrypted columns and TDE adds CPU cost; make the decision as a measured trade-off."],"level":"expert"},{"id":"audit-strategy","title":"Audit strategy","category":"Security","summary":"The audit trail provides evidence of security events and compliance requirements; what is logged, where the trail goes and how long it is kept set the balance between noise and evidence.","points":["Derive what to audit from threats and compliance: prioritize privileged operations, logins/login failures, schema (DDL) changes and access to sensitive data.","Do not fall into the log-everything trap; excessive auditing degrades performance and drowns the signal in noise, so target critical events selectively and purposefully.","Write the audit trail to a separate, secure target that the production DBA cannot modify (an external log collector/SIEM); prevent trail tampering through separation of duties.","Use engine capabilities: tune the level of detail to your needs with Oracle Unified/Fine-Grained Audit, MSSQL SQL Audit and PG pgAudit.","Set retention according to compliance requirements (KVKK/GDPR/PCI-DSS, etc.) and automate the archive/cleanup policy; preserve the integrity of the trail.","Do not just collect the trail; review it regularly and tie it to alerts: auditing creates value only when it flags anomalies and turns them into action, not as a dead archive."],"level":"expert"}]},"guide/best-practices-4.json":{"topics":[{"id":"oracle-rac-cache-fusion","title":"Oracle RAC: Cache Fusion and the interconnect","category":"Oracle","summary":"In RAC, multiple instances share the same database; Cache Fusion maintains coherency by shipping blocks between nodes over the interconnect instead of through disk.","points":["When an instance needs a block that resides in another instance's buffer cache, Cache Fusion transfers it RAM-to-RAM over the private interconnect; it does not write it to disk and read it back (disk ping).","Global Cache Service (GCS/LMS processes) manages block ownership and mode (shared/exclusive), while Global Enqueue Service (GES) manages locks and enqueues.","Every block has a master node; block location is tracked in the Global Resource Directory and can be remastered with DRM (Dynamic Resource Mastering) based on access intensity.","The interconnect requires a dedicated low-latency network; use Jumbo Frames (MTU 9000) and, if needed, RDMA/RoCE or InfiniBand, and never share it with the public network.","The 'gc buffer busy' and 'gc cr/current block busy' wait events indicate hot block contention; increasing sequence CACHE, reverse key indexes or spreading the load with partitioning can help.","If the gc blocks lost statistic keeps increasing, there is packet loss on the interconnect; review NIC, switch buffer and socket buffer sizes (rmem/wmem)."],"level":"expert"},{"id":"oracle-rac-node-eviction","title":"Oracle RAC: node eviction and split-brain","category":"Oracle","summary":"Clusterware evicts an unresponsive node from the cluster to prevent split-brain; it decides based on the network heartbeat, the disk heartbeat and the voting disk majority.","points":["CSSD monitors two separate heartbeats: the network heartbeat over the interconnect (misscount, default 30 s) and the disk heartbeat through the voting disks (disktimeout, default 200 s).","In a split-brain, the surviving subcluster (cohort) is determined by the voting disk majority; therefore the number of voting disks must be odd (3 or 5) and they must reside in separate failure groups.","Eviction is usually carried out by a node reboot or reboot-less fencing (restarting only the stack); the goal is to stop the faulty node from writing to shared storage.","Common eviction causes: loss of the interconnect, CSSD starved of CPU under heavy load, voting disk I/O latency, and OCR/voting disk access problems.","For root cause analysis, review ocssd.log, CSSD traces, the alert log and OS messages together with CHM/oclumon (Cluster Health Monitor) data."],"level":"expert"},{"id":"oracle-asm-redundancy-rebalance","title":"Oracle ASM: disk group redundancy and rebalance","category":"Oracle","summary":"ASM protects data by striping it across the disks in a disk group and mirroring it per failure group; it automatically rebalances extents when disks are added or dropped.","points":["Redundancy levels: EXTERNAL (no mirroring, protection is left to the storage array), NORMAL (2 copies, tolerates the loss of 1 disk), HIGH (3 copies, tolerates 2 simultaneous losses).","ASM mirrors failure groups, not disks; the primary extent lives in one failure group and its mirror copies in different failure groups, so no data is lost even if an entire failure group fails.","Adding or dropping a disk triggers a rebalance that redistributes extents; ASM_POWER_LIMIT (0-1024) sets the balance between speed and resource consumption.","The AU (Allocation Unit) size is set when the disk group is created; for large databases a 4 MB AU improves I/O efficiency.","Voting disks and the OCR can be stored in a normal/high redundancy disk group; a quorum failure group is used only for voting disks and holds no data extents."],"level":"expert"},{"id":"oracle-data-guard-modes","title":"Oracle Data Guard: standby types and protection modes","category":"Oracle","summary":"Data Guard provides disaster recovery by shipping redo to a standby; the choice of physical/logical standby and the three protection modes set the balance between data loss and availability.","points":["A physical standby is a block-for-block identical copy of the primary (Redo Apply/MRP); with Active Data Guard it can be queried read-only while open. A logical standby converts redo into SQL and applies it (SQL Apply) and can host different structures/indexes.","Maximum Performance (default): redo is shipped asynchronously (ASYNC) and the primary does not wait for acknowledgment; zero performance impact but a small risk of data loss.","Maximum Availability: redo is shipped synchronously (SYNC/AFFIRM); if the standby is unreachable, the primary does not stop but falls back to MAX PERFORMANCE behavior, and resynchronizes when the standby returns.","Maximum Protection: the primary does not commit until at least one standby acknowledges; it guarantees zero data loss, but the primary shuts down if all standbys are lost.","Fast-Start Failover (FSFO) uses an observer process to fail over automatically when the primary fails; RPO=0 is targeted with FastStartFailoverThreshold and synchronous transport.","Monitor apply lag on the standby with v$dataguard_stats and the 'apply lag'/'transport lag' metrics; real-time apply applies redo before it is archived."],"level":"expert"},{"id":"oracle-exadata-offload","title":"Oracle Exadata: Smart Scan and storage indexes","category":"Oracle","summary":"On Exadata, storage cells take over part of query processing (offload) and return only the relevant data to the DB node; Smart Scan, storage indexes and HCC dramatically reduce I/O.","points":["For full scan queries, Smart Scan performs predicate filtering and column projection in the storage cell; only the required rows/columns, not blocks, are returned to the DB node, which reduces interconnect traffic.","Smart Scan works only with direct path reads; it does not kick in for blocks read from the buffer cache or for certain operations; measure the benefit with the 'cell physical IO bytes eligible for predicate offload' and 'cell physical IO interconnect bytes returned by smart scan' statistics ('cell physical IO bytes saved by storage index' shows only the storage index savings).","A storage index keeps column min/max values in memory for each 1 MB storage region; if the query predicate falls outside the range, that region is skipped without being read (it works like a negative index).","Storage indexes are not persistent; they live in memory and are rebuilt when the cell restarts. Physically ordered data (clustering) increases the skip ratio.","HCC (Hybrid Columnar Compression) compresses data column-wise within Compression Units; QUERY LOW/HIGH is for query-heavy data, while ARCHIVE LOW/HIGH is for rarely accessed archive data and delivers very high compression at a high CPU cost."],"level":"expert"},{"id":"oracle-awr-ash-analysis","title":"Performance analysis with Oracle AWR and ASH","category":"Oracle","summary":"AWR periodically collects performance snapshots, while ASH samples active sessions every second; together they answer 'where did the time go' in terms of wait events.","points":["By default AWR takes a snapshot every 60 minutes and keeps them for 8 days; an AWR report between two snapshots shows which SQL and wait events consumed DB Time (requires the Diagnostics Pack license).","Always start the analysis from 'Top 10 Foreground Events by Total Wait Time' and DB Time; see the CPU vs I/O vs concurrency balance there instead of getting lost in individual statistics.","ASH samples v$active_session_history once per second and captures short-lived, sudden problems (spikes); it exposes momentary lock/latch storms that AWR misses, by SQL_ID and blocking session.","Average Active Sessions (AAS = DB Time / elapsed time) is a single-number summary of system load; AAS exceeding the number of CPU cores indicates saturation.","For SQL tuning, take the most expensive SQL_ID from the 'SQL ordered by Elapsed Time/Gets' section of AWR, confirm with ASH which step that SQL waits on, then examine the plan line by line with a SQL Monitor report."],"level":"expert"},{"id":"oracle-scn-read-consistency","title":"Oracle SCN and read consistency","category":"Oracle","summary":"The System Change Number (SCN) is the database's logical clock; Oracle isolates readers from writers by building a consistent image (read consistency) from undo as of the SCN at which the query started.","points":["The SCN increases monotonically with each commit and is recorded in redo/undo, the control file and block headers; if a block's last-change SCN is greater than the query SCN, the block is 'rolled back'.","For read consistency, Oracle produces a CR (Consistent Read) copy of the current block and rolls it back to the query's start SCN using undo; thus 'readers do not block writers and writers do not block readers'.","If undo is insufficient or undo_retention is exceeded, a long-running query gets 'ORA-01555 snapshot too old'; the fix is to enlarge the undo tablespace, adjust retention or shorten the query.","Statement-level consistency uses a separate SCN for each SQL statement; a SERIALIZABLE or read-only transaction keeps the single SCN from the start of the transaction for all statements.","In RAC the SCN is kept synchronized across all instances (Lamport or broadcast-on-commit); SCN advancement is coordinated between nodes for global consistency."],"level":"expert"},{"id":"postgres-patroni-failover","title":"PostgreSQL: automatic failover with Patroni","category":"PostgreSQL","summary":"Patroni provides leader election and automatic failover by keeping the PostgreSQL cluster state in a distributed consensus store (DCS); it prevents split-brain with a watchdog.","points":["Cluster state and the leader lock are kept in a DCS such as etcd/Consul/ZooKeeper; the leader periodically renews a lock with a TTL, and if it cannot renew it, the lock expires and a new leader election starts.","On failover, Patroni picks the most up-to-date replica (the one with the highest LSN); the new leader is promoted and the other replicas reconnect to it (reinitialize/rewind).","The watchdog (softdog) is a hardware-level safety net that resets the machine when the old leader loses contact with the DCS, preventing two nodes from being leader at the same time (split-brain).","maximum_lag_on_failover prevents a replica that has fallen far behind from becoming leader and causing data loss; synchronous_mode can target RPO=0, but write latency increases.","Clients should connect not directly to a node but through HAProxy using Patroni's REST health check, or through a VIP; this way traffic is automatically routed to the new leader when the leader changes.","A DCS majority (quorum) is mandatory; use a 3- or 5-member etcd cluster. If the DCS is unreachable, Patroni plays it safe and may demote the current leader to read-only."],"level":"expert"},{"id":"postgres-logical-replication-upgrade","title":"PostgreSQL: zero-downtime major upgrade with logical replication","category":"PostgreSQL","summary":"Because logical replication can carry row changes across versions, it enables major version upgrades with near-zero downtime; the old and new versions run in parallel for a while.","points":["Logical replication decodes changes from WAL logically (INSERT/UPDATE/DELETE); unlike physical replication, it works across different major versions and different platforms.","Flow: create an empty schema on the new version, define a PUBLICATION on the old version and a SUBSCRIPTION on the new one; tables are copied first (initial sync), then live changes start to flow.","Every table must have a REPLICA IDENTITY; for tables without a primary key, replicating UPDATE/DELETE requires REPLICA IDENTITY FULL, otherwise it errors out.","DDL changes are not replicated automatically and sequence current values are not carried over; at cutover, advance the sequences manually on the new version, otherwise conflicting keys will occur.","At cutover, briefly stop the application writing to the old version, and once lag reaches zero (verify with pg_stat_replication), point the application to the new version; if needed, keep a reverse subscription ready for a quick rollback.","Limitations: large objects and some DDL are not replicated; wal_level=logical and sufficient max_replication_slots/max_wal_senders must be set."],"level":"expert"},{"id":"postgres-pgvector-similarity","title":"PostgreSQL: similarity search with pgvector","category":"PostgreSQL","summary":"pgvector stores vector embeddings and performs approximate nearest neighbor (ANN) search; HNSW and IVFFlat indexes set the accuracy-speed-memory trade-off.","points":["Choose the distance operator by use case: <-> L2 (Euclidean), <=> cosine, <#> inner product; the index must use the same operator class as the query, otherwise it is not used.","HNSW builds a multi-layer graph; it gives high recall and fast queries but needs more memory and builds slowly. m (number of connections) and ef_construction tune build quality, ef_search tunes accuracy at query time.","IVFFlat partitions vectors into lists clusters and scans only probes clusters per query; it uses less memory, but the index must be created AFTER the data is loaded (clusters are meaningless on an empty table).","For IVFFlat, start with lists ≈ rows/1000 up to 1M rows and ≈ sqrt(rows) above 1M; increasing probes raises recall but slows the query, so tune this trade-off by measurement.","ANN returns approximate results; if 100% exactness is required, an exact scan without the index is needed. With filtered (WHERE) searches index recall can drop; if needed, consider iterative scan or a partial index.","Vector dimensions and row count determine memory requirements; keep maintenance_work_mem large enough during the build, otherwise the HNSW build spills to disk and becomes very slow."],"level":"expert"},{"id":"postgres-citus-distributed","title":"PostgreSQL: distributed scaling with Citus","category":"PostgreSQL","summary":"Citus scales PostgreSQL horizontally by splitting tables into shards by a distribution column and spreading them across worker nodes; the choice of distribution column determines performance.","points":["The coordinator node receives the query and routes it to the relevant shards based on the distribution column; worker nodes store the shards and process them in parallel. Shards are standard PostgreSQL tables (split by hash range).","The distribution column should be the common filter of most queries and JOINs (e.g. tenant_id); rows with the same column value land in the same shard, so JOINs and foreign keys stay local within the same node (colocation).","Colocation is critical: tables that are joined together run without cross-node data shuffling (repartition) if they are distributed with the same distribution column and shard count.","Make small tables that are frequently joined with all shards reference tables; they are fully copied to every worker and eliminate network cost in distributed JOINs.","Queries that do not include the distribution column fan out to all shards and are expensive; in multi-tenant SaaS, filtering by tenant_id reduces the query to a single shard (router execution)."],"level":"expert"},{"id":"postgres-timescaledb-hypertable","title":"PostgreSQL: TimescaleDB hypertables and continuous aggregates","category":"PostgreSQL","summary":"TimescaleDB scales time-series data with hypertables that automatically split it into time-interval chunks and with precomputed continuous aggregates.","points":["A hypertable looks like a single table from the outside, but behind the scenes it is split into chunks by the time (and optionally space) column; queries touch only the chunks in the relevant time range (chunk exclusion).","Set chunk_time_interval so that the most active chunks fit in memory (shared_buffers); too small causes excessive chunk/planning overhead, too large causes weak exclusion.","A continuous aggregate is an incrementally refreshed materialized view; it is kept current in the background by a refresh policy that recomputes only the time buckets that changed.","With real-time aggregation, a continuous aggregate combines the materialized older data with the latest data in the raw table to return up-to-date results at query time.","Old chunks can be shrunk substantially with columnar compression; a retention policy automatically drops chunks older than a given age with drop_chunks (far cheaper than row-by-row DELETE)."],"level":"expert"},{"id":"postgres-vacuum-xid-wraparound","title":"PostgreSQL: VACUUM and XID wraparound","category":"PostgreSQL","summary":"MVCC leaves dead row versions behind; VACUUM cleans them up and marks rows 'frozen', preventing data loss caused by wraparound of the 32-bit transaction ID (XID).","points":["PostgreSQL uses 32-bit XIDs, which are circular; every row version is stamped with xmin. If sufficiently old rows are not frozen, at XID wraparound 'past' transactions appear to be in the 'future' and rows can suddenly become invisible.","autovacuum does two jobs: regular cleanup (dead tuples, bloat) and freezing; for tables approaching autovacuum_freeze_max_age, a mandatory 'anti-wraparound' vacuum is triggered even if autovacuum is disabled.","Warnings are logged as wraparound approaches; if the limit gets very close, the database refuses new writes and goes read-only to prevent data loss, and the fix is to run an aggressive VACUUM (FREEZE).","Monitor the oldest unfrozen XID with pg_database.datfrozenxid and age(); long-running open transactions (idle in transaction) and unused replication slots block VACUUM progress, increasing bloat and wraparound risk.","Under high write load, autovacuum falling behind leads to bloat; raise autovacuum_vacuum_cost_limit, lower naptime and apply table-specific autovacuum settings to large/hot tables.","VACUUM generally does not return space to the operating system; it marks it for reuse. Physical shrinking requires VACUUM FULL (locks the table) or pg_repack (online)."],"level":"expert"},{"id":"mysql-innodb-cluster","title":"MySQL: Group Replication and InnoDB Cluster","category":"MySQL","summary":"InnoDB Cluster is a majority (quorum) cluster based on Group Replication that provides high availability by presenting the application a single connection point through MySQL Router.","points":["Group Replication propagates transactions to the group through a Paxos-based group communication layer; a transaction commits only after a majority of the group (quorum) agrees, which provides automatic failover and consistency.","Single-primary mode (default) allows writes on only one node, and the group automatically elects a new primary when the primary fails; in multi-primary mode all nodes accept writes, but conflict (certification) handling becomes more complex.","MySQL Router reads the cluster metadata and offers the application two ports: writes go to the current primary (6446), reads to the replicas (6447); on failover, routing is updated without changing the application's connection.","Loss of majority (e.g. 2 out of 3 nodes gone) blocks the group; to prevent split-brain the cluster accepts no new writes, and recovery is done manually with group_replication_force_members.","Group Replication requires row-based binlog (ROW format), GTID and InnoDB; a primary key on every table is mandatory, otherwise the node cannot join the cluster.","A newly joining node first synchronizes via state transfer from an existing donor (distributed recovery: clone plugin or binlog), then switches to the live stream."],"level":"expert"},{"id":"mysql-semisync-replication","title":"MySQL: semi-sync replication","category":"MySQL","summary":"Semi-synchronous replication reduces data loss by having the primary wait, before acknowledging a commit, until at least one replica has received the transaction into its relay log.","points":["With asynchronous replication, the primary acknowledges the commit without waiting for the replica; if the primary crashes, transactions not yet shipped are lost. Semi-sync waits for a receipt acknowledgment from at least N replicas (rpl_semi_sync_source_wait_for_replica_count).","AFTER_SYNC (default, recommended): the primary first receives the replica acknowledgment, then returns the commit to the client; this guarantees that every transaction the client sees as 'successful' exists on at least one replica.","In AFTER_COMMIT mode the primary commits locally first and then waits for the replica; if the replica lags, other sessions may see data that has not yet been replicated (similar to a phantom read).","If rpl_semi_sync_source_timeout is exceeded, the primary automatically falls back to asynchronous and does not stop; it returns to semi-sync when the replica comes back, so the 'guarantee' is conditional.","Semi-sync guarantees 'received', not 'applied on the replica'; for strict RPO=0, Group Replication or an external consensus layer is more appropriate."],"level":"expert"},{"id":"mysql-undo-purge","title":"MySQL InnoDB: undo tablespaces and purge","category":"MySQL","summary":"InnoDB keeps old row versions in the undo log for MVCC; purge threads clean up undo records and dead versions that no reader needs anymore.","points":["Every change produces an undo log record for rollback and consistent reads; a long-running transaction keeps old read views alive, blocking purge progress and causing undo to grow (history list length increases).","If the history list length (SHOW ENGINE INNODB STATUS) keeps growing, purge cannot keep up; the main cause is usually long open transactions or old reads kept active (e.g. mysqldump --single-transaction).","innodb_purge_threads controls purge parallelism, and innodb_max_purge_lag adds artificial delay to DML when purge falls far behind, preventing the load from overwhelming purge.","In MySQL 8, undo is kept in separate undo tablespaces and growing undo files can be shrunk with automatic truncation (innodb_undo_log_truncate); in older versions undo stayed in the system tablespace and never shrank.","Bloated undo forces consistent reads to walk longer version chains (I/O and CPU); short transactions and autocommit discipline minimize undo pressure."],"level":"expert"},{"id":"mysql-buffer-pool-lru","title":"MySQL InnoDB: buffer pool instances and LRU","category":"MySQL","summary":"The InnoDB buffer pool keeps data and index pages in memory; the midpoint-insertion LRU algorithm prevents table scans from evicting hot data from memory.","points":["On a dedicated database server the buffer pool is typically set to ~70-80% of RAM (innodb_buffer_pool_size; lower on a shared server); the goal is to keep the frequently accessed working set in memory and minimize disk I/O.","The LRU list is split into 'young' (hot) and 'old' (cold) sublists; a newly read page is inserted not at the head of the list but at the head of the old region (midpoint, ~37%), so a one-time scan does not evict hot pages.","A page in the old region is promoted to young only if it is accessed again after innodb_old_blocks_time has elapsed; this prevents large full scans from polluting the buffer pool.","innodb_buffer_pool_instances splits the pool into multiple independent instances; since each instance has its own mutex, buffer pool mutex contention decreases under high concurrency (meaningful for large pools).","Monitor the buffer pool hit ratio and the free/dirty page balance; a large number of dirty pages and aggressive flushing can cause a 'checkpoint storm', and innodb_io_capacity sets the flush rate according to the storage."],"level":"expert"},{"id":"sqlserver-always-on-ag","title":"SQL Server: Always On Availability Groups","category":"SQL Server","summary":"An Availability Group replicates a group of databases to synchronous/asynchronous replicas; on WSFC it provides automatic failover, readable replicas and read-only routing.","points":["With a synchronous-commit replica, the primary waits for the secondary to harden the log record before committing; this provides RPO=0, but if the replica lags it increases commit latency on the primary.","An asynchronous-commit replica does not make the primary wait; it suits remote disaster recovery (DR) sites, but failover may lose some data (RPO>0) and only forced manual failover (FORCE_FAILOVER_ALLOW_DATA_LOSS) is supported.","Automatic failover occurs only between a pair of replicas in synchronous-commit and 'automatic failover' mode, while WSFC quorum is maintained; health checks are done with sp_server_diagnostics and database health detection.","A readable secondary replica offloads reporting from the primary; thanks to read-only routing, clients connecting with ApplicationIntent=ReadOnly are automatically routed through the listener to a readable replica.","Queries on a readable replica automatically use snapshot isolation; for this, a 14-byte version tag is added to modified rows on the primary, and the tempdb version store on the secondary is loaded; also, if redo apply lags, data on the replica can become stale.","The AG listener presents clients a single connection point with a virtual network name + IP; on failover the listener moves to the new primary and the application keeps working without changing its connection string."],"level":"expert"},{"id":"sqlserver-columnstore","title":"SQL Server: columnstore indexes and batch mode","category":"SQL Server","summary":"A columnstore index stores data column-wise with high compression; batch mode processing and segment elimination deliver large speedups for analytical queries.","points":["Data is split into column segments in rowgroups (~1 million rows) and each segment is compressed; only the columns the query needs are read, which reduces I/O significantly.","Each segment holds min/max metadata; if the query predicate is outside the segment's value range, the segment is skipped without being read (segment elimination), so loading data in sorted order is very important.","Batch mode processes rows in groups of ~900 instead of row by row, increasing the work done per CPU cycle; it is many times faster than row mode for aggregates and joins (with SQL 2019+, batch mode is also possible on rowstore).","A clustered columnstore is ideal for large analytical/DW fact tables; for mixed OLTP + analytics workloads, real-time operational analytics (HTAP) can be built with a nonclustered columnstore on a rowstore table.","Hot (not yet compressed) rows are kept row-wise in the deltastore and compressed by the tuple mover when full; many small loads cause fragmentation, so bulk loading should be preferred."],"level":"expert"},{"id":"sqlserver-inmemory-oltp","title":"SQL Server: In-Memory OLTP (Hekaton)","category":"SQL Server","summary":"In-Memory OLTP removes bottlenecks in highly concurrent OLTP workloads by processing memory-optimized tables with lock/latch-free structures and natively compiled procedures.","points":["Memory-optimized tables are kept entirely in memory and all row versions live in memory; concurrency is handled with lock-free optimistic MVCC, eliminating classic latch and lock contention.","For durability, changes are still written to the log and flushed to checkpoint files (data/delta); SCHEMA_ONLY tables are not logged and are emptied on restart (ideal e.g. for transient staging/session data).","A natively compiled stored procedure compiles T-SQL into machine code (DLL), eliminating interpretation overhead; it can access only memory-optimized tables and supports a limited T-SQL subset.","Because concurrency is optimistic, conflicts are detected at run time; a write-write conflict rolls the transaction back with an error (requiring a retry), so the application must include retry logic.","Best-fit scenarios: very high insert rates (e.g. IoT/telemetry ingest), hot tables with lock/latch bottlenecks and temporary tables that use tempdb; all data must fit in memory and memory pressure must be monitored closely."],"level":"expert"},{"id":"sqlserver-query-store","title":"SQL Server: plan management with Query Store","category":"SQL Server","summary":"Query Store persists query texts, execution plans and runtime statistics; it lets you detect plan regressions and force a specific plan.","points":["Query Store keeps, inside the database, all plans each query has used over time along with duration/CPU/read metrics per plan; it answers 'why was this query fast yesterday and slow today' with historical data.","Plan regressions (a newly compiled plan being worse than the old one) are found with the 'Regressed Queries' and 'Top Resource Consumers' reports; stability is restored by forcing the known good plan with sys.sp_query_store_force_plan.","With Query Store hints in SQL Server 2022, hints (e.g. RECOMPILE, MAXDOP) can be applied without forcing a plan or changing the query text; this is powerful for tuning third-party application queries.","Automatic plan correction (automatic tuning, FORCE_LAST_GOOD_PLAN) detects regressions, automatically forces the previous good plan and reverts it if there is no improvement; enable it in production under supervision.","Set the capture mode (ALL/AUTO/CUSTOM) and storage size correctly; AUTO filters out rare/insignificant queries to reduce overhead and space, and a full Query Store switches to read-only mode and stops collecting data."],"level":"expert"},{"id":"mongodb-sharding-deep","title":"MongoDB: sharding, chunks and the balancer","category":"MongoDB","summary":"MongoDB splits data into chunks by shard key and distributes them across shards; the balancer evens out chunks, and the shard key choice determines distribution and query routing.","points":["Data is split into chunks by shard key ranges and the chunks are distributed across shards; the mongos router looks at the shard key and routes the query only to the relevant shard (targeted), otherwise it broadcasts to all shards (scatter-gather).","A good shard key has high cardinality, low frequency and is not monotonically increasing; a monotonically increasing key (e.g. timestamp/ObjectId) piles all new writes onto a single shard (hotspot), and hashed sharding spreads them out.","When the chunk count becomes unbalanced between shards, the balancer migrates chunks in the background; consistency between source and target is preserved during migration, but it incurs I/O cost and can be scheduled in a maintenance window.","A jumbo chunk is a chunk that exceeds the max chunk size and cannot be split because the shard key has low cardinality; it cannot be migrated and creates imbalance, and the fix is a more selective (compound) shard key.","Zone sharding (tag-aware) pins specific shard key ranges to specific shards; it is used to steer data placement geographically or by hardware (e.g. EU data on a shard in the EU region).","Although modern versions introduce size-based balancing and automatic defragmentation instead of chunk counts, the principle is the same: for targeted queries, the shard key must be present in every read/write."],"level":"expert"},{"id":"mongodb-oplog-election","title":"MongoDB: replica set oplog and elections","category":"MongoDB","summary":"In a replica set, the primary records write operations in the oplog and secondaries apply them; when the primary fails, a new primary is chosen through a Raft-like election.","points":["The oplog is a capped collection that records every change on the primary in an idempotent (safely re-appliable) form; secondaries tail and apply the oplog to stay in sync.","The oplog size determines the replication window; if a secondary stays offline longer than the oplog covers, it becomes 'stale' and requires a full resync (initial sync), so the oplog must be sized to handle the workload.","When the primary fails, secondaries vote using a Raft-like protocol; the candidate that wins a majority of votes becomes primary, so an odd number of votes (with an arbiter if needed) is kept to preserve the majority.","writeConcern 'majority' guarantees that a write has reached a majority of nodes, so it is not rolled back on failover (rollback protection); w:1 is faster, but the write can be lost if the primary fails.","readConcern and read preference control read freshness; reading from secondaries spreads load but can return stale data due to replication lag, while 'majority'/'linearizable' give stronger guarantees.","If the old primary ends up in the minority after a network partition, it steps down to secondary; when it rejoins the new primary, it moves its uncommitted writes into a rollback file (majority write concern prevents this)."],"level":"expert"},{"id":"mongodb-wiredtiger-checkpoint","title":"MongoDB: WiredTiger checkpoints and the journal","category":"MongoDB","summary":"WiredTiger provides durability through periodic checkpoints and a write-ahead journal; it delivers high throughput with document-level concurrency and compression.","points":["By default WiredTiger takes a checkpoint about every 60 seconds, writing a consistent in-memory snapshot to disk; between checkpoints, durability is preserved by recording every write in the journal (WAL).","Without the journal, a crash loses writes made after the last checkpoint; with the journal, recovery loads the last checkpoint and replays the subsequent operations from the journal.","Document-level concurrency control allows different documents in the same collection to be updated concurrently; MVCC snapshots provide consistent reads.","The WiredTiger cache (by default ~50% of RAM) holds frequently accessed data and indexes; if the working set does not fit in the cache, eviction and disk I/O increase, and cache saturation severely degrades performance.","Storage compression defaults to snappy (speed/ratio balance); zstd/zlib give higher ratios but need more CPU. Indexes are additionally shrunk with prefix compression."],"level":"expert"},{"id":"redis-cluster-hashslot","title":"Redis Cluster: hash slots and resharding","category":"Redis","summary":"Redis Cluster scales horizontally by dividing the key space into 16384 hash slots and distributing them across nodes; clients find the right node via MOVED/ASK redirections.","points":["Each key maps to a slot via CRC16(key) mod 16384; each master owns specific slot ranges, so the cluster distributes data without a central router.","If a client goes to the wrong node, the node replies with MOVED to indicate the slot's permanent owner and the client updates its slot map. During resharding, a temporary ASK redirection is used for the slot being migrated.","Resharding moves slots (and the keys in them) live from one node to another; migration is done key by key and the cluster keeps serving throughout.","Multi-key operations (MSET, transactions, Lua) work only on keys in the same slot; hash tags ({...}) are used to group related keys into the same slot, e.g. user:{123}:name and user:{123}:email.","For high availability, give each master at least one replica; when a master fails, its replica is promoted. The cluster decides by a majority of masters, and writes stop if the majority is lost.","If any slot is unassigned or has no master, the cluster rejects all writes (when cluster-require-full-coverage is on); all 16384 slots must be assigned."],"level":"expert"},{"id":"redis-replication-psync","title":"Redis: replication and PSYNC","category":"Redis","summary":"In Redis asynchronous replication, the master transfers a data snapshot to the replica and then streams subsequent commands; PSYNC performs a partial resync after short disconnects.","points":["On initial synchronization the master produces an RDB snapshot and sends it to the replica (full resync); meanwhile it buffers incoming writes in a replication backlog buffer and streams them after the RDB.","PSYNC tracks the replica's replication offset and the master replication ID; after a short disconnect, if the missed data is still in the backlog buffer, only the missing part is sent (partial resync), avoiding an expensive full resync.","repl-backlog-size determines how long a disconnect can be tolerated; at high write rates a small backlog forces a full resync on disconnects, which means RDB generation on the master and network cost.","Replication is asynchronous: the master acknowledges a write without waiting for replicas, so if the master crashes suddenly the latest writes can be lost; the WAIT command can be used to wait until a given number of replicas have received it, but it is not a true synchronous guarantee.","With diskless replication (repl-diskless-sync), the master can stream the RDB directly to the socket without writing it to disk; this speeds up initial sync in environments with slow disks but fast networks."],"level":"expert"},{"id":"redis-memory-fragmentation","title":"Redis: memory fragmentation and jemalloc","category":"Redis","summary":"Redis uses jemalloc by default; variable-size allocations and frees cause memory fragmentation over time, making RSS appear larger than the data actually used.","points":["mem_fragmentation_ratio = used_memory_rss / used_memory; close to 1 is ideal, noticeably above 1 (e.g. >1.5) indicates fragmentation, and below 1 indicates that data is being swapped to disk (bad).","Fragmentation arises when keys of different sizes are constantly created and deleted, leaving unusable gaps in jemalloc arenas; frequent changes to large values make it worse.","Active defragmentation (activedefrag yes) compacts memory blocks at run time with jemalloc's help to reduce fragmentation; its thresholds (active-defrag-ignore-bytes, -threshold-lower/upper) are tuned to limit the overhead.","Set maxmemory and maxmemory-policy correctly; since the actual RSS allocated by jemalloc can exceed maxmemory, keep maxmemory below RAM to avoid an operating system OOM.","If used_memory is low but RSS stays high, a restart (or a node restarted via failover) usually resets fragmentation for a lasting fix; active defrag reduces the need for restarts in most cases."],"level":"expert"},{"id":"elasticsearch-shard-sizing","title":"Elasticsearch: shard sizing","category":"Elasticsearch","summary":"Each shard is a Lucene index and carries a fixed resource cost; too many small shards (oversharding) or overly large shards degrade performance and stability.","points":["Rule of thumb: keep a shard in the 10-50 GB range; very large shards slow recovery and rebalancing, while very small shards (oversharding) put unnecessary load on the cluster state and heap.","Every shard, and especially every mapped field, consumes heap. The old \"~20 shards per GB of heap\" rule no longer applies from 8.3 onward (per-shard heap overhead dropped sharply); current guidance is to allow ~1 KB of heap per mapped field per index on data nodes plus overhead, and to keep shards per node below cluster.max_shards_per_node (default 1000). Too many shards still grow the cluster state and increase coordination overhead.","Query latency is related to shard count: a query fans out to all relevant shards and the results are merged; more shards than necessary slow even small queries due to coordination overhead.","For time-series/log data, use time-based indices (data streams) instead of one giant index; this makes moving/deleting old data per index (ILM) cheap and keeps shards manageable.","The number of primary shards is fixed at index creation and cannot be changed later (only via reindex/split/shrink); therefore plan it correctly up front based on growth estimates, while the replica count can be adjusted dynamically."],"level":"expert"},{"id":"elasticsearch-ilm-tiers","title":"Elasticsearch: ILM and hot-warm-cold-frozen tiers","category":"Elasticsearch","summary":"Index Lifecycle Management manages time-series data economically by automatically moving indices between cost/performance tiers according to their lifecycle phase.","points":["An ILM policy moves an index through phases: hot (active writes + frequent queries, fast SSD), warm (writes finished, occasional queries), cold (rare queries, cheaper hardware), frozen (very rare, served from object storage via searchable snapshots).","Rollover opens a new index when the hot index reaches a given size/age/document count; this way a single index does not grow forever and each phase is managed per index.","The frozen tier keeps data in object storage (S3/blob) via searchable snapshots and pulls it partially into memory only when needed; it greatly reduces disk cost, but query latency increases.","When moving to warm/cold, storage and heap are optimized with force merge, shrink and fewer replicas; since data no longer changes in these phases, segments can be merged down to a single segment.","Map data tiers to node roles (data_hot/data_warm/data_cold/data_frozen); ILM uses shard allocation filters to place the index on nodes with the right hardware."],"level":"expert"},{"id":"elasticsearch-segment-merge","title":"Elasticsearch: segment merging and force merge","category":"Elasticsearch","summary":"Lucene creates new immutable segments on every refresh; background merging combines and removes segments, while force merge is a manual operation to be used with care.","points":["Every refresh (default 1 s) produces a new segment, and deletes are marked as 'tombstones' in the segment; many small segments slow down reads, so background merging combines them.","Deleted/updated documents are physically removed from disk only during a merge; under heavy update load, 'deleted docs' accumulate in segments and disk/memory usage swells.","On indices that are no longer written to (read-only), force merge down to 1 segment improves query speed and disk efficiency; this is a typical step in the ILM warm phase.","NEVER run force merge on an actively written index; the very large segment it produces does not enter the normal merge cycle, subsequent deletes cannot be cleaned up, and it chokes I/O and heap for a long time.","Temporarily increasing refresh_interval during bulk loads (e.g. -1 or 30s) produces fewer, larger segments, reducing merge load and improving write throughput."],"level":"expert"},{"id":"elasticsearch-node-roles","title":"Elasticsearch: node roles and cluster topology","category":"Elasticsearch","summary":"Elasticsearch nodes take on roles such as master, data, coordinating and ingest; separating roles in large clusters provides stability and scalability.","points":["Master-eligible nodes manage the cluster state (indices, shard placement, mappings); for stable elections use an odd number (usually 3) of dedicated master nodes and rely on a quorum (majority) to prevent split-brain.","Data nodes host shards and handle indexing and query I/O; they consume the most CPU/RAM/disk and can be split into data tiers (hot/warm/cold/frozen).","A coordinating-only node has no other roles and distributes requests to the relevant shards and merges the results (scatter-gather); it protects data nodes by taking over heavy search/aggregation merging and acts as a load balancer.","An ingest node processes data through pipelines (transformation, enrichment) before indexing; moving heavy ingest pipelines to dedicated ingest nodes protects the query performance of data nodes.","Never make master nodes share heavy query/data load; a master slowing down under load delays cluster state updates and causes instability across the whole cluster (delayed shard allocation, elections)."],"level":"expert"}]},"guide/glossary.json":{"terms":[{"term":"ACID","full":"Atomicity, Consistency, Isolation, Durability","definition":"The four core properties that make transactions reliable: Atomicity (all or nothing), Consistency (rules are preserved), Isolation (concurrent transactions do not corrupt each other) and Durability (committed data is not lost). It is the transaction guarantee of relational databases.","tags":["transaction","transaction","acid","consistency","consistency"]},{"term":"BASE","full":"Basically Available, Soft state, Eventually consistent","definition":"A relaxed consistency approach, an alternative to ACID, favored in distributed and NoSQL systems. The system prioritizes always being available and accepts that data becomes consistent over time (eventual consistency). It trades strict consistency for scalability.","tags":["base","distributed","distributed","eventual-consistency","nosql"]},{"term":"MVCC","full":"Multi-Version Concurrency Control","definition":"A concurrency method that keeps multiple versions of the same row so that readers and writers work without locking each other. Each transaction sees its own snapshot of the data, so reads do not block writes and writes do not block reads. Used in systems such as PostgreSQL and Oracle.","tags":["concurrency","concurrency","lock","mvcc","snapshot"]},{"term":"WAL","full":"Write-Ahead Log","definition":"The principle that a log record is written durably to disk BEFORE the change is written to the data files. Crash recovery and durability are guaranteed by it. It is called WAL in PostgreSQL and the transaction log in SQL Server.","tags":["wal","log","recovery","recovery","durability"]},{"term":"Redo Log","full":"","definition":"The log that records changes made to the database and is used to reapply (redo) work lost in a crash. During recovery it rolls forward changes that were committed but not yet written to disk. Oracle's redo log corresponds to the general concept of WAL.","tags":["redo","log","recovery","recovery","oracle"]},{"term":"Undo / Rollback Segment","full":"","definition":"The area that stores the previous image of data changed by a transaction. It serves two purposes: undoing a change with rollback, and serving the old version to readers under MVCC. It is called undo/rollback segment in Oracle and undo/version store in other systems.","tags":["undo","rollback","rollback","mvcc","segment"]},{"term":"Buffer Pool / Buffer Cache","full":"","definition":"The memory cache where frequently used data and index pages are kept. Because disk reads are expensive, database performance depends heavily on the hit rate here (cache hit ratio). It is called the buffer pool in MySQL InnoDB and the buffer cache in SQL Server.","tags":["buffer-pool","buffer-cache","cache","cache","memory"]},{"term":"Dirty Page","full":"","definition":"A data page modified in memory but not yet written to disk. Durability is protected by WAL; the page itself stays dirty until the checkpoint or the background writer flushes it to disk. Too many dirty pages lengthen recovery time.","tags":["dirty-page","dirty-page","memory","checkpoint","io"]},{"term":"LSN","full":"Log Sequence Number","definition":"A continuously increasing unique sequence number assigned to every record in the WAL/transaction log. It is used to determine up to which log record a page is current and where recovery should continue. It also tracks progress in replication.","tags":["lsn","log","sequence-number","recovery","replication"]},{"term":"SCN","full":"System Change Number","definition":"In Oracle, a monotonically increasing logical timestamp assigned to every commit point in the database. It is used for read consistency, MVCC and ordering events during recovery. It can be thought of as Oracle's counterpart to the LSN.","tags":["scn","oracle","timestamp","read-consistency","commit"]},{"term":"Isolation Level","full":"Isolation Level","definition":"The setting that determines to what extent concurrent transactions can see each other's changes. The levels are Read Uncommitted, Read Committed, Repeatable Read and Serializable; consistency increases and concurrency decreases as the level rises. It controls dirty/non-repeatable/phantom read anomalies.","tags":["isolation","isolation-level","transaction","concurrency","concurrency"]},{"term":"Dirty Read","full":"","definition":"A transaction reading a change that another transaction has not yet committed (and may roll back). Because the data read may be invalid, it is a consistency anomaly. It is possible only at the Read Uncommitted isolation level.","tags":["dirty-read","anomaly","isolation","transaction","consistency"]},{"term":"Non-repeatable Read","full":"","definition":"Seeing a different value when the same row is read twice within the same transaction, because another transaction committed in between. It can occur at Read Committed; it is prevented at Repeatable Read and above.","tags":["non-repeatable-read","anomaly","isolation","transaction","non-repeatable"]},{"term":"Phantom Read","full":"","definition":"The set of rows changing when a query is re-run with the same condition within the same transaction, because of an intervening insert/delete (new 'phantom' rows appear). Per the SQL standard it is prevented at Serializable; PostgreSQL also prevents it at Repeatable Read.","tags":["phantom-read","phantom","anomaly","serializable","isolation"]},{"term":"Deadlock","full":"","definition":"A situation where two or more transactions each wait for a lock held by the other, so none can proceed. The database detects it and breaks the cycle, usually by choosing the cheapest transaction as the 'victim' and rolling it back. Always acquiring locks in the same order reduces the risk.","tags":["deadlock","deadlock","lock","lock","concurrency"]},{"term":"Lock Escalation","full":"","definition":"When a transaction holds many row/page locks, the database converts them into a single coarser lock (for example a table lock). It reduces lock management memory but lowers concurrency and can increase blocking. Seen especially in SQL Server.","tags":["lock-escalation","lock","lock","concurrency","sql-server"]},{"term":"Latch","full":"","definition":"A lightweight, low-cost concurrency lock used to protect in-memory structures (for example buffer pool pages) for a very short time. Unlike transaction locks, it protects the physical memory structure rather than logical data, and it is held only very briefly.","tags":["latch","lock","memory","concurrency","internal"]},{"term":"B-Tree Index","full":"Balanced Tree Index","definition":"The most common index type, keeping data in a sorted, balanced tree structure. It is efficient for equality and range queries, sorting and prefix searches. It is the default index type in most databases.","tags":["btree","index","index","search","range"]},{"term":"Hash Index","full":"","definition":"An index type that locates a key by passing it through a hash function. It is very fast only for exact equality (=) lookups; it cannot be used for range queries or sorting. Available, for example, in PostgreSQL and in MySQL Memory tables.","tags":["hash-index","index","index","equality","search"]},{"term":"Bitmap Index","full":"","definition":"An index that keeps bit arrays for each distinct value, suited to low-cardinality columns (few distinct values). It allows multiple conditions to be combined quickly with AND/OR; effective for data warehouse/OLAP workloads but inefficient under heavy writes.","tags":["bitmap-index","index","index","olap","cardinality"]},{"term":"Covering Index","full":"","definition":"An index that contains all the columns a query needs; the query is satisfied from the index alone without visiting the table (heap/clustered). This improves performance by eliminating expensive row lookups. It can be created with INCLUDE columns.","tags":["covering-index","index","index","performance","lookup"]},{"term":"Clustered Index","full":"","definition":"An index that keeps the table's rows physically ordered by the index key; the table itself is stored in that order. A table can have only one. It is fast for range queries; in InnoDB the primary key is always clustered.","tags":["clustered-index","index","index","physical","primary-key"]},{"term":"Cardinality","full":"","definition":"The number of distinct values in a column. High cardinality (e.g. email) means many different values, low cardinality (e.g. gender) means few. It is a critical measure for the optimizer when choosing indexes and estimating plan cost.","tags":["cardinality","cardinality","statistics","optimizer","distinct"]},{"term":"Selectivity","full":"","definition":"Indicates how small a fraction of the total rows a filter condition returns. High selectivity (few rows) makes index use attractive; at low selectivity (many rows) a full scan may be cheaper. The optimizer uses it when choosing a plan.","tags":["selectivity","selectivity","optimizer","index","statistics"]},{"term":"Execution Plan","full":"","definition":"The roadmap showing the steps (scan type, join method, sort, etc.) the optimizer chose to run a query. Examining it with EXPLAIN/EXPLAIN ANALYZE reveals the root cause of performance problems. Comparing estimated and actual costs is important.","tags":["execution-plan","query-plan","explain","optimizer","performance"]},{"term":"Cost-Based Optimizer","full":"CBO","definition":"The query optimizer that estimates the cost of possible execution plans from statistics and chooses the cheapest one. Up-to-date statistics are essential for correct decisions; stale statistics lead to bad plans. It is the standard approach in modern databases.","tags":["cbo","optimizer","cost","statistics","query-plan"]},{"term":"Statistics","full":"Statistics","definition":"Metadata collected for a table/index so that the optimizer can estimate row counts, cardinality and value distribution. Current statistics mean good query plans; when stale they must be refreshed with ANALYZE/UPDATE STATISTICS. They are one of the most common causes of poor performance.","tags":["statistics","statistics","optimizer","analyze","query-plan"]},{"term":"Histogram","full":"","definition":"A statistics structure that summarizes the distribution of values in a column by dividing them into ranges (buckets). It lets the optimizer estimate row counts more accurately, especially for skewed distributions. It is a component of statistics.","tags":["histogram","statistics","statistics","optimizer","distribution"]},{"term":"Partition","full":"","definition":"A technique that splits a large table physically into smaller pieces (for example by date or range) while it remains logically a single table. Queries scan only the relevant piece (partition pruning), and maintenance and archiving become easier. The difference from sharding: it stays within the same server/instance.","tags":["partition","partitioning","scaling","pruning","big-data"]},{"term":"Sharding","full":"","definition":"A method of distributing data horizontally across multiple separate servers/databases according to a shard key. It provides horizontal scaling beyond the limits of a single machine but complicates query and transaction management. The difference from partitioning: data is spread across different nodes.","tags":["sharding","scaling","distributed","distributed","horizontal-scaling"]},{"term":"Replication (Synchronous/Asynchronous)","full":"","definition":"Copying data from a primary node to one or more copies (replicas). In synchronous replication the commit waits for the replica's acknowledgment (no data loss, higher latency); in asynchronous replication it does not wait (fast, but data loss is possible if the replica is behind). Used for HA and read scaling.","tags":["replication","replication","synchronous","asynchronous","ha"]},{"term":"Logical vs Physical Replication","full":"","definition":"Physical replication creates an exact copy at the disk block/WAL level (same version, entire database). Logical replication transfers changes at the row/SQL level; it allows selective table replication and transfer across versions and different schemas. Logical replication is preferred for CDC and upgrade scenarios.","tags":["logical-replication","physical-replication","replication","cdc","wal"]},{"term":"Failover","full":"","definition":"Transferring service, automatically or manually, to a standby node when the primary node fails. It minimizes downtime; RPO determines how much data can be lost and RTO how long recovery takes. It comes into play during unplanned failures.","tags":["failover","ha","high-availability","standby","rpo"]},{"term":"Switchover","full":"","definition":"A planned, controlled exchange of the primary and standby roles (for example for maintenance). The difference from failover: it is a planned operation, not a failure, and is usually done without data loss.","tags":["switchover","planned","ha","maintenance","standby"]},{"term":"RPO","full":"Recovery Point Objective","definition":"The maximum acceptable amount of data loss after a disaster (expressed in time). For example, with an RPO of 5 minutes, at most 5 minutes of data loss is tolerated. Backup frequency and replication type are chosen to meet this target.","tags":["rpo","data-loss","dr","disaster-recovery","backup"]},{"term":"RTO","full":"Recovery Time Objective","definition":"The maximum target time for the system to become operational again after an outage. For example, with an RTO of 1 hour, the service must be back up within 1 hour at the latest. The HA/DR architecture and the level of automation are designed around this target.","tags":["rto","recovery-time","dr","disaster-recovery","ha"]},{"term":"HA","full":"High Availability","definition":"The design goal of keeping a system serving without interruption, or with very little, despite failures. It is achieved through redundancy, automatic failover and health checks. It is usually expressed as an uptime percentage (e.g. 99.99%).","tags":["ha","high-availability","high-availability","failover","redundancy"]},{"term":"DR","full":"Disaster Recovery","definition":"The plans and processes for bringing back systems and data, usually in a geographically separate region, after major disasters such as fire, flood or a region-wide outage. It is measured by RPO and RTO targets. HA focuses on everyday failures, DR on major disasters.","tags":["dr","disaster-recovery","disaster-recovery","rpo","rto"]},{"term":"Quorum","full":"","definition":"The minimum majority of nodes (usually N/2 + 1) required for a decision (for example leader election or a write acknowledgment) to be valid in a distributed system. It is used to prevent split-brain and preserve consistency. This is why cluster sizes are mostly odd numbers.","tags":["quorum","majority","distributed","distributed","split-brain"]},{"term":"Split-Brain","full":"","definition":"A situation where, after a network partition, both halves of a cluster believe they are the primary and keep writing independently; this leads to conflicting data and corruption. It is prevented with quorum and fencing mechanisms. It is one of the most dangerous failure scenarios in HA clusters.","tags":["split-brain","distributed","quorum","ha","consistency"]},{"term":"TDE","full":"Transparent Data Encryption","definition":"An encryption method, transparent to the application, that keeps database files (data and log) encrypted on disk. It protects data even if disks or backup files are stolen (data at rest). It does not protect data on the network (in transit); TLS is required for that.","tags":["tde","encryption","encryption","security","security"]},{"term":"RBAC","full":"Role-Based Access Control","definition":"An access control model in which privileges are assigned to roles rather than directly to users, and users are made members of roles. It simplifies privilege management and makes it easier to apply the principle of least privilege. It keeps administration scalable as the number of users grows.","tags":["rbac","role","privilege","security","security"]},{"term":"Connection Pool","full":"","definition":"A pool that creates connections in advance and reuses them instead of repeatedly opening and closing the application's database connections. It lowers connection setup cost, limits resource consumption and controls load. It protects the database by preventing excessive connections.","tags":["connection-pool","connection-pool","performance","resource","application"]},{"term":"Prepared Statement","full":"","definition":"A construct where an SQL statement is parsed and planned once, then executed repeatedly with different parameter values. It improves performance through plan reuse and provides security against SQL injection. It is used together with bind variables.","tags":["prepared-statement","bind","security","sql-injection","performance"]},{"term":"Bind Variable","full":"","definition":"A parameter passed through a placeholder instead of embedding the value in the SQL text. It makes efficient use of the plan cache by preventing the same query from being re-planned for different values, and it prevents SQL injection. It is critical for queries whose literal values change frequently.","tags":["bind-variable","parameter","prepared-statement","plan-cache","security"]},{"term":"Parameter Sniffing","full":"","definition":"The optimizer generating and caching the plan of a prepared/parameterized query based on the parameter value of its first execution. If that value is atypical, a plan that performs poorly for later, different values can become persistent. Solutions include plan recompilation (recompile) and optimizer hints.","tags":["parameter-sniffing","plan-cache","optimizer","performance","sql-server"]},{"term":"VACUUM / Bloat","full":"","definition":"Because of MVCC, dead versions of deleted/updated rows accumulate in the table; this swelling is called bloat. VACUUM reclaims the space of these dead rows and updates statistics (PostgreSQL). If it is not run regularly, the disk swells and performance drops.","tags":["vacuum","bloat","bloated","mvcc","postgresql"]},{"term":"Compaction","full":"","definition":"In LSM-tree based stores (e.g. Cassandra, RocksDB), the process of merging many small data files and purging dead/obsolete records. It improves read performance and reclaims disk space. It is the LSM-world equivalent of VACUUM.","tags":["compaction","lsm-tree","merge","cassandra","rocksdb"]},{"term":"Oplog","full":"Operations Log","definition":"In MongoDB, a special fixed-size (capped) collection in which all write operations applied to the primary are recorded in order. Replica set members read from it to stay in sync with the primary. It is the foundation of CDC and replication.","tags":["oplog","mongodb","replication","cdc","log"]},{"term":"Binlog","full":"Binary Log","definition":"In MySQL, the binary log that records all operations (or row changes) that modify data. It is used for replication, point-in-time recovery and CDC. It can be kept in row, statement or mixed format.","tags":["binlog","mysql","replication","cdc","recovery"]},{"term":"CDC","full":"Change Data Capture","definition":"A technique that captures insert/update/delete changes in a database and streams them to other systems (data warehouse, search engine, message queue). It usually runs with low overhead by reading the transaction log (binlog, WAL, oplog). It is the foundation of real-time data integration.","tags":["cdc","change-data-capture","data-streaming","binlog","integration"]},{"term":"Materialized View","full":"","definition":"A view that physically stores the result of a query on disk; unlike a regular view, it is not recomputed on every query. It speeds up expensive aggregations and joins, but its data goes stale and must be updated with a refresh. Common in OLAP/reporting.","tags":["materialized-view","view","cache","olap","reporting"]},{"term":"Foreign Key","full":"","definition":"A referential integrity constraint that lets a column in one table reference the primary/candidate key of another table. It prevents references to non-existent rows and preserves relationships. Rules such as ON DELETE/UPDATE CASCADE define the behavior of dependent rows.","tags":["foreign-key","foreign-key","integrity","relationship","constraint"]},{"term":"Normalization","full":"Normalization","definition":"The process of splitting data into related tables to reduce redundancy and lower the risk of inconsistency (normal forms such as 1NF, 2NF, 3NF). It prevents update anomalies and is preferred in OLTP systems. In return, queries may require more joins.","tags":["normalization","normalization","design","oltp","redundancy"]},{"term":"Denormalization","full":"Denormalization","definition":"A technique that deliberately duplicates data to reduce the number of joins and improve read performance. It speeds up queries but introduces data duplication and update complexity. Preferred for reporting/OLAP and read-heavy workloads.","tags":["denormalization","denormalization","performance","olap","read"]},{"term":"OLTP","full":"Online Transaction Processing","definition":"A type of system that processes a large number of short, frequent and concurrent transactions (insert/update/delete, single-row reads); e.g. banking, e-commerce. It requires low latency and high concurrency and is usually designed normalized. It is the operational counterpart of OLAP.","tags":["oltp","transaction","transaction","operational","normalization"]},{"term":"OLAP","full":"Online Analytical Processing","definition":"A type of system that runs complex aggregation and analysis queries over large data sets; e.g. reporting, business intelligence. Few but heavy queries run, and it is usually designed denormalized and columnar. It is the analytical counterpart of OLTP.","tags":["olap","analytics","reporting","data-warehouse","columnar"]},{"term":"ETL","full":"Extract, Transform, Load","definition":"The process of extracting data from sources (Extract), transforming it according to business rules (Transform) and loading it into the target system, usually a data warehouse (Load). The variant in which the transformation is done in the target after loading is called ELT. It is the classic approach to data integration.","tags":["etl","data-integration","data-warehouse","elt","transformation"]},{"term":"Hot/Cold Data","full":"","definition":"Frequently accessed, current data is called 'hot'; rarely accessed old/archive data is called 'cold'. Hot data is kept on fast/expensive storage and cold data on cheap/slow storage (data tiering). It is a strategy for balancing cost and performance.","tags":["hot-data","cold-data","hot-cold","archive","tiering"]},{"term":"Index Fragmentation","full":"","definition":"Over time, inserts/deletes/updates cause the logical order of index pages to diverge from their physical order and page fullness to drop. It creates unnecessary I/O and wasted space, degrading performance. It is fixed with REBUILD or REORGANIZE.","tags":["index-fragmentation","fragmentation","index","maintenance","performance"]},{"term":"Two-Phase Commit","full":"2PC","definition":"A protocol that provides an atomic commit for a distributed transaction spanning multiple databases/resources. It works in two phases: first the coordinator asks all participants whether they are ready (prepare); if all agree it commits, and if one refuses all roll back. It is slow and sensitive to coordinator failure.","tags":["two-phase-commit","2pc","distributed","distributed","atomic"]}]},"guide/glossary-2.json":{"terms":[{"term":"LSM Tree","full":"Log-Structured Merge Tree","definition":"A storage structure that first accumulates writes in an in-memory structure (memtable) and flushes them to disk as sorted blocks. It turns random writes into sequential writes for high write throughput; reads scan multiple levels. It underlies RocksDB, Cassandra, and LevelDB.","tags":["storage","storage engine","lsm","write"]},{"term":"B+ Tree","full":"","definition":"A balanced tree structure in which all data is held in leaf nodes and internal nodes contain only routing keys. Linked leaves speed up range scans. It is the most common structure for relational database indexes.","tags":["index","index structure","storage","storage engine","btree"]},{"term":"Bloom Filter","full":"","definition":"A probabilistic data structure that checks with little memory whether an element is in a set. It answers \"definitely not\" or \"probably yes\"; false positives are possible but false negatives are not. Used in LSM-based engines to avoid unnecessary disk reads.","tags":["storage","storage engine","probabilistic","filter","performance"]},{"term":"Consistent Hashing","full":"","definition":"A distribution method that ensures only a small fraction of keys move when nodes are added or removed. Keys and nodes are placed on a virtual ring. Used for data distribution in distributed caches and NoSQL systems.","tags":["distributed systems","distributed","hashing","sharding","scaling"]},{"term":"Vector Clock","full":"","definition":"A structure that keeps a counter per node to track the causal order of events in distributed systems. It determines whether two updates descend from one another or are concurrent (conflicting). Used in conflict detection and eventual consistency.","tags":["distributed systems","distributed","clock","causality","consistency"]},{"term":"Gossip Protocol","full":"","definition":"A communication method in which each node periodically spreads its state to a few randomly chosen peers, so information reaches the whole cluster like an epidemic. It requires no central coordinator, is scalable and fault-tolerant. Used for membership management in Cassandra and Consul.","tags":["distributed systems","distributed","gossip","cluster","membership"]},{"term":"Raft","full":"","definition":"A consensus algorithm designed to be easier to understand than Paxos. It elects a leader, appends all writes to a log replicated through the leader, and makes them durable with majority acknowledgment. Used by etcd and Consul.","tags":["distributed systems","distributed","consensus","raft","replication"]},{"term":"Paxos","full":"","definition":"The classic consensus algorithm that guarantees a majority agrees on a single value over an unreliable network. It is correct but considered complex to implement; it forms the theoretical foundation of many distributed systems. Used in systems such as Google Chubby.","tags":["distributed systems","distributed","consensus","paxos","agreement"]},{"term":"Leader Election","full":"","definition":"The process of selecting a single node (the leader) to coordinate a specific task in a distributed cluster. If the current leader fails, a new leader is elected automatically. It usually requires majority (quorum) approval to prevent split-brain.","tags":["distributed systems","distributed","leader","election","coordination"]},{"term":"Quorum Write/Read","full":"","definition":"The minimum number of nodes required for a read or write to be considered valid. When W + R > N holds, reaching the most recent data is guaranteed. It makes it possible to tune the trade-off between consistency and availability.","tags":["distributed systems","distributed","quorum","consistency","replication"]},{"term":"WAL Shipping","full":"Write-Ahead Log Shipping","definition":"Sending WAL records produced by the primary server to standby servers and replaying them there. This way the standby keeps a near-current copy of the primary database. It is the foundation of PostgreSQL streaming replication.","tags":["replication","replication","wal","high-availability","standby"]},{"term":"Log Shipping","full":"","definition":"A simple replication method that works by copying transaction log backups to a standby server at regular intervals and restoring them. Its lag is higher than streaming, but it is easy to set up. Commonly used for disaster recovery in SQL Server.","tags":["replication","replication","log","backup","standby"]},{"term":"Snapshot Isolation","full":"","definition":"An isolation level in which each transaction sees a consistent snapshot of the data as of the moment it started. Readers do not block writers; however, it is open to some anomalies such as write skew. Implemented together with MVCC.","tags":["isolation level","isolation","mvcc","snapshot","concurrency"]},{"term":"Serializable Snapshot Isolation (SSI)","full":"Serializable Snapshot Isolation","definition":"A method that provides true serializability on top of snapshot isolation by tracking dangerous read-write dependencies. When a conflict is detected, one of the transactions is rolled back. Used in PostgreSQL's SERIALIZABLE level.","tags":["isolation level","isolation","serializable","ssi","concurrency"]},{"term":"Gap Lock","full":"","definition":"A lock on the gap between index records (a range that does not yet exist) that prevents new rows from being inserted into that range. Used to prevent phantom reads. It comes into play at InnoDB's REPEATABLE READ level.","tags":["locking","lock","innodb","phantom","concurrency"]},{"term":"Next-Key Lock","full":"","definition":"The combination of a lock on an index record and a lock on the gap before it (gap lock). It prevents phantom inserts by protecting both the existing row and the range. It is InnoDB's default row-locking behavior.","tags":["locking","lock","innodb","gap","concurrency"]},{"term":"Optimistic Locking","full":"","definition":"A method that assumes conflicts are rare and works without taking locks, checking only at commit time whether the data has changed. Usually implemented with a version number or timestamp. If a conflict is found, the operation is retried.","tags":["locking","lock","optimistic","concurrency","concurrency"]},{"term":"Pessimistic Locking","full":"","definition":"A method that assumes conflicts will occur and takes a lock immediately when reading or modifying data. Other transactions wait until the lock is released. It protects data integrity in high-contention environments but reduces concurrency.","tags":["locking","lock","pessimistic","concurrency","concurrency"]},{"term":"Idempotency","full":"","definition":"The property that running an operation once or many times produces the same result. It enables safe retries after network errors. In distributed systems it prevents duplicate requests from causing side effects.","tags":["distributed systems","distributed","idempotent","retry","reliability"]},{"term":"Circuit Breaker","full":"","definition":"A design pattern that protects the system by temporarily cutting off calls to a service that keeps failing. While the circuit is \"open\", requests are rejected quickly, and a trial call is made after a while. It prevents cascading failures.","tags":["distributed systems","distributed","fault tolerance","resilience","pattern"]},{"term":"Saga Pattern","full":"","definition":"A pattern that manages long-running transactions spanning multiple services by making each step a local transaction and using compensating transactions on failure. It provides eventual consistency instead of ACID in a distributed environment. Common in microservice architectures.","tags":["distributed systems","distributed","saga","microservices","pattern"]},{"term":"Outbox Pattern","full":"","definition":"A pattern that writes the message to an \"outbox\" table in the same transaction to make the database update and message publication atomic. A separate process reads this table and publishes the message. This prevents inconsistency between data and event publication.","tags":["distributed systems","distributed","outbox","messaging","pattern"]},{"term":"Fan-out","full":"","definition":"Distributing a single operation to many sub-operations or targets; for example, writing a post into the feeds of thousands of followers. It can be done at write time (write fan-out) or at read time (read fan-out). An important design decision in scaling.","tags":["distributed systems","distributed","scaling","fanout","architecture"]},{"term":"Hotspot","full":"","definition":"Excessive concentration of load on a particular node, partition, or key. This point becomes a bottleneck and limits the performance of the entire system. A poorly chosen sharding key is a common cause.","tags":["distributed systems","distributed","hotspot","performance","sharding"]},{"term":"Skew (data imbalance)","full":"","definition":"Uneven distribution of data or load across partitions. Some partitions overflow while others stay empty, and parallel efficiency drops. Balanced through key selection or salting.","tags":["distributed systems","distributed","skew","imbalance","sharding"]},{"term":"Partial Index","full":"","definition":"An index that covers only the table rows satisfying a WHERE condition. It reduces index size, makes maintenance cheaper, and speeds up the frequently queried subset. Used, for example, to index only active rows.","tags":["index","index type","postgresql","partial","performance"]},{"term":"Expression Index","full":"","definition":"An index built on the result of an expression or function instead of a column. For example, built on LOWER(email), it speeds up case-insensitive searches. The query must use the same expression.","tags":["index","index type","expression","function","performance"]},{"term":"GIN Index","full":"Generalized Inverted Index","definition":"An inverted index type that indexes multiple components within a value. Ideal where a field contains many keys, such as arrays, JSONB, and full-text search. Used in PostgreSQL.","tags":["index","index type","postgresql","gin","jsonb"]},{"term":"GiST Index","full":"Generalized Search Tree","definition":"A tree-based, extensible index framework; custom search strategies can be defined for different data types. Well suited to geospatial (geometry), range, and nearest-neighbor queries. It forms the basis of PostGIS.","tags":["index","index type","postgresql","gist","geospatial"]},{"term":"TOAST","full":"The Oversized-Attribute Storage Technique","definition":"PostgreSQL's technique for compressing large field values that do not fit in a page and storing them in a separate area. Large text and JSONB values are split transparently. It preserves performance by keeping the main table compact.","tags":["storage","storage engine","postgresql","toast","large-values"]},{"term":"Index-Only Scan","full":"","definition":"A scan that produces the result from the index alone without reading the table at all, because all required data is in the index. It reduces disk access significantly. In PostgreSQL it works together with the visibility map.","tags":["index","index scan","scan","performance","postgresql"]},{"term":"HTAP","full":"Hybrid Transactional/Analytical Processing","definition":"An approach that runs both transactional (OLTP) and analytical (OLAP) workloads together in a single system. It eliminates the delay of moving data to a separate store for analysis (ETL). Usually combines row and column stores.","tags":["analytics","oltp","olap","htap","architecture"]},{"term":"Sharding Key","full":"","definition":"The column or columns that determine which shard a row lands on. A well-chosen key distributes load evenly and prevents hotspots. A poor choice leads to data imbalance and expensive cross-shard queries.","tags":["distributed systems","distributed","sharding","key","scaling"]},{"term":"Rebalancing","full":"","definition":"Redistributing data across partitions when nodes are added or removed or when load becomes unbalanced. The goal is to equalize load and use resources efficiently. It incurs network and I/O cost while running.","tags":["distributed systems","distributed","rebalancing","scaling","sharding"]},{"term":"Fencing","full":"","definition":"A mechanism that prevents data corruption by blocking a stale or isolated node from writing to a shared resource. Usually an increasing \"fencing token\" is used so that stale requests are rejected. It limits the impact of split-brain.","tags":["distributed systems","distributed","fencing","safety","split-brain"]},{"term":"Watermark","full":"","definition":"In stream processing systems, a progress marker indicating that all events up to a certain time are considered to have arrived. Used to handle late-arriving events and to decide when to close window computations.","tags":["stream processing","streaming","watermark","event time","event"]},{"term":"Tombstone","full":"","definition":"A special marker written to indicate that a record has been deleted. In LSM and distributed systems where data is not physically deleted immediately, it ensures the deletion propagates to all replicas. Actual cleanup happens during compaction.","tags":["storage","storage engine","lsm","tombstone","delete"]},{"term":"Compaction (LSM)","full":"","definition":"Merging and rewriting the sorted files (SSTables) on disk in LSM trees. It improves read performance and disk usage by purging old versions and tombstones. It runs in the background and generates write load.","tags":["storage","storage engine","lsm","compaction","maintenance"]},{"term":"Read Repair","full":"","definition":"A technique that, when an inconsistency between replicas is detected during a read, repairs it by writing the most recent value to the lagging nodes. The system makes itself consistent through normal read traffic. Used in systems such as Cassandra.","tags":["distributed systems","distributed","repair","consistency","replication"]},{"term":"Hinted Handoff","full":"","definition":"Storing a write as a \"hint\" on another node while the target node is temporarily unreachable and delivering it when the node returns. It ensures writes are not lost during transient failures. It supports eventual consistency.","tags":["distributed systems","distributed","handoff","availability","replication"]},{"term":"Eventual Consistency","full":"","definition":"A weak consistency model that guarantees all replicas will eventually converge to the same value if new updates stop. It allows short-lived inconsistencies but provides high availability and low latency. It is the default in many NoSQL systems.","tags":["distributed systems","distributed","consistency","eventual","nosql"]},{"term":"Strong Consistency","full":"","definition":"A consistency model in which all readers immediately see the most recent value once a write completes. It is intuitive to use, but because it requires coordination its latency and availability cost is high. Preferred in financial systems.","tags":["distributed systems","distributed","consistency","strong","strong consistency"]},{"term":"Linearizability","full":"","definition":"The strongest consistency level, guaranteeing that each operation appears to take effect at a single instant between its start and end, and that all operations fit into a single real-time order. The system behaves as if it were a single copy. It is expensive to implement.","tags":["distributed systems","distributed","consistency","linearizability","strong consistency"]},{"term":"Two-Phase Locking (2PL)","full":"Two-Phase Locking","definition":"A two-phase locking rule in which a transaction first only acquires locks (growing phase) and then only releases locks (shrinking phase). This discipline guarantees serializability. In its strict form (Strict 2PL), locks are held until commit.","tags":["locking","lock","2pl","serializable","concurrency"]},{"term":"Deadlock Detection","full":"","definition":"A mechanism that detects a deadlock, where two or more transactions wait on each other for locks they hold. It is usually found by searching for a cycle in the wait-for graph, and one transaction is chosen as the victim and rolled back. The system then starts flowing again.","tags":["locking","lock","deadlock","deadlock detection","concurrency"]}]},"guide/glossary-3.json":{"terms":[{"term":"Write Amplification","full":"Write Amplification","definition":"A single logical write causing many more physical writes in the storage layer (LSM compaction, page rewrites, WAL, SSD block erases). High values wear out disks and reduce write throughput.","tags":["storage","lsm","performance","ssd"]},{"term":"Read Amplification","full":"Read Amplification","definition":"A single logical read actually requiring multiple physical/internal reads. Looking up a key across many levels in an LSM tree is the typical example; bloom filters and compaction reduce it.","tags":["storage","lsm","performance"]},{"term":"LSM-tree","full":"Log-Structured Merge Tree","definition":"A data structure that first absorbs writes into an in-memory structure (memtable), flushes them to disk as sorted blocks (SSTables) and merges them in the background (compaction). Ideal for write-heavy workloads; used by RocksDB and Cassandra.","tags":["storage","lsm","write-heavy"]},{"term":"B+tree","full":"B+ Tree","definition":"The classic structure of relational database indexes. All data is kept in leaf nodes and the leaves are chained in a linked list; it provides balanced read performance for point and range queries.","tags":["index","btree","storage"]},{"term":"Fill Factor","full":"Fill Factor","definition":"The ratio specifying how full an index/table page is initially filled. Leaving free space reduces page splits on future updates/inserts; fillfactor in PG, fill factor in MSSQL.","tags":["index","storage","tuning"]},{"term":"HOT Update","full":"Heap-Only Tuple Update","definition":"In PostgreSQL, an update that changes no indexed column creates the new row version on the same page and does not update the indexes. It reduces index bloat and lowers update cost.","tags":["postgres","mvcc","update","performance"]},{"term":"Bloat","full":"Table/Index Bloat","definition":"A table/index occupying more space than necessary in MVCC engines because of uncleaned dead row versions or empty pages. It increases scan cost; space is reclaimed with VACUUM/reorg.","tags":["mvcc","postgres","maintenance","bloat"]},{"term":"Autovacuum","full":"Automatic VACUUM","definition":"The PostgreSQL background process that automatically performs dead tuple cleanup, statistics collection and transaction ID freezing. It is tuned to the workload via threshold and cost parameters.","tags":["postgres","vacuum","maintenance"]},{"term":"Freeze / Wraparound","full":"Transaction ID Freeze / Wraparound","definition":"In PostgreSQL, marking old rows as 'frozen' to prevent 32-bit transaction IDs from wrapping around. If neglected, the database stops accepting writes to prevent data loss.","tags":["postgres","mvcc","vacuum","wraparound"]},{"term":"Checkpoint","full":"Checkpoint","definition":"The event in which dirty pages in memory are written to disk and the point from which recovery will start is recorded. It shortens crash recovery time; its frequency is a trade-off between I/O peaks and recovery time.","tags":["recovery","wal","internals","checkpoint"]},{"term":"Fuzzy Checkpoint","full":"Fuzzy Checkpoint","definition":"A type of checkpoint that writes dirty pages continuously in the background, spread over time, without stopping the system. It keeps response times predictable by avoiding sudden I/O surges (checkpoint spikes).","tags":["recovery","checkpoint","internals"]},{"term":"Redo","full":"Redo Log / Roll Forward","definition":"The record kept to reapply (roll forward) the changes made by a transaction during recovery. It restores committed changes that had not reached disk before a crash.","tags":["recovery","oracle","wal","redo"]},{"term":"Undo","full":"Undo Log / Rollback","definition":"The record kept to roll back a transaction and to provide old row versions for MVCC. In Oracle/InnoDB, old versions are read from undo segments.","tags":["mvcc","oracle","innodb","undo","rollback"]},{"term":"MVCC Snapshot","full":"MVCC Snapshot","definition":"The logical point-in-time view that determines which row versions are visible so that a transaction sees a consistent read. It is taken at the start of the transaction or of the statement (depending on the isolation level).","tags":["mvcc","concurrency","isolation","snapshot"]},{"term":"Isolation Anomaly","full":"Isolation Anomalies","definition":"Inconsistencies produced by concurrent transactions under insufficient isolation: dirty read (reading uncommitted data), non-repeatable read (the same row changing), phantom (new rows appearing) and write skew (seemingly independent writes jointly breaking a rule).","tags":["isolation","concurrency","anomaly","acid"]},{"term":"2PL","full":"Two-Phase Locking","definition":"A protocol that ensures serializability with locks: a transaction first only acquires locks (growing phase), and after a certain point only releases them (shrinking phase). Strict 2PL holds locks until commit.","tags":["concurrency","locking","serializable","2pl"]},{"term":"MVTO","full":"Multi-Version Timestamp Ordering","definition":"A concurrency control method that assigns timestamps to transactions and orders reads/writes on multi-version data accordingly. It resolves conflicts with timestamps instead of locks.","tags":["concurrency","mvcc","timestamp","protocol"]},{"term":"Mutex","full":"Mutual Exclusion","definition":"A mutual exclusion primitive that guarantees only one thread accesses a resource at a time. In databases it plays a latch-like role in protecting shared memory structures.","tags":["concurrency","internals","mutex"]},{"term":"Spinlock","full":"Spinlock","definition":"A very short-lived lock that makes the thread loop (busy-wait) instead of sleeping until the lock is released. Cheap for very short critical sections; under contention it burns CPU.","tags":["concurrency","internals","spinlock","cpu"]},{"term":"Buffer Pool","full":"Buffer Pool / Buffer Cache","definition":"A cache that reduces disk I/O by keeping frequently accessed data pages in memory. The eviction algorithm (LRU, clock-sweep) decides which page is evicted; its size is set according to the working set.","tags":["memory","cache","internals","buffer"]},{"term":"Working Set","full":"Working Set","definition":"The set of data a workload actively accesses within a given time window. When the buffer pool is large enough to hold this set, physical I/O is minimized; it differs from the total data size.","tags":["memory","cache","sizing","performance"]},{"term":"Skew","full":"Data Skew","definition":"A situation where values are not evenly distributed and some occur far more frequently. It is the main driver of cardinality misestimates, parameter sniffing issues and sharding hot spots; it is handled with histograms.","tags":["statistics","distribution","skew","performance"]},{"term":"Plan Cache","full":"Plan Cache","definition":"The memory area where compiled execution plans are stored for reuse. It avoids hard parse cost but brings parameter sniffing and stale plan risks; plan cache in MSSQL, library cache in Oracle.","tags":["optimizer","plan-cache","parsing"]},{"term":"Hard/Soft Parse","full":"Hard Parse / Soft Parse","definition":"Hard parse: the SQL is parsed, optimized and a plan generated from scratch (expensive). Soft parse: an existing plan is found in the cache and reused (cheap). Bind variables increase the soft parse ratio.","tags":["parsing","oracle","performance","plan-cache"]},{"term":"Connection Storming","full":"Connection Storm","definition":"A large number of new database connections being opened simultaneously within a short time. It overwhelms the database with connection setup cost and memory pressure; it usually stems from pool exhaustion or a reconnect wave.","tags":["connections","pooling","overload"]},{"term":"Thundering Herd","full":"Thundering Herd Problem","definition":"Many clients hitting the same resource at the same time after an event (cache expiry, a lock being released, a service coming back). Mitigated with backpressure, jitter and single-flight patterns.","tags":["concurrency","cache","overload","resilience"]},{"term":"Backpressure","full":"Backpressure","definition":"A system slowing down or rejecting the producer instead of accepting load it cannot handle (queue limits, rate limits). It enables graceful degradation instead of collapsing under overload.","tags":["resilience","flow-control","overload"]},{"term":"Lease","full":"Lease","definition":"A leadership/resource right granted to a node for a fixed period that automatically becomes invalid when it expires. Because it is time-bound, it prevents a stuck node from remaining leader forever.","tags":["distributed","leader-election","lease"]},{"term":"Read-Your-Writes","full":"Read-Your-Writes Consistency","definition":"Session consistency that guarantees a user always sees their own change on the very next read. When routing to replicas with lag, it is achieved with special routing/tokens.","tags":["distributed","consistency","session","replication"]},{"term":"Causal Consistency","full":"Causal Consistency","definition":"A consistency model stronger than eventual that guarantees causally related operations are seen in the same order on all nodes. Unrelated (concurrent) operations may be seen in different orders.","tags":["distributed","consistency","causal"]},{"term":"CAP","full":"Consistency, Availability, Partition tolerance","definition":"The theorem stating that during a network partition, a distributed system can choose only one of consistency or availability. Without a partition, this trade-off is not mandatory.","tags":["distributed","cap","consistency","theory"]},{"term":"PACELC","full":"PACELC Theorem","definition":"A model extending CAP: under a partition (P) the choice is between Availability and Consistency, else (E) between Latency and Consistency. It also covers the latency-consistency trade-off during normal operation.","tags":["distributed","cap","pacelc","latency","theory"]},{"term":"Hot Partition","full":"Hot Partition / Hot Spot","definition":"Load piling up disproportionately on a single partition/shard. It stems from a low-cardinality, time-based or monotonically increasing shard key; it bottlenecks that node and stops scaling.","tags":["distributed","sharding","hot-spot","skew"]},{"term":"Columnstore","full":"Column-Oriented Storage","definition":"A format in which data is stored by column rather than by row. Analytical queries read only the needed columns, with high compression and vectorized execution; ideal for OLAP.","tags":["olap","columnar","analytics","compression"]},{"term":"Vectorized Execution","full":"Vectorized Execution","definition":"Query operators working on a block (batch) of column values at a time instead of row by row. It greatly increases analytical query throughput by making better use of CPU caches and SIMD.","tags":["olap","execution","performance","columnar"]},{"term":"Zone Map / Min-Max","full":"Zone Map / Min-Max Index","definition":"Lightweight metadata storing the minimum and maximum column value for each data block. It reduces scan cost by skipping blocks that cannot match the query predicate without reading them (block skipping).","tags":["olap","index","pruning","columnar"]},{"term":"Logical Replication","full":"Logical Replication","definition":"Replication that publishes and subscribes to row-level changes (logical events) instead of physical blocks. It allows selective tables, cross-version and bidirectional scenarios; PG publication/subscription provides it.","tags":["replication","postgres","logical","cdc"]},{"term":"GTID","full":"Global Transaction Identifier","definition":"A globally unique identifier assigned to every transaction in MySQL replication. By tracking replica position by transaction ID instead of file/offset, it makes failover and reconfiguration safe and simple.","tags":["mysql","replication","gtid","failover"]},{"term":"High Water Mark","full":"High Water Mark","definition":"The highest block boundary ever used in a segment/table. In Oracle, a full table scan reads up to this boundary; since it does not drop after mass deletes, it can lead to scanning empty blocks and bloat.","tags":["oracle","storage","internals","scan"]}]},"playbooks/playbooks.json":{"schemaVersion":1,"playbooks":[{"id":"db-slow","title":"Database is slow","icon":"🐢","symptom":"Application-wide slowness; queries take much longer than usual and users are complaining.","steps":[{"title":"First clarify what exactly is slow","detail":"Is everything slow, or a specific query/screen? When did it start, and does it coincide with a deploy/patch/data growth? Is it system-wide or a single DB? This narrows the search to one axis: CPU, I/O, locking or a plan change."},{"title":"Look at active sessions and wait events","detail":"What is running and what is waiting right now? A dominant wait event (I/O, lock, log) points to the bottleneck.","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":"Check for blocking","detail":"The most common cause of slowness is one session blocking others. Find the blocking session and identify the head of the chain.","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":"Extract the most expensive queries","detail":"Find the top 10 queries consuming the most resources. Usually 90% of the problem comes from a handful of queries.","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":"Examine the plan of the suspect query","detail":"Get EXPLAIN for the slow query. Look for full table scans, missing indexes, bad join order or stale statistics. If the plan changed suddenly, consider gathering statistics.","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":"Verify system resources","detail":"At the OS level, check CPU, I/O wait (iowait), memory and disk usage. Check the cache hit ratio; if it is low, there may be a memory bottleneck. The problem may be outside the DB (full disk, noisy neighbor VM)."},{"title":"Decide: quick fix or permanent fix","detail":"For immediate relief: kill the runaway query, add the missing index, gather statistics. For a permanent fix: redesign the query/index, correct memory settings, ensure plan stability (baseline/hint)."}],"related":["active-sessions","blocking-sessions","top-slow-queries","explain-plan","cache-hit-ratio"]},{"id":"disk-full","title":"Disk / tablespace full","icon":"💽","symptom":"'unable to extend', 'disk full', 'log file full' errors; writes are stopping.","steps":[{"title":"Don't panic; first find what is full","detail":"Is it a datafile, the transaction log, the archive/redo area, temp or the OS disk? Cleaning the wrong place does not help. Check the disk with df -h at the OS level and the space views inside the DB."},{"title":"Measure usage","detail":"Which tablespace/file/directory is full, and by what percentage?","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":"Find the largest consumers","detail":"Identify the largest tables/segments, the largest log/archive files and unneeded old backups. Most of the time a single runaway table or accumulated archived logs are to blame.","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":"Reclaim space safely (quick)","detail":"For logs/archives: back them up, then purge old archived logs/backups (Oracle: RMAN delete archivelog; MSSQL: take a log backup and shrink the log). In Redis: maxmemory-policy and eviction. NEVER delete a live datafile manually; NEVER delete archived logs without a backup."},{"title":"Grow capacity permanently","detail":"Add space to the datafile or enable autoextend (Oracle: ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE), grow the disk, add a tablespace. Fix the root cause: log rotation, archive cleanup job, retention policy."},{"title":"Prevent recurrence","detail":"Set up disk usage alerts (80%/90% thresholds), automate archive/log cleanup and define the retention policy clearly. Avoid having to open this playbook again."}],"related":["free-space","largest-tables","database-size","reclaim-space","last-backup-status"]},{"id":"blocking","title":"Blocking & lock crisis","icon":"🔒","symptom":"Queries hang, 'lock wait timeout', many sessions waiting but CPU is low.","steps":[{"title":"Find the head of the blocking chain (root blocker)","detail":"Work out who is blocking whom; the real culprit is the session that waits on nothing itself but makes others wait. Focus on the session at the top of the chain.","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":"Understand what the root blocker is doing","detail":"Is it an open but uncommitted transaction? Did the application leave it 'idle in transaction'? Is it a long batch? Understand what it is before killing it blindly — it may be about to finish.","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":"Decide: wait, resolve from the application, or kill","detail":"If it is about to finish, wait. If the application can commit/roll back, trigger that. Otherwise, if business is stalled, terminate the root blocker. Before killing, consider the impact (rollback time, data)."},{"title":"Terminate the root blocker if necessary","detail":"Kill only the session at the head of the chain; the sessions below it are released automatically.","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":"Fix the root cause","detail":"A missing index may cause wide locks; the application may hold transactions open too long; the isolation level may be wrong. Add indexes, shorten transactions, set an 'idle in transaction' timeout and choose an appropriate isolation level."}],"related":["blocking-sessions","kill-session","open-transactions"]},{"id":"connection-storm","title":"Connection storm (too many connections)","icon":"🌊","symptom":"'too many connections', 'ORA-00018', 'remaining connection slots reserved' — new connections are being rejected.","steps":[{"title":"Measure the current state","detail":"How many connections are there, what is the limit, and how many are active vs. idle?","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":"Identify the source: who/from where","detail":"Which application/host/user is opening the connections? Usually a single application server's pool has blown up or there is a connection leak (a pile of idle in transaction sessions).","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":"Buy breathing room immediately","detail":"Clean up long-idle/idle-in-transaction connections. If necessary, drop the offending application's connections in bulk. Use the slot reserved for admin login (PG superuser_reserved_connections)."},{"title":"The right fix: connection pooling","detail":"Raising max_connections without limit consumes memory and only postpones the problem. The right fix is a connection pool on the application side (HikariCP, PgBouncer, ProxySQL) with a reasonable pool size. Pool size is typically around (cores*2 + number of disks) — see the Tools > Connection Pool calculator."},{"title":"Prevent leaks and recurrence","detail":"Make sure the application closes connections (finally/using). Set idle_in_transaction_session_timeout and wait_timeout. Set up an alert on connection count."}],"related":["connection-count","kill-session","open-transactions"]},{"id":"replication-lag","title":"Replication lag / stopped","icon":"🔁","symptom":"The standby/replica is falling behind or has stopped completely; read replicas are showing stale data.","steps":[{"title":"How large is the lag, and is it growing or steady","detail":"What is the lag in seconds/bytes? Is it a steady offset or continuously growing? If it grows, the replica cannot keep up with apply; if it is steady, there was a momentary stall.","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":"Is replication running, are there errors","detail":"What is the IO/SQL thread state, the apply process, is the connection broken? If there is an error (e.g. conflicting row, missing table), apply has stopped; read the error message.","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":"Find the bottleneck","detail":"Why can it not keep up? Single-threaded apply plus a heavy write load (PG/MySQL mostly apply serially), an I/O bottleneck on the standby, the network, or a long query blocking apply (PG hot_standby_feedback / recovery conflict)?"},{"title":"Close the lag","detail":"Speed up apply: enable parallel apply (MySQL replica_parallel_workers — slave_parallel_workers before 8.0.26; PG has none, but there are recovery settings), improve standby I/O, resolve the query blocking apply. If it stopped with an error: fix the error (resolve the conflicting row manually) and resume replication."},{"title":"Rebuild if it is broken","detail":"If the replica is far behind/broken, resynchronizing is cleanest: rebuild the standby from a fresh backup (PG pg_basebackup, MySQL clone/xtrabackup, Oracle RMAN duplicate). Critical: find the cause before rebuilding, or it will happen again."}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"high-cpu","title":"High CPU","icon":"🔥","symptom":"Server CPU at 90%+; the whole system is slow and the load average is high.","steps":[{"title":"Verify the DB is actually consuming the CPU","detail":"Use top/mpstat at the OS level to verify that the process consuming CPU is really the DB. Sometimes a neighboring process or a backup/antivirus is the consumer. If iowait is high, the problem is disk, not CPU."},{"title":"Find the queries using the most CPU","detail":"Extract the top queries sorted by CPU time. Usually a few queries (bad plan, full scan, missing index) burn the 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":"Check for a plan / index problem","detail":"Get the plan of the offending query. Full table scans, nested loop explosions, hash join spills, or bad plans caused by parameter sniffing/stale statistics burn CPU. Add an index or gather statistics."},{"title":"Relieve immediately","detail":"Kill the runaway query and, if needed, limit it with Resource Manager/Resource Governor. For a load that started suddenly, review the latest deploy/parameter change."},{"title":"Fix permanently","detail":"Fix the query/index, ensure plan stability and schedule the statistics gathering job. If CPU is constantly at 100%, scaling (adding CPU, distributing reads to read replicas) may be needed."}],"related":["top-slow-queries","explain-plan","active-sessions","update-statistics"]},{"id":"memory-pressure","title":"Memory pressure / OOM","icon":"🧠","symptom":"The OOM killer kills the process, 'out of memory', low cache hit ratio, excessive swapping, Redis 'OOM command not allowed'.","steps":[{"title":"Who is consuming memory","detail":"Is it the DB's allocated memory (buffer pool/SGA), per-session memory (PGA/sort/work_mem), or the OS as a whole? Check free/vmstat at the OS level and the memory views inside the DB.","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":"Separate configuration from load","detail":"Does the total of the memory settings exceed physical RAM (over-commit)? Calculate shared_buffers + (work_mem * concurrency). Or is it a momentary load (a huge sort/hash, many connections)?"},{"title":"Redis-specific: eviction and fragmentation","detail":"If used_memory has hit maxmemory, check maxmemory-policy (with noeviction, writes are rejected). If mem_fragmentation_ratio is high, use MEMORY PURGE / activedefrag. Find big keys with --bigkeys.","engines":{"redis":"CONFIG GET maxmemory-policy\nMEMORY DOCTOR\nredis-cli --bigkeys"}},{"title":"Relieve immediately","detail":"Kill the runaway query/session eating memory, temporarily lower session memory such as work_mem, drop unnecessary connections. If the system is swapping, getting out of swap is the priority."},{"title":"Size correctly","detail":"Distribute RAM correctly with the Tools > Memory/Buffer Pool calculator. General rule: on a dedicated server, buffer pool/SGA ~70-80% of RAM; total session memory multiplied by concurrency and do not exceed RAM. Add RAM if necessary."}],"related":["cache-hit-ratio","show-config-param","reclaim-space"]},{"id":"log-growth","title":"Transaction log / undo bloat","icon":"📈","symptom":"The transaction log file or the undo/redo area is growing fast; 'log full', 'ORA-01555 snapshot too old', 'unable to extend undo'.","steps":[{"title":"Find the open transaction growing the log","detail":"The log/undo cannot be cleared until the oldest active transaction finishes. A single long open transaction may be pinning the entire 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":"Understand the engine-specific root cause","detail":"MSSQL: in the FULL recovery model, the log grows indefinitely if no log backups are taken. Oracle: ORA-01555 = insufficient undo retention/size + a long query. PG: if a replication slot does not advance, WAL accumulates; a long transaction blocks VACUUM."},{"title":"The right action for the MSSQL log","detail":"Take a log backup (BACKUP LOG) — this makes the log reusable. If the recovery model really should be SIMPLE, change it. Then, if needed (and only if needed), shrink the log.","engines":{"mssql":"BACKUP LOG [DB] TO DISK='C:\\backup\\DB_log.trn';\n-- Then, if needed: DBCC SHRINKFILE (N'DB_log', 512);"}},{"title":"PG WAL / replication slot check","detail":"A replication slot that does not advance (inactive) causes WAL accumulation. Before dropping it, verify the slot's owner (slot_type, database, plugin): it may be a temporarily disconnected standby or a logical subscriber/CDC (e.g. Debezium). If the slot is dropped, the attached standby must be rebuilt and the subscriber/CDC side must be resynchronized. Drop it only if it is truly unused; for lasting protection, set an upper bound with 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":"Tune undo/retention and prevent recurrence","detail":"Oracle: grow the UNDO tablespace, set undo_retention according to long queries (or RETENTION GUARANTEE). Prevent long open transactions in the application. Monitor that the log backup job runs."}],"related":["open-transactions","reclaim-space","last-backup-status"]},{"id":"deadlocks","title":"Deadlock analysis","icon":"♻️","symptom":"'deadlock detected', 'ORA-00060', 'chosen as deadlock victim' — transactions lock each other and one is aborted.","steps":[{"title":"Deadlocks are normal, but should not be frequent","detail":"The DB detects the deadlock and resolves it by choosing a victim — this is by design. The goal is to reduce the frequency. The application should retry the victim transaction. First capture the deadlock graph."},{"title":"Get the deadlock graph/details","detail":"Which two (or more) sessions, which resources and which lock modes conflicted?","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":"Resolve the conflict pattern","detail":"The most common cause: two transactions accessing resources in OPPOSITE order. Access resources in the SAME order in all code paths. The second most common cause: wide range locks due to a missing index — add the required index."},{"title":"Reduce lock duration and scope","detail":"Keep transactions short (do not include user interaction inside a transaction). Choose an appropriate isolation level. When needed, acquire row locks early (SELECT ... FOR UPDATE with deterministic ordering)."},{"title":"Retry in the application is mandatory","detail":"Deadlocks cannot be eliminated completely; implement automatic retry (exponential backoff) for the victim transaction. This keeps the user from seeing an error."}],"related":["blocking-sessions","open-transactions","list-indexes"]},{"id":"db-down","title":"Database won't open / unreachable","icon":"🚨","symptom":"Cannot connect to the service, 'connection refused', the instance does not start, the listener does not respond.","steps":[{"title":"Where does it break: network, service or instance","detail":"Is the server reachable (ping/ssh)? Is the DB process/service running (ps/systemctl/services.msc)? Is the port listening (ss -ltnp / telnet host port)? This places the problem in one layer: network / service / database instance."},{"title":"Check the service and the listener","detail":"If the DB process is not up, try starting it; if it is up but connections fail, look at the listener/port and the auth layer.","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":"Read the logs — the answer is there","detail":"If the DB does not open, the reason is written to the log: alert log (Oracle), postgresql.log / startup, mysqld error log, SQL Server ERRORLOG, redis log. Full disk, corrupted file, permissions, port conflict, missing file — all show up here."},{"title":"Address common causes: disk, permissions, port, corruption","detail":"Is the disk 100% full (the most common cause)? Are data directory permissions broken? Is the port taken by another process? Is recovery needed (after a crash)? Is a control file/redo log corrupted? Target each one based on the message in the log."},{"title":"Recover and keep an incident record","detail":"Crash recovery usually completes automatically; be patient. If there is corruption, recover from backup/PITR (see the Corruption playbook). Afterwards, write a root cause analysis (RCA): what happened, why, and how to prevent recurrence."}],"related":["version-uptime","connect-cli","last-backup-status"]},{"id":"corruption","title":"Data corruption","icon":"🧯","symptom":"'ORA-01578', 'page corruption', 'checksum mismatch', 'MISCONF', I/O consistency errors during reads.","steps":[{"title":"Stay calm and stop the spread","detail":"Panicked intervention makes things worse. First determine the scope and, if possible, stop writes to the affected object. If it is hardware-related (disk/controller), also check OS/hardware logs — fixing the DB does not fix the disk."},{"title":"Determine the scope: how much is corrupted","detail":"Is it a single block/page/object, or widespread? Run an integrity check.","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":"Priority: recovery from backup","detail":"With corruption, the safest path is restoring from a good backup. If block/page-level recovery is supported, recover only the corrupted part (Oracle RMAN BLOCKRECOVER, MSSQL page restore). Repair tools (DBCC REPAIR_ALLOW_DATA_LOSS, innodb_force_recovery) CAUSE DATA LOSS — they are the last resort."},{"title":"Verify integrity","detail":"After recovery, rerun the check (DBCC CHECKDB / RMAN VALIDATE / CHECK TABLE) and confirm it comes back clean. Also verify data consistency on the application side (row counts, critical totals)."},{"title":"Root cause: usually hardware","detail":"Corruption usually comes from disk/memory/controller failure. Test/replace the hardware, enable checksums (data_checksums, PAGE_VERIFY CHECKSUM), and tighten the backup + verification routine. Schedule regular DBCC CHECKDB / RMAN VALIDATE."}],"related":["last-backup-status","restore-dump","db-down"]},{"id":"long-running-query","title":"Long-running query / job","icon":"⏱️","symptom":"A single query/job has been running for hours without finishing; it consumes resources or affects others.","steps":[{"title":"Capture the query and its duration","detail":"Which query, how long has it been running, which user/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":"Is it progressing or stuck","detail":"Is it really running, or waiting on a lock? Look at progress indicators (Oracle v$session_longops, PG pg_stat_progress_* views). If it is waiting, switch to the Blocking playbook.","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":"Decide: wait or cancel","detail":"If it is progressing and will finish soon, wait (the rollback cost of cancelling may make it take even longer). If it is stuck, running incorrectly or affecting production, terminate it. Consider the rollback time before cancelling."},{"title":"Terminate if necessary","detail":"Prefer cancelling only the query (keeping the session); if that does not work, kill the 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":"Fix permanently","detail":"Optimize the query (index, plan, pagination/batching), move heavy jobs to off-peak hours and, if needed, run them in chunks. For recurring reports, consider a materialized view / read replica."}],"related":["active-sessions","kill-session","explain-plan","top-slow-queries"]},{"id":"xid-wraparound","title":"Postgres XID wraparound / autovacuum crisis","icon":"🧬","symptom":"'database is not accepting commands to avoid wraparound data loss'; autovacuum cannot keep up, datfrozenxid is aging.","steps":[{"title":"Understand the urgency","detail":"PostgreSQL transaction IDs are 32-bit; as they approach exhaustion, the server STOPS WRITES to prevent data loss. This is a critical, time-sensitive situation. The good news: it is solvable; it takes discipline, not panic."},{"title":"Measure how close you are","detail":"Extract the age of the oldest unfrozen XID for each database; proximity to autovacuum_freeze_max_age (default 200M) shows the danger.","engines":{"postgres":"SELECT datname, age(datfrozenxid) AS xid_age\nFROM pg_database ORDER BY xid_age DESC;"}},{"title":"Find the oldest tables","detail":"The problem usually comes from a few large/frequently written tables; focus on the oldest relfrozenxid values.","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":"Remove what is blocking VACUUM","detail":"Long open transactions, abandoned replication slots and prepared transactions block cleanup (freeze). Find and resolve them — otherwise VACUUM makes no progress no matter how long it runs.","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":"Run an aggressive FREEZE","detail":"Once the blockers are gone, force freezing starting with the oldest tables. Even if the server has stopped assigning new transaction IDs, do not switch to single-user mode (postgres --single): current PostgreSQL documentation states it is no longer needed and should be avoided. Connecting as a superuser in normal mode and running VACUUM is sufficient; in PG14+, VACUUM's failsafe mechanism kicks in as wraparound approaches.","engines":{"postgres":"VACUUM (FREEZE, VERBOSE) oldest_table;\n-- Entire database: vacuumdb --all --freeze --jobs=4"}},{"title":"Permanent: strengthen and monitor autovacuum","detail":"Increase autovacuum_vacuum_cost_limit, lower autovacuum_naptime, and set table-specific storage parameters on heavily written tables. Set up an alert on age(datfrozenxid); never go through this crisis again."}],"related":["open-transactions","reclaim-space"]},{"id":"tempdb-contention","title":"tempdb contention (SQL Server)","icon":"🌀","symptom":"PAGELATCH_UP/EX waits, contention on tempdb allocation pages (PFS/GAM/SGAM), general slowness.","steps":[{"title":"Confirm the contention","detail":"PAGELATCH waits on tempdb system pages (such as 2:1:1) indicate allocation contention.","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":"Break down tempdb usage","detail":"Who is using how much tempdb? Internal objects (sort/hash spills), the version store, or user objects?","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":"Fix the file configuration","detail":"Use multiple EQUALLY sized data files for tempdb, equal to the number of CPU cores (up to 8). SQL 2016+ recommends this at setup; on older versions use trace flags 1117/1118. Put the files on fast disk (local NVMe)."},{"title":"Find the runaway query","detail":"Queries with large sort/hash spills blow up tempdb. Target those with insufficient memory grants or bad plans; reduce spills with indexes/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":"Analogues in other databases","detail":"The same 'temporary space contention' is the TEMP tablespace in Oracle (v$tempseg_usage / v$sort_usage) and temp_files and work_mem overflow in PostgreSQL. The root fix is always the same: fix the spilling query and speed up the temporary space."}],"related":["free-space","top-slow-queries","reclaim-space"]},{"id":"cert-expiry","title":"TLS / certificate expired","icon":"🔐","symptom":"'certificate has expired', 'SSL handshake failed', clients suddenly cannot connect (with no code change).","steps":[{"title":"Where does it break: server, client or CA?","detail":"Is it the server certificate that expired, a client certificate, or a CA in the chain? The symptom is usually 'it worked yesterday, it doesn't work today, no code changed'."},{"title":"Check the expiry","detail":"Read the certificate's expiry date directly from the server (a quick, database-independent method).","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":"Install and load the new certificate","detail":"Replace the certificate and, in most cases, reload the service without restarting it.","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":"Verify","detail":"Confirm with the openssl command above that the TLS handshake succeeds with the new certificate and that the expiry has been extended; connect end-to-end with a client."},{"title":"Permanent: never again","detail":"Set up an alert 30 days before certificate expiry; use automated renewal (ACME/Let's Encrypt, HashiCorp Vault, cert-manager). Automate the reload after renewal."}],"related":["connect-cli"]},{"id":"upgrade-rollback","title":"Version upgrade / patch rollback","icon":"⏪","symptom":"Problems after an upgrade/patch: performance regression, application incompatibility or the database not opening.","steps":[{"title":"Stop first: do you have a fallback plan?","detail":"If you did not take a full backup + (if possible) a storage snapshot BEFORE the production upgrade, the safest path is to fix forward. In most databases, rolling back means restoring from backup. This step should really have been planned BEFORE the upgrade."},{"title":"Classify the problem","detail":"Is it an optimizer plan regression (queries slowed down), an application/driver incompatibility, or a failure to open/corruption? The solution path varies greatly accordingly."},{"title":"Rollback path by database","detail":"Oracle: patch rollback with datapatch, or Flashback Database (if you created a guaranteed restore point). PostgreSQL: rollback after pg_upgrade = old binaries + backup of the old data directory. MySQL: in-place DOWNGRADE is generally NOT SUPPORTED — restore from backup. SQL Server: there is no downgrade — restore from the pre-upgrade backup. In every case: the snapshot/backup is your insurance."},{"title":"If it is a plan regression: fix without rolling back","detail":"Usually there is no need to roll back the whole version; only the plans have changed.","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":"Permanent: next time","detail":"Test the upgrade in staging with a real workload first, apply canary/rolling upgrades, take a guaranteed restore point / snapshot and document the fallback procedure."}],"related":["last-backup-status","restore-dump","version-uptime"]},{"id":"es-red-cluster","title":"Elasticsearch red/yellow cluster & unassigned shards","icon":"🟥","symptom":"_cluster/health 'red' or 'yellow'; unassigned shards; reads/writes restricted or data inaccessible.","steps":[{"title":"Color and cause","detail":"yellow = replica shards are unassigned (data is present, durability is reduced). red = at least one PRIMARY shard is missing (that data is inaccessible). First determine which one and why.","engines":{"elasticsearch":"GET _cluster/health?level=indices\nGET _cluster/allocation/explain"}},{"title":"List unassigned shards and the reason","detail":"The unassigned.reason column gives the root cause (NODE_LEFT, ALLOCATION_FAILED, DISK, etc.).","engines":{"elasticsearch":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason&s=state"}},{"title":"Address the common causes","detail":"1) Disk watermark → above low, the node receives no new shards; at flood_stage, indices become read-only. 2) A node went down / left. 3) Allocation was left disabled. 4) Replica count > number of eligible nodes."},{"title":"Fix","detail":"Unblock according to whichever cause applies.","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":"If still red and the primary is truly lost","detail":"Restore the affected index from a snapshot or reindex it from the source data (_reindex). Permanent: disk watermark alerting, an appropriate replica count (≥1) and node health monitoring."}],"related":["replication-status","list-replicas","free-space"]},{"id":"time-skew","title":"Clock skew / NTP problem","icon":"⏱️","symptom":"Clock differences between cluster nodes; replication/election inconsistencies, 'certificate not yet valid', time jumps in logs.","steps":[{"title":"Recognize the symptom","detail":"Clock skew is insidious: Mongo/Redis Sentinel election flapping, TLS 'not yet valid/expired' errors, Kerberos errors, timestamps going backwards in logs. In distributed systems, it is the root of most 'unexplainable' oddities."},{"title":"Measure the difference","detail":"On each node, compare the time source and offset: `timedatectl`, `chronyc tracking` (or `ntpq -p`), and `date -u` across nodes. How many ms/s apart are they?"},{"title":"Fix NTP/chrony","detail":"Is chronyd/ntpd running, can it reach its time sources, is it synchronized? Start the service and fix the source; for large offsets, step carefully (a sudden jump can trigger some DBs)."},{"title":"Verify the affected component","detail":"After the clock is fixed, check: is the MongoDB replica set election stable, is Redis Sentinel/Cluster healthy, do TLS handshakes succeed, have the replication lag metrics recovered?"},{"title":"Permanent","detail":"Make NTP mandatory on all nodes and monitor the offset (alert: >100ms). On virtual machines, settle the host-guest time sync setting; preferably use a single, reliable time source hierarchy."}],"related":["replication-status"]}]},"playbooks/hadr.json":{"schemaVersion":1,"playbooks":[{"id":"hadr-oracle-dataguard","title":"Oracle Data Guard (physical standby) setup","icon":"🛡️","symptom":"Goal: set up disaster recovery and high availability for Oracle with a physical standby (Data Guard); be able to switch to the standby if the primary fails.","steps":[{"title":"Put the primary in FORCE LOGGING and ARCHIVELOG mode","detail":"For the standby to stay consistent with the primary, all changes must be written to redo. FORCE LOGGING logs even NOLOGGING operations. For ARCHIVELOG mode the database is brought up in MOUNT state.","engines":{"oracle":"ALTER DATABASE FORCE LOGGING;\n-- For ARCHIVELOG:\nSHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;"}},{"title":"Add standby redo log files","detail":"Standby redo logs (SRL) should have one more group than the online redo logs and the same size. They are created on both the primary and the standby; they are required for real-time apply. In the example, if online redo is 200M, SRLs are 200M as well.","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":"Set log_archive_dest and related parameters","detail":"On the primary, define dest_2 to ship redo to the standby. Set DB_UNIQUE_NAME, LOG_ARCHIVE_CONFIG (the db_unique_name of both sides), FAL_SERVER, and STANDBY_FILE_MANAGEMENT=AUTO. This way files are created automatically on the 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":"Create the standby with RMAN DUPLICATE FOR STANDBY","detail":"After the password file and static listener/tnsnames are set up on the standby server, RMAN connects to the primary and to the standby (AUXILIARY). FROM ACTIVE DATABASE copies over the network without a backup; DORECOVER applies 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":"Start redo apply (MRP) and verify","detail":"Start managed recovery while the standby is in MOUNT state; use USING CURRENT LOGFILE for real-time apply. Verify with v$managed_standby that the apply process (MRP0) is running.","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":"Enable Data Guard Broker","detail":"The broker centralizes the configuration and reduces failover/switchover to a single command. Set DG_BROKER_START=TRUE on both instances, then create and enable the configuration with dgmgrl. In 12c+ the broker manages redo transport: if LOG_ARCHIVE_DEST_2 was set manually, ADD DATABASE fails with ORA-16698; clear it first with 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":"Monitor the synchronization gap and complete testing","detail":"The sequence difference between primary and standby must be monitored continuously; a growing gap indicates a network/apply bottleneck. dgmgrl SHOW CONFIGURATION should show 'SUCCESS'. Optionally test a role change with a 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":"SQL Server Always On Availability Group setup","icon":"🔁","symptom":"Goal: set up automatic failover and a readable secondary replica for SQL Server with an Always On Availability Group (AG).","steps":[{"title":"Set up a Windows Server Failover Cluster (WSFC)","detail":"Always On relies on an underlying WSFC. Install the Failover Clustering feature on all nodes and configure the network and witness. Create the cluster with 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":"Enable Always On on each instance","detail":"Enable the Always On Availability Groups feature with SQL Server Configuration Manager or PowerShell. Then restart the SQL Server service. This binds cluster membership to the instance.","engines":{"mssql":"-- PowerShell:\nEnable-SqlAlwaysOn -ServerInstance node1\\SQL01 -Force\nEnable-SqlAlwaysOn -ServerInstance node2\\SQL01 -Force"}},{"title":"Back up the database and restore it on the secondary WITH NORECOVERY","detail":"The database to be added to the AG must be in FULL recovery model. Take a full and a log backup on the primary; restore them on the secondary WITH NORECOVERY (so it can keep receiving redo). This is manual seeding; the alternative is 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":"Create the endpoints and define the Availability Group","detail":"Create a HADR endpoint on port 5022 for inter-node redo traffic. Synchronous mode (SYNCHRONOUS_COMMIT) provides zero-data-loss automatic failover; asynchronous (ASYNCHRONOUS_COMMIT) is lagged but performant for remote DR.","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":"Join the secondary to the AG and join the database","detail":"JOIN the AG on the secondary instance, then add the restored database to the AG. This step is needed if manual seeding was used; with automatic seeding it is handled on the primary via SET(SEEDING_MODE).","engines":{"mssql":"-- On the secondary:\nALTER AVAILABILITY GROUP appag JOIN;\nALTER DATABASE app SET HADR AVAILABILITY GROUP = appag;"}},{"title":"Create the Availability Group Listener","detail":"The listener is the virtual name and IP that applications connect to; after a failover, connections are redirected automatically to the new primary. Use this name and MultiSubnetFailover=True in the application connection string.","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":"Test failover and verify synchronization state","detail":"When synchronization is 'HEALTHY', test a role change with a planned failover. For automatic failover, the mode must be synchronous and FAILOVER_MODE=AUTOMATIC. Monitor the state with 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":"PostgreSQL streaming replication + automatic failover","icon":"🐘","symptom":"Goal: set up a hot standby for PostgreSQL with streaming replication and add Patroni/repmgr for automatic failover.","steps":[{"title":"Set WAL and connection parameters on the primary","detail":"For streaming replication, wal_level must be at least 'replica'. max_wal_senders must cover the number of replicas + backup connections, and early WAL removal must be prevented with wal_keep_size or a replication slot. The changes require a restart.","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":"Define the replication role and pg_hba access","detail":"Create a role with the REPLICATION attribute for the standby to connect with. Add a line to pg_hba.conf allowing access to the 'replication' database from the standby IP, and reload with 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":"Create a persistent replication slot on the primary","detail":"A physical replication slot prevents the primary from removing needed WAL even when the standby falls behind; slot management is safer than wal_keep_size. We will use the slot name in the standby configuration.","engines":{"postgres":"SELECT pg_create_physical_replication_slot('standby1_slot');\nSELECT slot_name, active, restart_lsn FROM pg_replication_slots;"}},{"title":"Take a base backup on the standby server with pg_basebackup","detail":"With the standby's data directory empty, take a full copy from the primary. The -R flag automatically writes the standby.signal file and primary_conninfo; -C can also create the slot. The slot name is passed with -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":"Verify primary_conninfo and hot_standby, start the standby","detail":"Check primary_conninfo (host, user, slot) in the postgresql.auto.conf written by -R; the presence of the standby.signal file determines standby mode. With hot_standby=on the standby accepts read-only queries. Start the 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":"Monitor replication lag from the primary","detail":"On the primary, pg_stat_replication shows the sent/replayed LSN and lag for each standby. write/flush/replay lag should stay small; sync_state is 'sync' if a synchronous replica is used.","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":"Add Patroni or repmgr for automatic failover","detail":"Bare streaming replication requires a manual promote. Automatic failover requires a cluster manager: Patroni (leader election on etcd/Consul + automatic promote) or repmgr (monitoring/failover with repmgrd). On the application side, provide a single endpoint with HAProxy/pgBouncer. If a manual promote is needed: pg_ctl promote. Before promoting, make sure the old primary is really stopped and applications cannot write to it (split-brain risk). Rejoining the old primary as a standby of the new primary requires pg_rewind; for that, wal_log_hints = on or data checksums must be enabled.","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":"MySQL InnoDB Cluster (Group Replication) setup","icon":"🐬","symptom":"Goal: set up high availability with automatic failover for MySQL using InnoDB Cluster (Group Replication + MySQL Shell + MySQL Router).","steps":[{"title":"Meet the prerequisites","detail":"InnoDB Cluster requires MySQL 8.0+, a unique server_id, GTID mode, and the InnoDB storage engine on every node. All instances must be reachable from each other over the network, and tables must have primary keys. Prepare a clusterAdmin user for 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":"Configure each instance with MySQL Shell","detail":"In mysqlsh, dba.configureInstance() makes each node suitable for Group Replication; it fixes missing parameters and asks for a restart if needed. Repeat on all nodes.","engines":{"mysql":"-- mysqlsh:\ndba.configureInstance('icadmin@node1:3306')\ndba.configureInstance('icadmin@node2:3306')\ndba.configureInstance('icadmin@node3:3306')"}},{"title":"Create the cluster (seed node)","detail":"Connect to one node and start the cluster with dba.createCluster(); this node becomes the first primary. A cluster object is returned and used for subsequent additions.","engines":{"mysql":"-- mysqlsh, while connected to node1:\nvar cluster = dba.createCluster('appCluster')"}},{"title":"Add the other instances to the cluster","detail":"Add the secondary nodes with cluster.addInstance(). Choose clone (physical clone, fast for a new node) or incremental (via GTID) as recoveryMethod. At least 3 nodes provide an odd majority and automatic failover.","engines":{"mysql":"-- mysqlsh:\ncluster.addInstance('icadmin@node2:3306', {recoveryMethod: 'clone'})\ncluster.addInstance('icadmin@node3:3306', {recoveryMethod: 'clone'})"}},{"title":"Bootstrap MySQL Router","detail":"MySQL Router routes between the application and the cluster; it exposes read/write (6446) and read-only (6447) ports and redirects traffic automatically when the primary changes. Bootstrap generates the configuration from the cluster metadata.","engines":{"mysql":"mysqlrouter --bootstrap icadmin@node1:3306 --user=mysqlrouter --directory /etc/mysqlrouter\n# Then: mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf &"}},{"title":"Check cluster status and verify failover","detail":"cluster.status() shows each node's role (R/W primary, R/O secondary) and state (ONLINE). clusterName and topology should be healthy. Test failover by shutting down the primary and observing the automatic election of a new primary.","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":"MongoDB replica set + sharding setup","icon":"🍃","symptom":"Goal: set up a replica set with automatic failover for MongoDB and extend it to a sharded cluster for horizontal scaling.","steps":[{"title":"Start the mongod processes with the same replSet name","detail":"Start each replica set member with the same --replSet name and a reachable --bind_ip. In production, internal authentication with a keyFile or TLS is recommended. Aim for an odd number of voting members for a clear majority.","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":"Initiate the replica set","detail":"Connect to one member with mongosh, call rs.initiate(), and define all members. This triggers the first election and a primary is chosen. Verify PRIMARY/SECONDARY roles with 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":"Add members, set priorities and an arbiter if needed","detail":"Add members later with rs.add(). priority determines which member tends to become primary (0 = never becomes primary). An arbiter only votes and holds no data; it is used as a tie-breaker with an even number of data nodes, but a real member is preferred for data safety. In arbiter-based topologies (especially PSA), majority write concern cannot be satisfied when a data node goes down and writes wait; on 5.0+ the cluster-wide default write concern may need to be set explicitly with setDefaultRWConcern before adding an 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":"Set up the config server replica set for sharding","detail":"The sharded cluster's metadata is kept in a config server replica set (--configsvr). It is itself initiated as a replica set; 3 members are recommended for high availability.","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":"Start the shard replica sets and the mongos router","detail":"Each shard is its own replica set started with --shardsvr (data durability comes from intra-shard replication). The mongos router is started pointing at the config servers; applications connect to 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":"Add the shards and shard the collection","detail":"Connect to mongos and add each shard replica set with sh.addShard(). Enable sharding on the database and distribute the collection by a shard key with sh.shardCollection(). Key choice is critical: it should have high cardinality and an even distribution. Monitor chunk distribution with 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":"High availability with Redis Sentinel + Cluster","icon":"⚡","symptom":"Goal: set up a master-replica deployment with automatic failover for Redis using Sentinel; switch to Redis Cluster mode if horizontal scaling is needed.","steps":[{"title":"Configure the master and replicas","detail":"Run one master and at least two replicas. Replicas follow the master with REPLICAOF ('replicaof <host> <port>' can also be written in redis.conf). Enable AOF or RDB for persistence; authentication with requirepass/masterauth is recommended.","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":"Configure Sentinel with monitor and quorum","detail":"Define the master to monitor and the quorum in each Sentinel node's sentinel.conf. quorum is the minimum number of Sentinels required to consider the master 'down'. In production, 3 Sentinels on 3 separate servers are recommended (against 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":"Start the Sentinel processes","detail":"Start Sentinel on each node. Sentinels automatically discover each other and the replicas; they write the configuration to the file at runtime. Therefore sentinel.conf must be writable.","engines":{"redis":"redis-sentinel /etc/redis/sentinel.conf\n# or:\nredis-server /etc/redis/sentinel.conf --sentinel"}},{"title":"Query status through Sentinel and test failover","detail":"Query the current master and replicas with SENTINEL commands. Applications should learn the current master by asking Sentinel 'get-master-addr-by-name'. Verify automatic switchover with a planned failover.","engines":{"redis":"SENTINEL master mymaster\nSENTINEL get-master-addr-by-name mymaster\nSENTINEL replicas mymaster\n# planned switchover:\nSENTINEL failover mymaster"}},{"title":"Alternative: partition the data with Redis Cluster","detail":"If a single master's memory/throughput is not enough, use Redis Cluster: data is split into 16384 hash slots and distributed across multiple masters; each master has its own replica. The application must use a cluster-aware client. Set it up with 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":"Verify cluster state","detail":"After the cluster is created, verify that all slots are assigned and the nodes are connected. cluster_state should be 'ok' and all 16384 slots should appear assigned. Monitor node and slot distribution with CLUSTER commands.","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":"Elasticsearch resilience: replica + snapshot + CCR","icon":"🔎","symptom":"Goal: build resilience for Elasticsearch with replica shards, dedicated master/data nodes, snapshot backups (SLM), and cross-cluster replication (CCR).","steps":[{"title":"Separate roles: dedicated master and data nodes","detail":"For cluster stability, voting master-eligible nodes (preferably 3, an odd majority) are separated from the data workload. Assign roles with node.roles in elasticsearch.yml and define seed hosts for discovery. 3 masters prevent 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":"Set the index replica count","detail":"number_of_replicas is the number of copies kept for each primary shard; it should be at least 1 so that data stays available when a data node is lost. It can be changed dynamically on a live index; the replica count must be less than the number of data nodes.","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":"Define a snapshot repository","detail":"Backups are taken to a repository: a shared file system (fs) or the S3/GCS/Azure plugins. To be able to recover data even if the cluster is deleted, the repository must be on durable storage outside the cluster.","engines":{"elasticsearch":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": { \"location\": \"/mnt/es_backups\", \"compress\": true }\n}"}},{"title":"Schedule automatic snapshots with SLM","detail":"Snapshot Lifecycle Management (SLM) takes regular snapshots and applies a retention policy. A cron expression defines the frequency, which indices are included, and how many snapshots are kept.","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":"Verify the snapshot and the restore","detail":"Trigger the SLM policy manually to verify it works; the snapshot state should be SUCCESS. Perform restore tests regularly, because an untested backup is not a backup.","engines":{"elasticsearch":"POST /_slm/policy/daily_backup/_execute\nGET /_snapshot/backup_repo/_all\n# restore:\nPOST /_snapshot/backup_repo/<snapshot_name>/_restore"}},{"title":"Set up remote DR with Cross-Cluster Replication (CCR)","detail":"For cross-region disaster recovery, a second cluster (follower) follows indices in the remote primary cluster (leader). First define the remote cluster, then attach the follower index to the leader; changes on the leader flow automatically. CCR is a Platinum (commercial) feature.","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":"Security hardening: SQL databases (Oracle, PostgreSQL, MySQL, SQL Server)","icon":"🔒","symptom":"Goal: harden relational databases CIS-style — default accounts, strong password policy, least privilege, network restriction, TLS, auditing and encryption (TDE).","steps":[{"title":"Remove or lock default/unneeded accounts, drop sample schemas","detail":"Default accounts and sample schemas shipped with the installation are a well-known attack surface. Lock or remove the unused ones; rename top-level/service accounts.","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":"Enforce a strong password policy and expiration","detail":"Weak or never-changing passwords are the most common entry point. Enforce login attempt limits, complexity and password lifetime; store passwords with a strong hash.","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":"Least privilege: revoke PUBLIC/broad privileges, grant role-based privileges","detail":"Broad privileges granted to everyone (PUBLIC) make lateral movement easier. Revoke them, create roles and grant only what is needed.","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":"Restrict network/access — only known clients may connect","detail":"Avoid having the database listen openly to the world. Restrict the listen address, apply host/IP-based access rules and limit the port with a firewall.","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":"Enforce TLS/encrypted connections","detail":"Network traffic is exposed to passive sniffing. Configure a server certificate and require clients to connect encrypted.","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":"Enable auditing — make who did what visible","detail":"An audit trail is essential for monitoring privileged access and suspicious activity. Log logins, privilege changes and access to sensitive objects.","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":"Apply data encryption (at rest / TDE)","detail":"If a disk, backup or file is stolen, the data must be unreadable. Enable data-at-rest encryption (TDE) at column/tablespace/database level.","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":"Keep patches current and disable unneeded features","detail":"Known vulnerabilities are the most exploited risk. Apply security patches regularly; disable unused protocols, plugins and features (shrink the attack surface).","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":"Security hardening: MongoDB / Redis / Elasticsearch","icon":"🛡️","symptom":"Goal: harden NoSQL/data stores CIS-style — authentication, network restriction, blocking dangerous commands, TLS, least-privilege roles and auditing.","steps":[{"title":"ENABLE authentication — cut off unauthenticated access","detail":"These products often ship with authentication DISABLED by default and get compromised when left exposed to the internet. Enable authentication and create an administrator user.","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":"Restrict the network — open only the required interfaces and ports","detail":"Avoid having the service listen to the whole world on 0.0.0.0. Pin the bind address to an internal interface/loopback and enable 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":"Restrict dangerous commands","detail":"A single command can wipe all data. Rename or remove destructive/administrative commands, or block wildcard targeting.","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":"Enable TLS/encrypted connections","detail":"Credentials and data must not travel unencrypted over an open network. Enable TLS for client and inter-node traffic.","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":"Grant least-privilege roles — avoid broad roles","detail":"Give each user only the access its job requires. Instead of broad/administrative roles, use fine-grained roles limited to specific databases, key patterns or indices.","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":"Enable auditing/logging — monitor access and changes","detail":"Unauthorized access attempts and administrative operations must be recorded. Enable the audit log and ship it to a central location.","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":"Cloud-managed databases (RDS/Aurora/Cloud SQL/Atlas/Azure SQL)","icon":"☁️","symptom":"Goal: learn the key differences between cloud-managed databases and self-managed installations; understand how backup, parameters, failover, monitoring, upgrades and connectivity work in the cloud.","steps":[{"title":"Mindset shift: you access a service, not a server","detail":"With managed databases you have NO shell access to the OS, the file system or the database process; you cannot SSH in, edit config files by hand or become superuser. You only get the provider's management API/console and a normal SQL connection. AWS RDS/Aurora: management is at the resource (instance/cluster) level, the server is hidden. GCP Cloud SQL: instance API only, no OS. MongoDB Atlas: fully managed cluster, no access to mongod. Azure SQL Database: PaaS, neither server nor OS is visible underneath (Managed Instance gives somewhat more control). So most self-managed reflexes such as 'look at the file', 'kill the process' or 'run an OS tool' do not apply."},{"title":"Backup: automated snapshots + point-in-time recovery (PITR)","detail":"You do not schedule backups; the provider takes them automatically. Recovery is usually 'restore to a specific point in time' (PITR) or creating a new instance from a snapshot; the existing instance is not overwritten. AWS RDS/Aurora: automated backups + retention window; a PITR restore with per-second precision opens as a new instance/cluster; manual snapshots are also possible. GCP Cloud SQL: automated backups + PITR via binary logs; restore goes to a new instance. MongoDB Atlas: continuous cloud backup + PITR (M10+); restore from snapshots. Azure SQL: built-in automated backups, 1-35 day retention, PITR to any point and restore of deleted DBs. Common rule: verify the backup configuration (window, retention period) and run regular restore tests."},{"title":"Parameters: parameter groups / flags, not a config file","detail":"Instead of editing postgresql.conf or my.cnf by hand, you use the provider's parameter abstraction; some parameters are locked and some require a restart (static). AWS RDS/Aurora: set via a DB parameter group (and a cluster parameter group); dynamic parameters apply immediately, static ones on reboot. GCP Cloud SQL: 'database flags' are set from the console/gcloud. MongoDB Atlas: limited 'Additional Settings' + oplog size etc.; most internal parameters are managed. Azure SQL: most server-level settings are managed; behavior changes through SCOPED CONFIGURATION and the service tier choice. Before applying, check whether the parameter is dynamic or static."},{"title":"High availability / failover: managed Multi-AZ and replicas","detail":"You do not build failover yourself; you tick a box and leave it to the provider. The standby and automatic failover are managed, and connections are redirected to the new primary through a stable endpoint. AWS RDS: Multi-AZ (synchronous standby, automatic failover); Aurora: multi-copy storage + automatic failover, the cluster endpoint points to the writer. GCP Cloud SQL: HA configuration (regional, standby in a secondary zone) with one click; read replicas are added separately. MongoDB Atlas: replica set built in, multi-region/multi-cloud deployment available, failover automatic. Azure SQL: built-in HA (local replicas in Business Critical), a zone-redundant option and cross-region active geo-replication / failover groups. The application must always connect to the endpoint, never to a fixed IP."},{"title":"Monitoring: the provider's metrics and log service","detail":"Because you cannot run OS tools (top, iostat), you monitor through the provider's metrics platform; CPU, connection count, IOPS, replication lag and storage metrics live there. AWS: CloudWatch metrics + Enhanced Monitoring + Performance Insights (query-level analysis). GCP Cloud SQL: Cloud Monitoring (formerly Stackdriver) + Query Insights. MongoDB Atlas: built-in Atlas metrics/charts + Performance Advisor + real-time panel. Azure SQL: Azure Monitor + SQL Analytics + Query Performance Insight. Always set up threshold alarms (storage usage, CPU, connection limit, replica lag)."},{"title":"Version upgrades and maintenance: from the console, in a maintenance window","detail":"Instead of updating binaries by hand you pick a target version; minor version patches are mostly automatic, while major version upgrades are planned and usually involve downtime/an HA failover. AWS RDS/Aurora: engine version selection + automatic minor upgrade option + low-risk major upgrades with Blue/Green Deployments. GCP Cloud SQL: maintenance window + self-service major version upgrade. MongoDB Atlas: one-click version upgrade, near-zero downtime in a rolling fashion. Azure SQL: patched automatically by the platform (the notion of a version is largely abstracted away); behavior is controlled with the compatibility level. Always try it on a test/staging copy first and schedule the maintenance window outside business hours."},{"title":"Connectivity and security: endpoint + IAM/TLS","detail":"Connections always go through the DNS endpoint supplied by the provider and are encrypted (TLS); network access and identity can be tied to the cloud identity system. AWS RDS/Aurora: instance/cluster endpoint + Security Group for the network + optional IAM database authentication + TLS (rds-ca certificate). GCP Cloud SQL: public IP + Authorized Networks or private IP/VPC, Cloud SQL Auth Proxy, IAM database authentication, TLS. MongoDB Atlas: SRV connection string (mongodb+srv://), IP access list + private endpoint/peering, SCRAM or IAM/X.509, TLS mandatory. Azure SQL: server FQDN endpoint + firewall/Private Link + Microsoft Entra ID (formerly Azure AD) authentication + TLS mandatory. Rule: use the endpoint, not a fixed IP, in the connection string, enforce TLS and connect with a cloud IAM identity where possible."}],"related":["last-backup-status","promote-failover","connect-cli"]}]},"playbooks/automation.json":{"schemaVersion":1,"playbooks":[{"id":"auto-ansible","title":"Database automation with Ansible","icon":"🤖","symptom":"Goal: Make database installation, configuration, user and backup tasks repeatable and idempotent (re-running the same playbook does not break the system) with Ansible.","steps":[{"title":"Define the inventory and groups","detail":"Group servers in the inventory by database role; put primary, replica and backup servers in separate groups. Use a static INI/YAML inventory or a dynamic inventory for the cloud (aws_ec2, gcp_compute). Put group variables under group_vars/ and host-specific ones under 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":"Install the right collection (community.postgresql / community.mysql / community.mongodb)","detail":"Ansible core now ships database modules in separate collections; install the collection for your engine via requirements.yml. Use community.postgresql (postgresql_db, postgresql_user, postgresql_privs) for PostgreSQL, community.mysql (mysql_db, mysql_user) for MySQL/MariaDB and community.mongodb for MongoDB. The corresponding Python client (psycopg2, PyMySQL, pymongo) must be installed on the target host.","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":"Encrypt secrets with Ansible Vault","detail":"Do not leave database passwords, replication secrets and superuser credentials in plain text; keep them in an encrypted variable file with ansible-vault. Decrypt at run time with --ask-vault-pass or --vault-password-file. Secrets can then be stored safely in Git and the playbook stays 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":"Set up the role structure (roles/postgres, roles/mysql)","detail":"Create one role per engine and split the logic into tasks/, defaults/, templates/ and handlers/ subfolders. This structure improves reusability: defaults/main.yml for default values, templates/ for jinja2 configuration templates, handlers/ for 'restart only if something changed' logic. You can scaffold the role with 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":"Write installation and configuration tasks idempotently","detail":"Install the package, initialize the data directory and render postgresql.conf/my.cnf from a template; restart the service via a handler when the configuration changes. Modules are idempotent: an already installed package is not reinstalled, and the handler is not triggered if the file did not change. Render the configuration template with the template module and link it to the handler with '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":"Define database and user tasks","detail":"Create the database, users and privileges with modules; take the password from a Vault variable. The community.postgresql modules are idempotent: if the user/DB exists they do not recreate it, they apply only the difference. Grant privileges separately with postgresql_privs; in MySQL the priv field of mysql_user is used.","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":"Add the backup task and schedule","detail":"Deploy the backup script from a template and schedule it with ansible.builtin.cron; the backup job is then also managed as code. Make the backup target directory, retention period and schedule variables. The cron module is idempotent and identifies the entry by 'name', so re-running does not duplicate it.","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":"Database provisioning with Terraform (IaC)","icon":"🏗️","symptom":"Goal: Define database infrastructure (instance, parameter group, network) as code (Infrastructure as Code) with Terraform; set up the plan/apply flow, remote state and drift management.","steps":[{"title":"Choose and configure the provider (aws / google / azurerm)","detail":"Define the provider for the cloud you run on and pin its version. Lock the hashicorp/aws provider for AWS, hashicorp/google for GCP and hashicorp/azurerm for Azure in the required_providers block. Version pinning ensures consistent behavior across the team.","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":"Set up remote state and locking","detail":"Do not keep the state file locally; use a remote backend for teamwork and security. Common choices are the S3 backend + a DynamoDB lock table on AWS, the gcs backend on GCP and the azurerm backend (Storage Account) on Azure. Remote state prevents concurrent applies through locking and makes the state the single source of truth. Because passwords may sit in the state in plain text, keep the backend encrypted and access-restricted.","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":"Define the database resource","detail":"Write the main resource for your engine and cloud: aws_db_instance on AWS (or aws_rds_cluster for Aurora), google_sql_database_instance on GCP, azurerm_mssql_database on Azure (for SQL Server, under an azurerm_mssql_server). Specify engine, version, class, storage and backup window here.","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":"Write the parameter group as code","detail":"Define database settings (max_connections, work_mem, character set) with a parameter resource, not manually in the console. Use aws_db_parameter_group on AWS, settings.database_flags inside google_sql_database_instance on GCP, and the server configuration resource on Azure. Settings are then versioned and reproducible.","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":"Pass secrets via variables and a secrets manager","detail":"Do not hard-code the password in a .tf file; define it as a sensitive variable (variable ... sensitive = true) and supply the value via a TF_VAR_ environment variable, a secrets manager (AWS Secrets Manager, Vault) or a CI secret. Remember: the value is written to the state file, so remote state must be encrypted. The cleanest approach is never to pass the password into Terraform and read it from a secrets manager resource instead.","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":"Run the plan and apply flow","detail":"First prepare the provider/backend with terraform init, see what will change with terraform plan, approve, and apply with terraform apply. Do not apply without reviewing the plan output; the safe flow in CI is to post the 'plan' as a PR comment and apply after human approval. Catch destructive changes (e.g. instance re-creation) by the -/+ marker in the plan output.","engines":{"postgres":"terraform init\nterraform plan -out=plan.tfplan\nterraform apply plan.tfplan"}},{"title":"Detect and fix drift","detail":"If someone makes a manual change in the console, the real infrastructure and the state diverge (drift). Differences that appear in terraform plan 'when nothing should have changed' reveal drift; the difference is read with terraform refresh/plan. Either reflect the change back into code or pull the infrastructure back to the code with apply. Ban manual changes as a rule; code should be the single source of truth.","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":"Databases on Kubernetes (operator)","icon":"☸️","symptom":"Goal: Run the database on Kubernetes with an operator; instead of a bare StatefulSet, use the automatic failover, persistent storage and backup features the operator provides.","steps":[{"title":"Why an operator: a StatefulSet alone is not enough","detail":"A plain StatefulSet gives you pods with stable identities and PVCs, but not the logic for failover, replication setup, backup and upgrades. An operator packages this database-specific operational knowledge as a controller + custom resource (CRD). You write a CR called 'Cluster', and the operator manages the StatefulSet, Service, Secret and backup jobs behind it. In production, running databases with an operator is generally preferred.","engines":{"postgres":"# operators for PostgreSQL:\n#  - CloudNativePG (CNPG)\n#  - Zalando postgres-operator\n#  - Crunchy PGO"}},{"title":"Choose and install the operator","detail":"Choose an operator for your engine: CloudNativePG or Zalando postgres-operator for PostgreSQL; the Percona operators (Percona Operator for MySQL / for MongoDB) are common for MySQL and MongoDB. Install the operator with Helm or a bundle manifest; this step adds the CRDs and the controller pod to the 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":"Plan storage (StorageClass / PVC)","detail":"Database state lives in PVCs; specify the StorageClass to use in the CR. Choose a StorageClass that provides low-latency block storage, ideally local-like SSD; network file systems (NFS) are not recommended for most databases. Set the PVC size and expandability (allowVolumeExpansion) correctly from the start, because shrinking is hard.","engines":{"postgres":"spec:\n  storage:\n    storageClass: fast-ssd\n    size: 50Gi"}},{"title":"Apply the Cluster CR (replicas and automatic failover)","detail":"Specify the number of instances/replicas in the CR; the operator sets up synchronous/asynchronous replication and, if the primary fails, performs automatic failover by promoting a replica. The application should connect to the '-rw' (read-write) Service created by the operator; this service always points to the current primary. The connection target therefore does not change during 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":"Write backups to S3 via the CR","detail":"Operators also manage backups declaratively: you define the primary backup destination (object storage/S3) and WAL archiving in the CR. In CloudNativePG, S3 details are given with spec.backup.barmanObjectStore (deprecated since 1.26; for new deployments use the Barman Cloud Plugin and its ObjectStore resource); continuous WAL archiving + base backups enable point-in-time recovery. Reference credentials from a Kubernetes Secret.","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":"Scheduled backup and restore CRs","detail":"Create a Backup CR for a one-off backup and a ScheduledBackup CR for scheduled ones; take regular base backups with a cron expression. Restores are also declarative: you bootstrap a new Cluster from an existing object store backup with 'bootstrap.recovery'. Run restore drills regularly; taking backups is not enough, test the recovery.","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":"Schema migration CI/CD (Flyway/Liquibase)","icon":"🧬","symptom":"Goal: Manage database schema changes with versioned migration files; apply them automatically but in a controlled way within CI/CD using Flyway or Liquibase.","steps":[{"title":"Create versioned migration files (Vxxx__name.sql)","detail":"Write every schema change into a named file with an increasing version number; the Flyway convention is V<version>__<description>.sql (e.g. V1__initial_schemas.sql, V2__orders_table.sql). These files are kept in Git and applied in version order. Applied migrations are recorded in a tracking table (flyway_schema_history in Flyway), so each migration runs exactly once.","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":"Preserve immutability with checksums","detail":"Flyway/Liquibase store the checksum of every applied migration. If you later edit an already applied file, the checksum no longer matches and the tool fails. Rule: NEVER edit an applied migration; make the fix with a new version file. This guarantees that all environments (dev/staging/prod) share the same history.","engines":{"postgres":"# repair on checksum mismatch (use with care):\nflyway repair\n# the right way: add the change in a new Vx file"}},{"title":"Baseline an existing database","detail":"Use baseline when attaching the migration tool to an already populated database with no history; it treats the current state as the 'starting version' and does not try to recreate existing objects. Migrations after baselineVersion are applied. In an environment built from scratch no baseline is needed; migrations run from V1 onward.","engines":{"postgres":"flyway baseline -baselineVersion=1 -baselineDescription=\"existing schema\""}},{"title":"Migrate automatically in the CI pipeline","detail":"Run the migration tool in a CI step to apply changes automatically to the test and staging databases; the migrate command runs only new (unapplied) files and is idempotent. Applying the migration first to a temporary/ephemeral database and validating it catches broken SQL before it reaches prod. The schema must be migrated BEFORE the application code is deployed (with backward-compatible changes).","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":"Add a manual approval gate for prod","detail":"Test/staging are automatic, but make the prod migrate require manual approval; add an 'approval gate' (manual approval) step in CI/CD. Before approval, show which migrations will run with 'flyway info' or 'liquibase status'. Always take a fresh backup before applying to prod so you can roll back if something goes wrong.","engines":{"postgres":"# what will run before approval?\nflyway info\n# liquibase equivalent:\nliquibase status --verbose"}},{"title":"Define reversibility and a rollback strategy","detail":"Flyway Community has no automatic undo; adopt a forward-only migration principle: instead of undoing a mistake, write a new migration that fixes it. Liquibase changesets can define a rollback block and be rolled back with 'liquibase rollback'. The most robust strategy: keep changes small and backward compatible, take a backup before risky operations (dropping columns), and prepare a written rollback plan.","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":"Automated backups (pgBackRest/Barman/Xtrabackup/cron)","icon":"💾","symptom":"Goal: Automate database backups; build a reliable recovery chain with scheduled full+incremental+WAL/binlog archiving, retention, verification and an offsite copy.","steps":[{"title":"Choose the right backup tool","detail":"Choose a tool for your engine: pgBackRest or Barman for PostgreSQL (physical backup + WAL archive + PITR); Percona XtraBackup (online physical backup) for MySQL/MariaDB, or mysqldump/mydumper (logical) for small datasets. Physical tools give fast, consistent backups + point-in-time recovery on large databases; logical dumps are portable but slow on large data.","engines":{"postgres":"# pgBackRest or Barman","mysql":"# physical: xtrabackup\n# logical: mysqldump / mydumper"}},{"title":"Build the full + incremental + continuous archive chain","detail":"Set up point-in-time recovery with weekly/daily full backups, incremental/differential backups in between, and continuous WAL (PostgreSQL) or binlog (MySQL) archiving. pgBackRest manages full/diff/incremental backup types and the WAL archive; in PostgreSQL you point archive_command at pgBackRest. Incremental backups + archive let you return to any point in time.","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":"Schedule backups with cron/timer","detail":"Run backup commands regularly with cron or a systemd timer: for example a full backup Sunday night and incrementals on other nights. Schedule outside business hours and make sure full and incr jobs do not overlap. Write output logs somewhere and raise an alert on failure.","engines":{"postgres":"# crontab\n0 1 * * 0 pgbackrest --stanza=appdb --type=full backup\n0 1 * * 1-6 pgbackrest --stanza=appdb --type=incr backup"}},{"title":"Define a retention policy","detail":"Do not accumulate backups forever; set how many full backups and how many days of archives to keep, and let the tool expire old ones automatically. In pgBackRest this is managed with repo1-retention-full and the archive retention settings; in Barman with retention_policy. Retention must match your recovery objectives (how many days back you need to go).","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":"Copy backups offsite / to S3","detail":"Backups must not stay on the same machine/region as the database; copy them to another location or object storage (S3, GCS). pgBackRest can write directly to S3-compatible storage; otherwise sync to an offsite target with rsync/rclone. In a disaster (loss of local disk/region) only the offsite copy will save you.","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":"Verify the backup","detail":"A backup existing does not mean it can be restored; verify integrity regularly. In pgBackRest the 'verify' command and archive consistency checks, and in XtraBackup a successful prepare (--prepare) step, show the backup is sound. Run verification automatically after every backup and raise an alarm on failure.","engines":{"postgres":"pgbackrest --stanza=appdb verify","mysql":"# make the backup consistent (verification)\nxtrabackup --prepare --target-dir=/backup/full"}},{"title":"Run regular restore drills","detail":"The most critical step: periodically ACTUALLY restore the backup to a separate server and verify that the database opens and the data is consistent. This drill lets you measure your recovery time (RTO) and catch gaps in the procedure outside of an incident. An untested backup is not a backup.","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 & blue-green / zero-downtime deployment","icon":"🟦","symptom":"Goal: Deploy schema and application changes without downtime; approach zero downtime with expand-contract migrations, blue-green environments, feature flags and automatic rollback.","steps":[{"title":"GitOps: make Git the single source of truth","detail":"Define infrastructure, schema migrations and the application version in Git; deployment is triggered by a merge to the repository (GitOps). A reconciler (Argo CD/Flux or CI/CD) applies the desired state to the real environment. Every change is then auditable, repeatable and can be reverted to a previous commit when needed.","engines":{"postgres":"# repo/\n#   migrations/  (Vxxx__*.sql)\n#   k8s/         (Deployment, Service)\n#   flags/       (feature flag config)"}},{"title":"Change the schema backward-compatibly (expand-contract)","detail":"For zero downtime, do not change the schema in one breaking step; split it into two phases. EXPAND: add the new structure (new column/table) so that old and new code can run at the same time (nullable column, with default, dual writes). AFTER the application has moved to the new structure, CONTRACT: drop the no-longer-used old column/table in a separate migration. Old and new application versions then run side by side without problems during deployment.","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":"Put risky behavior behind a feature flag","detail":"Run the new code path (such as reading the new column) conditionally behind a feature flag; turn the flag on when the schema is ready and off without touching the deployment if something goes wrong. A flag separates code deployment (deploy) from feature activation (release). The expand phase ships, but the behavior change is enabled in a controlled way and can be reverted quickly.","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":"Set up the blue-green environment","detail":"Keep two identical environments: 'blue' (live) and 'green' (new version). Deploy the new application version to green and validate it before it receives real traffic; when ready, switch the router/traffic to green. Key point: the database is usually SHARED, so the schema must be compatible with both versions (expand-contract ensures this). Even though the connection target changes at cutover, schema compatibility means no downtime.","engines":{"postgres":"# traffic is switched to green (new version)\n# shared DB: schema must be compatible with both blue and green"}},{"title":"Validate the deployment with monitoring (canary/observation)","detail":"Before switching all traffic, monitor metrics such as error rate, latency, database error/lock counts and replication lag; optionally send a small percentage of traffic (canary) to green and observe. If health indicators are good, increase traffic gradually; if bad, do not proceed. Monitoring is the input to the automatic rollback decision.","engines":{"postgres":"-- live health queries\nSELECT count(*) FROM pg_stat_activity WHERE wait_event_type='Lock';\nSELECT * FROM pg_stat_replication;"}},{"title":"Prepare automatic rollback","detail":"If metrics exceed the threshold, the deployment must roll back automatically: in blue-green, quickly switch traffic back to blue (the old version). Application rollback is instant because blue is still running. On the schema side, rollback is usually NOT NEEDED: thanks to expand-contract the new schema is also compatible with the old code, so rolling back the application is enough. That is why schema changes should stay independent of application deployment and always be backward compatible.","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":"High availability & replication"},{"id":"backup","title":"Backup & recovery"},{"id":"ops","title":"Daily operations & incidents"},{"id":"pooling","title":"Connectivity & load balancing"},{"id":"patch","title":"Patches & minor updates"},{"id":"upgrade","title":"Major upgrades & migration"},{"id":"monitoring","title":"Monitoring & management"},{"id":"security","title":"Security & hardening"}],"guides":[{"id":"pg-install","file":"install/guides/pg-install.json","group":"PostgreSQL","engineId":"postgres","category":"install","title":"PostgreSQL single-server installation (production-ready)","summary":"Production-ready PostgreSQL installation on a single server from the PGDG repository: separate data directory, data checksums, settings calculated from RAM/CPU, secure access (peer + scram-sha-256), logging, pg_stat_stat","level":"Beginner","duration":"30–45 min","topology":"1 server","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":"PostgreSQL streaming replication (primary + standby)","summary":"Adds one or more hot standbys to an installed primary with pg_basebackup (replication user, pg_hba, replication slot, asynchronous or synchronous ANY/FIRST mode, lag monitoring) and produces the operations runbooks for t","level":"Intermediate","duration":"45–90 min","topology":"1 primary + 1..N standbys","tags":["postgresql","streaming replication","replication","standby","hot standby","pg_basebackup","replication slot","synchronous","synchronous replication","failover","switchover","pg_promote","pg_rewind","ha","cascading","delayed standby","recovery_min_apply_delay","slot cleanup","pg_wal full","synchronous_standby_names","rebuild standby"],"order":2,"steps":24,"scenarios":[{"k":"mode","v":"install","l":"Setup: add standbys to the primary","m":"Add / rebuild standby set up streaming replication add standby hot standby pg_basebackup read replica read-only replica physical replication physical standby standby.signal primary_conninfo One or more hot standbys are added to an installed primary with pg_basebackup (slot, synchronous/asynchronous, monitoring). The primary stays open; the initial copy puts disk and network load on the primary."},{"k":"mode","v":"switchover","l":"Planned switchover (role change, no data loss)","m":"Role change switchover planned switchover role change planned role swap primary maintenance make standby the primary pg_promote pg_ctl promote Planned maintenance: the primary is shut down cleanly, the first standby is promoted, the old primary comes back as a standby. No data loss; the write outage is usually under a minute."},{"k":"mode","v":"failover","l":"Manual failover (primary crashed, promote the standby)","m":"Role change could not connect to the primary server Connection refused primary down primary crashed primary unreachable failover manual failover promote standby pg_promote split-brain could not receive data from WAL stream primary server lost The primary has crashed or is unreachable: the old primary is isolated (fencing) and the most up-to-date standby is promoted by hand. In asynchronous mode th"},{"k":"mode","v":"rejoin","l":"Rejoin the old primary as a standby with pg_rewind","m":"Role change 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 rejoin old primary as standby rejoin old primary After a failover the old primary is resynchronized to the new primary with pg_rewind and rejoins as a standby; much faster than a full copy."},{"k":"mode","v":"cascade","l":"Add a new standby fed by a standby (cascading)","m":"Add / rebuild standby cascading replication cascade standby chained replication standby from standby remote data center DR standby The new standby receives WAL from the first standby instead of the primary: in a remote data center or a setup with many standbys this reduces the primary's network/WAL sender load. The primary is not affected."},{"k":"mode","v":"delayed","l":"Set up a delayed standby","m":"Add / rebuild standby recovery_min_apply_delay delayed standby time-delayed standby deleted by mistake undo DROP TABLE accidental delete pg_wal_replay_pause A standby that applies WAL hours later (`recovery_min_apply_delay`): to recover accidentally deleted data without restoring from backup. It is not a failover candidate."},{"k":"mode","v":"rebuild","l":"Rebuild a standby from scratch","m":"Add / rebuild 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 rebuild standby standby corrupt re-clone standby could not start WAL streaming wal_removed standby fell behind The standby has fallen behind, its WAL was removed or it is corrupt: the data directory is moved as"},{"k":"mode","v":"slotclean","l":"pg_wal growing, disk filling up (cleaning up unused slots)","m":"Maintenance and troubleshooting No space left on device could not write to file \"pg_wal pg_wal full pg_wal growing WAL bloat disk filled up replication slot inactive max_slot_wal_keep_size pg_wal directory large WAL disk full The cause of WAL accumulation is found (inactive slot, archive failure, wal_keep_size); the unused replication slot is dropped and WAL space is reclaimed. The primary stays o"},{"k":"mode","v":"syncchg","l":"Add / remove a synchronous standby (synchronous_standby_names)","m":"Maintenance and troubleshooting canceling wait for synchronous replication synchronous_standby_names COMMIT hanging commit hangs sync standby synchronous standby sync_state potential synchronous_commit Rewrites the synchronous list according to the mode in the form (ANY/FIRST, k) or reverts to asynchronous; applied with a reload, no restart needed. A wrong setting can leave commits hanging; it is "}]},{"id":"pg-patroni","file":"install/guides/pg-patroni.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"Automatic failover cluster with Patroni + etcd + HAProxy","summary":"Builds and operates a PostgreSQL cluster with automatic failover: Patroni manages PostgreSQL on every node, leader election runs in a 3- (or 5-) member etcd, and applications connect to the HAProxy write (5000) and read ","level":"Advanced","duration":"2–3 hours","topology":"3 PostgreSQL + etcd nodes (shared or separate) + HAProxy (+ VIP)","tags":["postgresql","patroni","etcd","haproxy","keepalived","vip","automatic failover","auto failover","switchover","ha","high availability","cluster","postgres cluster","patronictl","dcs","reinit","pg_rewind","edit-config","pause","resume","etcdctl member","quorum","failsafe","add node","remove node","patroni upgrade","cluster shutdown"],"order":3,"steps":40,"scenarios":[{"k":"mode","v":"install","l":"Installation: new Patroni cluster","m":"Installation patroni installation patroni install etcd installation haproxy automatic failover automatic failover high availability high availability HA cluster Builds an etcd + Patroni + HAProxy (+ VIP) cluster from scratch. The servers must not have a PostgreSQL cluster: Patroni runs initdb (pg-install is not applied)."},{"k":"mode","v":"ops","l":"Operations runbook: switchover, failover test, maintenance mode, parameter change","m":"Check and drill failover test failover test drill DR drill runbook Runs through the basic operations on a newly built cluster in order: status, switchover, failover test (the leader is stopped, short write outage), maintenance mode, parameter change. Run it in a maintenance window."},{"k":"mode","v":"health","l":"Is the cluster healthy? (health check)","m":"Check and drill patronictl list Pending restart Maintenance mode: on start failed patroni health cluster status replica lag lag timeline mismatch Read-only: roles, lag, timeline, REST API, etcd health, slots, pending restart and maintenance mode."},{"k":"mode","v":"switchover","l":"Planned switchover (move the leader to another node)","m":"Role change candidate name does not match with sync_standby Switchover failed does not exist in cluster or is tagged as nofailover switchover change leader role change planned maintenance Before maintenance the leader role moves to a replica in a controlled way; a few seconds of write outage, no data loss."},{"k":"mode","v":"rejoin","l":"Failover happened, the old leader does not rejoin (pg_rewind / reinit)","m":"Role change 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 old leader after failover pg_rewind: fatal could not find common ancestor timeline history mismatch When the crashed old leader comes back it should join as a replica; if pg_rewind fails, t"},{"k":"mode","v":"reinit","l":"Rebuild a replica from scratch (reinit)","m":"Member maintenance requested WAL segment has already been removed could not receive data from WAL stream invalid record length invalid page patronictl reinit corrupt replica replica behind rebuild replica replica start failed creating replica The replica is corrupt, far behind or has lost WAL: its data directory is wiped and rebuilt from the leader (or from a backup). The leader is not affected."},{"k":"mode","v":"addmember","l":"Add a new node to the cluster","m":"Member maintenance add node add member new replica scale out expand cluster Add the new node at the end of the list in the form: the software is installed, pg_hba and HAProxy are updated on the existing nodes, and the node joins as a replica."},{"k":"mode","v":"rmmember","l":"Remove a node from the cluster","m":"Member maintenance remove node remove member decommission decommission server remove replica A node that is a replica is removed permanently: Patroni is stopped, the DCS entry and the slot are cleaned up, and it is removed from HAProxy."},{"k":"mode","v":"editcfg","l":"Change a PostgreSQL or Patroni setting (edit-config)","m":"Settings and maintenance edit-config Pending restart postgresql.conf setting lost ALTER SYSTEM change parameter max_connections shared_buffers change parameter work_mem parameter lost setting not applied The setting is written to the DCS and applied to all nodes; for settings that require a restart, `restart --pending` (replicas first)."},{"k":"mode","v":"pause","l":"Stop automatic failover for maintenance (pause / resume)","m":"Settings and maintenance Maintenance mode: on patronictl pause patronictl resume maintenance mode maintenance mode disable automatic failover While paused, Patroni does not touch PostgreSQL and does not fail over; for network/etcd maintenance or manual intervention. Resume when done."},{"k":"mode","v":"upgrade","l":"Upgrade the Patroni software (rolling)","m":"Settings and maintenance patroni upgrade patroni update patroni version dnf update patroni apt upgrade patroni patroni 3 to 4 The cluster is paused, Patroni is updated and restarted node by node; PostgreSQL keeps running, no failover happens."},{"k":"mode","v":"stopstart","l":"Stop / start the whole cluster (correct order)","m":"Settings and maintenance shut down cluster start cluster shutdown order full shutdown planned shutdown data center maintenance power outage Planned full shutdown (storage, data center maintenance): replicas first, then the leader, etcd last; startup in reverse order."},{"k":"mode","v":"etcdmember","l":"Replace or add an etcd member (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 etcd member unhealthy A failed etcd member is removed and a new server is added (or the member count is increased); Patroni's etcd host list is updated."},{"k":"mode","v":"dcsloss","l":"etcd quorum lost: the leader became read-only","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 leader missing no leader Without etcd quorum Patroni makes the leader read-only, and no failover happens either. etcd is brought back or rebuilt from a single member."}]},{"id":"pg-pgbouncer","file":"install/guides/pg-pgbouncer.json","group":"PostgreSQL","engineId":"postgres","category":"pooling","title":"PgBouncer connection pooling","summary":"PgBouncer installation from the PGDG repository: transaction or session pool mode, scram-sha-256 authentication through auth_query (a SECURITY DEFINER function) or userlist.txt, pool sizes calculated from the database's ","level":"Intermediate","duration":"30–45 min","topology":"PgBouncer on the database server or on a separate server; with Patroni, in front of HAProxy/VIP","tags":["postgresql","pgbouncer","connection pool","connection pooler","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":"Backup and PITR with pgBackRest","summary":"Full / differential / incremental backups with pgBackRest, continuous WAL archiving and point-in-time recovery (PITR). For a single server, streaming replication (primary + standbys) or a Patroni cluster; the repository ","level":"Intermediate","duration":"45–90 min","topology":"1 server, primary + standbys or a Patroni cluster; optional backup server","tags":["postgresql","pgbackrest","backup","backup","restore","pitr","wal","archive","s3","minio","encryption","retention","recovery","patroni","streaming","standby","cron","backup-standby","systemd timer","delta restore","db-include","target-action","restore point","xid","lsn","clone","cloning","disaster","disaster recovery","verify","expire","type=standby","drill"],"order":10,"steps":28,"scenarios":[{"k":"purpose","v":"kur","l":"pgBackRest installation + first backup + test restore","m":"Installation stanza-create archive_command archive-push pgbackrest setup pgbackrest install set up backups backup setup backup schedule full diff incr scheduled backup ERROR: [087] archive_mode must be enabled set up WAL archiving first backup pgBackRest is not in place yet: software, repository, WAL archiving, first backup, scheduling and a test restore."},{"k":"purpose","v":"geri","l":"Disaster: in-place restore (to the latest state or to a point in time)","m":"Restore (same server) recovery_target_time recovery_target_xid recovery_target_lsn pg_create_restore_point PITR point in time recovery restore to a point in time accidentally deleted DROP TABLE DELETE without WHERE UPDATE without WHERE TRUNCATE restore point restore to time recover lost data Return the database, in place, to a moment covered by the backup and the archive: the latest point, a time,"},{"k":"purpose","v":"delta","l":"Corrupt or deleted files in the data directory (repair with delta restore)","m":"Restore (same server) 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 corrupt block data corruption block corruption deleted data file ERROR:  could not read block PANIC:  could not open file PANIC:  could not read fil"},{"k":"purpose","v":"dbinc","l":"Restore a single database only (--db-include)","m":"Restore (same server) db-include DROP DATABASE single database single database restore database dropped recover a table table restore single table restore The cluster stays up: the single database is restored to a temporary instance (separate directory and port), dumped with pg_dump and loaded into the live server under a new name. Use this card for a single dropped table too (with `pg_dump -t` fr"},{"k":"purpose","v":"clone","l":"Restore the backup to another server (clone / test / reporting)","m":"New server: clone, disaster, standby repo1-cipher-pass clone cloning test environment refresh the test environment refresh test staging restore to a new server reporting server encrypted repository The live server is not touched. The backup is opened on a new server; WAL archiving is disabled so the clone does not write to the live repository. An encrypted repository requires the key."},{"k":"purpose","v":"dr","l":"The server is lost completely: open the backup on a new server and replace it","m":"New server: clone, disaster, standby PANIC: could not locate a valid checkpoint record disaster disaster recovery DR server loss disk failure server lost data directory lost new primary encrypted repository could not open file \"global/pg_control\" server will not start PostgreSQL does not start Disk or server loss: the new server is opened from the backup and becomes the new primary; WAL archiving "},{"k":"purpose","v":"standby","l":"Build a new standby from the backup (--type=standby)","m":"New server: clone, disaster, standby type=standby build a standby add a replica standby from backup standby rebuild instead of pg_basebackup add standby A new standby without putting copy load on the primary: the files come from the backup, and the missing WAL is filled in from the archive and via streaming. The primary stays up."},{"k":"purpose","v":"verify","l":"Are the backups really sound? (info / check / verify and a restore drill)","m":"Checks and maintenance ERROR: [082] ERROR: [028] ERROR: [056] pgbackrest verify pgbackrest check pgbackrest info backup verification backup verification restore test drill when was the last backup verify command is the backup valid restore drill monthly verification Read-only checks and a restore drill into a separate directory that does not touch the live server. A monthly verification job (cron)"},{"k":"purpose","v":"retention","l":"Change the retention period and remove old backups (expire)","m":"Checks and maintenance No space left on device WARN: option 'repo1-retention-full' is not set repo1-retention-full expire retention retention repository full backup disk full delete old backups repo full bucket full backup disk filled up Changes the repo1-retention-full/diff settings, first shows with --dry-run what would be removed, then runs expire. A removed backup cannot be brought back."}]},{"id":"pg-pitr-basebackup","file":"install/guides/pg-pitr-basebackup.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"Backup and PITR with pg_basebackup + WAL archive (without pgBackRest)","summary":"Physical backup with PostgreSQL's own tools, no extra software: continuous WAL archiving with archive_command, a nightly base backup with pg_basebackup, retention with pg_archivecleanup. Recovery to a point in time / tra","level":"Intermediate","duration":"45–90 min","topology":"1 server; with streaming or Patroni the archive directory is shared by all nodes (NFS)","tags":["postgresql","pg_basebackup","archive_command","restore_command","pitr","point in time recovery","recovery.signal","recovery_target_time","pg_archivecleanup","wal archive","timeline","base backup","backup","restore","pg_verifybackup","cron","drill"],"order":12,"steps":14,"scenarios":[{"k":"op","v":"kur","l":"Set up WAL archiving and a nightly base backup","m":"Setup archive_mode archive_command archive_timeout pg_stat_archiver wal_level WAL archive WAL archiving continuous archiving continuous archiving setup pg_basebackup cron base backup nightly backup schedule nightly backup set up wal archiving Initial setup: continuous WAL archiving with archive_command, a pg_basebackup script, scheduling in /etc/cron.d, retention and WAL cleanup. archive_mode need"},{"k":"op","v":"pitr","l":"Recover to a specific point (PITR: time, xid, LSN, restore point)","m":"Restore 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 deleted by mistake accidental delete restore to a point in time s"},{"k":"op","v":"drill","l":"Restore drill (in a separate directory, without touching production)","m":"Checks and maintenance backup successfully verified pg_verifybackup pg_amcheck restore test restore drill drill backup verification backup verify RTO does the backup restore To prove that the backup actually restores (monthly recommended): the latest base backup is verified (pg_verifybackup), extracted into a separate directory and started on a separate port with the archive; the time (RTO) is mea"},{"k":"op","v":"cleanup","l":"Clean up old base backups and the WAL archive","m":"Checks and maintenance No space left on device could not write to file archive command failed pg_archivecleanup archive disk full archive full disk full delete old backups backup retention retention pg_wal growing archive_status .ready The archive or backup disk is filling up, or retention has changed: old base backups are deleted and pg_archivecleanup removes WAL older than the oldest backup (dry"}]},{"id":"pg-logical-repl","file":"install/guides/pg-logical-repl.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"PostgreSQL logical replication (publication / subscription)","summary":"Table-level logical replication between two PostgreSQL clusters: wal_level=logical, replica identity checks, publication and subscription, initial sync, monitoring, conflict handling, and manual sequence synchronization.","level":"Advanced","duration":"45–90 min (+ initial copy time)","topology":"2 servers: publisher + subscriber; the publisher can be a single server, a streaming replication setup or a Patroni cluster","tags":["postgresql","logical replication","logical decoding","publication","subscription","cdc","data migration","migration","upgrade","conflict","replica identity","sequence","patroni","failover slot","permanent slots","sync_replication_slots"],"order":15,"steps":25,"scenarios":[{"k":"purpose","v":"sync","l":"Continuous replication (reporting, integration, partial copy)","m":"logical replication publication subscription CREATE SUBSCRIPTION table replication partial replication Selected tables flow continuously to another cluster (reporting server, data warehouse feed, integration between services). The subscriber stays writable."},{"k":"purpose","v":"migrate","l":"Migration: new server / new major version + cutover","m":"zero downtime upgrade near zero downtime cutover server migration logical upgrade Move to a new server or a new major version with seconds of downtime: initial copy and continuous sync, then a short write freeze and the cutover."}]},{"id":"pg-dump-restore","file":"install/guides/pg-dump-restore.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"PostgreSQL logical backup and restore (pg_dump / pg_restore)","summary":"Logical backup and restore with pg_dump, pg_dumpall and pg_restore at table, schema, database or whole-cluster level; structure only (DDL) or data only, moving to another server/version, restoring a single table from a d","level":"Intermediate","duration":"20–90 min (depending on data size)","topology":"1 server, streaming replication (primary + standby) or a Patroni cluster; source + target for a migration, plus a backup server for remote scheduled backups","tags":["postgresql","postgres","pg_dump","pg_restore","pg_dumpall","logical dump","logical backup","export","import","dump","restore","restore dump","database migration","migration","upgrade","globals","systemd timer","cron","pgpass","backup","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 single table(s)","m":"Export / import pg_dump -t export table import table table export single table backup no matching tables were found The structure and data of the selected tables (including indexes, constraints, triggers) are dumped and loaded into another database."},{"k":"mode","v":"schema","l":"Export / import a schema","m":"Export / import pg_dump -n export schema schema export no matching schemas were found All objects in the schema (tables, views, functions, sequences) are dumped."},{"k":"mode","v":"db","l":"Back up / restore a single database","m":"Export / import pg_dump pg_restore database dump database backup database restore errors ignored on restore A whole database (structure + data) is dumped to a single dump and restored to the same or another database. Roles (globals) are not included."},{"k":"mode","v":"cluster","l":"Whole cluster (roles + tablespaces + every database)","m":"Export / import pg_dumpall pg_dumpall -g globals full cluster backup roles backup Globals with pg_dumpall -g, a separate pg_dump for each database; the restore is rehearsed on a temporary test cluster."},{"k":"mode","v":"ddl","l":"Schema structure only (DDL)","m":"Structure and data separately pg_dump -s --schema-only DDL export schema only Table, index, view and function definitions without data; for review, version control or building an empty environment."},{"k":"mode","v":"data","l":"Data only","m":"Structure and data separately pg_dump -a --data-only data only --disable-triggers circular foreign-key constraints Only the rows (COPY) are transferred while the structure is already in place on the target."},{"k":"mode","v":"migrate","l":"Move to another server / version","m":"Migration and recovery server migration migration dump restore upgrade aborting because of server version mismatch migrate to new version On the target server, the pg_dump of the new version connects to the source over the network; the data is transferred through a pipe or a file."},{"k":"mode","v":"partial","l":"Restore a single table / object from a dump","m":"Migration and recovery restore single table pg_restore -L pg_restore --list DROP TABLE dropped table deleted rows accidentally deleted table restore From an existing custom/directory dump, only one table with its indexes, constraints and data is restored using a list file (-L)."},{"k":"mode","v":"sched","l":"Scheduled backup (systemd timer / cron)","m":"Scheduled backup pg_dump cron systemd timer automated backup scheduled backup daily backup backup retention Daily globals + database backup, readability check, retention, logging and failure notification."}]},{"id":"pg-minor-update","file":"install/guides/pg-minor-update.json","group":"PostgreSQL","engineId":"postgres","category":"patch","title":"PostgreSQL minor version update (patch)","summary":"Minor update within the same major version (e.g. 17.4 → 17.x): release notes review, backup, and keeping the current RPM/deb packages for rollback. On a single server with a short outage; in a streaming replication clust","level":"Intermediate","duration":"20–60 min","topology":"1 server, primary + standbys, or a Patroni cluster","tags":["postgresql","minor","patch","minor update","update","upgrade","rolling","switchover","dnf","apt","downgrade","rollback","extension","patroni","patronictl","failover"],"order":20,"steps":17,"scenarios":[{"k":"purpose","v":"upd","l":"Minor update","m":"minor update minor version update patch CVE security update dnf upgrade postgresql apt upgrade postgresql Move to a newer minor version within the same major version (e.g. 17.4 → 17.6): security and bug fixes. Short outage on a single server, rolling in a cluster."},{"k":"purpose","v":"rb","l":"Rollback: return to the previous minor version (downgrade)","m":"downgrade rollback rollback revert to previous version undefined symbol A bug or regression appeared in the new minor version: roll back with the saved RPM/deb packages of the previous version. The data directory does not change."}]},{"id":"pg-major-upgrade","file":"install/guides/pg-major-upgrade.json","group":"PostgreSQL","engineId":"postgres","category":"upgrade","title":"PostgreSQL major upgrade (pg_upgrade / logical replication)","summary":"Major version upgrade (e.g. 16 → 17): choice of method (pg_upgrade --link / --copy / --clone, or a near-zero-downtime migration with logical replication), pg_upgrade --check, extension compatibility, carrying over the co","level":"Advanced","duration":"1–3 hours (+ depending on data size)","topology":"In place on the same server (pg_upgrade; single server, primary + standbys or Patroni cluster) or to a new server (logical)","tags":["postgresql","major","upgrade","version upgrade","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":"Major upgrade","m":"major upgrade pg_upgrade pg_upgradecluster version upgrade major version upgrade postgresql 17 upgrade postgresql 18 Major version upgrade (e.g. 16 → 17): pre-checks, backup, pg_upgrade or logical replication, verification and cleanup."},{"k":"purpose","v":"rb","l":"Rollback (upgrade failed / abandoned)","m":"roll back upgrade rollback pg_control.old database files are incompatible with server upgrade failed The upgrade stopped halfway or the new version has problems: return to the old version. With --link, once the new cluster has been started, the only way back is the backup."}]},{"id":"pg-security","file":"install/guides/pg-security.json","group":"PostgreSQL","engineId":"postgres","category":"security","title":"PostgreSQL security hardening (status audit, network, TLS, authentication, privileges, audit, encryption)","summary":"This is not an installation guide: it finds and closes the security gaps of a running PostgreSQL server. First the read-only Status audit lists the findings by severity and tells you which card each one leads to; then yo","level":"Intermediate","duration":"5 min (audit) – 3 hours (full hardening)","topology":"Single PostgreSQL server + application server (client verification); replica/Patroni environments covered in the notes","tags":["postgresql","postgres","security","security","hardening","hardening","cis benchmark","pg_hba","tls","ssl","sslmode","verify-full","scram-sha-256","md5","pgaudit","audit","auditing","public schema","privileges","role","luks","pgcrypto","encryption","encryption","firewall"],"order":40,"steps":3,"scenarios":[{"k":"scenario","v":"scan","l":"Status audit: what are my gaps? (read-only)","m":"Start here security audit security audit hardening check CIS security gap vulnerability vulnerability pentest finding audit report audit query CIS check security scan security scan The first scan to run: pg_hba, listen address, TLS, md5 passwords, superusers, PUBLIC privileges, audit, file permissions and firewall; it changes nothing."},{"k":"scenario","v":"all","l":"Full hardening (all areas in order)","m":"Start here hardening hardening full security full hardening apply CIS benchmark STIG Newly installed or never hardened server: all areas are applied in a safe order; PostgreSQL is restarted once or twice (a maintenance window is required)."},{"k":"scenario","v":"net","l":"Network and connections: who can connect from where? (listen, firewall, pg_hba)","m":"Network and identity pg_hba.conf 0.0.0.0/0 trust listen_addresses firewall no pg_hba.conf entry pg_hba.conf rejects connection port 5432 open FATAL: no pg_hba.conf entry for host 28000 Connection refused listen_addresses = * host all all 0.0.0.0/0 external access restrict access PostgreSQL listens on all interfaces, pg_hba has 0.0.0.0/0 or trust, the port is open to everyone. Changing listen_addre"},{"k":"scenario","v":"tls","l":"Encrypt connections (TLS, sslmode=verify-full)","m":"Network and identity ssl tls sslmode verify-full hostssl certificate verify failed SSL off no encryption server.crt certificate expired 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 certificate chain certificate chain hostnossl ssl = off or clients do n"},{"k":"scenario","v":"auth","l":"Authentication and passwords (scram-sha-256, remote login for postgres)","m":"Network and identity md5 scram-sha-256 password_encryption password authentication failed authentication method 10 not supported postgres remote login password policy password policy 28P01 FATAL: password authentication failed for user SCRAM authentication requires libpq version 10 or above pass-the-hash VALID UNTIL password expiry md5/password method, md5-hashed passwords, the postgres user can l"},{"k":"scenario","v":"priv","l":"Least privilege: roles, PUBLIC and the application account","m":"Privileges and audit trail public schema PUBLIC GRANT REVOKE superuser least privilege least privilege permission denied for schema role design DEFAULT PRIVILEGES 42501 permission denied for table must be owner of table pg_read_server_files pg_execute_server_program SECURITY DEFINER REASSIGN OWNED privilege report The application account owns the tables or is a superuser, everyone can write to the"},{"k":"scenario","v":"audit","l":"Audit: who did what? (pgaudit, central logging)","m":"Privileges and audit trail pgaudit audit audit trail audit log AUDIT: SESSION log_connections syslog SIEM GDPR PCI shared_preload_libraries could not access file \"pgaudit\" pgaudit must be loaded via shared_preload_libraries log_statement who did what who changed The auditor or the security team wants a record of DDL and role changes; pgaudit installation and shipping the log to a central server. O"},{"k":"scenario","v":"tde","l":"Encrypt data at rest (LUKS / pgcrypto)","m":"Data and backups TDE transparent data encryption disk encryption encryption at rest LUKS dm-crypt pgcrypto pg_tde cryptsetup luksFormat pgp_sym_encrypt at-rest encryption Protection against a stolen disk or backup is required. PostgreSQL has no built-in TDE: LUKS at the disk level (downtime for the move) or pgcrypto at the column level."},{"k":"scenario","v":"backup","l":"Backup security (encrypted backups, permissions)","m":"Data and backups backup encryption backup encryption repo-cipher-type pgbackrest cipher gpg .pgpass backup permissions cipher: none repo-cipher-pass unencrypted backup unencrypted backup Backup files are unencrypted or in a world-readable directory; pgBackRest repository encryption or pg_dump + GPG, .pgpass and directory permissions. No downtime."},{"k":"scenario","v":"os","l":"Operating system side (service account, permissions, core dumps, SELinux)","m":"Server and version SELinux permissive chmod 700 data directory has invalid permissions core dump postgres account sudo avc: denied LimitCORE core dumped passwd -l The postgres account has a usable password, data directory permissions are loose, SELinux is Permissive/Disabled. The core dump setting takes effect at the next restart."},{"k":"scenario","v":"patch","l":"Patches and version: am I on a supported version?","m":"Server and version EOL end of life support ended minor version CVE security patch security release out of support server_version patch schedule minor update pgsql-announce Checks the version, end-of-support date and pending minor updates; links to the patch guide for applying them. Read-only."}]},{"id":"mysql-install","file":"install/guides/mysql-install.json","group":"MySQL","engineId":"mysql","category":"install","title":"MySQL single-server installation (production-ready)","summary":"Installs a single MySQL 8.4 LTS (or 8.0) server from the official repository from scratch, ready for production: separate directories for data, binlog and logs, a my.cnf calculated from RAM/CPU/disk type, GTID and binlog","level":"Beginner","duration":"30–45 min","topology":"1 server","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":"MySQL GTID replication (async / semi-sync)","summary":"Builds GTID-based replication with 1 source + N replicas from installed MySQL servers: replication user, seeding the existing data with the clone plugin or mysqldump, SOURCE_AUTO_POSITION, TLS, optional semi-sync, super_","level":"Intermediate","duration":"45–90 min","topology":"1 source + 1..5 replicas","tags":["mysql","mysql replication","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":"Builds a single-primary InnoDB Cluster from 3 (or 5–9) installed MySQL servers in the same data center: dba.configureInstance, createCluster and addInstance with clone through the MySQL Shell AdminAPI; MySQL Router boots","level":"Advanced","duration":"90–120 min","topology":"3–9 MySQL nodes + 1 Router server","tags":["mysql","innodb cluster","group replication","mysql shell","mysqlsh","adminapi","mysql router","ha","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":"Backup, restore and PITR with Percona XtraBackup","summary":"Physical backups with Percona XtraBackup: installing the matching XtraBackup version, a backup user with auth_socket, a compressed/encrypted full + incremental backup script, scheduling with a systemd timer or cron, binl","level":"Intermediate","duration":"45–90 min","topology":"1 server (standalone, source, replica or InnoDB Cluster secondary); backup storage on a local disk or NFS","tags":["mysql","xtrabackup","percona","backup","backup","restore","incremental","pitr","binlog","recovery","zstd","encryption","replica","slave-info","safe-slave-backup","innodb cluster","cron","systemd timer","flock"],"order":4,"steps":26,"scenarios":[{"k":"scenario","v":"setup","l":"Set up backups, schedule them and test a restore (full flow)","m":"Setup and drill xtrabackup installation backup script backup script systemd timer cron restore drill xtrabackup backup physical backup physical backup hot backup incremental backup full backup percona-xtrabackup XtraBackup installation, backup user, full + incremental backup script, scheduling and a restore drill (including PITR). Choose this if you are setting it up for the first time. The backup"},{"k":"scenario","v":"r_full","l":"Full restore: return to the latest full backup","m":"Restore restore restore copy-back datadir lost InnoDB: corruption MY-011971 MY-012592 Database page corruption mysqld does not start full restore ERROR 2002 Can't connect to local MySQL server through socket MY-012574 Unable to lock ./ibdata1 datadir deleted disk failure disk failure data loss The data directory is lost/corrupted or the server does not start; returning to the chain's full backup i"},{"k":"scenario","v":"r_inc","l":"Restore from a full + incremental chain (up to the latest incremental)","m":"Restore incremental restore apply-log-only incremental-dir to_lsn from_lsn This incremental backup seems not to be proper for the target incremental chain incremental chain xtrabackup_checkpoints applying incremental You want to return to the time of the latest incremental backup (or an incremental of your choice); the incrementals are applied on top of the full backup in order with `--apply-log-o"},{"k":"scenario","v":"r_pitr","l":"Recover to a point in time (PITR): before a wrong DELETE / DROP","m":"Point in time PITR point in time deleted by mistake DROP TABLE wrong DELETE TRUNCATE mysqlbinlog stop-datetime stop-position exclude-gtids binlog replay wrong UPDATE DELETE without WHERE accidental delete accidental drop DROP DATABASE undo undo delete recovery with binlog Data deleted or corrupted by mistake: the latest backup is restored and the binlogs are applied up to just before the faulty tr"},{"k":"scenario","v":"r_table","l":"Restore a single table (--export + IMPORT TABLESPACE)","m":"Point in time single 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 restore one table table restore ibd file You only need the backed-up version of one table; without restoring the whole server, the table is imported from th"},{"k":"scenario","v":"r_replica","l":"Build a new replica from a backup","m":"Migration and cloning new replica set up replica xtrabackup_binlog_info xtrabackup_slave_info gtid_purged slave-info replication from backup 1236 build replica from backup seed replica Could not find first log file name in binary log index file purged required binary logs 1236 rebuild replica You want to prepare a new (or rebuilt) replica from an existing XtraBackup backup instead of cloning over "},{"k":"scenario","v":"r_other","l":"Restore the backup to another server (migration, test, clone)","m":"Migration and cloning another server test server restore test migration migration clone clone to test rsync server_uuid load into test environment backup verification restore drill open backup server migration You want to open a production backup on a test/development server, move a server, or verify on a separate server that the backup really restores. The source server is not affected."}]},{"id":"mysql-upgrade","file":"install/guides/mysql-upgrade.json","group":"MySQL","engineId":"mysql","category":"upgrade","title":"MySQL 8.0 → 8.4 LTS upgrade (in-place)","summary":"Upgrades an installed MySQL 8.0 to 8.4 LTS on the same server (in-place): single server, source + replicas (replicas first, source last) or InnoDB Cluster (Router → Shell → metadata → secondaries one by one → primary las","level":"Intermediate","duration":"60–120 min (depending on data size)","topology":"1 server, source + replicas, or InnoDB Cluster (3+ members + MySQL Router servers)","tags":["mysql","upgrade","8.0","8.4","lts","upgrade","checkForServerUpgrade","mysql_native_password","authentication_policy","rollback","fallback","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":"MySQL replication day-to-day operations and failure scenarios (8.0 / 8.4)","summary":"Day-to-day operations and failure recovery runbook for an existing source + replica topology (async / semi-sync, GTID or binlog position): health and lag checks, planned switchover, failover, rejoining the old source, re","level":"Advanced","duration":"5 min (check) – 2 hours (rebuild)","topology":"1 source + 1..5 replicas (async / semi-sync)","tags":["mysql","replication","replication","gtid","errant gtid","errant transaction","switchover","failover","replica","source","super_read_only","semi-sync","show replica status","seconds_behind_source","replication delay","lag","1062","1032","1236","duplicate entry","can't find record","gtid_next","sql_replica_skip_counter","source_delay","delayed replica","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":"Status and lag check (is replication healthy?)","m":"Monitoring and checks SHOW REPLICA STATUS SHOW SLAVE STATUS Seconds_Behind_Source Seconds_Behind_Master Replica_IO_Running Slave_IO_Running Replica_SQL_Running: No replication delay replication lag replica lag Rpl_semi_sync_source_status 1045 2003 2026 Error connecting to source replication status Replica_IO_Running: No Replica_IO_Running: Connecting Slave_IO_Running: No Slave_SQL_Running: No Seco"},{"k":"scenario","v":"switchover","l":"Planned switchover (hand the source role over to a replica)","m":"Role change switchover planned switchover role change role change promote replica change source maintenance planned maintenance graceful takeover graceful-master-takeover replace source change primary change primary new source The source and replicas are healthy; you are handing the source role over to a replica in a planned way for maintenance, hardware replacement or an upgrade. Writes stop for "},{"k":"scenario","v":"failover","l":"Source lost: fail over to the most advanced replica","m":"Role change failover source crashed source down master down primary down Error connecting to source Can't connect to MySQL server 2003 2013 Lost connection most advanced replica most advanced replica Replica_IO_Running: Connecting Slave_IO_Running: Connecting 2005 Unknown MySQL server host error reconnecting to source source unreachable source shut down primary crashed server crashed server crashe"},{"k":"scenario","v":"rejoin","l":"Rejoin the old source as a replica after failover","m":"Role change old source old source old master rejoin rejoin errant split brain 1236 old primary old primary old master came back split-brain Replica has more GTIDs than the source has Slave has more GTIDs than the master has rejoining reintegrate A failover has been done and the old source server is back. It is first closed to writes and its binlogs are copied, then checked for errant transactions;"},{"k":"scenario","v":"rebuild","l":"Rebuild a replica from scratch (clone / XtraBackup / mysqldump)","m":"Recovery 1236 Cannot replicate because the source purged required binary logs purged required binary logs rebuild rebuild replica reseed clone instance new replica data drift 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 the slave requires Go"},{"k":"scenario","v":"errant","l":"Errant GTID: the replica has transactions the source does not have","m":"Recovery errant gtid errant transaction gtid_subtract extra gtid extra gtid Having extra GTID events incompatible GTID set 1236 GTID_SUBTRACT gtid_executed difference written to replica write on replica super_read_only off Replica has more GTIDs than the source has There are transactions written directly to the replica (while super_read_only was off) or left over from an old source; on failover th"},{"k":"scenario","v":"sqlerr","l":"Replication stopped: 1062 Duplicate entry / 1032 Can't find record","m":"Recovery 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-01058"},{"k":"scenario","v":"delayed","l":"Delayed replica (SOURCE_DELAY): set it up or recover deleted data","m":"Day-to-day operations SOURCE_DELAY MASTER_DELAY delayed replica delayed replica deleted by mistake DROP TABLE wrong DELETE SQL_Delay SQL_BEFORE_GTIDS SQL_Remaining_Delay accidental DELETE DELETE without WHERE accidental delete accidental drop dropped table table dropped data recovery UNTIL SQL_BEFORE_GTIDS You want to keep a replica deliberately N seconds behind (set / remove the delay), or to rec"},{"k":"scenario","v":"repoint","l":"Move a replica to a different source (chaining / repointing)","m":"Day-to-day operations repoint change source chained replication chained replication intermediate intermediate replica relay replica CHANGE MASTER TO CHANGE REPLICATION SOURCE TO cascading replication cascading replication connect replica to another source change SOURCE_HOST change MASTER_HOST log_replica_updates You want to attach a replica under another replica (chain / intermediate replica) or b"},{"k":"scenario","v":"binlog","l":"Binlog cleanup and retention (disk filling up)","m":"Day-to-day operations PURGE BINARY LOGS PURGE MASTER LOGS binlog_expire_logs_seconds expire_logs_days binlog disk full No space left on device errno 28 binlog cleanup 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 growing binlog_expire_logs_auto_purge binlog_transaction_compression Binlogs are filling"}]},{"id":"mysql-ic-ops","file":"install/guides/mysql-ic-ops.json","group":"MySQL","engineId":"mysql","category":"ops","title":"MySQL InnoDB Cluster day-to-day operations and failure scenarios (MySQL Shell 8.4)","summary":"Not an installation: a scenario-based runbook for the day-to-day operation and failure recovery of an InnoDB Cluster that is already running (Group Replication + MySQL Router, deployed with the MySQL Shell AdminAPI). Pic","level":"Advanced","duration":"5 min (check) – 1 hour (adding a member)","topology":"3–9 MySQL nodes (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":"Cluster status and health check (is everything OK?)","m":"Monitoring and checks cluster.status status() OK_PARTIAL OK_NO_TOLERANCE OK_NO_TOLERANCE_PARTIAL instanceErrors replicationLag flow control RECOVERING applier queue cluster status Cluster is ONLINE and can tolerate can tolerate up to ONE failure health check health check queue growing replication_group_members group_replication_member_stats cluster state Read-only: `cluster.status({extended: 1})`,"},{"k":"scenario","v":"add","l":"Add a new member (addInstance: clone / incremental)","m":"Members addInstance add member add node 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 increase capacity increase fault tolerance OK_NO_TOLERANCE You want to add a new MySQL node to the cluster, increase fault tolerance (3 → 5) or bring back a node that was removed. The clust"},{"k":"scenario","v":"remove","l":"Remove a member (removeInstance, force if needed)","m":"Members removeInstance remove member remove node remove instance force MISSING decommission Cluster.removeInstance is not reachable and cannot be safely removed delete node scale down server decommission You want to remove a node from the cluster permanently (hardware return, downsizing), or the node is lost and has to be deleted from the metadata (`force`). Removing a secondary causes no downtime"},{"k":"scenario","v":"rejoin","l":"Rejoin a failed member (MISSING / ERROR / OFFLINE)","m":"Failure and recovery rejoinInstance MISSING ERROR OFFLINE UNREACHABLE expelled expelled from the group 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 member down member O"},{"k":"scenario","v":"reboot","l":"Whole cluster went down: restart it (rebootClusterFromCompleteOutage)","m":"Failure and recovery rebootClusterFromCompleteOutage complete outage full outage all nodes down power outage 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, but GR is "},{"k":"scenario","v":"quorum","l":"Majority lost (NO_QUORUM): force quorum with the remaining members","m":"Failure and recovery NO_QUORUM forceQuorumUsingPartitionOf quorum majority lost 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 writes stuck writes hang forceQuorum Most members disappeared at the same time (e.g. 2 out of 3) and the survivors stopped accepting writes with `NO"},{"k":"scenario","v":"switch","l":"Planned primary switchover (setPrimaryInstance)","m":"Roles and routing setPrimaryInstance change primary switchover hand over primary The appointed primary member has a version that is greater group_replication_set_as_primary Cluster.setPrimaryInstance runningTransactionsTimeout planned primary switchover move primary planned switchover You want to hand over the primary role to a specific secondary in a planned way for maintenance (OS patch, hardwar"},{"k":"scenario","v":"router","l":"Add Router or re-bootstrap it","m":"Roles and routing 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 cannot connect Router installation Y"},{"k":"scenario","v":"options","l":"Change cluster options (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 member keeps getting expelled failover priority You want"},{"k":"scenario","v":"rescan","l":"Sync the metadata with the actual group (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 metadata mismatch metadata out of sync `cluster.status()` mentions a member that is not in the metadata, or a stale entry that is no longer in the group (`START"}]},{"id":"mysql-logical-backup","file":"install/guides/mysql-logical-backup.json","group":"MySQL","engineId":"mysql","category":"backup","title":"MySQL logical backup and restore (mysqldump, MySQL Shell)","summary":"Logical backup and restore with mysqldump or MySQL Shell (util.dumpInstance / dumpSchemas / dumpTables + util.loadDump): a table, a schema or the whole instance; structure only or data only; migration to another server; ","level":"Intermediate","duration":"20–90 min (depending on data size)","topology":"1 server (+ a target server for migration and instance restore)","tags":["mysql","mysqldump","mysql shell","mysqlsh","dumpInstance","dumpSchemas","dumpTables","loadDump","logical backup","logical backup","export","import","restore","restore","migration","migration","login-path","mysql_config_editor","systemd timer","cron","backup","replica","dump-replica","source-data","innodb cluster","cron","systemd timer","flock","login-path"],"order":15,"steps":8,"scenarios":[{"k":"mode","v":"table","l":"Table export / import","m":"Backup and restore table backup table export copy table copy table mysqldump table Back up one or more tables, copy them to another schema/server, or restore them. The source stays up; no locks are taken on InnoDB tables."},{"k":"mode","v":"schema","l":"Back up / restore a schema (database)","m":"Backup and restore schema backup database backup database backup dumpSchemas mysqldump database Back up an entire database (schema) or load it into another schema/server. The source stays up; tables with the same names on the target are dropped and recreated."},{"k":"mode","v":"instance","l":"Whole instance (users + all schemas)","m":"Backup and restore all databases all databases dumpInstance migrate users full logical backup The backup is taken on this server and restored on an empty server: users, privileges and all schemas. For rebuilding a server or changing version/platform."},{"k":"mode","v":"ddl","l":"Structure only (DDL)","m":"Structure and data separately structure only schema only no-data ddlOnly CREATE TABLE export Table, view, trigger, procedure and event definitions; no data. For schema comparison, building an empty environment or putting the structure under version control."},{"k":"mode","v":"data","l":"Data only","m":"Structure and data separately data only data only no-create-info dataOnly data transfer Only the rows are transferred while the structure already exists on the target (e.g. refreshing test data). Triggers on the target may fire during the load; the guide checks for this."},{"k":"mode","v":"migrate","l":"Migration to another server","m":"Migration migration migration server replacement move to new server 8.0 to 8.4 migration dump and load migrate to HeatWave You work on the target server and connect to the source over the network with a login-path. At cutover the application stops writing; the downtime equals the dump + load time. For changing server, operating system or version."},{"k":"mode","v":"extract","l":"Extract a single table from a full dump","m":"Recovery restore single table extract table from dump extract table from dump undo DROP TABLE deleted rows ERROR 1146 Table doesn't exist You have a full dump and need only one table (deleted rows, dropped table). It is extracted into a separate schema without touching the original schema, or the dropped table is put back."},{"k":"mode","v":"sched","l":"Scheduled backup (systemd timer / cron)","m":"Scheduled backup scheduled backup scheduled backup cron mysqldump systemd timer automatic backup nightly backup Automatic logical backup every day at a set time: a separate backup user, login-path, script, systemd timer or cron, retention period and error notification. The source stays up."}]},{"id":"mysql-security","file":"install/guides/mysql-security.json","group":"MySQL","engineId":"mysql","category":"security","title":"MySQL security hardening (status audit, network, TLS, authentication, privileges, audit, encryption)","summary":"This is not an installation guide: it finds and closes the gaps of a running MySQL 8.4 / 8.0 server. First the read-only Status audit lists the findings by severity and tells you which card each finding leads to; then yo","level":"Intermediate","duration":"5 min (audit) – 3 hours (full hardening)","topology":"Single MySQL server + application server (client verification); replica/InnoDB Cluster in the notes","tags":["mysql","security","security","hardening","security hardening","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","anonymous user","privileges","role","login-path"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"Status audit: what are my gaps? (read-only)","m":"Getting started security check security audit hardening check CIS security gap security weakness vulnerability pentest finding mysql_secure_installation audit query security assessment security scan CRITICAL The scan to run first: anonymous, passwordless and remote root accounts, access from anywhere (%), weak authentication plugin, global privileges, TLS, audit, encryption, file permissions and f"},{"k":"scenario","v":"all","l":"Full hardening (all in order)","m":"Getting started hardening security hardening complete security full hardening apply CIS benchmark STIG Newly installed or never hardened server: all areas are applied in a safe order; MySQL is restarted several times (a maintenance window is required)."},{"k":"scenario","v":"net","l":"Network and connections: who can connect from where? (bind_address, firewall, host restriction)","m":"Network and identity 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 external access restrict access MySQL listens on all interfaces, accounts have host '%', the port is open to everyone. Changing bind_address requires a restart."},{"k":"scenario","v":"tls","l":"Encrypt connections (TLS, require_secure_transport, VERIFY_IDENTITY)","m":"Network and identity ssl tls require_secure_transport ssl-mode VERIFY_IDENTITY VERIFY_CA ERROR 3159 Connections using insecure transport are prohibited server-cert.pem certificate expiry ERROR 2026 SSL connection error certificate verify failed ERROR 3888 certificate chain certificate chain The automatically generated self-signed certificate is in use, unencrypted connections are accepted, or clie"},{"k":"scenario","v":"auth","l":"Authentication and passwords (caching_sha2, password policy, root)","m":"Network and identity mysql_native_password caching_sha2_password validate_password password_history FAILED_LOGIN_ATTEMPTS remote root anonymous user anonymous user 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 mysql_native_password accounts, no pass"},{"k":"scenario","v":"priv","l":"Least privilege: roles and an application account without DDL","m":"Privileges and audit trail GRANT REVOKE ALL PRIVILEGES SUPER FILE least privilege minimal privileges CREATE ROLE ERROR 1142 command denied ERROR 3530 ERROR 1227 you need (at least one of) the SUPER privilege DEFAULT ROLE privilege report CREATE/ALTER/DROP or *.* privileges on the application account, accounts with FILE/SUPER; read/write/DDL roles and a privilege report. No downtime."},{"k":"scenario","v":"audit","l":"Audit: who did what?","m":"Privileges and audit trail audit audit_log audit_log_filter audit trail general_log Enterprise Audit Percona audit SIEM GDPR PCI audit_log_filter_set_filter ERROR 1126 MySQL Enterprise license who did what The auditor wants session and query logging. MySQL Community has no built-in audit: Enterprise Audit, the Percona Server Audit Log Filter, or limited options for Community. **License:** Enterpri"},{"k":"scenario","v":"tde","l":"Encrypt data at rest (InnoDB tablespace encryption, keyring)","m":"Data and backups TDE encryption at rest 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 Table, redo, undo and binlog encryption against theft of the disk or a backup copy; one restart to install the keyring component, ALTER time"},{"k":"scenario","v":"backup","l":"Backup security (encrypted backups, login-path, permissions)","m":"Data and backups encrypted backup backup encryption mysqldump gpg xtrabackup --encrypt login-path mysql_config_editor .my.cnf password xbcrypt --encrypt-key-file plain backup unencrypted backup Backups unencrypted or in a world-readable directory, plain-text password in scripts; mysqldump + GPG or XtraBackup --encrypt, a backup account and login-path."},{"k":"scenario","v":"os","l":"Operating system side (service account, permissions, core dump, SELinux)","m":"Server and version SELinux permissive secure_file_priv local_infile core dump mysql account .mysql_history ERROR 1290 secure-file-priv option ERROR 3948 Loading local data is disabled avc: denied Data directory or certificate files are open to others, secure_file_priv is empty, local_infile is on, SELinux is disabled. Some settings take effect on restart."},{"k":"scenario","v":"patch","l":"Patches and versions: am I on a supported version?","m":"Server and version EOL end of life end of support MySQL 8.0 EOL CPU Critical Patch Update CVE security patch patch schedule MySQL 8.0 end of life 8.4 LTS Version, end of support (8.0 ended in April 2026) and pending updates; links to the upgrade guide for applying them. Read-only."}]},{"id":"mssql-linux-install","file":"install/guides/mssql-linux-install.json","group":"SQL Server","engineId":"mssql","category":"install","title":"SQL Server installation on Linux (RHEL / Ubuntu, production-ready)","summary":"Installs SQL Server 2022 or 2025 from the Microsoft repository on a single Linux server (RHEL family or Ubuntu): edition choice, secure handling of the sa password, mssql-tools18 (sqlcmd/bcp), separate data/log/tempdb/ba","level":"Beginner","duration":"40–60 min","topology":"1 server","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":"SQL Server 2022 installation on Windows (unattended, production-ready)","summary":"Installs SQL Server 2022 silently (unattended) on a single Windows Server with a ConfigurationFile.ini generated from your choices (features, instance, service accounts/gMSA, collation, data/log/tempdb/backup directories","level":"Intermediate","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":"Builds an Always On Availability Group on Windows servers with SQL Server 2022 installed: Windows Server Failover Cluster (WSFC) and quorum witness (file share / cloud witness), enabling Always On, mirroring endpoints an","level":"Advanced","duration":"90–150 min","topology":"2–9 Windows nodes (WSFC) + witness","tags":["sql server","mssql","always on","availability group","ag","wsfc","failover cluster","listener","witness","automatic seeding","hadr","windows","high availability"],"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":"Builds a Pacemaker-managed Always On AG from three Linux servers (RHEL 9 or Ubuntu 22.04) with SQL Server 2022 installed: mssql-server-ha, hadr.hadrenabled, database mirroring endpoints authenticated by certificate, a CL","level":"Advanced","duration":"120–180 min","topology":"3 Linux nodes: 3 synchronous replicas or 2 synchronous + a configuration-only replica","tags":["sql server","mssql","always on","availability group","ag","linux","pacemaker","corosync","pcs","stonith","fencing","mssql-server-ha","ocf:mssql:ag","cluster_type external","configuration only","listener","virtual ip","rhel","ubuntu","high availability"],"order":4,"steps":30,"scenarios":[]},{"id":"mssql-cu-patch","file":"install/guides/mssql-cu-patch.json","group":"SQL Server","engineId":"mssql","category":"patch","title":"Applying a SQL Server Cumulative Update (CU)","summary":"Applies a Cumulative Update (CU) within the same major version to an installed SQL Server 2022/2025 instance: with dnf/apt on Linux, as a silent (/quiet) installation with the CU setup file on Windows. Single server, or ","level":"Intermediate","duration":"30–90 min","topology":"1 server or an AG (2+ replicas)","tags":["sql server","mssql","cu","cumulative update","patch","update","rolling","availability group","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":"SQL Server Always On AG operations (Windows and Linux)","summary":"Not an installation: day-to-day operation and failure scenarios of an installed Always On Availability Group: health check, diagnosing lag and log growth, planned failover, forced failover (FORCE_FAILOVER_ALLOW_DATA_LOSS","level":"Advanced","duration":"15–90 min (depending on the scenario)","topology":"AG with 2–9 replicas (Windows WSFC or 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":"AG health check (dashboard queries)","m":"Monitoring and diagnosis NOT SYNCHRONIZING NOT_HEALTHY DISCONNECTED RESOLVING PARTIALLY_HEALTHY AG health availability group health sys.dm_hadr dashboard replica state Msg 35206 connection timeout AG dashboard Show Dashboard Is everything fine? Replica / database state, failover readiness, the cluster and recent role changes. Changes nothing."},{"k":"op","v":"lag","l":"Secondary falling behind / log file growing (lag diagnosis)","m":"Monitoring and diagnosis 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 lag redo blocked log growing The send/redo queue is growing, data on the secondary is stale, the log on the primary is about to fill up (log_reuse_wait = AVAILABILITY_REPLICA) or commits are slow (HADR_SYNC_COMMIT)."},{"k":"op","v":"failover","l":"Planned failover (switchover, no data loss)","m":"Role change planned failover manual failover switchover ALTER AVAILABILITY GROUP FAILOVER pcs resource move Msg 41142 41142 Msg 19407 change role move primary Failover Cluster Manager Move maintenance failover Moves the primary to a synchronous, SYNCHRONIZED secondary for maintenance, patching or testing; a few seconds of downtime, no data loss."},{"k":"op","v":"forced","l":"Primary lost: forced failover (FORCE_FAILOVER_ALLOW_DATA_LOSS)","m":"Role change FORCE_FAILOVER_ALLOW_DATA_LOSS forced failover forced failover quorum lost quorum loss FixQuorum forcequorum disaster recovery DR Msg 41131 Msg 35250 site loss primary down Msg 47104 ForceQuorum Cluster is running in ForceQuorum state RESOLVING_NORMAL primary crashed data loss The primary (or the whole site / cluster quorum) is lost and you cannot wait: a secondary is made primary, acc"},{"k":"op","v":"suspend","l":"Suspend / resume data movement (SUSPEND / RESUME)","m":"Maintenance SUSPENDED SET HADR SUSPEND SET HADR RESUME is_suspended suspend_reason data movement data movement suspended Msg 35264 35264 Msg 35265 SUSPEND_FROM_REDO SUSPEND_FROM_USER SUSPEND_FROM_PARTNER stalled suspend database Temporarily stop sending data to a secondary (network maintenance, bulk operations) or resume a database that is stuck in SUSPENDED."},{"k":"op","v":"mode","l":"Change synchronous / asynchronous mode","m":"Maintenance SYNCHRONOUS_COMMIT ASYNCHRONOUS_COMMIT availability mode REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT required_synchronized_secondaries_to_commit synchronous asynchronous Msg 35211 35211 Msg 35212 35212 FAILOVER_MODE Switch a replica between SYNCHRONOUS_COMMIT and ASYNCHRONOUS_COMMIT (remote site, slow network, DR drill) and configure commit protection (REQUIRED_SYNCHRONIZED_SECONDARIES"},{"k":"op","v":"adddb","l":"Add a database to the AG (automatic seeding or backup/restore)","m":"Change ADD DATABASE automatic seeding SET HADR AVAILABILITY GROUP Msg 35250 Msg 1478 1478 seeding failed add database Msg 1475 1475 might contain bulk logged changes manual seeding Bring a new or existing database into the AG: automatic seeding for small/medium databases, from backup for large databases or over a slow network."},{"k":"op","v":"addrep","l":"Add a replica (node)","m":"Change ADD REPLICA Add-ClusterNode pcs cluster node add new replica add a replica Msg 41158 41158 Failed to join local availability replica JOIN Add a new secondary to the AG: the node joins the cluster, the endpoint is prepared, ADD REPLICA + JOIN, and the databases arrive by seeding."},{"k":"op","v":"removerep","l":"Remove a replica (node)","m":"Change REMOVE REPLICA Remove-ClusterNode pcs cluster node remove remove replica decommission Msg 35223 35223 remove node Remove a secondary that will be decommissioned or rebuilt from the AG and from the cluster."},{"k":"op","v":"routing","l":"Read-only routing","m":"Connectivity read-only routing READ_ONLY_ROUTING_URL READ_ONLY_ROUTING_LIST ApplicationIntent=ReadOnly readable secondary read-only secondary Msg 978 978 Msg 976 976 not accessible for queries Msg 10786 ApplicationIntent Route ApplicationIntent=ReadOnly connections through the listener to readable secondaries (offloading reporting from the primary)."},{"k":"op","v":"listener","l":"Create or modify the listener (IP, port)","m":"Connectivity listener ADD LISTENER MODIFY LISTENER MultiSubnetFailover RegisterAllProvidersIP Msg 19471 19471 Msg 41009 virtualip Msg 19456 19456 HostRecordTTL listener port DNS record Create the listener if there is none, change its port or (on Windows) add an IP for a second subnet."}]},{"id":"mssql-backup","file":"install/guides/mssql-backup.json","group":"SQL Server","engineId":"mssql","category":"backup","title":"SQL Server backup strategy, automation and restore","summary":"Sets up backups for an installed SQL Server instance (Linux or Windows; single instance, FCI or AG replicas) and recovers from them. Backup: choice of recovery model, weekly full + daily differential + log backup every n","level":"Intermediate","duration":"30–90 min","topology":"1 server, Failover Cluster Instance or Always On AG replicas (restore to another server: source + target)","tags":["sql server","mssql","backup","backup file","backing up","restore","restore database","pitr","stopat","point in time","differential","transaction log","copy_only","compression","checksum","encryption","backup encryption","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":"Set up a backup strategy (scheduled backups + retention)","m":"Backup backup plan backup strategy log file growing LOG_BACKUP Msg 9002 transaction log is full sql agent job Msg 3013 3013 BACKUP DATABASE is terminating abnormally backups not running backup job New server or a database with no regular backups: full / differential / log jobs, retention and monitoring are set up."},{"k":"mode","v":"oneoff","l":"Take a one-off backup (e.g. COPY_ONLY before a change)","m":"Backup copy_only one-off backup backup before change manual backup Msg 4214 4214 Msg 4208 4208 BACKUP LOG is not allowed while the recovery model is SIMPLE A single backup before a change, patch or upgrade, or to copy to a test environment; COPY_ONLY does not break the chain."},{"k":"mode","v":"verify","l":"Verify backups (history, VERIFYONLY, test restore)","m":"Rehearsal and checks VERIFYONLY HEADERONLY FILELISTONLY test restore is the backup corrupt Msg 3241 Msg 3183 Msg 3203 To prove the backups can actually be restored: backup history, VERIFYONLY, HEADERONLY / FILELISTONLY and a test restore to a temporary database + CHECKDB."},{"k":"mode","v":"chain","l":"Is the backup chain intact? (LSN gap, missing file)","m":"Rehearsal and checks Msg 4305 4305 too recent to apply LSN log chain chain broken broken log chain Msg 3136 last_log_backup_lsn backup chain Before a restore or as a regular audit: rebuilds the chain from msdb history and finds LSN gaps, files missing from disk and backups taken out of band. Changes nothing."},{"k":"mode","v":"restore","l":"Restore a database from backup (chain, STOPAT / STOPATMARK, MOVE / REPLACE, another server)","m":"Restore and point in time STOPAT STOPATMARK STOPBEFOREMARK point in time PITR accidentally deleted undo 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 Data was deleted or wrongly updated, a copy of the database is needed or it is moving to another"},{"k":"mode","v":"damaged","l":"Database will not open: data file missing or damaged (tail-log + restore)","m":"Loss / 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 deleted data file missing database cannot be opened Msg 823 NO_TRUNCATE CONTINUE_AFTER_ERROR EMERGENCY The database is RECOVERY_PENDING or SUSPECT and the .mdf/.ndf cannot be read, but the instance is running: first take a tail-log backup "},{"k":"mode","v":"page","l":"A few pages are corrupt (Msg 823 / 824): page restore","m":"Loss / corruption Msg 824 824 Msg 823 823 Msg 829 829 suspect_pages torn page incorrect checksum corrupt page page corruption page restore CHECKDB found Msg 8928 Msg 8939 Msg 8976 repair_allow_data_loss Queries or CHECKDB fail on specific pages (msdb.dbo.suspect_pages): only the damaged pages are restored from backup. On Enterprise/Developer the database stays online; on Standard/Express it is off"},{"k":"mode","v":"master","l":"Restore the master database (single-user mode)","m":"System databases master Msg 3108 3108 single user mode single-user start in single user mode -m Msg 3417 3417 Msg 17113 Msg 18461 master corrupt force-setup REBUILDDATABASE master is damaged, or logins / linked servers / server settings were lost: the instance is started in single-user mode (-m), master is restored and SQL Server shuts itself down. Requires downtime; if the instance does not start"},{"k":"mode","v":"sysdb","l":"Restore the msdb or model database","m":"System databases msdb model Msg 3168 3168 jobs disappeared msdb suspect Msg 1807 msdb corrupt model corrupt Jobs, backup history, Database Mail (msdb) or the template for new databases (model) are damaged / lost: restored WITH REPLACE from a backup taken on the same build; Agent is stopped briefly."},{"k":"mode","v":"orphan","l":"Users cannot connect after a restore (orphaned users, login mapping)","m":"Migration and access Msg 18456 18456 Login failed for user orphaned user orphan orphaned database user sp_help_revlogin sp_change_users_login Msg 15023 15023 Msg 916 Msg 4060 login mapping SID The database was moved or restored to another server and the application gets 'Login failed': logins are transferred from the source with their SID and password hash, and remaining users are mapped to logins"},{"k":"mode","v":"enc","l":"Encrypted backup cannot be restored (certificate missing, Msg 33111)","m":"Migration and access Msg 33111 33111 Cannot find server certificate with thumbprint restore encrypted backup encrypted backup backup certificate certificate TDE Msg 15581 15581 master key The backup (or the backup of a TDE database) is encrypted with a certificate that does not exist on this server: the thumbprint is identified, the certificate is loaded with its private key and the backup is veri"}]},{"id":"mssql-bcp-export","file":"install/guides/mssql-bcp-export.json","group":"SQL Server","engineId":"mssql","category":"backup","title":"SQL Server table-level export/import (bcp, sqlpackage)","summary":"Not a backup but logical data movement: exporting tables with bcp and loading them into another database (native or character format, format file, batch, TABLOCK, identity column, UTF-8), writing a query result to a CSV/","level":"Intermediate","duration":"20–60 min","topology":"1 server (commands run on the SQL Server itself or on a management server)","tags":["sql server","mssql","bcp","bulk copy","export","import","queryout","format file","native","csv","utf-8","sqlpackage","bacpac","dacpac","extract","publish","azure sql","data transfer","migration","copy table"],"order":16,"steps":8,"scenarios":[{"k":"op","v":"table","l":"Move tables: bcp out → bcp in","m":"Data transfer bcp bcp out bcp in bulk copy copy table move table Unexpected EOF encountered in BCP data-file Unable to open BCP host data-file Copy one or more tables to another SQL Server database; the tables must already exist on the target."},{"k":"op","v":"query","l":"Write a query result to a file: bcp queryout (CSV/text)","m":"Export queryout csv csv export query result to file export to file Write the result of a query to a CSV/text file to hand over to another system (Excel, ETL, reporting)."},{"k":"op","v":"bacpac","l":"Schema + data: sqlpackage Export / Import (.bacpac)","m":"Data transfer bacpac sqlpackage SqlPackage Export SqlPackage Import Azure SQL Database migration SQL71564 SQL71501 Move schema and data in a single file; especially for migration to Azure SQL Database or small/medium databases."},{"k":"op","v":"dacpac","l":"Schema only: sqlpackage Extract / Script / Publish (.dacpac)","m":"Schema dacpac Extract Publish schema comparison schema compare BlockOnPossibleDataLoss The schema update is terminating because data loss might occur Extract only the schema, compare it with the target and generate a change script, or deploy an empty schema to a new environment."}]},{"id":"mssql-security","file":"install/guides/mssql-security.json","group":"SQL Server","engineId":"mssql","category":"security","title":"SQL Server security hardening (Linux and Windows: assessment, network, TLS, authentication, permissions, audit, TDE)","summary":"This is not an installation guide: it finds and closes the gaps of a running SQL Server 2019 / 2022 / 2025 instance (Linux or Windows). First a read-only Security assessment lists the gaps by severity and tells you which","level":"Intermediate","duration":"5 min (assessment) – 3 hours (full hardening)","topology":"Single SQL Server instance (Linux or Windows) + application server; Always On AG in the notes","tags":["sql server","mssql","security","security","hardening","security hardening","cis benchmark","stig","force encryption","tls","certificate","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":"Security assessment: what are my gaps? (read-only)","m":"Beginner security assessment security audit hardening check CIS STIG security gap weakness vulnerability pentest finding SQL Vulnerability Assessment assessment query security check security scan CRITICAL The scan to run first: sa, password policy, empty passwords, sysadmin members, public/guest permissions, xp_cmdshell, unencrypted connections, audit, TDE and certificate backups, ports and firewa"},{"k":"scenario","v":"all","l":"Full hardening (everything in order)","m":"Beginner hardening security hardening full security full hardening apply CIS benchmark STIG Newly installed or never hardened instance: all areas are applied in a safe order; SQL Server is restarted once or twice (a maintenance window is needed)."},{"k":"scenario","v":"net","l":"Network and connectivity: who can connect from where? (listening, firewall, Browser)","m":"Network and identity 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 external access restrict access SQL Server listens on all interfaces, 1433 is open to everyone, SQL Browser runs without need. Changing the listen address requires a restart"},{"k":"scenario","v":"tls","l":"Encrypt connections (Force Encryption, certificate, client validation)","m":"Network and identity Force Encryption TrustServerCertificate Encrypt=Mandatory certificate chain was issued by an authority that is not trusted SSL Provider error 0x80090325 tlscert certificate expiry TDS 8.0 strict The target principal name is incorrect certificate verify failed Encrypt=Strict Force Strict Encryption Unable to load user-specified certificate certificate chain certificate chain Co"},{"k":"scenario","v":"auth","l":"Authentication and passwords (sa, CHECK_POLICY, Windows authentication)","m":"Network and identity sa CHECK_POLICY CHECK_EXPIRATION mixed mode Login failed for user Error 18456 Error 18486 account is locked out password policy 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 enabled, no password policy on SQL logins, empty/weak passwords; a personal DBA acco"},{"k":"scenario","v":"priv","l":"Least privilege and attack surface (roles, public, guest, xp_cmdshell)","m":"Permissions and audit trail db_owner sysadmin public guest xp_cmdshell TRUSTWORTHY CONTROL SERVER least privilege minimum permissions Msg 262 Msg 229 permission was denied Msg 15247 Msg 916 db_ddladmin EXECUTE AS permission report Application user is db_owner or sysadmin, extra permissions on public/guest, xp_cmdshell/TRUSTWORTHY enabled; role design and a permission report. No downtime."},{"k":"scenario","v":"audit","l":"Audit: who did what? (Server Audit, Database Audit Specification)","m":"Permissions and audit trail SQL Server Audit server audit audit specification fn_get_audit_file C2 audit login auditing audit trail SIEM GDPR PCI sqlaudit Server Audit Specification Database Audit Specification Msg 33072 who did what The auditor wants a record of logins, permission and schema changes; Server Audit to a file (or the Windows event log), forwarding to a central system and retention. "},{"k":"scenario","v":"tde","l":"Encrypt data at rest (TDE)","m":"Data and backups 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 certificate backup TDE against a disk, data file or backup being opened on another server: DMK, certificate, DEK. Done online; the certificate backup is MANDATORY (if i"},{"k":"scenario","v":"backup","l":"Backup security (encrypted backups, certificate, permissions)","m":"Data and backups backup encryption backup encryption WITH ENCRYPTION BACKUP CERTIFICATE backup permissions Msg 33111 Msg 15151 RESTORE VERIFYONLY key_algorithm Msg 15240 Backups are unencrypted, the backup directory is open to everyone, passwords are in plain text in scripts; BACKUP … WITH ENCRYPTION, certificate backup and directory permissions. No downtime. Backup encryption is available in Stan"},{"k":"scenario","v":"os","l":"Operating system side (service account, file permissions, SELinux)","m":"Server and version service account sql server service account LocalSystem gMSA virtual account icacls SELinux mssql user NT Service\\MSSQLSERVER mssql-server-selinux unconfined_service_t Configuration Manager Log On The service runs as LocalSystem or an administrator account, data/backup directories are open to everyone, SELinux is disabled; changing the service account requires a restart."},{"k":"scenario","v":"patch","l":"Patches and version: am I on a supported version and the latest CU?","m":"Server and version CU cumulative update GDR EOL end of support out of support CVE security update ProductUpdateLevel ProductUpdateReference Latest updates patch schedule Version, CU level and end of support (2016 ended in July 2026); links to the CU patch guide for applying updates. Read-only."}]},{"id":"mongo-replset","file":"install/guides/mongo-replset.json","group":"MongoDB","engineId":"mongodb","category":"ha","title":"MongoDB replica set installation (3 members, keyfile and authorization)","summary":"Builds a three-member MongoDB 8.0 (or 7.0) replica set from scratch: official repository, XFS data directory, WiredTiger cache sized to RAM, keyfile authentication between members, authorization, administrator and applic","level":"Intermediate","duration":"60–90 min","topology":"3 servers (1 primary + 2 secondaries), an odd number of data-bearing members","tags":["mongodb","mongo","replica set","replset","rs.initiate","keyfile","failover","stepdown","write concern","wiredtiger","high availability","ha","rhel","ubuntu"],"order":1,"steps":36,"scenarios":[]},{"id":"mongo-sharded","file":"install/guides/mongo-sharded.json","group":"MongoDB","engineId":"mongodb","category":"ha","title":"MongoDB sharded cluster installation (config servers + shards + mongos)","summary":"A sharded cluster for when the data or write load outgrows a single replica set: a three-member config server replica set, two or three shard replica sets and two mongos routers; internal authentication with a keyfile, a","level":"Advanced","duration":"2–3 hours","topology":"3 config servers + 2×3 shard members + 2 mongos (at least 11 servers; smaller for testing)","tags":["mongodb","sharding","sharded cluster","mongos","config server","shard key","addShard","balancer","hashed","scaling","rhel","ubuntu"],"order":2,"steps":60,"scenarios":[]},{"id":"mongo-upgrade","file":"install/guides/mongo-upgrade.json","group":"MongoDB","engineId":"mongodb","category":"upgrade","title":"MongoDB rolling upgrade (7.0 → 8.0): replica set and sharded cluster","summary":"Upgrades a running MongoDB 7.0 replica set or sharded cluster to 8.0 without downtime (rolling): pre-checks (version, FCV, member state, driver compatibility), backup, secondaries first, then stepDown and the former prim","level":"Advanced","duration":"60–120 min (independent of data size; a few minutes per member)","topology":"Replica set (3+ members) or sharded cluster (config servers + shards + mongos)","tags":["mongodb","upgrade","major version upgrade","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":"MongoDB backup and restore (mongodump, mongorestore, snapshot)","summary":"Sets up a backup routine for an installed MongoDB and walks through restoring from backup scenario by scenario. Backup: mongodump of a single collection, a database or the whole deployment (--archive --gzip; on a replica","level":"Intermediate","duration":"45–90 min","topology":"Backup server or a member + replica set / single instance / sharded cluster (mongos)","tags":["mongodb","mongo","backup","backup","restore","restore","mongodump","mongorestore","oplog","oplogReplay","nsFrom","nsTo","mongoexport","mongoimport","fsyncLock","lvm snapshot","systemd timer","cron","retention","balancer","activeWindow","config server","cron","systemd timer","flock","oplog","restore scenarios","point in time recovery","pitr","oplogLimit","nsInclude","dryRun","sharded restore","clone","dropped collection","accidentally deleted"],"order":15,"steps":20,"scenarios":[{"k":"scenario","v":"setup","l":"Set up backups: mongodump, scheduling and test restore","m":"Backup mongodump set up backup backup setup systemd timer cron retention mongorestore backup user backup.yaml Failed: can't create session oplog overflow mongodump was unable to capture all new oplog entries backup Backup user, credentials file, manual and scheduled mongodump, test restore under a new name; optional snapshot and mongoexport. Start here if you are setting this up for the first time"},{"k":"scenario","v":"r_dry","l":"Can the backup really be restored? (--dryRun and rehearsal on a temporary instance)","m":"Rehearsal and checks dryRun --dryRun restore test restore rehearsal backup verification backup verify gzip: invalid compressed data unexpected EOF archive is corrupt corrupt archive gzip -t does the backup restore Without touching live data: archive integrity, the namespaces and oplog it contains, and optionally a full restore to a temporary mongod with document counts."},{"k":"scenario","v":"r_full","l":"Restore all data from backup (full restore, --drop)","m":"Restore full restore full restore full database restore --drop mongorestore --drop data loss data loss ransomware ransom all data deleted dropDatabase no oplog file to replay oplogReplay applyOps not authorized … applyOps admin.tempusers Data has been largely lost or corrupted and the deployment will go back to the moment the backup was taken. Writes made after the backup are lost; the application"},{"k":"scenario","v":"r_ns","l":"Bring back a single database or collection (--nsInclude, under a new name or in place)","m":"Restore --nsInclude --nsFrom --nsTo single collection single collection restore collection deleted collection dropped drop collection deleted documents deleted documents deleteMany namespace restore E11000 duplicate key error $merge orders_restored restore single database A collection/database was dropped or corrupted, the rest is intact: only that namespace comes from the backup; other data is un"},{"k":"scenario","v":"r_pitr","l":"Return to a specific moment: backup + oplog (accidental drop, bad update/delete)","m":"Point-in-time recovery point in time PITR point-in-time recovery --oplogReplay --oplogLimit oplog replay oplog.rs accidentally deleted accidental drop bad update wrong update before drop Timestamp oplog rolled over rollback to time restore to a specific time The bad operation happened after the backup and the writes up to that moment are also needed: the last full backup is restored and the live o"},{"k":"scenario","v":"r_other","l":"Restore the backup to another cluster / server (clone, test environment, migration)","m":"Migration and clone clone clone restore to another server restore to another cluster migration server migration test environment refresh nsExclude admin.system.users Invalid feature compatibility version cannot use --oplogReplay with excludes specified copy to test environment The source deployment is not touched: the backup is restored to another replica set or instance (test/reporting copy, serv"},{"k":"scenario","v":"r_shard","l":"Restore a sharded cluster: the limits of mongodump and the right method","m":"Migration and clone sharded restore sharded cluster restore mongos restore shard key shardCollection config server restore balancer inconsistent inconsistent backup PBM Percona Backup Ops Manager --oplog mode only supported on full dumps backup through mongos The backed-up topology is a sharded cluster: logical restore through mongos (recreating shard keys, balancer) or physical rebuild of the who"}]},{"id":"mongo-ops","file":"install/guides/mongo-ops.json","group":"MongoDB","engineId":"mongodb","category":"ops","title":"MongoDB day-to-day operations and failure scenarios (replica set and sharded cluster)","summary":"Not an installation: a scenario-based runbook for running an installed MongoDB replica set and sharded cluster day to day and recovering them when they break. Health check, adding/removing members, priority/hidden/delaye","level":"Advanced","duration":"5 min (check) – several hours (initial sync, drain, reshard)","topology":"Replica set (3+ members) or sharded cluster (config server + shard replica sets + 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","operations","failure"],"order":20,"steps":3,"scenarios":[{"k":"scenario","v":"health","l":"Health check: members, lag, oplog window (shards and balancer when sharded)","m":"Monitoring and checks rs.status rs.status() replication lag lag replLag printSecondaryReplicationInfo sh.status health health check NotYetInitialized RECOVERING STARTUP2 UNKNOWN (not reachable/healthy) DOWN ReplicaSetNoPrimary Server selection timed out lastHeartbeatMessage Read-only: member states, replication lag, oplog window, configuration; on a sharded cluster the shard list, balancer and chu"},{"k":"scenario","v":"add","l":"Add a new member to the replica set (initial sync)","m":"Member management rs.add add member add member initial sync new secondary newlyAdded STARTUP2 scale out rs.add() Quorum check failed Found two member configurations with same host field votes: 0 A new server has been built (same version, keyfile, replSetName); it is added with priority 0 / votes 0 and given a vote and priority once initial sync finishes and it becomes SECONDARY. No downtime."},{"k":"scenario","v":"remove","l":"Remove a member from the replica set","m":"Member management rs.remove remove member remove member decommission remove server REMOVED Our replica set config is invalid or we are not a member of it rs.remove() The server is being retired or is permanently lost: the member is removed from the configuration and the remaining votes are checked to still form a majority. If it is the primary, step it down first."},{"k":"scenario","v":"member_cfg","l":"Change a member's role: priority, hidden, delayed member","m":"Member management priority hidden delayed secondaryDelaySecs slaveDelay hidden member delayed member votes rs.reconfig priority must be 0 when hidden=true only one voting member can be added or removed at a time rs.reconfig() Change a member's priority for becoming primary, make it a hidden member for reporting/backup or a delayed member as protection against accidental deletes, or return it to no"},{"k":"scenario","v":"arbiter","l":"Replace the arbiter with a data-bearing member (PSA → PSS)","m":"Member management arbiter arbiterOnly PSA rs.addArb majority write concern cache pressure w: majority waiting Rejecting reconfig where the new config has a PSA topology implicit default write concern majority commit point Replica set with primary + secondary + arbiter: when a data member goes down, majority writes and cache pressure become a problem. A new data member is added and the arbiter is r"},{"k":"scenario","v":"resync","l":"Resync a lagging or corrupted member","m":"Failure and recovery too stale too stale to catch up RS102 resync resync initial sync RECOVERING WiredTiger error WT_PANIC corrupted data file data corruption OplogStartMissing maintenance mode InitialSyncFailure fassert checksum mismatch The member is stuck in RECOVERING and reports 'too stale to catch up', or its data files are corrupted: the member's data directory is emptied and it is resynced"},{"k":"scenario","v":"force","l":"Majority lost: force a reconfiguration with the surviving members (force reconfig)","m":"Failure and recovery force reconfig force: true no primary primary missing majority loss majority lost NotWritablePrimary not master no replset config split brain DR site site loss ReplicaSetNoPrimary Server selection timed out rs.reconfig force Most members are permanently lost (site/server loss) and the survivors cannot elect a primary: rs.reconfig(cfg, {force: true}) with the surviving members."},{"k":"scenario","v":"stepdown","l":"Planned primary change (rs.stepDown, making a specific member primary)","m":"Maintenance stepDown rs.stepDown replSetStepDown change primary switchover planned maintenance maintenance rs.freeze No electable secondaries caught up InterruptedDueToReplStateChange PrimarySteppedDown primary change Maintenance/patching is due on the primary server: the primary steps down and a caught-up secondary becomes primary. The application may see write errors for a few seconds."},{"k":"scenario","v":"oplog","l":"Change the oplog size (replSetResizeOplog)","m":"Maintenance oplog oplog window oplog retention window replSetResizeOplog oplogSizeMB oplogMinRetentionHours too stale oplog overflow rs.printReplicationInfo getReplicationInfo log length start to end The oplog window is short: secondaries become 'too stale' after maintenance, the backup cannot capture the oplog, or it is not long enough for point-in-time recovery. It is grown on each member withou"},{"k":"scenario","v":"fcv","l":"Check and finalize the Feature Compatibility Version (FCV)","m":"Maintenance FCV featureCompatibilityVersion setFeatureCompatibilityVersion Invalid feature compatibility version IncompatibleServerVersion after upgrade UPGRADE PROBLEM requires confirm: true getParameter featureCompatibilityVersion After an upgrade the FCV is still at the old version, or members run different versions: the binary version and FCV of every member are checked and the FCV is raised t"},{"k":"scenario","v":"addshard","l":"Add a shard to the cluster (sh.addShard)","m":"Sharded cluster addShard sh.addShard add shard add shard scale out new shard shardAdded is not running with --shardsvr in use by another shard Capacity is running out: a new shard replica set (shardsvr) has been built and is added to the cluster; the balancer migrates chunks to it gradually."},{"k":"scenario","v":"rmshard","l":"Remove a shard (removeShard: drain and movePrimary)","m":"Sharded cluster removeShard remove shard decommission shard drain draining movePrimary dbsToMove jumboChunks moveCollection collectionsToMove ShardNotFound draining started successfully A shard is being retired: its chunks are drained to the other shards, the databases for which it is the primary shard are moved (movePrimary), and then it is removed from the cluster."},{"k":"scenario","v":"balancer","l":"Balancer: time window, stop / start, disable per collection","m":"Sharded cluster balancer activeWindow stopBalancer startBalancer sh.stopBalancer disableBalancing balancer window chunk migration moveChunk isBalancerRunning getBalancerState Balancer timed out Chunk migrations cause load during peak hours, migrations must stop for maintenance/backup, or a single collection must not be balanced."},{"k":"scenario","v":"chunks","l":"Uneven distribution or jumbo chunks (diagnosis and fix)","m":"Sharded cluster jumbo jumbo chunk chunk too big ChunkTooBig unbalanced imbalance hot shard balancerCollectionStatus clearJumboFlag moveChunk failed getShardDistribution low cardinality splitFind Cannot split on the boundary jumboChunks One shard is much fuller than the others, the balancer does not migrate, there are chunks flagged 'jumbo', or moveChunk fails: the distribution, jumbo chunks and ba"},{"k":"scenario","v":"reshard","l":"Change the shard key (reshardCollection)","m":"Sharded cluster reshardCollection reshard change shard key change shard key refineCollectionShardKey abortReshardCollection commitReshardCollection forceRedistribution ReshardCollectionInProgress ReshardCollectionAborted unshardCollection The current shard key produces a hot shard, jumbo chunks or scatter-gather queries: the collection is redistributed online with a new shard key (5.0+). At the en"},{"k":"mcfg","v":"priority","l":"Change priority (preference for becoming primary)","m":"Changes the member's priority for becoming primary (e.g. preferring the member in the main data center, setting the DR member to 0). An election may take place; there may be a short write interruption."},{"k":"mcfg","v":"hidden","l":"Hidden member: invisible to applications, for backup/reporting","m":"The member is invisible to applications (no reads are routed to it); it is set aside for reporting, analytics or backup. It gets priority 0 and cannot become primary."},{"k":"mcfg","v":"delayed","l":"Delayed member: protection against accidental deletes","m":"The member applies the oplog with the specified delay; data deleted by mistake remains on it for that long. It becomes hidden, priority 0 and votes 0."},{"k":"mcfg","v":"normal","l":"Return to normal (priority 1, visible, no delay, voting)","m":"Reverts the previous role: priority 1, visible, no delay, voting."},{"k":"bal_op","v":"window","l":"Define a time window (activeWindow)","m":"Migrations run only during certain hours (e.g. at night); no load during the day."},{"k":"bal_op","v":"unwindow","l":"Remove the time window (run at any time)","m":"The time window is removed; the balancer migrates at any hour when needed (e.g. to rebalance quickly after adding a new shard)."},{"k":"bal_op","v":"stop","l":"Stop the balancer (maintenance/backup)","m":"Before a backup, upgrade or maintenance: the running migration completes and no new one starts. Always restart it when the work is done."},{"k":"bal_op","v":"start","l":"Start the balancer","m":"Enables a stopped balancer; balancing resumes where it left off."},{"k":"bal_op","v":"coll_off","l":"Disable balancing for a single collection","m":"Only the chunks of one collection are not migrated (deliberate placement, problematic collection). The others keep being balanced."},{"k":"bal_op","v":"coll_on","l":"Enable balancing for a single collection","m":"Re-enables balancing that was disabled for the collection."}]},{"id":"mongo-security","file":"install/guides/mongo-security.json","group":"MongoDB","engineId":"mongodb","category":"security","title":"MongoDB security hardening (authorization, TLS, roles, audit)","summary":"Scenario-based runbook for hardening a running MongoDB (standalone, replica set or sharded cluster) without downtime: read-only security audit, rolling authorization with keyFile + transitionToAuth, least-privilege custo","level":"Advanced","duration":"10 min (audit) – 2 hours (full hardening)","topology":"Standalone, replica set with 3+ members, or sharded cluster","tags":["mongodb security","mongodb security","hardening","security hardening","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":"Security status audit (read-only)","m":"Audit security audit security audit security checklist CIS MongoDB hardening security hardening Access control is not enabled for the database is auth disabled bindIp 0.0.0.0 exposed mongodb mongodb exposed to the internet ransom ransomware getCmdLineOpts security checklist MongoDB audit report Changes nothing. Unauthenticated access, listening addresses, TLS, authentication mechanisms, highly pri"},{"k":"op","v":"all","l":"Full hardening (everything in order)","m":"Audit full hardening full hardening CIS benchmark security project pentest finding audit findings Audit → internal auth + authorization (no downtime with transitionToAuth) → least-privilege users → SCRAM-SHA-256 → bindIp/firewall → TLS (allowTLS → preferTLS → requireTLS) → audit (Enterprise/Percona) → final audit. Rolling in a replica set; every risky step asks for confirmation."},{"k":"op","v":"auth","l":"Authorization disabled: enable authorization (keyFile, no downtime)","m":"Identity and access Access control is not enabled for the database Read and write access to data and configuration is unrestricted authorization disabled authorization: enabled auth disabled no authentication transitionToAuth keyFile internal authentication internal member authentication unauthenticated access permissions on /etc/mongod.keyfile are too open Authentication failed __system localhost"},{"k":"op","v":"users","l":"Least-privilege users and custom roles (application, backup, monitoring)","m":"Identity and access least privilege least privilege custom role createRole root user application connects as root not authorized on Unauthorized command find requires authentication requires authentication readWrite clusterMonitor backup role createUser grantRolesToUser monitor_user backup_user Applications connect with root or a shared admin account. A custom role that grants the application only"},{"k":"op","v":"scram","l":"Disable SCRAM-SHA-1, SCRAM-SHA-256 only","m":"Identity and access 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 Credentials of older users are stored with SHA-1, or the audit found SCRAM-SHA-1 enabled. Users are migrated to SHA-256, then `authentica"},{"k":"op","v":"bind","l":"Restrict network access (bindIp 0.0.0.0, firewall)","m":"Network and encryption bindIp 0.0.0.0 bindIpAll bind_ip_all 0.0.0.0:27017 port 27017 open shodan exposed to the internet firewall javascriptEnabled noscripting net.bindIp ufw allow 27017 firewall-cmd 27017 mongod listens on all interfaces (0.0.0.0, `bindIpAll`) or the port is open to everyone. bindIp is reduced to localhost + the server's internal IP, and the firewall allows only the members and t"},{"k":"op","v":"tls","l":"Encrypt connections: TLS (allowTLS → preferTLS → requireTLS)","m":"Network and encryption TLS SSL net.tls.mode requireTLS preferTLS allowTLS unencrypted connection 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 Traffic between"},{"k":"op","v":"auditlog","l":"Enable the audit log (Enterprise / Percona)","m":"Audit trail and data protection audit log auditLog audit trail who deleted it who dropped Unrecognized option: auditLog unrecognised option '--auditDestination' GDPR PCI DSS logging Who logged in and when, user/role changes and drop operations must be recorded. In MongoDB Enterprise and Percona Server for MongoDB it is enabled with `auditLog`; Community does not have this feature (an alternative i"},{"k":"op","v":"ear","l":"Encryption at rest (encrypt data on disk)","m":"Audit trail and data protection encryption at rest disk encryption enableEncryption encryptionKeyFile KMIP Vault LUKS dm-crypt data encryption Unrecognized option: security.enableEncryption Unable to retrieve key Unable to use encryption at rest cryptsetup luksFormat If a disk or a backup is stolen, the data must be unreadable. On Enterprise/Percona, WiredTiger encryption is enabled by rebuilding "}]},{"id":"redis-sentinel","file":"install/guides/redis-sentinel.json","group":"Redis","engineId":"redis","category":"ha","title":"High availability with Redis Sentinel (master + replicas + 3 Sentinels)","summary":"Builds a three-server Redis 7.2 / 8.2 or Valkey high-availability setup from scratch: one master, two replicas and one Sentinel on each server. maxmemory sized from RAM, persistence (RDB/AOF), password, kernel settings, ","level":"Intermediate","duration":"45–60 min","topology":"3 servers: Redis (1 master + 2 replicas) and Sentinel on each","tags":["redis","valkey","sentinel","replica","replication","failover","quorum","high availability","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":"Redis Cluster installation (3 masters + 3 replicas)","summary":"Builds a Redis Cluster from scratch (Redis 7.2 / 8.2 or Valkey): the 16384 hash slots are distributed across three (or more) masters, and each master has a replica on a different server. Layout with one instance per serv","level":"Intermediate","duration":"45–60 min","topology":"6 instances: 6 servers × 1 or 3 servers × 2","tags":["redis","valkey","cluster","redis cluster","sharding","hash slot","reshard","failover","replica","scaling","rhel","ubuntu"],"order":2,"steps":33,"scenarios":[]},{"id":"redis-backup","file":"install/guides/redis-backup.json","group":"Redis","engineId":"redis","category":"backup","title":"Redis backup and restore (RDB, AOF, replica and cluster)","summary":"Sets up a backup routine for an installed Redis or Valkey (single instance, master/replica with Sentinel, or Redis Cluster) and describes how to restore from backup. Step by step: RDB backups (save rule, BGSAVE, LASTSAVE","level":"Intermediate","duration":"30–60 min","topology":"Single instance, replica or Redis Cluster server (Redis 7.2 / 8.x, Valkey)","tags":["redis","valkey","backup","backup","restore","recovery","rdb","dump.rdb","bgsave","lastsave","aof","appendonly","bgrewriteaof","appendonlydir","redis cluster","systemd timer","cron","retention","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 day-to-day operations and failure scenarios (Cluster and Sentinel)","summary":"Not an installation: a scenario-based runbook for managing an installed Redis Cluster or a Sentinel-managed master/replica setup day to day and recovering it when it breaks (Redis 7.2, Redis 8 and Valkey). Cluster: healt","level":"Advanced","duration":"5 min (check) – 1 hour (reshard)","topology":"Redis Cluster (3+ masters + replicas) or Sentinel (1 master + replicas + 3 Sentinels)","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","operations","failure"],"order":20,"steps":3,"scenarios":[{"k":"scenario","v":"c_health","l":"Cluster health check (--cluster check, CLUSTER INFO / NODES)","m":"Redis Cluster: monitoring 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 cluster status cluster health cluster_state Read-only: are all slots covered, do the nodes agree on "},{"k":"scenario","v":"c_add","l":"Add a node to the cluster (master or replica)","m":"Redis Cluster: node management add-node --cluster add-node add cluster node add node scale out --cluster-slave --cluster-replica cluster-master-id new master new replica Node is not empty. Either the node already knows other nodes New node added correctly increase capacity A new instance (cluster-enabled yes, empty) is ready: it joins the cluster as a master and receives slots, or becomes a replic"},{"k":"scenario","v":"c_remove","l":"Remove a node from the cluster (empty its slots + del-node)","m":"Redis Cluster: node management del-node --cluster del-node remove cluster node remove node scale in empty slots cluster-weight Node … is not empty is not empty! Reshard data away and try again CLUSTER RESET SOFT decommission node A master or replica is being retired: the master's slots are moved to the other masters, then the node is removed from the cluster. No downtime; ASK redirections occur du"},{"k":"scenario","v":"c_reshard","l":"Redistribute slots (rebalance / reshard)","m":"Redis Cluster: node management rebalance reshard --cluster rebalance --cluster reshard slot distribution unbalanced 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 move slots Slots or memory are unbalanced across the masters, a new master"},{"k":"scenario","v":"c_failover","l":"Manual failover: promote a replica to master (CLUSTER FAILOVER, FORCE, TAKEOVER)","m":"Redis Cluster: failures and maintenance CLUSTER FAILOVER failover FORCE TAKEOVER manual failover master maintenance promote replica to 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 change master Maintenance is planned on the master's server (planned, no data loss), or the master is unreachable and "},{"k":"scenario","v":"c_replace","l":"Replace a failed master / node (FORGET, new replica, --cluster fix)","m":"Redis Cluster: failures and maintenance CLUSTER FORGET --cluster fix node lost failed node fail noaddr slots not covered CLUSTERDOWN replace node failed master handshake ERR Can't forget my master! CLUSTERDOWN Hash slot not served server lost disk failure A node's server is permanently lost: if its replica became master, the old node is forgotten and a new replica is added in its place; if it had "},{"k":"scenario","v":"s_health","l":"Sentinel status: master, replicas, Sentinels, quorum","m":"Sentinel SENTINEL MASTERS SENTINEL master get-master-addr-by-name CKQUORUM +sdown +odown -odown +switch-master NOQUORUM NOGOODSLAVE sentinel status +tilt -failover-abort-not-elected s_down o_down master_link_status:down sentinel health Read-only: the master address the Sentinels see, the replicas, the other Sentinels, CKQUORUM and the recent failover events. Start here if you are not sure what to "},{"k":"scenario","v":"s_failover","l":"Manual failover with Sentinel (SENTINEL FAILOVER)","m":"Sentinel SENTINEL FAILOVER sentinel failover switch-master planned failover INPROG NOGOODSLAVE -failover-abort-no-good-slave NOGOODSLAVE No suitable replica to promote sentinel maintenance Maintenance is planned on the master's server: Sentinel promotes the most suitable replica to master and points the other replicas and the clients to the new master. Downtime 1–2 s; no data loss while the master"},{"k":"scenario","v":"s_replace","l":"Add / remove / replace a node in a Sentinel setup (SENTINEL RESET)","m":"Sentinel SENTINEL RESET sentinel reset replicaof new replica new sentinel add sentinel remove sentinel old replica still listed num-other-sentinels known-replica known-sentinel old sentinel still listed A replica or Sentinel has been permanently removed or newly added: the new replica connects with REPLICAOF, and the Sentinels' stale entries are cleared with SENTINEL RESET."},{"k":"scenario","v":"s_quorum","l":"Change the quorum and Sentinel settings (SENTINEL SET)","m":"Sentinel SENTINEL SET quorum down-after-milliseconds failover-timeout parallel-syncs CKQUORUM NOQUORUM failover too late unnecessary failover The number of Sentinels has changed, or failover triggers too late/too early: quorum, down-after-milliseconds, failover-timeout and parallel-syncs are changed on all Sentinels."},{"k":"scenario","v":"memory","l":"Memory pressure: usage, fragmentation, eviction diagnosis","m":"Memory and keys 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 memory full Out of memory: Killed process oom-killer used_memory_rss mem_clients_normal omem used_memory is approaching maxmemory, 'OOM command not allowed' errors, evictio"},{"k":"scenario","v":"bigkeys","l":"Find big and hot keys (--bigkeys, --memkeys, --hotkeys)","m":"Memory and keys --bigkeys --memkeys --hotkeys big key large key hot key frequently accessed key MEMORY USAGE UNLINK slowlog latency SCAN HGETALL KEYS * --keystats latency spikes latency spike One node's memory or CPU is higher than the others', there are latency spikes, DEL takes long: the largest and most frequently accessed keys are found and cleaned up safely (UNLINK)."},{"k":"add_role","v":"master","l":"Master (receives slots: rebalance after adding)","m":"Joins as an empty master, then receives slots through a rebalance. Adds capacity; a replica should also be added for the new master."},{"k":"add_role","v":"replica","l":"Replica (copy of a master)","m":"Becomes a copy of a master and takes over if the master fails. Data equal to the master's memory goes over the network (full sync)."},{"k":"rs_mode","v":"rebalance","l":"Rebalance: distribute the slots evenly across the masters (including empty masters)","m":"All masters get an equal share (including a new master without slots). A simulation is shown first. The most common operation after adding a new master."},{"k":"rs_mode","v":"move","l":"Reshard: move a given number of slots from one master to another","m":"A given number of slots is moved from one master to another: to relieve a hot/large master or to drain a master gradually."},{"k":"fo_mode","v":"default","l":"Planned (default): syncs with the master and takes over, no data loss","m":"The master must be running."},{"k":"fo_mode","v":"force","l":"FORCE: the master is unreachable, take over without waiting for sync","m":"The master's last writes may be lost; a majority of masters still has to vote."},{"k":"fo_mode","v":"takeover","l":"TAKEOVER: a majority of masters is also gone, take over without a vote","m":"Last resort: risk of split brain and epoch collision."},{"k":"srep_op","v":"add_replica","l":"Add a new replica","m":"Redis on the new server connects to the current master; the Sentinels discover it by themselves."},{"k":"srep_op","v":"rm_replica","l":"Remove a replica permanently","m":"The replica is shut down and its entry is cleared on all Sentinels."},{"k":"srep_op","v":"add_sentinel","l":"Add a new Sentinel","m":"A Sentinel is added on a new server (the total number of Sentinels should stay odd: 3 → 5)."},{"k":"srep_op","v":"rm_sentinel","l":"Remove a Sentinel permanently","m":"The Sentinel is shut down and its entry is cleared on the others; then the quorum is reviewed."}]},{"id":"redis-security","file":"install/guides/redis-security.json","group":"Redis","engineId":"redis","category":"security","title":"Redis / Valkey security hardening (ACL, TLS, dangerous commands)","summary":"Not an installation or day-to-day operations guide: a scenario-based runbook for hardening a running Redis or Valkey (single server, replica + Sentinel or Cluster) without downtime. Read-only security audit, ACL users an","level":"Advanced","duration":"5 min (audit) – 90 min (full hardening)","topology":"Single server, master + replica + Sentinel, or Redis Cluster","tags":["redis security","redis security","valkey","hardening","hardening","ACL","ACL SETUSER","aclfile","requirepass","masterauth","protected-mode","bind","rename-command","enable-protected-configs","TLS","tls-replication","sentinel password","CIS"],"order":20,"steps":7,"scenarios":[{"k":"op","v":"audit","l":"Security status audit (read-only)","m":"Audit redis security audit redis security audit CIS Redis hardening hardening redis exposed exposed redis unauthorized redis redis without password -NOAUTH crackit redis cryptominer kinsing NOAUTH Authentication required redis security check redis pentest Changes nothing. Passwordless access, the default user, listening addresses, protected-mode, dangerous commands, TLS, the Sentinel password and "},{"k":"op","v":"all","l":"Full hardening (all in order)","m":"Audit full hardening full hardening redis security project pentest finding Audit → ACL users (admin, application, replication, Sentinel) and disabling the default user → bind/protected-mode/firewall → dangerous commands and protected configs → TLS → file permissions → final audit. Rolling on replicas/Sentinel; risky steps ask for confirmation."},{"k":"op","v":"acl","l":"ACL users and disabling the default user (Redis 6+)","m":"Identity and privileges ACL ACL SETUSER aclfile default user default user NOPERM WRONGPASS invalid username-password pair -NOAUTH Authentication required user default on nopass masteruser least privilege least privilege 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 per-user password ACL SAVE Everyone connects "},{"k":"op","v":"auth","l":"Redis without a password: requirepass / masterauth (quick fix)","m":"Identity and privileges requirepass masterauth auth-pass redis without password 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 set password There is no password at all and no time to move to ACL. A shared password is set online on a"},{"k":"op","v":"net","l":"Restrict network access (bind 0.0.0.0, protected-mode, firewall)","m":"Network and encryption bind 0.0.0.0 protected-mode no DENIED Redis is running in protected mode 0.0.0.0:6379 port 6379 open 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 listens on all interfaces or runs with `protected-mode no`. bind is reduced to loc"},{"k":"op","v":"cmds","l":"Dangerous commands and CONFIG abuse (FLUSHALL, CONFIG, DEBUG, MODULE)","m":"Network and encryption 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 malicious module SLAVEOF exploit Aga"},{"k":"op","v":"tls","l":"Encrypt connections: TLS (clients, replication, Sentinel, cluster bus)","m":"Network and encryption TLS SSL tls-port tls-replication tls-cluster tls-auth-clients unencrypted connection 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 encrypted connection encryption in transit Traffic is unencrypted. The TLS port is opened online next to the existing "},{"k":"op","v":"perms","l":"File permissions and service user","m":"Traces and files redis.conf permissions world readable chmod dump.rdb appendonly runs as root running as root file permissions Permission denied 640 User=redis redis root redis.conf (contains passwords), the ACL file, RDB/AOF and log files are readable by other users, or Redis runs as root. Permissions are tightened to 640/750 and ownership is set to the service user."}]},{"id":"es-cluster","file":"install/guides/es-cluster.json","group":"Elasticsearch","engineId":"elasticsearch","category":"install","title":"Elasticsearch cluster installation (3+ nodes, TLS, Kibana)","summary":"Builds an Elasticsearch 8.x / 9.x cluster of 3 or more nodes from scratch: Elastic repository, heap sized to RAM, memory lock, vm.max_map_count, automatic security on the first node (TLS + elastic password), the other no","level":"Intermediate","duration":"60–90 min","topology":"3+ Elasticsearch nodes (+ optional Kibana server)","tags":["elasticsearch","elastic","kibana","cluster","elasticsearch cluster","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":"Elasticsearch rolling upgrade (8.x minor or 8.19 → 9.x)","summary":"Not an installation: moves a running Elasticsearch cluster to a new version without downtime by upgrading the nodes one at a time (a minor upgrade within 8.x, or the 8.19 → 9.x major upgrade). The steps cover pre-checks ","level":"Advanced","duration":"15–30 min per node","topology":"Existing cluster with 3+ nodes (+ Kibana)","tags":["elasticsearch","upgrade","rolling upgrade","minor","major","9.x","8.19","kibana","upgrade assistant","deprecation","snapshot","rollback","rolling upgrade","node roles","master-eligible","deprecation api","versionlock"],"order":2,"steps":34,"scenarios":[{"k":"path","v":"minor","l":"Within the same major version (e.g. 8.18 → 8.19, 9.1 → 9.2)","m":"Within the same major version (8.18 → 8.19, 9.1 → 9.2): the repository does not change; the deprecation check is still done. Skipping intermediate minor versions is supported."},{"k":"path","v":"major","l":"Major version: 8.19.x → 9.x","m":"The source cluster must first be upgraded to 8.19.x (latest patch). The pre-checks include the Upgrade Assistant / deprecation API and system feature migration steps."}]},{"id":"es-snapshot","file":"install/guides/es-snapshot.json","group":"Elasticsearch","engineId":"elasticsearch","category":"backup","title":"Elasticsearch snapshot and restore (fs / S3 repository, SLM)","summary":"Sets up snapshots (backups) for an installed Elasticsearch 8.x / 9.x cluster and covers recovery: a shared file system (NFS + path.repo) or S3 (keystore) repository, activation through a rolling restart and verification,","level":"Intermediate","duration":"45–90 min","topology":"Elasticsearch 8.x / 9.x cluster (1–N nodes) + shared directory (NFS) or S3-compatible object storage","tags":["elasticsearch","snapshot","restore","backup","backup","restore","repository","path.repo","nfs","s3","keystore","slm","snapshot lifecycle","retention","rename_pattern","feature_states","readonly repository","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":"Elasticsearch day-to-day operations and failure scenarios (8.x / 9.x)","summary":"Not an installation: a scenario-based runbook for running an installed Elasticsearch 8.x / 9.x cluster day to day and recovering it when it breaks. Health and allocation explain, resolving unassigned shards (disk, filter","level":"Advanced","duration":"5 min (check) – hours (reindex, draining a node)","topology":"Elasticsearch 8.x / 9.x cluster (1–N nodes), TLS and authentication enabled","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","operations","troubleshooting"],"order":20,"steps":6,"scenarios":[{"k":"scenario","v":"health","l":"Cluster health: status, nodes, unassigned shards, disk","m":"Monitoring and diagnostics red yellow _cluster/health _cat/shards UNASSIGNED allocation explain _cluster/allocation/explain status red status yellow cluster health pending tasks master_not_discovered_exception NoNodeAvailableException circuit_breaking_exception heap cluster health check Read-only: green/yellow/red, node list, unassigned (UNASSIGNED) shards and their cause (allocation explain), dis"},{"k":"scenario","v":"unassigned","l":"Resolve an unassigned (UNASSIGNED) shard (based on explain)","m":"Monitoring and diagnostics 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 REPLIC"},{"k":"scenario","v":"watermark","l":"View / change the disk watermark settings","m":"Disk and blocks watermark disk.watermark low disk watermark high disk watermark flood stage disk watermark disk is full disk full cluster.routing.allocation.disk cluster.routing.allocation.disk.watermark.high disk usage exceeded disk threshold Shards are being moved or not allocated because of disk usage; the watermark thresholds (low / high / flood_stage) are to be changed temporarily or permanen"},{"k":"scenario","v":"readonly","l":"Index became read-only: remove the read_only_allow_delete block","m":"Disk and blocks 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 read only read-only blocked by: [FORBIDDEN/12/index read-only / allow delete (api)] index read-only write rejected Writes are rejected with 'FORBIDDEN/12/index read-only / allo"},{"k":"scenario","v":"add_node","l":"Add a node to the cluster (enrollment token)","m":"Node management add a node 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 increase capacity new node More capacity is needed: Elasticsearch has been installed on a new server, which joins the cluster with an enrollment to"},{"k":"scenario","v":"remove_node","l":"Remove a node safely (exclude, drain, shut down)","m":"Node management remove a node remove node decommission cluster.routing.allocation.exclude._name exclude voting_config_exclusions relocating drain node voting configuration retire node server replacement A node is being retired or its hardware replaced: its shards are moved to the other nodes with an allocation exclude, the node is verified to be empty, it is given a voting exclusion if it is maste"},{"k":"scenario","v":"reindex","l":"Reindex: zero-downtime cutover to a new index with an alias for a mapping/settings change","m":"Index management _reindex reindex change mapping mapper_parsing_exception illegal_argument_exception: mapper alias _aliases seamless cutover zero downtime task version_conflict_engine_exception cannot be changed from type mapper [ change analyzer A mapping field type, the analyzer or the shard count must change: a new index is created, the data is copied with _reindex, and the alias is switched at"},{"k":"scenario","v":"ilm","l":"Set up / troubleshoot index lifecycle (ILM) and rollover","m":"Index management 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 not being deleted log index growing Log/time series indices keep growing or are not deleted: an ILM policy (rollover + delete), an index templat"},{"k":"scenario","v":"resize","l":"Fix the shard count: _shrink (reduce) / _split (increase)","m":"Index management _shrink _split shrink split shard count number_of_shards too many shards too many small 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 The index's shard count was chosen badly: very small shards (oversharding) are merged with _shrink, very large shards are divided wit"},{"k":"scenario","v":"snaprestore","l":"Restore a single index from a snapshot under a new name","m":"Index management snapshot restore _restore single index restore rename_pattern rename_replacement index was deleted deleted index index_not_found_exception cannot restore index because an open index with same name already exists snapshot_missing_exception accidentally deleted recover index An index was deleted or corrupted: only that index is restored from the snapshot repository under a new name "},{"k":"un_fix","v":"retry","l":"Retry limit exceeded: retry (_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'. Fix the underlying error (disk, mapping, memory) first."},{"k":"un_fix","v":"replicas","l":"More replicas than nodes: reduce the replicas","m":"same_shard a copy of this shard is already allocated to this node explain: same_shard (a copy cannot be placed on the same node); replicas 0 on a single-node cluster."},{"k":"un_fix","v":"filter","l":"Allocation filtering / exclude is blocking: remove the filter","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…]' or a cluster exclude)."},{"k":"un_fix","v":"disk","l":"Disk watermark is blocking","m":"disk_threshold the node is above the low watermark the node is above the high watermark explain: disk_threshold decider. Points you to the watermark scenario."},{"k":"un_fix","v":"stale","l":"Last resort: allocate a primary from an old (stale) copy","m":"no_valid_shard_copy all the copies of its data in the cluster are stale or corrupt allocate_stale_primary The up-to-date copy of the primary is lost and only an old copy exists: the latest writes in that copy are lost."},{"k":"un_fix","v":"empty","l":"Very last resort: allocate an empty primary (all data in the shard is lost)","m":"no_valid_shard_copy there are no copies of its data in the cluster allocate_empty_primary No copy exists; if the index cannot be restored from a snapshot."},{"k":"wm_op","v":"show","l":"Show only (disk usage and current thresholds)","m":"Read-only: disk per node and the current thresholds."},{"k":"wm_op","v":"set","l":"Change the thresholds","m":"Raise the thresholds temporarily (e.g. `50gb` of free space instead of a percentage on a large disk). Reset to the defaults once the problem is solved."},{"k":"wm_op","v":"reset","l":"Reset to defaults (85% / 90% / 95%)","m":"Removes previously changed thresholds and returns to the defaults (85% / 90% / 95%)."},{"k":"ilm_op","v":"setup","l":"New policy + template + initial index (write alias)","m":"Creates the policy (rollover + delete), the index template and the initial index (`-000001`, write alias). For a new log stream."},{"k":"ilm_op","v":"debug","l":"Why are existing indices not progressing? (explain, retry)","m":"Indices do not roll over or are not deleted: `_ilm/explain` shows the failing step; once the cause is fixed, a retry is done."},{"k":"ilm_op","v":"rollover","l":"Roll over manually","m":"A new write index is created without waiting for the conditions (after a mapping change or for a bloated index)."},{"k":"rs_kind","v":"shrink","l":"_shrink: reduce the shard count (the target must be a factor of the current count)","m":""},{"k":"rs_kind","v":"split","l":"_split: increase the shard count (the target must be a multiple of the current count)","m":""}]},{"id":"es-security","file":"install/guides/es-security.json","group":"Elasticsearch","engineId":"elasticsearch","category":"security","title":"Elasticsearch security hardening (security, TLS, roles, audit)","summary":"Not an installation or day-to-day operations guide: a scenario-based runbook for hardening a running Elasticsearch cluster. Read-only security audit, enabling disabled security together with transport TLS (full cluster r","level":"Advanced","duration":"10 min (audit) – 2 hours (full hardening)","topology":"Elasticsearch cluster with 1–12 nodes (+ Kibana)","tags":["elasticsearch security","elasticsearch security","hardening","hardening","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":"Security status audit (read-only)","m":"Audit elasticsearch security audit elasticsearch security audit CIS Elasticsearch hardening hardening exposed elasticsearch exposed elasticsearch xpack.security.enabled missing authentication credentials for REST request security_exception elasticsearch security check elasticsearch pentest RISK Changes nothing. Whether security is enabled, unauthenticated access, HTTP/transport TLS, anonymous acce"},{"k":"op","v":"all","l":"Full hardening (all in order)","m":"Audit full hardening full hardening elastic security project pentest finding Audit → security + transport/HTTP TLS (if disabled; full cluster restart) → users/roles/API keys, disabling anonymous, taking elastic out of daily use → network and IP filtering → audit logging (if the license allows) → snapshot repository access → Kibana connection → final audit."},{"k":"op","v":"enable","l":"Security disabled: enable security and transport TLS","m":"Identity and encryption xpack.security.enabled: false security disabled unauthenticated access Elasticsearch built-in security features are not enabled Transport SSL must be enabled if security is enabled invalid configuration for xpack.security.transport.ssl 7.x upgrade security Security is disabled xpack.security.transport.ssl no authentication A cluster with `xpack.security.enabled: false`, or "},{"k":"op","v":"httptls","l":"Move HTTP (REST) traffic to TLS","m":"Identity and encryption 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 password in clear on the network Security is enabled but port 9200 is plain HTTP: passwords can be read on the network. HTTP certificates are distributed and t"},{"k":"op","v":"users","l":"Users, roles, API keys; elastic and anonymous access","m":"Identity and encryption elastic user 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 api key expiration api key without expiration break-glass Applications connect wi"},{"k":"op","v":"auditlog","l":"Enable audit logging","m":"Traces and access audit log xpack.security.audit.enabled audit logging _audit.json audit trail who deleted GDPR PCI DSS xpack.security.audit.logfile.events.include access log Who accessed which index, failed login attempts, authorization denials and security setting changes must be recorded. `xpack.security.audit.enabled` (static, rolling restart) — requires a license (the step checks the license)"},{"k":"op","v":"net","l":"Network access and IP filtering","m":"Traces and access network.host 0.0.0.0 0.0.0.0:9200 port 9200 open xpack.security.transport.filter xpack.security.http.filter ip filtering shodan firewall 0.0.0.0:9300 network.host: 0.0.0.0 _site_ public IP 9200/9300 listen on all interfaces or are open to everyone. `network.host` is reduced to the internal IP, and with the firewall and Elasticsearch IP filtering only the nodes, Kibana and the app"},{"k":"op","v":"snapshot","l":"Secure snapshot repository access","m":"Traces and access snapshot repo path.repo repository_exception s3.client access_key readonly repository snapshot security snapshot_operator Object Lock backup security The snapshot directory is world-readable, the privilege to manage the repository is broad, or cloud access keys are in elasticsearch.yml. Directory permissions, a snapshot operator role, the keystore and a read-only repository regis"},{"k":"op","v":"kibana","l":"Secure the Kibana connection","m":"Traces and access kibana_system elasticsearch.username: elastic kibana.yml password 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 connects with the `elastic` user or without encr"}]},{"id":"mon-prometheus-grafana","file":"install/guides/mon-prometheus-grafana.json","group":"Monitoring","engineId":null,"category":"monitoring","title":"Database monitoring with Prometheus + Grafana","summary":"Sets up a central monitoring server (Prometheus, Alertmanager, Grafana) and adds node_exporter plus the database exporters you select to the monitored servers: PostgreSQL, MySQL, Oracle, SQL Server, MongoDB, Redis, Elast","level":"Intermediate","duration":"60–120 min","topology":"1 monitoring server + monitored servers","tags":["prometheus","grafana","alertmanager","node_exporter","postgres_exporter","mysqld_exporter","oracledb_exporter","sql_exporter","mongodb_exporter","redis_exporter","elasticsearch_exporter","database monitoring","monitoring","alert","dashboard"],"order":1,"steps":44,"scenarios":[]},{"id":"ora19-install","file":"install/guides/ora19-install.json","group":"Oracle","engineId":"oracle","category":"install","title":"Oracle Database 19c single instance installation (EE / SE2)","summary":"Installs Oracle Database 19c from scratch on a single server running Oracle Linux / RHEL 7.9, 8 or 9: operating system preparation (preinstall RPM or manual), OFA directories, silent software installation together with t","level":"Intermediate","duration":"90–150 min","topology":"1 server","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":"Installing Oracle AI Database 26ai / 23ai (Linux)","summary":"Installs Oracle AI Database 26ai (the release that replaces 23ai, version number 23.26.x) on a single server; you choose one of three official paths: an OFA installation from the gold image ZIP for production (runInstall","level":"Intermediate","duration":"45–120 min","topology":"1 server","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":"Storage preparation for Oracle: multipath, persistent device names, ASM disks","summary":"Applied once, before installing Oracle Grid Infrastructure, Oracle Restart or the database: SAN LUNs are identified by WWID, DM-Multipath aliases are configured where needed, and ASM disks get a persistent name and grid:","level":"Intermediate","duration":"30–90 min","topology":"1 server, or 2–8 RAC nodes + SAN/iSCSI/VM disks","tags":["oracle","asm","multipath","device-mapper-multipath","dm-multipath","multipath.conf","wwid","udev","udev rule","asmlib","oracleasm","afd","asm filter driver","lvm","xfs","fstab","san","lun","storage preparation","storage","disk","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 (single server)","summary":"Installs Oracle Restart (Grid Infrastructure 19c standalone, HA_CONFIG) and ASM on a single server: grid user and ASM groups, persistent disk names (udev, ASMLib or ASM Filter Driver; single-path or multipath disks), gri","level":"Advanced","duration":"90–150 min","topology":"1 server + non-shared ASM disks","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":"Applying an Oracle Database 19c Release Update (RU)","summary":"Applies a quarterly Release Update to a single-instance or Oracle Restart (Grid Infrastructure + ASM) 19c database: OPatch update, conflict and space prechecks, ORACLE_HOME backup and guaranteed restore point, opatch app","level":"Intermediate","duration":"60–120 min (outage: in-place 30–60 min, out-of-place 10–20 min)","topology":"1 server (single instance or Oracle Restart); for RAC, the rolling patch guide","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":"Oracle RAC 19c installation (Grid Infrastructure + ASM + RAC database)","summary":"Installs Oracle Grid Infrastructure 19c (Flex ASM, SCAN) and an Oracle RAC 19c database from scratch on two or more nodes: network and DNS plan, shared disk preparation (udev or ASMLib; multipathed or single-path), users","level":"Advanced","duration":"4–6 hours","topology":"2–8 nodes + shared disk (SAN/iSCSI) + SCAN in DNS","tags":["oracle","rac","19c","grid infrastructure","clusterware","asm","scan","gridsetup","dbca","srvctl","crsctl","cluster","ha","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 rolling Release Update (opatchauto)","summary":"Applies a quarterly GI Release Update to a running 2–8 node RAC 19c cluster in rolling mode, without downtime: OPatch update, conflict check with opatchauto analyze, OCR backup and fallback point, node-by-node apply with","level":"Advanced","duration":"45–90 min per node","topology":"Existing 2–8 node RAC 19c","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 day-to-day operations and failure scenarios","summary":"Day-to-day operation and incident response for an installed, running Oracle RAC (Grid Infrastructure 19c) cluster: health check, node eviction diagnosis, taking a node down for maintenance, planned shutdown and startup o","level":"Advanced","duration":"15 min – 3 hours (depending on the scenario)","topology":"Existing 2–8 node RAC (Grid Infrastructure 19c; 23ai/26ai differences in the notes)","tags":["oracle","rac","grid infrastructure","clusterware","crsctl","srvctl","olsnodes","cluvfy","addnode","add node","remove node","delete node","ocr","olr","voting disk","votedisk","ocrconfig","ocrcheck","scan","vip","oifcfg","interconnect","eviction","ocssd","misscount","service","relocate","application continuity","taf","fan","day-to-day operations","failure"],"order":12,"steps":10,"scenarios":[{"k":"scenario","v":"health","l":"Cluster health check (daily / before maintenance)","m":"Checks and diagnosis crsctl check cluster crsctl stat res -t cluvfy OFFLINE resource CRS-4535 CRS-4530 CRS-4639 health check cluster health check INTERMEDIATE CRS-4537 CRS-2765 CRS-2878 CRS-4700 CRS-4701 crsctl check crs cluster state cluster status resource down resource offline orachk tfactl pre-maintenance check Changes nothing: checks the Clusterware stack, resources, OCR/voting, ASM, intercon"},{"k":"scenario","v":"eviction","l":"A node was evicted from the cluster or rebooted on its own (eviction diagnosis)","m":"Checks and diagnosis 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 node evicted 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 node restarted server rebooted unexpectedly rebootless restart instance ev"},{"k":"scenario","v":"stopnode","l":"Stop a node for maintenance and bring it back","m":"Start / stop crsctl stop crs crsctl start crs node maintenance OS patch reboot CRS-4133 CRS-4123 drain_timeout srvctl stop instance CRS-2673 CRS-2677 CRS-2675 crsctl disable crs crsctl enable crs maintenance mode maintenance kernel update kernel upgrade firmware Choose it when an operating system patch, kernel or hardware maintenance is planned on a node. The other nodes keep running; the services"},{"k":"scenario","v":"stopall","l":"Planned shutdown and startup of the whole cluster","m":"Start / stop crsctl stop cluster -all crsctl start cluster -all full shutdown cluster shutdown planned outage srvctl stop database CRS-2677 CRS-4133 CRS-4123 crsctl stop crs crsctl start crs SAN maintenance data center maintenance datacenter maintenance power off shut down all servers Choose it when all nodes must be shut down at the same time, such as for SAN or data center maintenance. Full outa"},{"k":"scenario","v":"addnode","l":"Add a new node to the cluster","m":"Add / remove nodes addnode.sh addNode gridSetup.sh -addNode add node 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 add new server extend cluster Choose it when a new server joins the cluster (capacity increase, hardware refresh). No downtime: pre-checks (cluvfy), Grid home (addnode.sh), DB "},{"k":"scenario","v":"delnode","l":"Remove a node from the cluster","m":"Add / remove nodes crsctl delete node deinstall -local dbca -deleteInstance remove node delete node remove node cluvfy stage -post nodedel crsctl unpin css CRS-4658 CRS-4661 runInstaller -updateNodeList srvctl remove instance decommission remove server from cluster decommission old server Choose it when a server is permanently removed from the cluster (return, hardware failure). The instance is de"},{"k":"scenario","v":"service","l":"Add, modify, relocate or remove a service","m":"Services and network srvctl add service srvctl modify service srvctl relocate service srvctl remove service ORA-12514 PRCD-1026 preferred available TAF Application Continuity TAC FAN relocate service PRKO-2017 PRCR-1090 ORA-12514: TNS:listener does not currently know of service srvctl start service srvctl status service failback service did not fail back drain_timeout add service create service Ch"},{"k":"scenario","v":"net","l":"SCAN, VIP, listener port or interconnect change","m":"Services and network srvctl modify scan srvctl modify scan_listener srvctl modify nodeapps srvctl modify listener oifcfg setif oifcfg delif oifcfg getif IP change SCAN IP VIP change interconnect private network ORA-12545 listener port PRCS-1034 PRKO-2310 PRCR-1079 oifcfg HAIP 169.254 port change SCAN change VIP change remote_listener ASMNET1LSNR_ASM Choose it when the SCAN name/IPs, a node VIP, th"},{"k":"scenario","v":"ocr_backup","l":"Back up OCR and OLR, check the backups","m":"OCR, OLR and voting ocrconfig -manualbackup ocrconfig -showbackup ocrconfig -local -manualbackup OLR backup OCR backup OCR backup ocrcheck ocrdump PROT- ocrconfig -copy ocrconfig -showbackuploc OLR backup where is the OCR backup manual OCR backup No downtime: manual OCR backup, OLR backup on every node and copying the backups outside the cluster disks. Choose it before any risky work such as patch"},{"k":"scenario","v":"ocr_restore","l":"OCR corrupt or CRS disk group lost: restore from backup","m":"OCR, OLR and voting PROT-602 PROC-26 CRS-4535 CRS-4000 CRS-1606 ocrconfig -restore crsctl start crs -excl -nocrs OCR corruption OCR corrupted CRS disk group lost voting disk lost ORA-15017 ORA-15063 PROT-35 PROT-19 ORA-15018 ORA-15033 ocrdump OCR restore CRSD does not start Logical corruption check failed Choose it if `ocrcheck` shows corruption, CRSD does not start (PROT-/PROC- errors) or the CRS"},{"k":"scenario","v":"olr_restore","l":"Clusterware does not start on one node: OLR corrupt","m":"OCR, OLR and voting ocrcheck -local ocrconfig -local -restore OLR corrupted OLR corrupt ohasd does not start CRS-4124 CRS-4639 PROCL- CRS-4123 ohasd.trc olr.loc ocrconfig -local -showbackup Local registry integrity check failed OHASD does not start single node does not start Choose it if, while the other nodes are running, `crsctl start crs` cannot bring up OHASD on just one node and `ocrcheck -lo"},{"k":"scenario","v":"ocrvote_move","l":"Move OCR and voting files to another disk group","m":"OCR, OLR and voting crsctl replace votedisk crsctl query css votedisk ocrconfig -add ocrconfig -delete move voting disk votedisk move OCR storage migration ORA-15274 ocrconfig -backuploc asmcmd spmove asmcmd pwmove move CRS disk group move voting files OCR move storage replacement No downtime: ocrconfig -add/-delete and crsctl replace votedisk. Choose it for a storage (SAN) replacement, to increas"}]},{"id":"ora-asm-ops","file":"install/guides/ora-asm-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle ASM day-to-day operations and failure scenarios","summary":"Day-to-day operation of ASM disk groups in an installed Oracle Restart or RAC environment, and what to do when something fails: capacity check (usable_file_mb), rebalance monitoring, adding/replacing/dropping disks, offl","level":"Advanced","duration":"10 min – a few hours (depending on rebalance duration)","topology":"Oracle Restart (single server) or 2–8 node RAC, Grid Infrastructure 19c (23ai/26ai differences in the 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","day-to-day operations","failure"],"order":13,"steps":4,"scenarios":[{"k":"op","v":"capacity","l":"Capacity and disk group health check","m":"Checks and monitoring usable_file_mb required_mirror_free_mb asmcmd lsdg disk group full ASM usage free_mb capacity ORA-15041 ORA-01653 ORA-17502 ORA-15041: diskgroup space exhausted diskgroup space exhausted disk group full ASM disk group full usable_file_mb negative negative usable_file_mb ASM out of space asmcmd du Changes nothing: is a disk group full, how much space is left (the correct readi"},{"k":"op","v":"rebal","l":"Monitor, speed up or slow down a rebalance","m":"Checks and monitoring v$asm_operation gv$asm_operation rebalance REBALANCE POWER asm_power_limit asmcmd lsop est_minutes resync compact slow rebalance rebalance slow rebalance stuck rebalance hung ERRS ARB0 when will rebalance finish Progress and remaining time of an ongoing rebalance/resync (v$asm_operation, est_minutes) and changing the POWER. The database stays open; only the I/O load changes."},{"k":"op","v":"add","l":"Add a new disk to a disk group","m":"Disk operations ADD DISK ALTER DISKGROUP ADD DISK adding a disk freeing space increasing capacity ORA-15041 ORA-15031 ORA-15033 ORA-15014 ORA-15410 ORA-15032 ORA-15075 ORA-17502 add disk new LUN new LUN grow disk group extend diskgroup The disk group is running low on space or needs to grow: prepare the new LUN on all nodes (multipath/udev/ASMLib/AFD), add it with ADD DISK and monitor the rebalanc"},{"k":"op","v":"replace","l":"Replace a disk with a new one (REPLACE DISK)","m":"Disk operations REPLACE DISK disk replacement replace disk ORA-15145 storage migration disk refresh ORA-15221 ORA-15033 replace failed disk replace failed disk disk replacement storage migration storage migration ASM Replaces a failed (offline) disk or a disk due for renewal with a new one in a single operation; only that disk's data is copied (12.1+, NORMAL/HIGH). With EXTERNAL, add + drop is don"},{"k":"op","v":"drop","l":"Drop a disk from a disk group","m":"Disk operations DROP DISK dropping a disk UNDROP DISKS FORMER ORA-15041 ORA-15067 return LUN drop disk DROPPING detach disk remove disk from diskgroup give back LUN To return a LUN or remove it from the storage: with DROP DISK the data is moved to the remaining disks; the disk is not detached until the rebalance has finished and the disk is FORMER. The database stays open."},{"k":"op","v":"failed","l":"A disk is offline or failed (NORMAL/HIGH redundancy)","m":"Disk operations 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 disk went offline SYNCING disk cannot be read I/O error ASM In a NORMAL/HIGH disk group a disk shows as O"},{"k":"op","v":"create","l":"Create a new disk group","m":"Disk group management CREATE DISKGROUP creating a disk group au_size compatible.asm compatible.rdbms failgroup ORA-15018 ORA-15221 ORA-15072 ORA-15031 ORA-15033 create diskgroup new disk group new disk group To create a new data, FRA or application disk group: redundancy, AU size, compatible.asm/rdbms attributes; on RAC, mount on all nodes and the Clusterware resource. Existing disk groups are not"},{"k":"op","v":"mount","l":"Mount or dismount a disk group","m":"Disk group management srvctl start diskgroup srvctl stop diskgroup ALTER DISKGROUP MOUNT DISMOUNT ORA-15027 ORA-15017 disk group will not mount v$asm_client ORA-15063 ORA-15040 ORA-15130 ORA-15032 ORA-15042 PRCR-1065 CRS-2674 MOUNT FORCE diskgroup dismounted disk group will not open insufficient number of disks To take a disk group down in a planned way (dismount) or to bring up a disk group that "},{"k":"op","v":"spbackup","l":"ASM spfile, password file and metadata backup","m":"Configuration asmcmd spbackup asmcmd spget asmcmd pwget asmcmd pwcopy md_backup md_restore ASM spfile backup orapwASM asmcmd spcopy GPnP profile profile.xml ASM password file backup ASM metadata backup ASM backup No downtime: the ASM spfile, the password file, the disk group structure (md_backup) and, on RAC, the GPnP profile are backed up outside ASM, so that the ASM configuration can be rebuilt "},{"k":"op","v":"dstring","l":"Change the ASM disk discovery string (asm_diskstring)","m":"Configuration asm_diskstring asmcmd dsset asmcmd dsget discovery string discovery string ORA-15014 kfod ORA-15020 discovered duplicate ASM disk afd_dsset asm_diskstring change disk not found To change the path pattern ASM uses to look for disks (e.g. moving to multipath, a new udev directory, ASMLib/AFD): first test with kfod, then asmcmd dsset. All existing disks (including OCR/voting) must also "}]},{"id":"ora-autoupgrade","file":"install/guides/ora-autoupgrade.json","group":"Oracle","engineId":"oracle","category":"upgrade","title":"19c → Oracle AI Database 26ai (23ai) upgrade: AutoUpgrade","summary":"Use this for a major release upgrade (19c → 26ai); for a quarterly RU within the same release use the \"Applying an Oracle Database 19c Release Update (RU)\" guide. Uses AutoUpgrade, the tool Oracle recommends: latest auto","level":"Advanced","duration":"2–4 hours (downtime: 30–90 min depending on database size and components)","topology":"1 server, Oracle Restart or RAC (26ai home on all nodes)","tags":["oracle","upgrade","autoupgrade","19c","26ai","23ai","noncdb","pdb","target_cdb","restore point","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 (with Broker; single instance, Oracle Restart, RAC)","summary":"Builds a physical standby for a running 19c primary database with RMAN DUPLICATE … FROM ACTIVE DATABASE and adds it to the Data Guard Broker. The result is a standby that receives and applies redo in real time, a broker ","level":"Advanced","duration":"2–4 hours (depending on data size)","topology":"Primary (single instance / Oracle Restart / RAC 2–8 nodes) + physical standby (single instance / Oracle Restart / RAC) + optional observer","tags":["oracle","data guard","dataguard","standby","physical standby","dgmgrl","broker","rman duplicate","switchover","failover","fast-start failover","observer","19c","dr","rac","oracle restart","srvctl","standby redo log","apply instance"],"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, schema refresh and transportable","summary":"Table, schema, tablespace or full export and import with expdp/impdp, migration between two databases (dump file + scp + checksum, or NETWORK_LINK) and DDL extraction from a dump (SQLFILE). Includes directory, user and p","level":"Intermediate","duration":"30–90 min (depending on data volume)","topology":"1 database (single instance, Oracle Restart or RAC); source + target for a migration","tags":["oracle","data pump","datapump","expdp","impdp","export","import","dump","parfile","remap_schema","network_link","sqlfile","table_exists_action","flashback_time","data migration","migration","logical backup","logical export","rac","scan","cluster","acfs","cron","wallet","scheduling","refresh","schema refresh","prod to test","drop user cascade","transportable tablespace","tts","xtts","full transportable","transportable=always","dbms_tts","transport_set_check","endian","rman convert","cross-platform","11g to 19c","non-cdb to pdb","encryption","encryption_password","compression","attach","kill_job","start_job","continue_client","orphan","dba_datapump_jobs","lob","securefile","data_options"],"order":20,"steps":16,"scenarios":[{"k":"op","v":"export","l":"Export: write a dump file from the database","m":"Export / import expdp export take a dump logical export logical backup ORA-39001 ORA-39000 ORA-39070 ORA-31633 ORA-01555 ORA-39095 ORA-39094 ORA-00439 LRM-00123 UDE-00002 ORA-39127 A table, schema, tablespace or the whole database is written to a dump file (expdp). The database stays open and the application keeps running. This is a logical copy: it does not replace an RMAN backup (point-in-time a"},{"k":"op","v":"import","l":"Import: load a dump file into the database","m":"Export / import impdp import load a 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 A previously taken dump file is loaded with impdp; schema, tablespace and table names can be remapped. The target stays open; the application that us"},{"k":"op","v":"move","l":"Migration: move data from the source database to the target","m":"Migration and refresh data migration migration NETWORK_LINK database link scp checksum ORA-31679 ORA-02085 ORA-39183 Export → file copy → import, or directly with NETWORK_LINK without a dump file. Choose this card if the schema does not exist on the target yet (initial load); if the same schema already exists on the target and is to be replaced with the production copy, choose the \"Schema refresh\""},{"k":"op","v":"sqlfile","l":"Extract only the DDL from a dump file (SQLFILE)","m":"Export / import SQLFILE extract DDL dump contents what is in the dump metadata DBMS_METADATA Nothing is loaded; the CREATE statements of the objects are written to a .sql file. The database is not changed."},{"k":"op","v":"refresh","l":"Schema refresh from production to test","m":"Migration and refresh refresh schema refresh refresh test prod to test production to test UAT refresh DROP USER CASCADE ORA-01940 ORA-02380 ORA-01917 ORA-31684 ORA-00031 TABLE_EXISTS_ACTION=REPLACE A schema that already exists in test/UAT is replaced with its current production version: backup of the target schema, closing sessions, DROP USER … CASCADE or TABLE_EXISTS_ACTION=REPLACE, preserving pa"},{"k":"op","v":"tts","l":"Move a tablespace by copying datafiles (transportable tablespace)","m":"Transportable (datafile copy) 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 Large tablespaces (hundreds of GB and more) are moved by copying datafiles rather than row by row; same or different platform/endian (RMAN CONVERT). The charact"},{"k":"op","v":"fulltts","l":"Move the whole database: full transportable (11g/12c → 19c, non-CDB → PDB)","m":"Transportable (datafile copy) full transportable TRANSPORTABLE=ALWAYS FULL=Y VERSION=12 11g to 19c 11g to 19c non-CDB to PDB migrate to PDB migration upgrade by migration ORA-39123 ORA-01647 ORA-29349 ORA-39127 With FULL=Y TRANSPORTABLE=ALWAYS the user tablespaces are moved to a new PDB as datafiles and everything else (users, PL/SQL, grants) as metadata; VERSION=12 on an 11.2.0.3+ source. On the "},{"k":"op","v":"jobfix","l":"Interrupted / stalled Data Pump job: monitor, resume, terminate","m":"Monitoring and failures ATTACH START_JOB CONTINUE_CLIENT KILL_JOB STOP_JOB dba_datapump_jobs orphan master table NOT RUNNING IDLING stalled resumable ORA-39171 ORA-31626 ORA-31633 ORA-39002 ORA-31634 ORA-39097 ORA-39126 An expdp/impdp job is stalled (resumable wait), stopped or interrupted; you want to attach to it with ATTACH and resume it, terminate it with KILL_JOB, or clean up an orphaned mast"}]},{"id":"ora-dataguard-ops","file":"install/guides/ora-dataguard-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle Data Guard daily operations and failure scenarios (19c / 23ai)","summary":"Daily operations and failure recovery runbook for an existing Data Guard (physical standby) configuration; for the initial setup use the Data Guard installation guide. If you are not sure what to do, start with 'Status a","level":"Advanced","duration":"5 min (check) – 4 hours (rebuild)","topology":"Primary (single instance / Oracle Restart / RAC) + physical standby (single instance / Oracle Restart / RAC) + optional observer","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":"Status and lag check (is everything all right?)","m":"Monitoring and checks ORA-16809 ORA-16810 ORA-16853 ORA-16855 ORA-16857 ORA-16766 ORA-16825 apply lag transport lag delay lag SHOW CONFIGURATION WARNING Data Guard status ORA-16854 ORA-16856 ORA-16191 ORA-16198 ORA-03135 ORA-16714 ORA-16792 ORA-16047 standby falling behind high transport lag Data Guard health check health check Read-only: configuration, transport/apply lag, archive gap, processes "},{"k":"scenario","v":"switchover","l":"Planned switchover (swap the roles)","m":"Role transition switchover role change role transition DR drill ORA-16475 ORA-16470 ORA-16467 Ready for Switchover ORA-16541 ORA-16516 switchover_status TO STANDBY SESSIONS ACTIVE switchback switch roles back planned maintenance site switch Primary and standby are healthy and you want to swap the roles in a planned way (maintenance, DR drill, switching the roles back); no data is lost, the applica"},{"k":"scenario","v":"failover","l":"Primary lost: fail over to the standby","m":"Failure and recovery failover primary crashed primary down site loss disaster ORA-16472 ORA-01034 ORA-12541 FAILOVER TO ACTIVATE PHYSICAL STANDBY ORA-12514 ORA-12170 ORA-03113 ORA-16625 ORA-16778 primary unreachable data center loss FAILOVER TO IMMEDIATE emergency The primary cannot be reached and will not come back (server, storage or site loss); the standby becomes the primary. Irreversible: in "},{"k":"scenario","v":"reinstate","l":"After a failover, bring the old primary back as a standby (reinstate)","m":"Failure and recovery reinstate ORA-16661 ORA-16795 ORA-38729 ORA-16653 needs to be reinstated old primary flashback ORA-16649 split-brain old primary does not open FastStartFailoverAutoReinstate REINSTATE DATABASE standby_became_primary_scn A failover was performed and the old primary server is back; if Flashback Database is enabled, it is turned into a standby of the new primary without a rebuild"},{"k":"scenario","v":"gap","l":"Archive gap: the standby is waiting for missing archived logs","m":"Failure and recovery archive gap archive log gap 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 missing archived log missing archive log ORA-16089 `v$archive_gap` returns rows or the alert log shows FAL/gap messages; apply is stuck at a sequence. The databases stay open. If the missing a"},{"k":"scenario","v":"rollfwd","l":"Standby far behind or archived logs gone: roll forward (RECOVER FROM SERVICE)","m":"Failure and recovery roll forward roll forward standby RECOVER STANDBY DATABASE FROM SERVICE nologging ORA-01578 ORA-26040 standby behind resync resynchronize ORA-10567 BACKUP INCREMENTAL FROM SCN standby days behind NONLOGGED BLOCK incremental roll forward The archived logs in the gap exist nowhere, NOLOGGING operations corrupted the standby, or the standby is days behind; the standby is synchron"},{"k":"scenario","v":"rebuild","l":"Rebuild the standby from scratch","m":"Failure and recovery rebuild rebuild standby re-create standby ORA-16795 DUPLICATE standby lost control file lost ORA-00205 ORA-01157 standby files deleted DROP DATABASE standby disk loss Reinstate is not possible (no flashback) and neither is a roll forward, or the standby's files/control file are lost; the standby is dropped and created again with DUPLICATE. The primary stays open but is unprote"},{"k":"scenario","v":"snapshot","l":"Convert to snapshot standby / convert back (writable copy for testing)","m":"Testing and reporting snapshot standby CONVERT TO SNAPSHOT STANDBY test environment read write standby ORA-16000 ORA-38784 ORA-16433 CONVERT TO PHYSICAL STANDBY SNAPSHOT STANDBY guaranteed restore point open standby for testing You want to open the standby read-write temporarily for testing, then discard the changes and return to a physical standby; the primary is not affected and redo keeps arriv"},{"k":"scenario","v":"apply","l":"Stop / start Redo Apply, change the apply instance, open read-only","m":"Daily operations 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 stopped MRP not running You want to stop or restart MRP for maintenance, move apply to another instance on a RAC standby, or open the standby for reportin"},{"k":"scenario","v":"prot","l":"Protection mode, Fast-Start Failover and observer","m":"Daily operations 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 data protection mode You want to switch between MaxPerformance / MaxAvailability / MaxProtection, enable or disable FSFO, or manage the observer."},{"k":"scenario","v":"files","l":"Adding datafiles, redo logs and standby redo logs / UNNAMED file","m":"Daily operations standby_file_management UNNAMED ORA-01111 ORA-01110 ORA-01157 ORA-01274 add data file add datafile add redo log standby redo log SRL ORA-16789 db_file_name_convert ADD STANDBY LOGFILE ORA-01136 datafile added standby A datafile or redo log is to be added on the primary, an UNNAMED file appeared on the standby (ORA-01111/ORA-01274), or standby redo logs are missing."},{"k":"scenario","v":"backup","l":"Take an RMAN backup from the standby","m":"Daily operations rman standby backup backup from standby archivelog deletion policy RMAN-08137 recovery catalog offload backup RMAN-06820 BACKED UP 1 TIMES CONFIGURE DB_UNIQUE_NAME rman backup on standby You want to run RMAN backups on the standby to offload the backup workload from the primary, and set the archived log deletion policy accordingly."}]},{"id":"ora-backup-strategy","file":"install/guides/ora-backup-strategy.json","group":"Oracle","engineId":"oracle","category":"backup","title":"Oracle backup strategy and automation (RMAN + Data Pump)","summary":"Sets up a permanent, automated backup scheme for an installed database (recovery after a failure is covered in a separate guide: \"RMAN restore and recovery\"): weekly level 0 + daily level 1 (or a full backup every day), ","level":"Intermediate","duration":"60–120 min (excluding the first level 0)","topology":"1 server (single instance or Oracle Restart) or RAC (2–8 nodes)","tags":["oracle","rman","backup","backup","strategy","level 0","level 1","incremental","archivelog","fra","retention","block change tracking","data pump","expdp","crontab","cron","systemd timer","dbms_scheduler","automation","wallet","mkstore","rac","oracle restart","snapshot controlfile","asm","/etc/cron.d","ol7","recovery catalog","create catalog","register database","restore test","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":"Restore and recovery with RMAN","summary":"Walks you step by step through the right RMAN recovery at the moment of failure: 19 scenarios — loss of a datafile, SYSTEM/UNDO, tempfile, corrupt blocks, online redo log, controlfile and spfile; full restore, restore to","level":"Advanced","duration":"30 min – several hours (depends on database size)","topology":"1 server (single instance or Oracle Restart) or RAC; source + target in the new-host scenario","tags":["oracle","rman","restore","recovery","database recovery","pitr","point in time","resetlogs","recover table","controlfile","spfile","autobackup","datafile","preview","validate","dbid","new server","disaster recovery","rac","oracle restart","cluster_database","srvctl","asm","block corruption","corrupt block","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":"Dry run: RESTORE … PREVIEW / VALIDATE (changes nothing)","m":"Dry run and backup records RESTORE PREVIEW RESTORE VALIDATE restore dry run restore test is my backup working backup test restore drill recovery drill RMAN VALIDATE is the backup valid Nothing is broken; you want to see, without writing any data, that the backups are really sufficient for a recovery (the database stays open). It is also the first step for the monthly restore drill and when you are"},{"k":"scenario","v":"catalog","l":"Backups moved to another directory / RMAN cannot find the backup (CATALOG)","m":"Dry run and backup records CATALOG START WITH RMAN-06023 RMAN-06025 RMAN-06026 ORA-19505 ORA-27037 EXPIRED backup relocated backup moved no backup found CROSSCHECK NFS changed backup not found The backup pieces exist but RMAN does not know about them (moved, NFS changed, copied from another server): you get RMAN-06023/06025. The database stays open."},{"k":"scenario","v":"arch","l":"Restore archive logs only (RESTORE ARCHIVELOG)","m":"Dry run and backup records RESTORE ARCHIVELOG ORA-00308 restore archive logs archive log restore archive gap LogMiner SET ARCHIVELOG DESTINATION You need the archive logs of a specific sequence range on disk for LogMiner, a standby gap or manual recovery; the database stays open and no file is overwritten."},{"k":"scenario","v":"datafile","l":"Loss/corruption of a single datafile or tablespace (the database stays open)","m":"Loss / corruption ORA-01157 ORA-01110 ORA-01116 ORA-27041 ORA-00376 ORA-01113 datafile missing datafile deleted missing datafile lost datafile tablespace lost ORA-27037 ORA-01135 ORA-01115 ORA-01114 datafile corrupt The database is open, only one datafile or tablespace cannot be read (ORA-01157/01110/01116); everything else works. If the file belongs to SYSTEM/UNDO choose the \"SYSTEM or UNDO\" card"},{"k":"scenario","v":"block","l":"Corrupt blocks (ORA-01578, block media recovery)","m":"Loss / corruption ORA-01578 ORA-26040 ORA-01110 corrupt block block corruption corrupt block v$database_block_corruption RECOVER CORRUPTION LIST block media recovery nologging ORA-19566 Marked Corrupt Corrupt block relative dba DBVERIFY A query returns ORA-01578/ORA-26040, a backup stops with ORA-19566 or VALIDATE found corrupt blocks; the rest of the file is intact. The database stays open and on"},{"k":"scenario","v":"sysdf","l":"SYSTEM or UNDO datafile lost (the database does not open)","m":"Loss / corruption ORA-01157 ORA-01110 ORA-01116 ORA-01147 ORA-00376 ORA-01541 SYSTEM datafile UNDO datafile undo tablespace lost system tablespace lost database will not open ORA-01113 system01.dbf undotbs The lost or corrupt file belongs to the SYSTEM or UNDO tablespace: it cannot be taken offline, so the database (only that PDB if it is a PDB file) is shut down and the file is restored in MOUNT."},{"k":"scenario","v":"temp","l":"Tempfile lost or corrupt (TEMP tablespace)","m":"Loss / corruption ORA-01157 ORA-01187 ORA-25153 ORA-01116 ORA-01122 ORA-01110 tempfile temp tablespace temporary tablespace TEMP lost temp01.dbf dba_temp_files Sort/hash operations return ORA-01157/ORA-01187/ORA-25153 and the file is a tempfile (the number in the error message is greater than `db_files`, e.g. `data file 201`). No restore is needed; a new tempfile is added and the database stays op"},{"k":"scenario","v":"redo","l":"Online redo log lost or corrupt","m":"Loss / corruption ORA-00313 ORA-00312 ORA-00327 ORA-00366 ORA-00367 ORA-00353 ORA-00354 ORA-00316 ORA-01624 redo log lost online redo lost redo log CLEAR LOGFILE ORA-00314 ORA-00320 ORA-00321 ORA-00340 ORA-00350 ORA-00470 CLEAR UNARCHIVED ORA-00313/00312 (cannot be opened) or ORA-00327/00366/00367 (corrupt) in the alert log. If one member is lost or the group is INACTIVE, the database stays open; "},{"k":"scenario","v":"controlfile","l":"All controlfile copies lost","m":"Loss / corruption ORA-00205 ORA-00202 ORA-00210 ORA-27037 controlfile missing control file lost RESTORE CONTROLFILE autobackup ORA-00206 ORA-00214 ORA-00221 ORA-00227 controlfile corrupt The instance cannot find or read the controlfile at the MOUNT stage (ORA-00205/00202/00210); datafiles and redo are intact. If one of the copies is intact, no restore is needed (the diagnosis step shows this); if "},{"k":"scenario","v":"spfile","l":"spfile lost","m":"Loss / corruption ORA-01078 LRM-00109 ORA-01565 spfile lost spfile missing parameter file RESTORE SPFILE CRS-5017 PRCR-1079 spfile deleted The spfile was deleted or corrupted; the instance does not start (ORA-01078/LRM-00109), or it is running but the file is gone. Datafiles are not touched and there is no data loss; if the instance is still running there is no outage either."},{"k":"scenario","v":"full","l":"Full restore, same server (datafiles lost; controlfile and redo intact)","m":"Loss / corruption all datafiles missing all datafiles lost full restore RESTORE DATABASE disk failure storage failure disk lost LUN lost datafiles deleted All or most datafiles are gone, the controlfile and online redo logs are intact; the database is shut down and there is no data loss. If the old disk/directory of the files can no longer be used choose the \"another directory\" card, if the contro"},{"k":"scenario","v":"relocate","l":"Disk gone: restore the database to another directory / ASM (SET NEWNAME)","m":"Loss / corruption SET NEWNAME SWITCH DATAFILE ALL restore to another directory restore to ASM new location disk replaced mount point lost relocate ORA-15001 ORA-15040 disk group dismounted The disk or disk group holding the datafiles is unusable; the controlfile and redo are intact. The files are restored to a new directory or ASM disk group; the database is shut down and there is no data loss."},{"k":"scenario","v":"pitr","l":"Point-in-time / SCN recovery (PITR, RESETLOGS)","m":"Point-in-time recovery and rollback PITR point in time recovery point-in-time restore recover to SCN wrong DELETE wrong UPDATE TRUNCATE user error logical error UNTIL TIME data deleted DROP USER UNTIL SCN RESETLOGS A wrong DELETE/UPDATE/TRUNCATE or a faulty application release: takes the database (or only the PDB) back to a point in the past; all changes after that point are lost (RESETLOGS). Look"},{"k":"scenario","v":"flashback","l":"Rewind with Flashback Database (restore point / SCN / time)","m":"Point-in-time recovery and rollback FLASHBACK DATABASE FLASHBACK PLUGGABLE DATABASE restore point guaranteed restore point rewind database upgrade fallback revert after test FLASHBACK_ON FLASHBACK TO RESTORE POINT Flashback is enabled or a guaranteed restore point exists, and no file is lost: the database is rewound within minutes without a restore (Enterprise Edition). RESETLOGS is required. Is i"},{"k":"scenario","v":"tspitr","l":"Take a tablespace back to a point in the past (TSPITR)","m":"Point-in-time recovery and rollback TSPITR tablespace point in time RECOVER TABLESPACE AUXILIARY DESTINATION DBMS_TTS recover tablespace ORA-29308 The faulty operation affected only objects in one tablespace; the rest of the database must stay at the present (Enterprise Edition). The tablespace is offline for the duration and all objects in it go back to the target point; if only one or two tables"},{"k":"scenario","v":"table","l":"Recover a single table (RECOVER TABLE, 12c+ Enterprise Edition)","m":"Point-in-time recovery and rollback RECOVER TABLE table dropped DROP TABLE restore table table recovery ORA-00942 REMAP TABLE ORA-38305 FLASHBACK TABLE recyclebin PURGE A single table was accidentally dropped or damaged; the rest of the database must stay as it is (12c+ Enterprise Edition). If it was dropped and not purged, check the recycle bin first: `FLASHBACK TABLE … TO BEFORE DROP` takes seco"},{"k":"scenario","v":"pdbdrop","l":"Bring back a dropped PDB (DROP PLUGGABLE DATABASE)","m":"Point-in-time recovery and rollback DROP PLUGGABLE DATABASE PDB dropped dropped PDB RMAN-06813 ORA-65011 ORA-65179 KEEP DATAFILES recover PDB unplug DBMS_PDB.RECOVER A PDB was removed with `DROP PLUGGABLE DATABASE`. If the files are still there, it is plugged in again with the XML; if they were deleted, it is brought back from backup (21c+ a single command, 19c through a temporary CDB). The CDB st"},{"k":"scenario","v":"newhost","l":"Restore to another server (new host, from controlfile autobackup)","m":"Another server and standby new server another server server loss disaster recovery DR new host duplicate database clone test environment server burned down server lost restore to another host The server is completely gone, or a copy/test environment is to be built: the backup is restored to another server (single instance on a file system only). Recovery goes up to the last backed-up archive log; "},{"k":"scenario","v":"fromsvc","l":"Data Guard: get the file from the standby/primary over the network (FROM SERVICE)","m":"Another server and standby RESTORE FROM SERVICE FROM SERVICE RECOVER STANDBY DATABASE restore from standby Data Guard standby lagging archive gap standby controlfile ORA-01110 v$archive_gap apply lag Data Guard is in place: you get a lost datafile on the primary from the standby, and a lagging standby or a lost standby controlfile from the primary, without using backup pieces (12c+)."}]},{"id":"ora-goldengate","file":"install/guides/ora-goldengate.json","group":"Oracle","engineId":"oracle","category":"ha","title":"Oracle GoldenGate Microservices: one-way Oracle → Oracle replication","summary":"Replicates a PDB schema to another database continuously (with a lag of seconds) using GoldenGate 21c or 23ai/26ai Microservices. At the end, Integrated Extract and a Distribution Path run on the source and a Parallel Re","level":"Advanced","duration":"3–5 hours","topology":"Source DB server (OGG source deployment) + target DB server (OGG target deployment); on RAC, GoldenGate on node 1 (or as a cluster resource with XAG)","tags":["oracle","goldengate","ogg","microservices","replication","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 day-to-day operations and failure scenarios (Microservices 21c / 23ai)","summary":"Day-to-day operations and failure recovery runbook for an installed GoldenGate Microservices replication (for installation see the GoldenGate installation guide). If you are not sure what to do, start with 'Health and la","level":"Advanced","duration":"5 min (check) – 2 hours (upgrade, adding a table)","topology":"Source server (source deployment, Integrated Extract, Distribution Path) + target server (target deployment, Parallel Replicat)","tags":["oracle","goldengate","ogg","microservices","adminclient","runbook","abend","extract","replicat","lag","stats","info all","heartbeat","report","discard","reperror","handlecollisions","cdr","conflict","add trandata","schematrandata","aftercsn","atcsn","mapexclude","tableexclude","resync","purge trail","purgeoldextracts","trail","alter extract scn","etrollover","upgrade","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":"Health and lag check (is everything all right?)","m":"Monitoring and checks INFO ALL lag lag STATS heartbeat GG_LAG Service Manager ggserr.log OGG-01027 OGG-01738 Long Running Transaction STOPPED RUNNING Distribution Path DISTPATH Receiver INFO DISTPATH replication is lagging replication lag Read-only: Service Manager and the microservices, INFO ALL, lag, STATS, heartbeat lag, capture status in the database and ggserr.log; nothing is changed. If you "},{"k":"scenario","v":"ext_abend","l":"Extract ABENDED (stopped with an error)","m":"Failure (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 missing log archived log not found logmining OGG-00868 Extract STARTING Extract stopped capture ABORTED The Extract on the source is ABENDED or keeps restarting: the cause (connection, missing archived log, log mining, privileges, d"},{"k":"scenario","v":"rep_abend","l":"Replicat ABENDED (cannot apply to the target)","m":"Failure (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 conflict Replicat stopped ORA-01400 ORA-12899 value too large The Replicat on the target could not apply a transaction: the table, operation and error are fo"},{"k":"scenario","v":"add_table","l":"Add a populated table to replication (initial load + AFTERCSN)","m":"Table management add table add table ADD TRANDATA ADD SCHEMATRANDATA AFTERCSN ATCSN initial load initial load flashback_scn MAPEXCLUDE new table Adds an existing table that already contains data to a running replication without downtime: ADD TRANDATA, expdp/impdp at a consistent SCN, and a separate Replicat that applies only this table's changes after that SCN (AFTERCSN). The other tables keep flo"},{"k":"scenario","v":"remove_table","l":"Remove a table from replication","m":"Table management remove table remove table TABLEEXCLUDE MAPEXCLUDE DELETE TRANDATA exclude The table will no longer be replicated: TABLEEXCLUDE in the Extract (or removing the TABLE line), MAPEXCLUDE in the Replicat, and DELETE TRANDATA if needed. The processes are restarted briefly."},{"k":"scenario","v":"resync","l":"Resynchronize a single table (resync)","m":"Table management resync resynchronize out of sync data difference diverged Veridata OGG-01004 ORA-01403 ORA-00001 row count row count mismatch A table's data on the target has diverged from the source (manual change, skipped error, incomplete initial load): the table is reloaded from the source at a consistent SCN and a temporary Replicat applies the changes after that SCN. The other tables are no"},{"k":"scenario","v":"purge","l":"Trail files are filling the disk: cleanup (Purge Trail)","m":"Maintenance trail purge PURGEOLDEXTRACTS Purge Trail disk full No space left on device trail cleanup var/lib/data trail directory full ENOSPC The trail directory is growing or the disk is about to fill up: you see which files are being read, a Purge Trail task (the equivalent of PURGEOLDEXTRACTS in Classic) is defined with USECHECKPOINTS; in an emergency, files already read are deleted manually."},{"k":"scenario","v":"reposition","l":"Reposition Extract to a specific SCN","m":"Maintenance reposition ALTER EXTRACT SCN BEGIN SCN ETROLLOVER start from SCN trail loss first_scn rewind There is an SCN from which you want Extract to start over (trail loss, wrong starting point, rollback on the target): it is started from a new trail file with ALTER EXTRACT … SCN and ETROLLOVER. The same data may arrive at the target a second time."},{"k":"scenario","v":"upgrade","l":"GoldenGate version upgrade / patch (Microservices)","m":"Maintenance upgrade patch upgrade OPatch out-of-place oggHome Service Manager upgrade bundle patch 21c 23ai 26ai release update GoldenGate patch Service Manager PATCH Moving to a new GoldenGate release or patch: out-of-place (install a new home, move Service Manager and the deployments to the new home with REST) or in-place with OPatch. Extract/Replicat stop briefly; rollback means going back to t"},{"k":"scenario","v":"dg_role","l":"Keep Extract running after a Data Guard switchover/failover on the source","m":"Role transition Data Guard switchover failover role transition HANDLEDLFAILOVER ORA-01033 ORA-16000 ORA-01109 new primary standby FSFO ORA-12514 role change Extract new primary FAILOVERTARGETDESTID A switchover or failover happened on the source database; Extract must connect to the new primary and continue from its checkpoint: the connection (credential alias/service), capture status, HANDLEDLFAI"}]},{"id":"ora-security","file":"install/guides/ora-security.json","group":"Oracle","engineId":"oracle","category":"security","title":"Oracle Database security hardening (assessment, network, TLS, authentication, privileges, Unified Auditing, TDE)","summary":"This is not an installation guide: it finds and closes the gaps in a running Oracle 19c / 23ai (26ai) database (single instance, Oracle Restart or RAC; CDB/PDB). First the read-only Security assessment lists the findings","level":"Advanced","duration":"5 min (assessment) – 4 hours (full hardening)","topology":"Single instance / Oracle Restart / RAC (2–8 nodes), CDB + PDB; application server (client verification)","tags":["oracle","security","security","hardening","database hardening","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","default password"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"Security assessment: what are my gaps? (read-only)","m":"Start here database security audit security audit hardening check CIS STIG DBSAT security gap security weakness vulnerability pentest finding dba_users_with_defpwd audit query security assessment security scan CRITICAL The first scan to run: accounts with default passwords and open accounts, old password verifiers, profiles, DBA/ANY privileges, UTL_* PL/SQL packages granted to PUBLIC, auditing, TD"},{"k":"scenario","v":"all","l":"Full hardening (all areas in order)","m":"Start here hardening database hardening complete security full hardening apply CIS benchmark STIG Newly installed or never hardened database: all areas are applied in a safe order; the listener and the database are restarted once or twice (a maintenance window is required)."},{"k":"scenario","v":"net","l":"Network and connections: who can connect from where? (listener, TCP.VALIDNODE_CHECKING, firewall)","m":"Network and authentication TCP.VALIDNODE_CHECKING TCP.INVITED_NODES listener 1521 5500 ORA-12537 TNS-12537 ORA-12541 ADMIN_RESTRICTIONS firewall TNS-12546 ORA-12170 invited nodes listener.log external access restrict access The listener is open to every address, invited nodes are not defined, 1521/5500 are open to everyone. The listener is restarted (existing sessions are not affected)."},{"k":"scenario","v":"tls","l":"Encrypt connections (TCPS wallet or native network encryption)","m":"Network and authentication TCPS SSL TLS wallet orapki SQLNET.ENCRYPTION_SERVER ORA-28860 ORA-29024 ORA-28759 ORA-29106 ORA-12696 ORA-12660 SSL_SERVER_DN_MATCH certificate expiry ORA-29003 ORA-29002 TNS-12560 TLS certificate chain certificate chain native network encryption Network traffic is unencrypted; TLS (TCPS, wallet, certificate, SSL_SERVER_DN_MATCH on the client) or certificate-less native "},{"k":"scenario","v":"auth","l":"Authentication and passwords (12C verifier, profiles, default accounts)","m":"Network and authentication ALLOWED_LOGON_VERSION_SERVER ORA-28040 ORA-01017 ORA-28000 ORA-28001 ORA-28003 ORA-28007 password_versions 10G PROFILE ORA12C_STRONG_VERIFY_FUNCTION default password default password SCOTT ORA-65066 ORA-00988 password_versions 10G password profile password policy FAILED_LOGIN_ATTEMPTS 10G/11G password verifiers, no password profile, accounts with default passwords or sam"},{"k":"scenario","v":"priv","l":"Least privilege: roles, PUBLIC and the application account","m":"Privileges and audit trail PUBLIC UTL_FILE UTL_TCP UTL_HTTP UTL_SMTP DBA role RESOURCE ANY privilege least privilege minimum privileges ORA-01031 ORA-00942 schema-only account PLS-00201 ORA-04021 GRANT ANY UNLIMITED TABLESPACE privilege report DBA/RESOURCE/ANY privileges on the application account, UTL_FILE/UTL_TCP/UTL_HTTP granted to PUBLIC; schema owner, read/write roles and a privilege report. "},{"k":"scenario","v":"audit","l":"Auditing: who did what? (Unified Auditing, AUDSYS cleanup)","m":"Privileges and audit trail unified auditing AUDIT POLICY AUDSYS DBMS_AUDIT_MGMT UNIFIED_AUDIT_TRAIL ORA_LOGON_FAILURES ORA_SECURECONFIG UNIFIED_AUDIT_SYSTEMLOG SYSAUX full audit trail SIEM ORA-46267 unified audit policy who did what who changed The auditor wants records of logons, account changes and schema changes; unified audit policies, purging AUDSYS according to a retention period and forward"},{"k":"scenario","v":"tde","l":"Encrypt data at rest (TDE)","m":"Data and backups 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 against reading datafiles, backups or disk copies: WALLET_ROOT, keystore, master key, tablespace encryption (online). Requires the Advanced Sec"},{"k":"scenario","v":"backup","l":"Backup security (RMAN encryption, password-free automation, permissions)","m":"Data and backups RMAN encryption CONFIGURE ENCRYPTION ORA-19913 expdp ENCRYPTION secure external password store mkstore SEPS backup permissions ORA-19914 Advanced Security license backup not encrypted unencrypted backup ENCRYPTION_PWD_PROMPT RMAN/Data Pump backups are unencrypted, scripts contain plain-text passwords, the backup directory is world-readable; RMAN encryption, ENCRYPTION_PWD_PROMPT, "},{"k":"scenario","v":"os","l":"Operating system side (oracle account, file permissions, core dumps, SELinux)","m":"Server and version SELinux orapw oracle account file permissions core dump ORACLE_HOME permissions sudo avc: denied passwd -l oracle setuid 6751 limits.d The oracle account has a password and can log in, permissions on the password file / network admin / wallet are loose, SELinux is disabled. The core dump limit takes effect in a new session / after a restart."},{"k":"scenario","v":"patch","l":"Patches and version: am I on the latest RU?","m":"Server and version RU release update CPU Critical Patch Update opatch lspatches datapatch dba_registry_sqlpatch CVE end of support opatch Doc ID 742060.1 patch schedule Installed RU, datapatch status and support timeline; links to the RU patch guide for applying it. Read-only."}]},{"id":"ora-oem","file":"install/guides/ora-oem.json","group":"Oracle","engineId":"oracle","category":"monitoring","title":"Installing Oracle Enterprise Manager Cloud Control 13c Release 5 (13.5)","summary":"Installs Oracle Enterprise Manager Cloud Control 13.5: an existing 19c database is prepared as the Management Repository (the documented parameters, resetting the optimizer settings), a silent installation is done on the","level":"Advanced","duration":"3–5 hours","topology":"OMS server + repository database server (+ target servers to monitor)","tags":["oracle","enterprise manager","oem","em13c","13.5","cloud control","oms","agent","emctl","emcli","monitoring","monitoring","rac","cluster","discover_db","data guard","exportconfig"],"order":40,"steps":32,"scenarios":[]}]}}